Merge branch 'develop' of https://github.com/frappe/erpnext into rebrand-ui
This commit is contained in:
commit
0a8939e1c1
@ -132,16 +132,10 @@ def allow_regional(fn):
|
||||
|
||||
return caller
|
||||
|
||||
def get_last_membership():
|
||||
def get_last_membership(member):
|
||||
'''Returns last membership if exists'''
|
||||
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
||||
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
|
||||
dict(member=member, paid=1), order_by='to_date desc', limit=1)
|
||||
|
||||
return last_membership and last_membership[0]
|
||||
|
||||
def is_member():
|
||||
'''Returns true if the user is still a member'''
|
||||
last_membership = get_last_membership()
|
||||
if last_membership and getdate(last_membership.to_date) > getdate():
|
||||
return True
|
||||
return False
|
||||
if last_membership:
|
||||
return last_membership[0]
|
||||
|
@ -254,7 +254,8 @@ def create_account(**kwargs):
|
||||
account_name = kwargs.get('account_name'),
|
||||
account_type = kwargs.get('account_type'),
|
||||
parent_account = kwargs.get('parent_account'),
|
||||
company = kwargs.get('company')
|
||||
company = kwargs.get('company'),
|
||||
account_currency = kwargs.get('account_currency')
|
||||
))
|
||||
|
||||
account.save()
|
||||
|
@ -165,9 +165,9 @@ def toggle_disabling(doc):
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
|
||||
def get_doctypes_with_dimensions():
|
||||
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
|
||||
doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset",
|
||||
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
|
||||
"Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
|
||||
"Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
|
||||
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
|
||||
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
|
||||
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"automatically_fetch_payment_terms",
|
||||
"delete_linked_ledger_entries",
|
||||
"deferred_accounting_settings_section",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_based_on",
|
||||
@ -219,6 +220,12 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Book Deferred Entries Based On",
|
||||
"options": "Days\nMonths"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delete_linked_ledger_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -226,7 +233,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-13 11:32:52.268826",
|
||||
"modified": "2021-01-05 13:04:00.118892",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
@ -254,4 +261,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -159,8 +159,8 @@ class GLEntry(Document):
|
||||
|
||||
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
|
||||
and self.cost_center and _check_is_group():
|
||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
|
||||
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
|
||||
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||
|
||||
def validate_party(self):
|
||||
validate_party_frozen_disabled(self.party_type, self.party)
|
||||
@ -170,7 +170,7 @@ class GLEntry(Document):
|
||||
account_currency = get_account_currency(self.account)
|
||||
|
||||
if not self.account_currency:
|
||||
self.account_currency = company_currency
|
||||
self.account_currency = account_currency or company_currency
|
||||
|
||||
if account_currency != self.account_currency:
|
||||
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
frappe.ui.form.on('POS Closing Entry', {
|
||||
onload: function(frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
|
||||
frm.set_query("pos_profile", function(doc) {
|
||||
return {
|
||||
filters: { 'user': doc.user }
|
||||
|
@ -11,6 +11,7 @@
|
||||
"column_break_3",
|
||||
"posting_date",
|
||||
"pos_opening_entry",
|
||||
"status",
|
||||
"section_break_5",
|
||||
"company",
|
||||
"column_break_7",
|
||||
@ -184,11 +185,27 @@
|
||||
"label": "POS Opening Entry",
|
||||
"options": "POS Opening Entry",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nSubmitted\nQueued\nCancelled",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:03:22.226113",
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "POS Invoice Merge Log",
|
||||
"link_fieldname": "pos_closing_entry"
|
||||
}
|
||||
],
|
||||
"modified": "2021-01-12 12:21:05.388650",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry",
|
||||
|
@ -6,13 +6,12 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate, get_datetime, flt
|
||||
from collections import defaultdict
|
||||
from frappe.utils import get_datetime, flt
|
||||
from erpnext.controllers.status_updater import StatusUpdater
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices, unconsolidate_pos_invoices
|
||||
|
||||
class POSClosingEntry(Document):
|
||||
class POSClosingEntry(StatusUpdater):
|
||||
def validate(self):
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
@ -57,20 +56,29 @@ class POSClosingEntry(Document):
|
||||
if not invalid_rows:
|
||||
return
|
||||
|
||||
error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
|
||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||
error_list = []
|
||||
for row in invalid_rows:
|
||||
for msg in row.get('msg'):
|
||||
error_list.append(_("Row #{}: {}").format(row.get('idx'), msg))
|
||||
|
||||
def on_submit(self):
|
||||
merge_pos_invoices(self.pos_transactions)
|
||||
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
|
||||
opening_entry.pos_closing_entry = self.name
|
||||
opening_entry.set_status()
|
||||
opening_entry.save()
|
||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||
|
||||
def get_payment_reconciliation_details(self):
|
||||
currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
||||
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
||||
{"data": self, "currency": currency})
|
||||
|
||||
def on_submit(self):
|
||||
consolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
def on_cancel(self):
|
||||
unconsolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
def update_opening_entry(self, for_cancel=False):
|
||||
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
|
||||
opening_entry.pos_closing_entry = self.name if not for_cancel else None
|
||||
opening_entry.set_status()
|
||||
opening_entry.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
|
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// render
|
||||
frappe.listview_settings['POS Closing Entry'] = {
|
||||
get_indicator: function(doc) {
|
||||
var status_color = {
|
||||
"Draft": "red",
|
||||
"Submitted": "blue",
|
||||
"Queued": "orange",
|
||||
"Cancelled": "red"
|
||||
|
||||
};
|
||||
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
|
||||
}
|
||||
};
|
@ -13,7 +13,6 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
|
||||
class TestPOSClosingEntry(unittest.TestCase):
|
||||
def test_pos_closing_entry(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
||||
@ -45,6 +44,49 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def test_cancelling_of_pos_closing_entry(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
||||
pos_inv1.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500
|
||||
})
|
||||
pos_inv1.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||
payment = pcv_doc.payment_reconciliation[0]
|
||||
|
||||
self.assertEqual(payment.mode_of_payment, 'Cash')
|
||||
|
||||
for d in pcv_doc.payment_reconciliation:
|
||||
if d.mode_of_payment == 'Cash':
|
||||
d.closing_amount = 6700
|
||||
|
||||
pcv_doc.submit()
|
||||
|
||||
pos_inv1.load_from_db()
|
||||
self.assertRaises(frappe.ValidationError, pos_inv1.cancel)
|
||||
|
||||
si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice)
|
||||
self.assertRaises(frappe.ValidationError, si_doc.cancel)
|
||||
|
||||
pcv_doc.load_from_db()
|
||||
pcv_doc.cancel()
|
||||
si_doc.load_from_db()
|
||||
pos_inv1.load_from_db()
|
||||
self.assertEqual(si_doc.docstatus, 2)
|
||||
self.assertEqual(pos_inv1.status, 'Paid')
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def init_user_and_profile(**args):
|
||||
user = 'test@example.com'
|
||||
test_user = frappe.get_doc('User', user)
|
||||
|
@ -2,6 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
{% include 'erpnext/selling/sales_common.js' %};
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
|
||||
setup(doc) {
|
||||
@ -9,12 +10,19 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
||||
this._super(doc);
|
||||
},
|
||||
|
||||
company: function() {
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
onload(doc) {
|
||||
this._super();
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
|
||||
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||
this.frm.script_manager.trigger("is_pos");
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
refresh(doc) {
|
||||
|
@ -6,10 +6,9 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from frappe.utils import cint, flt, getdate, nowdate, get_link_to_form
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
||||
@ -58,6 +57,22 @@ class POSInvoice(SalesInvoice):
|
||||
self.apply_loyalty_points()
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
|
||||
def before_cancel(self):
|
||||
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
|
||||
pos_closing_entry = frappe.get_all(
|
||||
"POS Invoice Reference",
|
||||
ignore_permissions=True,
|
||||
filters={ 'pos_invoice': self.name },
|
||||
pluck="parent",
|
||||
limit=1
|
||||
)
|
||||
frappe.throw(
|
||||
_('You need to cancel POS Closing Entry {} to be able to cancel this document.').format(
|
||||
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
||||
),
|
||||
title=_('Not Allowed')
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
# run on cancel method of selling controller
|
||||
@ -78,7 +93,7 @@ class POSInvoice(SalesInvoice):
|
||||
mode_of_payment=pay.mode_of_payment, status="Paid"),
|
||||
fieldname="grand_total")
|
||||
|
||||
if pay.amount != paid_amt:
|
||||
if paid_amt and pay.amount != paid_amt:
|
||||
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
@ -297,7 +312,9 @@ class POSInvoice(SalesInvoice):
|
||||
self.set(fieldname, profile.get(fieldname))
|
||||
|
||||
if self.customer:
|
||||
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
||||
customer_price_list, customer_group, customer_currency = frappe.db.get_value(
|
||||
"Customer", self.customer, ['default_price_list', 'customer_group', 'default_currency']
|
||||
)
|
||||
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
||||
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
|
||||
else:
|
||||
@ -305,6 +322,8 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
if selling_price_list:
|
||||
self.set('selling_price_list', selling_price_list)
|
||||
if customer_currency != profile.get('currency'):
|
||||
self.set('currency', customer_currency)
|
||||
|
||||
# set pos values in items
|
||||
for item in self.get("items"):
|
||||
|
@ -290,7 +290,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
def test_merging_into_sales_invoice_with_discount(self):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
@ -306,7 +306,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
merge_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||
@ -315,7 +315,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
@ -348,7 +348,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
merge_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||
@ -357,7 +357,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
def test_merging_with_validate_selling_price(self):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||
|
||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||
@ -393,7 +393,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
merge_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv2.load_from_db()
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||
|
@ -7,6 +7,8 @@
|
||||
"field_order": [
|
||||
"posting_date",
|
||||
"customer",
|
||||
"column_break_3",
|
||||
"pos_closing_entry",
|
||||
"section_break_3",
|
||||
"pos_invoices",
|
||||
"references_section",
|
||||
@ -76,11 +78,22 @@
|
||||
"label": "Consolidated Credit Note",
|
||||
"options": "Sales Invoice",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_closing_entry",
|
||||
"fieldtype": "Link",
|
||||
"label": "POS Closing Entry",
|
||||
"options": "POS Closing Entry"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:08:41.317100",
|
||||
"modified": "2020-12-01 11:53:57.267579",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Merge Log",
|
||||
|
@ -5,10 +5,13 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_doc
|
||||
from frappe.model import default_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, getdate, nowdate
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.model.mapper import map_doc, map_child_doc
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
|
||||
from six import iteritems
|
||||
|
||||
@ -61,7 +64,13 @@ class POSInvoiceMergeLog(Document):
|
||||
|
||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||
|
||||
self.update_pos_invoices(sales_invoice, credit_note)
|
||||
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
||||
|
||||
def on_cancel(self):
|
||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||
|
||||
self.update_pos_invoices(pos_invoice_docs)
|
||||
self.cancel_linked_invoices()
|
||||
|
||||
def process_merging_into_sales_invoice(self, data):
|
||||
sales_invoice = self.get_new_sales_invoice()
|
||||
@ -83,7 +92,7 @@ class POSInvoiceMergeLog(Document):
|
||||
|
||||
credit_note.is_consolidated = 1
|
||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||
credit_note.return_against = self.consolidated_invoice
|
||||
# credit_note.return_against = self.consolidated_invoice
|
||||
credit_note.save()
|
||||
credit_note.submit()
|
||||
self.consolidated_credit_note = credit_note.name
|
||||
@ -111,7 +120,9 @@ class POSInvoiceMergeLog(Document):
|
||||
i.qty = i.qty + item.qty
|
||||
if not found:
|
||||
item.rate = item.net_rate
|
||||
items.append(item)
|
||||
item.price_list_rate = 0
|
||||
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
||||
items.append(si_item)
|
||||
|
||||
for tax in doc.get('taxes'):
|
||||
found = False
|
||||
@ -147,6 +158,8 @@ class POSInvoiceMergeLog(Document):
|
||||
invoice.set('taxes', taxes)
|
||||
invoice.additional_discount_percentage = 0
|
||||
invoice.discount_amount = 0.0
|
||||
invoice.taxes_and_charges = None
|
||||
invoice.ignore_pricing_rule = 1
|
||||
|
||||
return invoice
|
||||
|
||||
@ -159,17 +172,21 @@ class POSInvoiceMergeLog(Document):
|
||||
|
||||
return sales_invoice
|
||||
|
||||
def update_pos_invoices(self, sales_invoice, credit_note):
|
||||
for d in self.pos_invoices:
|
||||
doc = frappe.get_doc('POS Invoice', d.pos_invoice)
|
||||
if not doc.is_return:
|
||||
doc.update({'consolidated_invoice': sales_invoice})
|
||||
else:
|
||||
doc.update({'consolidated_invoice': credit_note})
|
||||
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
|
||||
for doc in invoice_docs:
|
||||
doc.load_from_db()
|
||||
doc.update({ 'consolidated_invoice': None if self.docstatus==2 else (credit_note if doc.is_return else sales_invoice) })
|
||||
doc.set_status(update=True)
|
||||
doc.save()
|
||||
|
||||
def get_all_invoices():
|
||||
def cancel_linked_invoices(self):
|
||||
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
|
||||
if not si_name: continue
|
||||
si = frappe.get_doc('Sales Invoice', si_name)
|
||||
si.flags.ignore_validate = True
|
||||
si.cancel()
|
||||
|
||||
def get_all_unconsolidated_invoices():
|
||||
filters = {
|
||||
'consolidated_invoice': [ 'in', [ '', None ]],
|
||||
'status': ['not in', ['Consolidated']],
|
||||
@ -180,7 +197,7 @@ def get_all_invoices():
|
||||
|
||||
return pos_invoices
|
||||
|
||||
def get_invoices_customer_map(pos_invoices):
|
||||
def get_invoice_customer_map(pos_invoices):
|
||||
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] }
|
||||
pos_invoice_customer_map = {}
|
||||
for invoice in pos_invoices:
|
||||
@ -190,20 +207,82 @@ def get_invoices_customer_map(pos_invoices):
|
||||
|
||||
return pos_invoice_customer_map
|
||||
|
||||
def merge_pos_invoices(pos_invoices=[]):
|
||||
if not pos_invoices:
|
||||
pos_invoices = get_all_invoices()
|
||||
|
||||
pos_invoice_map = get_invoices_customer_map(pos_invoices)
|
||||
create_merge_logs(pos_invoice_map)
|
||||
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
||||
invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
|
||||
invoice_by_customer = get_invoice_customer_map(invoices)
|
||||
|
||||
def create_merge_logs(pos_invoice_customer_map):
|
||||
for customer, invoices in iteritems(pos_invoice_customer_map):
|
||||
if len(invoices) >= 5 and closing_entry:
|
||||
enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
|
||||
closing_entry.set_status(update=True, status='Queued')
|
||||
else:
|
||||
create_merge_logs(invoice_by_customer, closing_entry)
|
||||
|
||||
def unconsolidate_pos_invoices(closing_entry):
|
||||
merge_logs = frappe.get_all(
|
||||
'POS Invoice Merge Log',
|
||||
filters={ 'pos_closing_entry': closing_entry.name },
|
||||
pluck='name'
|
||||
)
|
||||
|
||||
if len(merge_logs) >= 5:
|
||||
enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
|
||||
closing_entry.set_status(update=True, status='Queued')
|
||||
else:
|
||||
cancel_merge_logs(merge_logs, closing_entry)
|
||||
|
||||
def create_merge_logs(invoice_by_customer, closing_entry={}):
|
||||
for customer, invoices in iteritems(invoice_by_customer):
|
||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||
merge_log.posting_date = getdate(nowdate())
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get('name', None)
|
||||
|
||||
merge_log.set('pos_invoices', invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Submitted')
|
||||
closing_entry.update_opening_entry()
|
||||
|
||||
def cancel_merge_logs(merge_logs, closing_entry={}):
|
||||
for log in merge_logs:
|
||||
merge_log = frappe.get_doc('POS Invoice Merge Log', log)
|
||||
merge_log.flags.ignore_permissions = True
|
||||
merge_log.cancel()
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Cancelled')
|
||||
closing_entry.update_opening_entry(for_cancel=True)
|
||||
|
||||
def enqueue_job(job, invoice_by_customer, closing_entry):
|
||||
check_scheduler_status()
|
||||
|
||||
job_name = closing_entry.get("name")
|
||||
if not job_already_enqueued(job_name):
|
||||
enqueue(
|
||||
job,
|
||||
queue="long",
|
||||
timeout=10000,
|
||||
event="processing_merge_logs",
|
||||
job_name=job_name,
|
||||
closing_entry=closing_entry,
|
||||
invoice_by_customer=invoice_by_customer,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||
)
|
||||
|
||||
if job == create_merge_logs:
|
||||
msg = _('POS Invoices will be consolidated in a background process')
|
||||
else:
|
||||
msg = _('POS Invoices will be unconsolidated in a background process')
|
||||
|
||||
frappe.msgprint(msg, alert=1)
|
||||
|
||||
def check_scheduler_status():
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
||||
|
||||
def job_already_enqueued(job_name):
|
||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||
if job_name in enqueued_jobs:
|
||||
return True
|
@ -7,7 +7,7 @@ import frappe
|
||||
import unittest
|
||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
|
||||
class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
@ -34,7 +34,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
})
|
||||
pos_inv3.submit()
|
||||
|
||||
merge_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
@ -79,7 +79,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
pos_inv_cn.paid_amount = -300
|
||||
pos_inv_cn.submit()
|
||||
|
||||
merge_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
@ -6,7 +6,6 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
class POSOpeningEntry(StatusUpdater):
|
||||
|
@ -5,7 +5,7 @@
|
||||
frappe.listview_settings['POS Opening Entry'] = {
|
||||
get_indicator: function(doc) {
|
||||
var status_color = {
|
||||
"Draft": "grey",
|
||||
"Draft": "red",
|
||||
"Open": "orange",
|
||||
"Closed": "green",
|
||||
"Cancelled": "red"
|
||||
|
@ -12,8 +12,6 @@
|
||||
"company",
|
||||
"country",
|
||||
"column_break_9",
|
||||
"update_stock",
|
||||
"ignore_pricing_rule",
|
||||
"warehouse",
|
||||
"campaign",
|
||||
"company_address",
|
||||
@ -25,8 +23,14 @@
|
||||
"hide_images",
|
||||
"hide_unavailable_items",
|
||||
"auto_add_item_to_cart",
|
||||
"item_groups",
|
||||
"column_break_16",
|
||||
"update_stock",
|
||||
"ignore_pricing_rule",
|
||||
"allow_rate_change",
|
||||
"allow_discount_change",
|
||||
"section_break_23",
|
||||
"item_groups",
|
||||
"column_break_25",
|
||||
"customer_groups",
|
||||
"section_break_16",
|
||||
"print_format",
|
||||
@ -309,6 +313,7 @@
|
||||
"default": "1",
|
||||
"fieldname": "update_stock",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Update Stock",
|
||||
"read_only": 1
|
||||
},
|
||||
@ -329,13 +334,34 @@
|
||||
"fieldname": "auto_add_item_to_cart",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically Add Filtered Item To Cart"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_rate_change",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow User to Edit Rate"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_discount_change",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow User to Edit Discount"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_23",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_25",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-10 13:59:28.877572",
|
||||
"modified": "2021-01-06 14:42:41.713864",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
@ -275,8 +275,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
|
||||
supplier: function() {
|
||||
var me = this;
|
||||
if(this.frm.updating_party_details)
|
||||
|
||||
// Do not update if inter company reference is there as the details will already be updated
|
||||
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
|
||||
return;
|
||||
|
||||
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
|
||||
{
|
||||
posting_date: this.frm.doc.posting_date,
|
||||
|
@ -57,8 +57,8 @@
|
||||
"set_warehouse",
|
||||
"rejected_warehouse",
|
||||
"col_break_warehouse",
|
||||
"set_from_warehouse",
|
||||
"is_subcontracted",
|
||||
"supplier_warehouse",
|
||||
"items_section",
|
||||
"update_stock",
|
||||
"scan_barcode",
|
||||
@ -515,6 +515,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "update_stock",
|
||||
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
|
||||
"fieldname": "set_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Accepted Warehouse",
|
||||
@ -543,17 +544,6 @@
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname": "items_section",
|
||||
"fieldtype": "Section Break",
|
||||
@ -1232,7 +1222,9 @@
|
||||
"fieldname": "inter_company_invoice_reference",
|
||||
"fieldtype": "Link",
|
||||
"label": "Inter Company Invoice Reference",
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -1356,13 +1348,25 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Represents Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)",
|
||||
"description": "Sets 'From Warehouse' in each row of the items table.",
|
||||
"fieldname": "set_from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Set From Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-11 12:46:12.796378",
|
||||
"modified": "2020-12-26 20:49:03.305063",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -443,7 +443,7 @@ class PurchaseInvoice(BuyingController):
|
||||
else:
|
||||
self.stock_received_but_not_billed = None
|
||||
self.expenses_included_in_valuation = None
|
||||
|
||||
|
||||
self.negative_expense_to_be_booked = 0.0
|
||||
gl_entries = []
|
||||
|
||||
@ -457,7 +457,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||
|
||||
|
||||
gl_entries = merge_similar_entries(gl_entries)
|
||||
|
||||
self.make_payment_gl_entries(gl_entries)
|
||||
@ -480,7 +480,7 @@ class PurchaseInvoice(BuyingController):
|
||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
# Didnot use base_grand_total to book rounding loss gle
|
||||
# Did not use base_grand_total to book rounding loss gle
|
||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||
self.precision("grand_total"))
|
||||
gl_entries.append(
|
||||
@ -511,8 +511,8 @@ class PurchaseInvoice(BuyingController):
|
||||
voucher_wise_stock_value = {}
|
||||
if self.update_stock:
|
||||
for d in frappe.get_all('Stock Ledger Entry',
|
||||
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
|
||||
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
|
||||
fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
|
||||
voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
|
||||
|
||||
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
|
||||
if d.category in ('Valuation', 'Total and Valuation')
|
||||
@ -563,16 +563,17 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
else:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": warehouse_debit_amount,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
if not self.is_internal_transfer():
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": warehouse_debit_amount,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
|
||||
# Amount added through landed-cost-voucher
|
||||
if landed_cost_entries:
|
||||
@ -582,7 +583,8 @@ class PurchaseInvoice(BuyingController):
|
||||
"against": item.expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(amount),
|
||||
"credit": flt(amount["base_amount"]),
|
||||
"credit_in_account_currency": flt(amount["amount"]),
|
||||
"project": item.project or self.project
|
||||
}, item=item))
|
||||
|
||||
@ -624,13 +626,14 @@ class PurchaseInvoice(BuyingController):
|
||||
if expense_booked_in_pr:
|
||||
expense_account = service_received_but_not_billed_account
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": amount,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item))
|
||||
if not self.is_internal_transfer():
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": amount,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item))
|
||||
|
||||
# If asset is bought through this document and not linked to PR
|
||||
if self.update_stock and item.landed_cost_voucher_amount:
|
||||
@ -795,10 +798,10 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
# Stock ledger value is not matching with the warehouse amount
|
||||
if (self.update_stock and voucher_wise_stock_value.get(item.name) and
|
||||
warehouse_debit_amount != flt(voucher_wise_stock_value.get(item.name), net_amt_precision)):
|
||||
warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)):
|
||||
|
||||
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
|
||||
stock_amount = flt(voucher_wise_stock_value.get(item.name), net_amt_precision)
|
||||
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
|
||||
stock_adjustment_amt = warehouse_debit_amount - stock_amount
|
||||
|
||||
gl_entries.append(
|
||||
@ -999,10 +1002,10 @@ class PurchaseInvoice(BuyingController):
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
|
||||
self.update_project()
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@ -87,6 +88,7 @@
|
||||
"po_detail",
|
||||
"purchase_receipt",
|
||||
"pr_detail",
|
||||
"sales_invoice_item",
|
||||
"item_weight_details",
|
||||
"weight_per_unit",
|
||||
"total_weight",
|
||||
@ -553,8 +555,8 @@
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Brand",
|
||||
"print_hide": 1,
|
||||
"options": "Brand"
|
||||
"options": "Brand",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_group",
|
||||
@ -562,9 +564,9 @@
|
||||
"fieldname": "item_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Group",
|
||||
"options": "Item Group",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"options": "Item Group"
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
|
||||
@ -759,10 +761,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
|
||||
"fieldname": "from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Supplier Warehouse",
|
||||
"label": "From Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@ -779,11 +782,20 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Sales Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2020-08-20 11:48:01.398356",
|
||||
"links": [],
|
||||
"modified": "2020-12-26 17:20:36.415791",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
@ -791,4 +803,4 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
var me = this;
|
||||
this._super();
|
||||
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
|
||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
// show debit_to in print format
|
||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||
@ -130,16 +131,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
|
||||
this.set_default_print_format();
|
||||
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
||||
frappe.model.with_doc("Customer", me.frm.doc.customer, function() {
|
||||
var customer = frappe.model.get_doc("Customer", me.frm.doc.customer);
|
||||
var internal = customer.is_internal_customer;
|
||||
var disabled = customer.disabled;
|
||||
if (internal == 1 && disabled == 0) {
|
||||
me.frm.add_custom_button("Inter Company Invoice", function() {
|
||||
me.make_inter_company_invoice();
|
||||
}, __('Create'));
|
||||
}
|
||||
});
|
||||
let internal = me.frm.doc.is_internal_customer;
|
||||
if (internal) {
|
||||
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Invoice" :
|
||||
"Inter Company Purchase Invoice";
|
||||
|
||||
me.frm.add_custom_button(button_label, function() {
|
||||
me.make_inter_company_invoice();
|
||||
}, __('Create'));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -60,6 +60,8 @@
|
||||
"ignore_pricing_rule",
|
||||
"sec_warehouse",
|
||||
"set_warehouse",
|
||||
"column_break_55",
|
||||
"set_target_warehouse",
|
||||
"items_section",
|
||||
"update_stock",
|
||||
"scan_barcode",
|
||||
@ -1969,13 +1971,31 @@
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_55",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_internal_customer && doc.update_stock",
|
||||
"fieldname": "set_target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-11 12:48:31.769958",
|
||||
"links": [
|
||||
{
|
||||
"custom": 1,
|
||||
"group": "Reference",
|
||||
"link_doctype": "POS Invoice",
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-01-12 12:16:15.192520",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -6,7 +6,7 @@ import frappe, erpnext
|
||||
import frappe.defaults
|
||||
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
|
||||
from frappe import _, msgprint, throw
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
@ -21,6 +21,8 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
||||
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
|
||||
from erpnext.healthcare.utils import manage_invoice_submit_cancel
|
||||
|
||||
@ -231,7 +233,25 @@ class SalesInvoice(SellingController):
|
||||
if len(self.payments) == 0 and self.is_pos:
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
|
||||
def check_if_consolidated_invoice(self):
|
||||
# since POS Invoice extends Sales Invoice, we explicitly check if doctype is Sales Invoice
|
||||
if self.doctype == "Sales Invoice" and self.is_consolidated:
|
||||
invoice_or_credit_note = "consolidated_credit_note" if self.is_return else "consolidated_invoice"
|
||||
pos_closing_entry = frappe.get_all(
|
||||
"POS Invoice Merge Log",
|
||||
filters={ invoice_or_credit_note: self.name },
|
||||
pluck="pos_closing_entry"
|
||||
)
|
||||
if pos_closing_entry:
|
||||
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
|
||||
frappe.bold("Consolidated Sales Invoice"),
|
||||
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
||||
)
|
||||
frappe.throw(msg, title=_("Not Allowed"))
|
||||
|
||||
def before_cancel(self):
|
||||
self.check_if_consolidated_invoice()
|
||||
|
||||
super(SalesInvoice, self).before_cancel()
|
||||
self.update_time_sheet(None)
|
||||
|
||||
@ -433,7 +453,9 @@ class SalesInvoice(SellingController):
|
||||
if not for_validate and not self.customer:
|
||||
self.customer = pos.customer
|
||||
|
||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
||||
if not for_validate:
|
||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
||||
|
||||
if pos.get('account_for_change_amount'):
|
||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||
|
||||
@ -1534,7 +1556,7 @@ def validate_inter_company_transaction(doc, doctype):
|
||||
details = get_inter_company_details(doc, doctype)
|
||||
price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
|
||||
valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
|
||||
if not valid_price_list:
|
||||
if not valid_price_list and not doc.is_internal_transfer():
|
||||
frappe.throw(_("Selected Price List should have buying and selling fields checked."))
|
||||
|
||||
party = details.get("party")
|
||||
@ -1557,6 +1579,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||
source_doc = frappe.get_doc(doctype, source_name)
|
||||
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
|
||||
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
|
||||
source_document_warehouse_field = 'target_warehouse'
|
||||
target_document_warehouse_field = 'from_warehouse'
|
||||
else:
|
||||
@ -1570,6 +1593,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
set_purchase_references(target)
|
||||
|
||||
def update_details(source_doc, target_doc, source_parent):
|
||||
target_doc.inter_company_invoice_reference = source_doc.name
|
||||
@ -1577,19 +1601,38 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
|
||||
target_doc.company = details.get("company")
|
||||
target_doc.supplier = details.get("party")
|
||||
target_doc.is_internal_supplier = 1
|
||||
target_doc.ignore_pricing_rule = 1
|
||||
target_doc.buying_price_list = source_doc.selling_price_list
|
||||
|
||||
# Invert Addresses
|
||||
update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
|
||||
update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
|
||||
|
||||
if currency:
|
||||
target_doc.currency = currency
|
||||
|
||||
update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
|
||||
doctype=target_doc.doctype, party_address=target_doc.supplier_address,
|
||||
company_address=target_doc.shipping_address)
|
||||
|
||||
else:
|
||||
currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
|
||||
target_doc.company = details.get("company")
|
||||
target_doc.customer = details.get("party")
|
||||
target_doc.selling_price_list = source_doc.buying_price_list
|
||||
|
||||
update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
|
||||
update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
|
||||
update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
|
||||
|
||||
if currency:
|
||||
target_doc.currency = currency
|
||||
|
||||
update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
|
||||
doctype=target_doc.doctype, party_address=target_doc.customer_address,
|
||||
company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
|
||||
|
||||
item_field_map = {
|
||||
"doctype": target_doctype + " Item",
|
||||
"field_no_map": [
|
||||
@ -1597,25 +1640,33 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
"expense_account",
|
||||
"cost_center",
|
||||
"warehouse"
|
||||
]
|
||||
],
|
||||
"field_map": {
|
||||
'rate': 'rate',
|
||||
}
|
||||
}
|
||||
|
||||
if source_doc.get('update_stock'):
|
||||
item_field_map.update({
|
||||
'field_map': {
|
||||
source_document_warehouse_field: target_document_warehouse_field,
|
||||
'batch_no': 'batch_no',
|
||||
'serial_no': 'serial_no'
|
||||
}
|
||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||
item_field_map["field_map"].update({
|
||||
"name": target_detail_field,
|
||||
})
|
||||
|
||||
if source_doc.get('update_stock'):
|
||||
item_field_map["field_map"].update({
|
||||
source_document_warehouse_field: target_document_warehouse_field,
|
||||
'batch_no': 'batch_no',
|
||||
'serial_no': 'serial_no'
|
||||
})
|
||||
|
||||
doclist = get_mapped_doc(doctype, source_name, {
|
||||
doctype: {
|
||||
"doctype": target_doctype,
|
||||
"postprocess": update_details,
|
||||
"set_target_warehouse": "set_from_warehouse",
|
||||
"field_no_map": [
|
||||
"taxes_and_charges"
|
||||
"taxes_and_charges",
|
||||
"set_warehouse",
|
||||
"shipping_address"
|
||||
]
|
||||
},
|
||||
doctype +" Item": item_field_map
|
||||
@ -1624,6 +1675,110 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
|
||||
return doclist
|
||||
|
||||
def set_purchase_references(doc):
|
||||
# add internal PO or PR links if any
|
||||
if doc.is_internal_transfer():
|
||||
if doc.doctype == 'Purchase Receipt':
|
||||
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
|
||||
|
||||
if so_item_map:
|
||||
pd_item_map, parent_child_map, warehouse_map = \
|
||||
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
|
||||
|
||||
update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||
|
||||
elif doc.doctype == 'Purchase Invoice':
|
||||
dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference)
|
||||
# First check for Purchase receipt
|
||||
if list(dn_item_map.values()):
|
||||
pd_item_map, parent_child_map, warehouse_map = \
|
||||
get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item')
|
||||
|
||||
update_pi_items(doc, 'pr_detail', 'purchase_receipt',
|
||||
dn_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||
|
||||
if list(so_item_map.values()):
|
||||
pd_item_map, parent_child_map, warehouse_map = \
|
||||
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
|
||||
|
||||
update_pi_items(doc, 'po_detail', 'purchase_order',
|
||||
so_item_map, pd_item_map, parent_child_map, warehouse_map)
|
||||
|
||||
def update_pi_items(doc, detail_field, parent_field, sales_item_map,
|
||||
purchase_item_map, parent_child_map, warehouse_map):
|
||||
for item in doc.get('items'):
|
||||
item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||
if doc.update_stock:
|
||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
|
||||
|
||||
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
|
||||
for item in doc.get('items'):
|
||||
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
|
||||
def get_delivery_note_details(internal_reference):
|
||||
so_item_map = {}
|
||||
|
||||
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
|
||||
filters={'parent': internal_reference})
|
||||
|
||||
for d in si_item_details:
|
||||
so_item_map.setdefault(d.name, d.so_detail)
|
||||
|
||||
return so_item_map
|
||||
|
||||
def get_sales_invoice_details(internal_reference):
|
||||
dn_item_map = {}
|
||||
so_item_map = {}
|
||||
|
||||
si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail',
|
||||
'dn_detail'], filters={'parent': internal_reference})
|
||||
|
||||
for d in si_item_details:
|
||||
if d.dn_detail:
|
||||
dn_item_map.setdefault(d.name, d.dn_detail)
|
||||
if d.so_detail:
|
||||
so_item_map.setdefault(d.name, d.so_detail)
|
||||
|
||||
return dn_item_map, so_item_map
|
||||
|
||||
def get_pd_details(doctype, sd_detail_map, sd_detail_field):
|
||||
pd_item_map = {}
|
||||
accepted_warehouse_map = {}
|
||||
parent_child_map = {}
|
||||
|
||||
pd_item_details = frappe.get_all(doctype,
|
||||
fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))})
|
||||
|
||||
for d in pd_item_details:
|
||||
pd_item_map.setdefault(d.get(sd_detail_field), d.name)
|
||||
parent_child_map.setdefault(d.get(sd_detail_field), d.parent)
|
||||
accepted_warehouse_map.setdefault(d.get(sd_detail_field), d.warehouse)
|
||||
|
||||
return pd_item_map, parent_child_map, accepted_warehouse_map
|
||||
|
||||
def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None,
|
||||
company_address=None, shipping_address_name=None, master_doctype=None):
|
||||
# Update Party Details
|
||||
party_details = get_party_details(party=party, party_type=party_type, company=company,
|
||||
doctype=doctype, party_address=party_address, company_address=company_address,
|
||||
shipping_address=shipping_address_name)
|
||||
|
||||
# Update taxes and charges if any
|
||||
doc.taxes_and_charges = party_details.get('taxes_and_charges')
|
||||
doc.set('taxes', party_details.get('taxes'))
|
||||
|
||||
def update_address(doc, address_field, address_display_field, address_name):
|
||||
doc.set(address_field, address_name)
|
||||
fetch_values = get_fetch_values(doc.doctype, address_field, address_name)
|
||||
|
||||
for key, value in fetch_values.items():
|
||||
doc.set(key, value)
|
||||
|
||||
doc.set(address_display_field, get_address_display(doc.get(address_field)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_programs(customer):
|
||||
''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
|
||||
|
@ -22,6 +22,7 @@ from erpnext.regional.india.utils import get_ewb_data
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def make(self):
|
||||
@ -688,7 +689,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertTrue(gle)
|
||||
|
||||
def test_pos_gl_entry_with_perpetual_inventory(self):
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
||||
@ -745,7 +746,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(pos_return.get('payments')[0].amount, -1000)
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||
@ -1801,6 +1802,24 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
||||
add_taxes(si)
|
||||
si.save()
|
||||
|
||||
rate = 0.0
|
||||
for d in si.get('items'):
|
||||
rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": si.posting_date,
|
||||
"posting_time": si.posting_time,
|
||||
"qty": -1 * flt(d.get('stock_qty')),
|
||||
"serial_no": d.serial_no,
|
||||
"company": si.company,
|
||||
"voucher_type": 'Sales Invoice',
|
||||
"voucher_no": si.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
|
||||
rate = flt(rate, 2)
|
||||
|
||||
si.submit()
|
||||
|
||||
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
|
||||
@ -1810,18 +1829,23 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
target_doc.save()
|
||||
target_doc.submit()
|
||||
|
||||
tax_amount = flt(rate * (12/100), 2)
|
||||
si_gl_entries = [
|
||||
["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
|
||||
["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
|
||||
["_Test Account Excise Duty - TCP1", 0.0, tax_amount, nowdate()],
|
||||
["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
|
||||
|
||||
pi_gl_entries = [
|
||||
["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
|
||||
["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
|
||||
["_Test Account Excise Duty - TCP1", tax_amount , 0.0, nowdate()],
|
||||
["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()]
|
||||
]
|
||||
|
||||
# Sale and Purchase both should be at valuation rate
|
||||
self.assertEqual(si.items[0].rate, rate)
|
||||
self.assertEqual(target_doc.items[0].rate, rate)
|
||||
|
||||
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
||||
|
||||
def test_eway_bill_json(self):
|
||||
@ -1841,7 +1865,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
||||
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
||||
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
||||
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||
@ -1857,7 +1881,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
# reset
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||
frappe.flags.country = country
|
||||
|
||||
|
||||
def test_einvoice_json(self):
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||
|
||||
|
@ -565,11 +565,12 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
|
||||
"fieldname": "target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Customer Warehouse (Optional)",
|
||||
"label": "Target Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1
|
||||
@ -815,7 +816,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-23 19:59:04.879322",
|
||||
"modified": "2020-12-26 17:25:04.090630",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -34,6 +34,9 @@ def valdiate_taxes_and_charges_template(doc):
|
||||
|
||||
validate_disabled(doc)
|
||||
|
||||
# Validate with existing taxes and charges template for unique tax category
|
||||
validate_for_tax_category(doc)
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, doc)
|
||||
@ -41,3 +44,7 @@ def valdiate_taxes_and_charges_template(doc):
|
||||
def validate_disabled(doc):
|
||||
if doc.is_default and doc.disabled:
|
||||
frappe.throw(_("Disabled template must not be default template"))
|
||||
|
||||
def validate_for_tax_category(doc):
|
||||
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
|
||||
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
|
||||
|
@ -152,7 +152,7 @@
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
|
||||
</tr>
|
||||
|
@ -49,12 +49,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
elif d.po_detail:
|
||||
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
|
||||
|
||||
expense_account = d.expense_account or aii_account_map.get(d.company)
|
||||
expense_account = d.unrealized_profit_loss_account or d.expense_account \
|
||||
or aii_account_map.get(d.company)
|
||||
|
||||
row = {
|
||||
'item_code': d.item_code,
|
||||
'item_name': item_record.item_name,
|
||||
'item_group': item_record.item_group,
|
||||
'item_name': item_record.item_name if item_record else d.item_name,
|
||||
'item_group': item_record.item_group if item_record else d.item_group,
|
||||
'description': d.description,
|
||||
'invoice': d.parent,
|
||||
'posting_date': d.posting_date,
|
||||
@ -315,7 +316,9 @@ def get_items(filters, additional_query_columns):
|
||||
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
|
||||
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||
`tabPurchase Invoice`.unrealized_profit_loss_account,
|
||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||
|
@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
'company': d.company,
|
||||
'sales_order': d.sales_order,
|
||||
'delivery_note': d.delivery_note,
|
||||
'income_account': d.income_account,
|
||||
'income_account': d.unrealized_profit_loss_account or d.income_account,
|
||||
'cost_center': d.cost_center,
|
||||
'stock_qty': d.stock_qty,
|
||||
'stock_uom': d.stock_uom
|
||||
@ -379,6 +379,7 @@ def get_items(filters, additional_query_columns):
|
||||
select
|
||||
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||
|
@ -14,13 +14,15 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
if not filters: filters = {}
|
||||
|
||||
invoice_list = get_invoices(filters, additional_query_columns)
|
||||
columns, expense_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
|
||||
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts \
|
||||
= get_columns(invoice_list, additional_table_columns)
|
||||
|
||||
if not invoice_list:
|
||||
msgprint(_("No record found"))
|
||||
return columns, invoice_list
|
||||
|
||||
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
||||
invoice_expense_map, expense_accounts)
|
||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||
@ -52,10 +54,17 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
# map expense values
|
||||
base_net_total = 0
|
||||
for expense_acc in expense_accounts:
|
||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||
if inv.is_internal_supplier and inv.company == inv.represents_company:
|
||||
expense_amount = 0
|
||||
else:
|
||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||
base_net_total += expense_amount
|
||||
row.append(expense_amount)
|
||||
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.append(flt(internal_invoice_map.get((inv.name, account))))
|
||||
|
||||
# net total
|
||||
row.append(base_net_total or inv.base_net_total)
|
||||
|
||||
@ -96,7 +105,8 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
"width": 80
|
||||
}
|
||||
]
|
||||
expense_accounts = tax_accounts = expense_columns = tax_columns = []
|
||||
expense_accounts = tax_accounts = expense_columns = tax_columns = unrealized_profit_loss_accounts = \
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
expense_accounts = frappe.db.sql_list("""select distinct expense_account
|
||||
@ -112,17 +122,25 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
and parent in (%s) order by account_head""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
|
||||
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||
order by unrealized_profit_loss_account""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
||||
unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
|
||||
|
||||
for account in tax_accounts:
|
||||
if account not in expense_accounts:
|
||||
tax_columns.append(account + ":Currency/currency:120")
|
||||
|
||||
columns = columns + expense_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \
|
||||
columns = columns + expense_columns + unrealized_profit_loss_account_columns + \
|
||||
[_("Net Total") + ":Currency/currency:120"] + tax_columns + \
|
||||
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
|
||||
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
|
||||
|
||||
return columns, expense_accounts, tax_accounts
|
||||
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
@ -199,6 +217,19 @@ def get_invoice_expense_map(invoice_list):
|
||||
|
||||
return invoice_expense_map
|
||||
|
||||
def get_internal_invoice_map(invoice_list):
|
||||
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
|
||||
and is_internal_supplier = 1 and company = represents_company""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
||||
|
||||
internal_invoice_map = {}
|
||||
for d in unrealized_amount_details:
|
||||
if d.unrealized_profit_loss_account:
|
||||
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
|
||||
|
||||
return internal_invoice_map
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
tax_details = frappe.db.sql("""
|
||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
||||
|
@ -15,13 +15,14 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
||||
if not filters: filters = frappe._dict({})
|
||||
|
||||
invoice_list = get_invoices(filters, additional_query_columns)
|
||||
columns, income_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
|
||||
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(invoice_list, additional_table_columns)
|
||||
|
||||
if not invoice_list:
|
||||
msgprint(_("No record found"))
|
||||
return columns, invoice_list
|
||||
|
||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
|
||||
invoice_income_map, income_accounts)
|
||||
#Cost Center & Warehouse Map
|
||||
@ -70,12 +71,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
||||
# map income values
|
||||
base_net_total = 0
|
||||
for income_acc in income_accounts:
|
||||
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
|
||||
if inv.is_internal_customer and inv.company == inv.represents_company:
|
||||
income_amount = 0
|
||||
else:
|
||||
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
|
||||
|
||||
base_net_total += income_amount
|
||||
row.update({
|
||||
frappe.scrub(income_acc): income_amount
|
||||
})
|
||||
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.update({
|
||||
frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
|
||||
})
|
||||
|
||||
# net total
|
||||
row.update({'net_total': base_net_total or inv.base_net_total})
|
||||
|
||||
@ -230,6 +241,8 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tax_accounts = []
|
||||
income_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
income_accounts = frappe.db.sql_list("""select distinct income_account
|
||||
@ -243,12 +256,18 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
and parent in (%s) order by account_head""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
|
||||
from `tabSales Invoice` where docstatus = 1 and name in (%s)
|
||||
and ifnull(unrealized_profit_loss_account, '') != ''
|
||||
order by unrealized_profit_loss_account""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
|
||||
|
||||
for account in income_accounts:
|
||||
income_columns.append({
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": 'currency',
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
})
|
||||
|
||||
@ -258,15 +277,24 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": 'currency',
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
})
|
||||
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
unrealized_profit_loss_account_columns.append({
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
})
|
||||
|
||||
net_total_column = [{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": 'currency',
|
||||
"options": "currency",
|
||||
"width": 120
|
||||
}]
|
||||
|
||||
@ -301,9 +329,10 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
}
|
||||
]
|
||||
|
||||
columns = columns + income_columns + net_total_column + tax_columns + total_columns
|
||||
columns = columns + income_columns + unrealized_profit_loss_account_columns + \
|
||||
net_total_column + tax_columns + total_columns
|
||||
|
||||
return columns, income_accounts, tax_accounts
|
||||
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
@ -368,7 +397,8 @@ def get_invoices(filters, additional_query_columns):
|
||||
return frappe.db.sql("""
|
||||
select name, posting_date, debit_to, project, customer,
|
||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount {0}
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
||||
is_internal_customer, represents_company, company {0}
|
||||
from `tabSales Invoice`
|
||||
where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
|
||||
conditions, filters, as_dict=1)
|
||||
@ -385,6 +415,19 @@ def get_invoice_income_map(invoice_list):
|
||||
|
||||
return invoice_income_map
|
||||
|
||||
def get_internal_invoice_map(invoice_list):
|
||||
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
|
||||
base_net_total as amount from `tabSales Invoice` where name in (%s)
|
||||
and is_internal_customer = 1 and company = represents_company""" %
|
||||
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
|
||||
|
||||
internal_invoice_map = {}
|
||||
for d in unrealized_amount_details:
|
||||
if d.unrealized_profit_loss_account:
|
||||
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
|
||||
|
||||
return internal_invoice_map
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
tax_details = frappe.db.sql("""select parent, account_head,
|
||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||
|
@ -164,16 +164,16 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
|
||||
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
|
||||
let me = this;
|
||||
frappe.model.with_doc("Supplier", me.frm.doc.supplier, () => {
|
||||
let supplier = frappe.model.get_doc("Supplier", me.frm.doc.supplier);
|
||||
let internal = supplier.is_internal_supplier;
|
||||
let disabled = supplier.disabled;
|
||||
if (internal === 1 && disabled === 0) {
|
||||
me.frm.add_custom_button("Inter Company Order", function() {
|
||||
me.make_inter_company_order(me.frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
});
|
||||
let internal = me.frm.doc.is_internal_supplier;
|
||||
if (internal) {
|
||||
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Sales Order" :
|
||||
"Inter Company Sales Order";
|
||||
|
||||
me.frm.add_custom_button(button_label, function() {
|
||||
me.make_inter_company_order(me.frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,7 +353,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
make_purchase_receipt: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
||||
frm: cur_frm
|
||||
frm: cur_frm,
|
||||
freeze_message: __("Creating Purchase Receipt ...")
|
||||
})
|
||||
},
|
||||
|
||||
@ -380,7 +381,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99],
|
||||
per_ordered: ["<", 100],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
|
@ -134,6 +134,8 @@
|
||||
"ref_sq",
|
||||
"column_break_74",
|
||||
"party_account_currency",
|
||||
"is_internal_supplier",
|
||||
"represents_company",
|
||||
"inter_company_order_reference"
|
||||
],
|
||||
"fields": [
|
||||
@ -1101,13 +1103,28 @@
|
||||
{
|
||||
"fieldname": "items_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "supplier.is_internal_supplier",
|
||||
"fieldname": "is_internal_supplier",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Internal Supplier"
|
||||
},
|
||||
{
|
||||
"fetch_from": "supplier.represents_company",
|
||||
"fieldname": "represents_company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-03 16:46:44.229351",
|
||||
"modified": "2021-01-20 22:07:23.487138",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -123,8 +123,8 @@ class PurchaseOrder(BuyingController):
|
||||
if self.is_subcontracted == "Yes":
|
||||
for item in self.items:
|
||||
if not item.bom:
|
||||
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}"\
|
||||
.format(item.item_code, item.idx)))
|
||||
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}")
|
||||
.format(item.item_code, item.idx))
|
||||
|
||||
def get_schedule_dates(self):
|
||||
for d in self.get('items'):
|
||||
|
@ -224,7 +224,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99],
|
||||
per_ordered: ["<", 100],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
@ -280,7 +280,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99]
|
||||
per_ordered: ["<", 100]
|
||||
}
|
||||
});
|
||||
dialog.hide();
|
||||
|
@ -52,7 +52,10 @@ class Supplier(TransactionBase):
|
||||
self.validate_internal_supplier()
|
||||
|
||||
def validate_internal_supplier(self):
|
||||
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
|
||||
internal_supplier = frappe.db.get_value("Supplier",
|
||||
{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
|
||||
|
||||
if internal_supplier:
|
||||
frappe.throw(_("Internal Supplier for company {0} already exists").format(
|
||||
frappe.bold(self.represents_company)))
|
||||
|
||||
|
@ -44,7 +44,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
||||
material_request_type: "Purchase",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_ordered: ["<", 99.99],
|
||||
per_ordered: ["<", 100],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
|
@ -89,6 +89,9 @@ class AccountsController(TransactionBase):
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
|
||||
self.validate_date_with_fiscal_year()
|
||||
self.validate_inter_company_reference()
|
||||
|
||||
self.set_incoming_rate()
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
self.calculate_taxes_and_totals()
|
||||
@ -124,14 +127,20 @@ class AccountsController(TransactionBase):
|
||||
self.set_inter_company_account()
|
||||
|
||||
validate_regional(self)
|
||||
|
||||
|
||||
validate_einvoice_fields(self)
|
||||
|
||||
if self.doctype != 'Material Request':
|
||||
apply_pricing_rule_on_transaction(self)
|
||||
|
||||
|
||||
def before_cancel(self):
|
||||
validate_einvoice_fields(self)
|
||||
|
||||
def on_trash(self):
|
||||
# delete sl and gl entries on deletion of transaction
|
||||
if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
|
||||
frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
|
||||
frappe.db.sql("delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
|
||||
|
||||
def validate_deferred_start_and_end_date(self):
|
||||
for d in self.items:
|
||||
@ -223,6 +232,17 @@ class AccountsController(TransactionBase):
|
||||
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
||||
self.meta.get_label(date_field), self)
|
||||
|
||||
def validate_inter_company_reference(self):
|
||||
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||
return
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
|
||||
or self.get('inter_company_order_reference')):
|
||||
msg = _("Internal Sale or Delivery Reference missing. ")
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
||||
def validate_due_date(self):
|
||||
if self.get('is_pos'): return
|
||||
|
||||
@ -299,6 +319,7 @@ class AccountsController(TransactionBase):
|
||||
args["doctype"] = self.doctype
|
||||
args["name"] = self.name
|
||||
args["child_docname"] = item.name
|
||||
args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0
|
||||
|
||||
if not args.get("transaction_date"):
|
||||
args["transaction_date"] = args.get("posting_date")
|
||||
@ -465,8 +486,10 @@ class AccountsController(TransactionBase):
|
||||
account_currency = get_account_currency(gl_dict.account)
|
||||
|
||||
if gl_dict.account and self.doctype not in ["Journal Entry",
|
||||
"Period Closing Voucher", "Payment Entry"]:
|
||||
"Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
|
||||
self.validate_account_currency(gl_dict.account, account_currency)
|
||||
|
||||
if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
|
||||
set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
|
||||
self.company_currency)
|
||||
|
||||
@ -979,9 +1002,9 @@ class AccountsController(TransactionBase):
|
||||
It will an internal transfer if its an internal customer and representation
|
||||
company is same as billing company
|
||||
"""
|
||||
if self.doctype == 'Sales Invoice':
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
|
||||
internal_party_field = 'is_internal_customer'
|
||||
else:
|
||||
elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||
internal_party_field = 'is_internal_supplier'
|
||||
|
||||
if self.get(internal_party_field) and (self.represents_company == self.company):
|
||||
|
@ -34,7 +34,6 @@ class BuyingController(StockController):
|
||||
self.validate_items()
|
||||
self.set_qty_as_per_stock_uom()
|
||||
self.validate_stock_or_nonstock_items()
|
||||
self.update_tax_category_for_internal_transfer()
|
||||
self.validate_warehouse()
|
||||
self.validate_from_warehouse()
|
||||
self.set_supplier_address()
|
||||
@ -90,11 +89,6 @@ class BuyingController(StockController):
|
||||
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
|
||||
self.update_tax_category(msg)
|
||||
|
||||
def update_tax_category_for_internal_transfer(self):
|
||||
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
|
||||
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
|
||||
self.update_tax_category(msg)
|
||||
|
||||
def update_tax_category(self, msg):
|
||||
tax_for_valuation = [d for d in self.get("taxes")
|
||||
if d.category in ["Valuation", "Valuation and Total"]]
|
||||
@ -214,6 +208,48 @@ class BuyingController(StockController):
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
def set_incoming_rate(self):
|
||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
||||
return
|
||||
|
||||
ref_doctype_map = {
|
||||
"Purchase Order": "Sales Order Item",
|
||||
"Purchase Receipt": "Delivery Note Item",
|
||||
"Purchase Invoice": "Sales Invoice Item",
|
||||
}
|
||||
|
||||
ref_doctype = ref_doctype_map.get(self.doctype)
|
||||
items = self.get("items")
|
||||
for d in items:
|
||||
if not cint(self.get("is_return")):
|
||||
# Get outgoing rate based on original item cost based on valuation method
|
||||
|
||||
if not d.get(frappe.scrub(ref_doctype)):
|
||||
outgoing_rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.get('from_warehouse'),
|
||||
"posting_date": self.get('posting_date') or self.get('transation_date'),
|
||||
"posting_time": self.get('posting_time'),
|
||||
"qty": -1 * flt(d.get('stock_qty')),
|
||||
"serial_no": d.get('serial_no'),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
|
||||
rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
|
||||
else:
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||
.format(d.idx), alert=1)
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
supplied_items_cost = 0.0
|
||||
for d in self.get("supplied_items"):
|
||||
@ -233,7 +269,7 @@ class BuyingController(StockController):
|
||||
|
||||
d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount"))
|
||||
supplied_items_cost += flt(d.amount)
|
||||
|
||||
|
||||
return supplied_items_cost
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
@ -549,6 +585,8 @@ class BuyingController(StockController):
|
||||
from_warehouse_sle = self.get_sl_entries(d, {
|
||||
"actual_qty": -1 * pr_qty,
|
||||
"warehouse": d.from_warehouse,
|
||||
"outgoing_rate": d.rate,
|
||||
"recalculate_rate": 1,
|
||||
"dependant_sle_voucher_detail_no": d.name
|
||||
})
|
||||
|
||||
|
@ -655,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """
|
||||
select name
|
||||
from `tabHealthcare Service Unit`
|
||||
where
|
||||
is_group = 0
|
||||
and company = {company}
|
||||
and name like {txt}""".format(
|
||||
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||
|
||||
if filters and filters.get('inpatient_record'):
|
||||
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
|
||||
|
||||
# if the patient is admitted, then appointments should be allowed against the admission service unit,
|
||||
# inspite of it being an Inpatient Occupancy service unit
|
||||
if service_unit:
|
||||
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
|
||||
else:
|
||||
query += " and allow_appointments = 1"
|
||||
else:
|
||||
query += " and allow_appointments = 1"
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
@ -262,6 +262,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
|
||||
if doc.get("is_return"):
|
||||
if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
|
||||
doc.consolidated_invoice = ""
|
||||
doc.set('payments', [])
|
||||
for data in source.payments:
|
||||
paid_amount = 0.00
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form
|
||||
from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime
|
||||
from frappe import _, throw
|
||||
from erpnext.stock.get_item_details import get_bin_details
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
@ -39,7 +39,6 @@ class SellingController(StockController):
|
||||
self.set_customer_address()
|
||||
self.validate_for_duplicate_items()
|
||||
self.validate_target_warehouse()
|
||||
self.set_incoming_rate()
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
|
||||
@ -302,7 +301,7 @@ class SellingController(StockController):
|
||||
sales_order.update_reserved_qty(so_item_rows)
|
||||
|
||||
def set_incoming_rate(self):
|
||||
if self.doctype not in ("Delivery Note", "Sales Invoice"):
|
||||
if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
|
||||
return
|
||||
|
||||
items = self.get("items") + (self.get("packed_items") or [])
|
||||
@ -312,15 +311,26 @@ class SellingController(StockController):
|
||||
d.incoming_rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1*flt(d.qty),
|
||||
"serial_no": d.serial_no,
|
||||
"posting_date": self.get('posting_date') or self.get('transaction_date'),
|
||||
"posting_time": self.get('posting_time') or nowtime(),
|
||||
"qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
|
||||
"serial_no": d.get('serial_no'),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
|
||||
# For internal transfers use incoming rate as the valuation rate
|
||||
if self.is_internal_transfer():
|
||||
rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||
.format(d.idx), alert=1)
|
||||
|
||||
elif self.get("return_against"):
|
||||
# Get incoming rate of return entry from reference document
|
||||
# based on original item cost as per valuation method
|
||||
@ -381,7 +391,7 @@ class SellingController(StockController):
|
||||
})
|
||||
if item_row.warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||
|
||||
|
||||
return sle
|
||||
|
||||
def set_po_nos(self, for_validate=False):
|
||||
@ -449,13 +459,19 @@ class SellingController(StockController):
|
||||
non_stock_items = [d.item_code, d.description]
|
||||
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
||||
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
|
||||
duplicate_items_msg += "<br><br>"
|
||||
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
||||
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
|
||||
get_link_to_form("Selling Settings", "Selling Settings")
|
||||
)
|
||||
if stock_items in check_list:
|
||||
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
|
||||
frappe.throw(duplicate_items_msg)
|
||||
else:
|
||||
check_list.append(stock_items)
|
||||
else:
|
||||
if non_stock_items in chk_dupl_itm:
|
||||
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
|
||||
frappe.throw(duplicate_items_msg)
|
||||
else:
|
||||
chk_dupl_itm.append(non_stock_items)
|
||||
|
||||
|
@ -93,6 +93,12 @@ status_map = {
|
||||
["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"],
|
||||
["Closed", "eval:self.docstatus == 1 and self.pos_closing_entry"],
|
||||
["Cancelled", "eval:self.docstatus == 2"],
|
||||
],
|
||||
"POS Closing Entry": [
|
||||
["Draft", None],
|
||||
["Submitted", "eval:self.docstatus == 1"],
|
||||
["Queued", "eval:self.status == 'Queued'"],
|
||||
["Cancelled", "eval:self.docstatus == 2"],
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import frappe, erpnext
|
||||
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
||||
from frappe import _
|
||||
import frappe.defaults
|
||||
from collections import defaultdict
|
||||
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
@ -23,6 +24,8 @@ class StockController(AccountsController):
|
||||
self.validate_inspection()
|
||||
self.validate_serialized_batch()
|
||||
self.validate_customer_provided_item()
|
||||
self.validate_internal_transfer()
|
||||
self.validate_putaway_capacity()
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
if self.docstatus == 2:
|
||||
@ -72,6 +75,7 @@ class StockController(AccountsController):
|
||||
warehouse_with_no_account = []
|
||||
precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
|
||||
for item_row in voucher_details:
|
||||
|
||||
sle_list = sle_map.get(item_row.name)
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
@ -216,7 +220,7 @@ class StockController(AccountsController):
|
||||
""", (self.doctype, self.name), as_dict=True)
|
||||
|
||||
for sle in stock_ledger_entries:
|
||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
return stock_ledger
|
||||
|
||||
def make_batches(self, warehouse_field):
|
||||
@ -391,6 +395,84 @@ class StockController(AccountsController):
|
||||
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||
d.allow_zero_valuation_rate = 1
|
||||
|
||||
def validate_internal_transfer(self):
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
|
||||
and self.is_internal_transfer():
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
|
||||
def validate_in_transit_warehouses(self):
|
||||
if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
|
||||
for item in self.get('items'):
|
||||
if not item.target_warehouse:
|
||||
frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
|
||||
|
||||
if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
|
||||
for item in self.get('items'):
|
||||
if not item.from_warehouse:
|
||||
frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
|
||||
|
||||
def validate_multi_currency(self):
|
||||
if self.currency != self.company_currency:
|
||||
frappe.throw(_("Internal transfers can only be done in company's default currency"))
|
||||
|
||||
def validate_packed_items(self):
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
|
||||
frappe.throw(_("Packed Items cannot be transferred internally"))
|
||||
|
||||
def validate_putaway_capacity(self):
|
||||
# if over receipt is attempted while 'apply putaway rule' is disabled
|
||||
# and if rule was applied on the transaction, validate it.
|
||||
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
|
||||
valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
|
||||
"Stock Reconciliation")
|
||||
|
||||
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
|
||||
valid_doctype = False
|
||||
|
||||
if valid_doctype:
|
||||
rule_map = defaultdict(dict)
|
||||
for item in self.get("items"):
|
||||
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
|
||||
rule = frappe.db.get_value("Putaway Rule",
|
||||
{
|
||||
"item_code": item.get("item_code"),
|
||||
"warehouse": item.get(warehouse_field)
|
||||
},
|
||||
["name", "disable"], as_dict=True)
|
||||
if rule:
|
||||
if rule.get("disabled"): continue # dont validate for disabled rule
|
||||
|
||||
if self.doctype == "Stock Reconciliation":
|
||||
stock_qty = flt(item.qty)
|
||||
else:
|
||||
stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
|
||||
|
||||
rule_name = rule.get("name")
|
||||
if not rule_map[rule_name]:
|
||||
rule_map[rule_name]["warehouse"] = item.get(warehouse_field)
|
||||
rule_map[rule_name]["item"] = item.get("item_code")
|
||||
rule_map[rule_name]["qty_put"] = 0
|
||||
rule_map[rule_name]["capacity"] = get_available_putaway_capacity(rule_name)
|
||||
rule_map[rule_name]["qty_put"] += flt(stock_qty)
|
||||
|
||||
for rule, values in rule_map.items():
|
||||
if flt(values["qty_put"]) > flt(values["capacity"]):
|
||||
message = self.prepare_over_receipt_message(rule, values)
|
||||
frappe.throw(msg=message, title=_("Over Receipt"))
|
||||
|
||||
def prepare_over_receipt_message(self, rule, values):
|
||||
message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
|
||||
.format(
|
||||
frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
|
||||
frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
|
||||
)
|
||||
message += "<br><br>"
|
||||
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
|
||||
message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||
return message
|
||||
|
||||
def repost_future_sle_and_gle(self):
|
||||
args = frappe._dict({
|
||||
"posting_date": self.posting_date,
|
||||
|
@ -10,6 +10,7 @@ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
|
||||
validate_taxes_and_charges, validate_inclusive_tax
|
||||
from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
|
||||
|
||||
class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
@ -106,7 +107,7 @@ class calculate_taxes_and_totals(object):
|
||||
elif item.discount_amount and item.pricing_rules:
|
||||
item.rate = item.price_list_rate - item.discount_amount
|
||||
|
||||
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']:
|
||||
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']:
|
||||
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
|
||||
if flt(item.rate_with_margin) > 0:
|
||||
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
|
||||
@ -758,3 +759,35 @@ def get_rounded_tax_amount(itemised_tax, precision):
|
||||
for taxes in itemised_tax.values():
|
||||
for tax_account in taxes:
|
||||
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
|
||||
|
||||
class init_landed_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
|
||||
self.set_account_currency()
|
||||
self.set_exchange_rate()
|
||||
self.set_amounts_in_company_currency()
|
||||
|
||||
def set_account_currency(self):
|
||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||
for d in self.doc.get(self.tax_field):
|
||||
if not d.account_currency:
|
||||
account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
|
||||
d.account_currency = account_currency or company_currency
|
||||
|
||||
def set_exchange_rate(self):
|
||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||
for d in self.doc.get(self.tax_field):
|
||||
if d.account_currency == company_currency:
|
||||
d.exchange_rate = 1
|
||||
elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
|
||||
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
|
||||
account_currency=d.account_currency, company=self.doc.company)
|
||||
|
||||
if not d.exchange_rate:
|
||||
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
|
||||
|
||||
def set_amounts_in_company_currency(self):
|
||||
for d in self.doc.get(self.tax_field):
|
||||
d.amount = flt(d.amount, d.precision("amount"))
|
||||
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
|
@ -8,12 +8,12 @@
|
||||
"is_mandatory": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-05-14 17:38:27.496696",
|
||||
"modified": "2021-01-21 15:28:52.483839",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Opportunity",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Opportunity",
|
||||
"show_full_form": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Opportunity",
|
||||
"validate_action": 1
|
||||
}
|
@ -44,6 +44,7 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
create_mpesa_settings(payment_gateway_name="Payment")
|
||||
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
|
||||
frappe.db.set_value("Account", mpesa_account, "account_currency", "KES")
|
||||
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES")
|
||||
|
||||
pos_invoice = create_pos_invoice(do_not_submit=1)
|
||||
pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 500})
|
||||
@ -69,6 +70,8 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
|
||||
self.assertEquals(integration_request.status, "Completed")
|
||||
|
||||
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
|
||||
|
||||
def create_mpesa_settings(payment_gateway_name="Express"):
|
||||
if frappe.db.exists("Mpesa Settings", payment_gateway_name):
|
||||
return frappe.get_doc("Mpesa Settings", payment_gateway_name)
|
||||
|
@ -100,7 +100,6 @@ class ClinicalProcedure(Document):
|
||||
allow_start = self.set_actual_qty()
|
||||
if allow_start:
|
||||
self.db_set('status', 'In Progress')
|
||||
insert_clinical_procedure_to_medical_record(self)
|
||||
return 'success'
|
||||
return 'insufficient stock'
|
||||
|
||||
@ -247,21 +246,3 @@ def make_procedure(source_name, target_doc=None):
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def insert_clinical_procedure_to_medical_record(doc):
|
||||
subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
|
||||
if doc.practitioner:
|
||||
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
|
||||
if subject and doc.notes:
|
||||
subject += '<br/>' + doc.notes
|
||||
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.start_date
|
||||
medical_record.reference_doctype = 'Clinical Procedure'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
@ -264,7 +264,7 @@ def get_filters(entry):
|
||||
|
||||
def get_current_healthcare_service_unit(inpatient_record):
|
||||
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
|
||||
if ip_record.inpatient_occupancies:
|
||||
if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies:
|
||||
return ip_record.inpatient_occupancies[-1].service_unit
|
||||
return
|
||||
|
||||
|
@ -142,11 +142,15 @@ def create_inpatient(patient):
|
||||
return inpatient_record
|
||||
|
||||
|
||||
def get_healthcare_service_unit():
|
||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||
def get_healthcare_service_unit(unit_name=None):
|
||||
if not unit_name:
|
||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||
else:
|
||||
service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
|
||||
|
||||
if not service_unit:
|
||||
service_unit = frappe.new_doc("Healthcare Service Unit")
|
||||
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
|
||||
service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
|
||||
service_unit.company = "_Test Company"
|
||||
service_unit.service_unit_type = get_service_unit_type()
|
||||
service_unit.inpatient_occupancy = 1
|
||||
|
@ -359,6 +359,7 @@
|
||||
{
|
||||
"fieldname": "normal_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Normal Test Result",
|
||||
"options": "Normal Test Result",
|
||||
"print_hide": 1
|
||||
},
|
||||
@ -380,6 +381,7 @@
|
||||
{
|
||||
"fieldname": "sensitivity_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Sensitivity Test Result",
|
||||
"options": "Sensitivity Test Result",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
@ -529,6 +531,7 @@
|
||||
{
|
||||
"fieldname": "descriptive_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Descriptive Test Result",
|
||||
"options": "Descriptive Test Result",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
@ -549,13 +552,14 @@
|
||||
{
|
||||
"fieldname": "organism_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Organism Test Result",
|
||||
"options": "Organism Test Result",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-30 18:18:38.516215",
|
||||
"modified": "2020-11-30 11:04:17.195848",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Lab Test",
|
||||
|
@ -17,11 +17,9 @@ class LabTest(Document):
|
||||
self.validate_result_values()
|
||||
self.db_set('submitted_date', getdate())
|
||||
self.db_set('status', 'Completed')
|
||||
insert_lab_test_to_medical_record(self)
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set('status', 'Cancelled')
|
||||
delete_lab_test_from_medical_record(self)
|
||||
self.reload()
|
||||
|
||||
def on_update(self):
|
||||
@ -330,60 +328,6 @@ def get_employee_by_user_id(user_id):
|
||||
return frappe.get_doc('Employee', emp_id)
|
||||
return None
|
||||
|
||||
def insert_lab_test_to_medical_record(doc):
|
||||
table_row = False
|
||||
subject = cstr(doc.lab_test_name)
|
||||
if doc.practitioner:
|
||||
subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '<br>'
|
||||
if doc.normal_test_items:
|
||||
item = doc.normal_test_items[0]
|
||||
comment = ''
|
||||
if item.lab_test_comment:
|
||||
comment = str(item.lab_test_comment)
|
||||
table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name
|
||||
|
||||
if item.lab_test_event:
|
||||
table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event
|
||||
|
||||
if item.result_value:
|
||||
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
|
||||
|
||||
if item.normal_range:
|
||||
table_row += ' ' + _('Normal Range: ') + item.normal_range
|
||||
table_row += ' ' + comment
|
||||
|
||||
elif doc.descriptive_test_items:
|
||||
item = doc.descriptive_test_items[0]
|
||||
|
||||
if item.lab_test_particulars and item.result_value:
|
||||
table_row = item.lab_test_particulars + ' ' + item.result_value
|
||||
|
||||
elif doc.sensitivity_test_items:
|
||||
item = doc.sensitivity_test_items[0]
|
||||
|
||||
if item.antibiotic and item.antibiotic_sensitivity:
|
||||
table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity
|
||||
|
||||
if table_row:
|
||||
subject += '<br>' + table_row
|
||||
if doc.lab_test_comment:
|
||||
subject += '<br>' + cstr(doc.lab_test_comment)
|
||||
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.result_date
|
||||
medical_record.reference_doctype = 'Lab Test'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions = True)
|
||||
|
||||
def delete_lab_test_from_medical_record(self):
|
||||
medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
|
||||
|
||||
if medical_record_id and medical_record_id[0][0]:
|
||||
frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_lab_test_prescribed(patient):
|
||||
|
@ -31,12 +31,12 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('service_unit', function(){
|
||||
frm.set_query('service_unit', function() {
|
||||
return {
|
||||
query: 'erpnext.controllers.queries.get_healthcare_service_units',
|
||||
filters: {
|
||||
'is_group': false,
|
||||
'allow_appointments': true,
|
||||
'company': frm.doc.company
|
||||
company: frm.doc.company,
|
||||
inpatient_record: frm.doc.inpatient_record
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -18,6 +18,7 @@ from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_pr
|
||||
class PatientAppointment(Document):
|
||||
def validate(self):
|
||||
self.validate_overlaps()
|
||||
self.validate_service_unit()
|
||||
self.set_appointment_datetime()
|
||||
self.validate_customer_created()
|
||||
self.set_status()
|
||||
@ -68,6 +69,19 @@ class PatientAppointment(Document):
|
||||
overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
|
||||
frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
|
||||
|
||||
def validate_service_unit(self):
|
||||
if self.inpatient_record and self.service_unit:
|
||||
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||
|
||||
is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit,
|
||||
'inpatient_occupancy')
|
||||
service_unit = get_current_healthcare_service_unit(self.inpatient_record)
|
||||
if is_inpatient_occupancy_unit and service_unit != self.service_unit:
|
||||
msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '<br>'
|
||||
msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.')
|
||||
frappe.throw(msg, title=_('Invalid Healthcare Service Unit'))
|
||||
|
||||
|
||||
def set_appointment_datetime(self):
|
||||
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
|
||||
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
|
||||
from frappe.utils import nowdate, add_days
|
||||
from frappe.utils import nowdate, add_days, now_datetime
|
||||
from frappe.utils.make_random import get_random
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
@ -78,6 +78,59 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
|
||||
|
||||
def test_appointment_booking_for_admission_service_unit(self):
|
||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
|
||||
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
patient = create_patient()
|
||||
# Schedule Admission
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
|
||||
# Admit
|
||||
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
|
||||
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
|
||||
self.assertEqual(appointment.service_unit, service_unit)
|
||||
|
||||
# Discharge
|
||||
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
mark_invoiced_inpatient_occupancy(ip_record1)
|
||||
discharge_patient(ip_record1)
|
||||
|
||||
def test_invalid_healthcare_service_unit_validation(self):
|
||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
|
||||
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
patient = create_patient()
|
||||
# Schedule Admission
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
|
||||
# Admit
|
||||
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
|
||||
appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
|
||||
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
|
||||
self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
|
||||
|
||||
# Discharge
|
||||
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
mark_invoiced_inpatient_occupancy(ip_record1)
|
||||
discharge_patient(ip_record1)
|
||||
|
||||
|
||||
def create_healthcare_docs():
|
||||
patient = create_patient()
|
||||
@ -125,7 +178,7 @@ def create_encounter(appointment):
|
||||
encounter.submit()
|
||||
return encounter
|
||||
|
||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0):
|
||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
|
||||
item = create_healthcare_service_items()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
|
||||
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
|
||||
@ -136,12 +189,15 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
|
||||
appointment.appointment_date = appointment_date
|
||||
appointment.company = '_Test Company'
|
||||
appointment.duration = 15
|
||||
if service_unit:
|
||||
appointment.service_unit = service_unit
|
||||
if invoice:
|
||||
appointment.mode_of_payment = 'Cash'
|
||||
appointment.paid_amount = 500
|
||||
if procedure_template:
|
||||
appointment.procedure_template = create_clinical_procedure_template().get('name')
|
||||
appointment.save(ignore_permissions=True)
|
||||
if save:
|
||||
appointment.save(ignore_permissions=True)
|
||||
return appointment
|
||||
|
||||
def create_healthcare_service_items():
|
||||
@ -152,6 +208,7 @@ def create_healthcare_service_items():
|
||||
item.item_name = 'Consulting Charges'
|
||||
item.item_group = 'Services'
|
||||
item.is_stock_item = 0
|
||||
item.stock_uom = 'Nos'
|
||||
item.save()
|
||||
return item.name
|
||||
|
||||
|
@ -210,7 +210,7 @@
|
||||
{
|
||||
"fieldname": "drug_prescription",
|
||||
"fieldtype": "Table",
|
||||
"label": "Items",
|
||||
"label": "Drug Prescription",
|
||||
"options": "Drug Prescription"
|
||||
},
|
||||
{
|
||||
@ -328,7 +328,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-16 21:00:08.644531",
|
||||
"modified": "2020-11-30 10:39:00.783119",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient Encounter",
|
||||
|
@ -17,10 +17,6 @@ class PatientEncounter(Document):
|
||||
def on_update(self):
|
||||
if self.appointment:
|
||||
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
|
||||
update_encounter_medical_record(self)
|
||||
|
||||
def after_insert(self):
|
||||
insert_encounter_to_medical_record(self)
|
||||
|
||||
def on_submit(self):
|
||||
if self.therapies:
|
||||
@ -33,8 +29,6 @@ class PatientEncounter(Document):
|
||||
if self.inpatient_record and self.drug_prescription:
|
||||
delete_ip_medication_order(self)
|
||||
|
||||
delete_medical_record(self)
|
||||
|
||||
def set_title(self):
|
||||
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
|
||||
self.practitioner_name or self.practitioner)[:100]
|
||||
@ -102,61 +96,7 @@ def create_therapy_plan(encounter):
|
||||
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
|
||||
|
||||
|
||||
def insert_encounter_to_medical_record(doc):
|
||||
subject = set_subject_field(doc)
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.encounter_date
|
||||
medical_record.reference_doctype = 'Patient Encounter'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def update_encounter_medical_record(encounter):
|
||||
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
|
||||
|
||||
if medical_record_id and medical_record_id[0][0]:
|
||||
subject = set_subject_field(encounter)
|
||||
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
|
||||
else:
|
||||
insert_encounter_to_medical_record(encounter)
|
||||
|
||||
|
||||
def delete_medical_record(encounter):
|
||||
record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name})
|
||||
if record:
|
||||
frappe.delete_doc('Patient Medical Record', record, force=1)
|
||||
|
||||
def delete_ip_medication_order(encounter):
|
||||
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
|
||||
if record:
|
||||
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
||||
|
||||
|
||||
def set_subject_field(encounter):
|
||||
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
|
||||
if encounter.symptoms:
|
||||
subject += frappe.bold(_('Symptoms: ')) + '<br>'
|
||||
for entry in encounter.symptoms:
|
||||
subject += cstr(entry.complaint) + '<br>'
|
||||
else:
|
||||
subject += frappe.bold(_('No Symptoms')) + '<br>'
|
||||
|
||||
if encounter.diagnosis:
|
||||
subject += frappe.bold(_('Diagnosis: ')) + '<br>'
|
||||
for entry in encounter.diagnosis:
|
||||
subject += cstr(entry.diagnosis) + '<br>'
|
||||
else:
|
||||
subject += frappe.bold(_('No Diagnosis')) + '<br>'
|
||||
|
||||
if encounter.drug_prescription:
|
||||
subject += '<br>' + _('Drug(s) Prescribed.')
|
||||
if encounter.lab_test_prescription:
|
||||
subject += '<br>' + _('Test(s) Prescribed.')
|
||||
if encounter.procedure_prescription:
|
||||
subject += '<br>' + _('Procedure(s) Prescribed.')
|
||||
|
||||
return subject
|
||||
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
@ -0,0 +1,55 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-25 13:40:23.054469",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"date_fieldname",
|
||||
"add_edit_fields",
|
||||
"selected_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "selected_fields",
|
||||
"fieldtype": "Code",
|
||||
"label": "Selected Fields",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "add_edit_fields",
|
||||
"fieldtype": "Button",
|
||||
"in_list_view": 1,
|
||||
"label": "Add / Edit Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "date_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Date Fieldname",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-30 13:54:37.474671",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient History Custom Document Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class PatientHistoryCustomDocumentType(Document):
|
||||
pass
|
@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Patient History Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query('document_type', 'custom_doctypes', () => {
|
||||
return {
|
||||
filters: {
|
||||
custom: 1,
|
||||
is_submittable: 1,
|
||||
module: 'Healthcare',
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
field_selector: function(frm, doc, standard=1) {
|
||||
let document_fields = [];
|
||||
if (doc.selected_fields)
|
||||
document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname);
|
||||
|
||||
frm.call({
|
||||
method: 'get_doctype_fields',
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
document_type: doc.document_type,
|
||||
fields: document_fields
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
let doctype = 'Patient History Custom Document Type';
|
||||
if (standard)
|
||||
doctype = 'Patient History Standard Document Type';
|
||||
|
||||
frm.events.show_field_selector_dialog(frm, doc, doctype, r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
show_field_selector_dialog: function(frm, doc, doctype, doc_fields) {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __('{0} Fields', [__(doc.document_type)]),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Fields'),
|
||||
fieldtype: 'MultiCheck',
|
||||
fieldname: 'fields',
|
||||
options: doc_fields,
|
||||
columns: 2
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
d.$body.prepend(`
|
||||
<div class="columns-search">
|
||||
<input type="text" placeholder="${__('Search')}" data-element="search" class="form-control input-xs">
|
||||
</div>`
|
||||
);
|
||||
|
||||
frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area');
|
||||
|
||||
d.set_primary_action(__('Save'), () => {
|
||||
let values = d.get_values().fields;
|
||||
|
||||
let selected_fields = [];
|
||||
|
||||
frappe.model.with_doctype(doc.document_type, function() {
|
||||
for (let idx in values) {
|
||||
let value = values[idx];
|
||||
|
||||
let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0];
|
||||
if (field) {
|
||||
selected_fields.push({
|
||||
label: field.label,
|
||||
fieldname: field.fieldname,
|
||||
fieldtype: field.fieldtype
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
d.refresh();
|
||||
frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields));
|
||||
});
|
||||
|
||||
d.hide();
|
||||
});
|
||||
|
||||
d.show();
|
||||
},
|
||||
|
||||
get_date_field_for_dt: function(frm, row) {
|
||||
frm.call({
|
||||
method: 'get_date_field_for_dt',
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
document_type: row.document_type
|
||||
},
|
||||
callback: function(data) {
|
||||
if (data.message) {
|
||||
frappe.model.set_value('Patient History Custom Document Type',
|
||||
row.name, 'date_fieldname', data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Patient History Custom Document Type', {
|
||||
document_type: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.document_type) {
|
||||
frm.events.get_date_field_for_dt(frm, row);
|
||||
}
|
||||
},
|
||||
|
||||
add_edit_fields: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.document_type) {
|
||||
frm.events.field_selector(frm, row, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Patient History Standard Document Type', {
|
||||
add_edit_fields: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.document_type) {
|
||||
frm.events.field_selector(frm, row);
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-25 13:41:37.675518",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"standard_doctypes",
|
||||
"section_break_2",
|
||||
"custom_doctypes"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Custom Document Types",
|
||||
"options": "Patient History Custom Document Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "standard_doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Standard Document Types",
|
||||
"options": "Patient History Standard Document Type",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-25 13:43:38.511771",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient History Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, cint
|
||||
from frappe.model.document import Document
|
||||
from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes
|
||||
|
||||
class PatientHistorySettings(Document):
|
||||
def validate(self):
|
||||
self.validate_submittable_doctypes()
|
||||
self.validate_date_fieldnames()
|
||||
|
||||
def validate_submittable_doctypes(self):
|
||||
for entry in self.custom_doctypes:
|
||||
if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')):
|
||||
msg = _('Row #{0}: Document Type {1} is not submittable. ').format(
|
||||
entry.idx, frappe.bold(entry.document_type))
|
||||
msg += _('Patient Medical Record can only be created for submittable document types.')
|
||||
frappe.throw(msg)
|
||||
|
||||
def validate_date_fieldnames(self):
|
||||
for entry in self.custom_doctypes:
|
||||
field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname)
|
||||
if not field:
|
||||
frappe.throw(_('Row #{0}: No such Field named {1} found in the Document Type {2}.').format(
|
||||
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
||||
|
||||
if field.fieldtype not in ['Date', 'Datetime']:
|
||||
frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
|
||||
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
||||
|
||||
def get_doctype_fields(self, document_type, fields):
|
||||
multicheck_fields = []
|
||||
doc_fields = frappe.get_meta(document_type).fields
|
||||
|
||||
for field in doc_fields:
|
||||
if field.fieldtype not in frappe.model.no_value_fields or \
|
||||
field.fieldtype in frappe.model.table_fields and not field.hidden:
|
||||
multicheck_fields.append({
|
||||
'label': field.label,
|
||||
'value': field.fieldname,
|
||||
'checked': 1 if field.fieldname in fields else 0
|
||||
})
|
||||
|
||||
return multicheck_fields
|
||||
|
||||
def get_date_field_for_dt(self, document_type):
|
||||
meta = frappe.get_meta(document_type)
|
||||
date_fields = meta.get('fields', {
|
||||
'fieldtype': ['in', ['Date', 'Datetime']]
|
||||
})
|
||||
|
||||
if date_fields:
|
||||
return date_fields[0].get('fieldname')
|
||||
|
||||
def create_medical_record(doc, method=None):
|
||||
medical_record_required = validate_medical_record_required(doc)
|
||||
if not medical_record_required:
|
||||
return
|
||||
|
||||
if frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }):
|
||||
return
|
||||
|
||||
subject = set_subject_field(doc)
|
||||
date_field = get_date_field(doc.doctype)
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.get(date_field)
|
||||
medical_record.reference_doctype = doc.doctype
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def update_medical_record(doc, method=None):
|
||||
medical_record_required = validate_medical_record_required(doc)
|
||||
if not medical_record_required:
|
||||
return
|
||||
|
||||
medical_record_id = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
|
||||
|
||||
if medical_record_id:
|
||||
subject = set_subject_field(doc)
|
||||
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
|
||||
else:
|
||||
create_medical_record(doc)
|
||||
|
||||
|
||||
def delete_medical_record(doc, method=None):
|
||||
medical_record_required = validate_medical_record_required(doc)
|
||||
if not medical_record_required:
|
||||
return
|
||||
|
||||
record = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
|
||||
if record:
|
||||
frappe.delete_doc('Patient Medical Record', record, force=1)
|
||||
|
||||
|
||||
def set_subject_field(doc):
|
||||
from frappe.utils.formatters import format_value
|
||||
|
||||
meta = frappe.get_meta(doc.doctype)
|
||||
subject = ''
|
||||
patient_history_fields = get_patient_history_fields(doc)
|
||||
|
||||
for entry in patient_history_fields:
|
||||
fieldname = entry.get('fieldname')
|
||||
if entry.get('fieldtype') == 'Table' and doc.get(fieldname):
|
||||
formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname))
|
||||
subject += frappe.bold(_(entry.get('label')) + ': ') + '<br>' + cstr(formatted_value) + '<br>'
|
||||
|
||||
else:
|
||||
if doc.get(fieldname):
|
||||
formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc)
|
||||
subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '<br>'
|
||||
|
||||
return subject
|
||||
|
||||
|
||||
def get_date_field(doctype):
|
||||
dt = get_patient_history_config_dt(doctype)
|
||||
|
||||
return frappe.db.get_value(dt, { 'document_type': doctype }, 'date_fieldname')
|
||||
|
||||
|
||||
def get_patient_history_fields(doc):
|
||||
dt = get_patient_history_config_dt(doc.doctype)
|
||||
patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields')
|
||||
|
||||
if patient_history_fields:
|
||||
return json.loads(patient_history_fields)
|
||||
|
||||
|
||||
def get_formatted_value_for_table_field(items, df):
|
||||
child_meta = frappe.get_meta(df.options)
|
||||
|
||||
table_head = ''
|
||||
table_row = ''
|
||||
html = ''
|
||||
create_head = True
|
||||
for item in items:
|
||||
table_row += '<tr>'
|
||||
for cdf in child_meta.fields:
|
||||
if cdf.in_list_view:
|
||||
if create_head:
|
||||
table_head += '<td>' + cdf.label + '</td>'
|
||||
if item.get(cdf.fieldname):
|
||||
table_row += '<td>' + str(item.get(cdf.fieldname)) + '</td>'
|
||||
else:
|
||||
table_row += '<td></td>'
|
||||
create_head = False
|
||||
table_row += '</tr>'
|
||||
|
||||
html += "<table class='table table-condensed table-bordered'>" + table_head + table_row + "</table>"
|
||||
|
||||
return html
|
||||
|
||||
|
||||
def get_patient_history_config_dt(doctype):
|
||||
if frappe.db.get_value('DocType', doctype, 'custom'):
|
||||
return 'Patient History Custom Document Type'
|
||||
else:
|
||||
return 'Patient History Standard Document Type'
|
||||
|
||||
|
||||
def validate_medical_record_required(doc):
|
||||
if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \
|
||||
or get_module(doc) != 'Healthcare':
|
||||
return False
|
||||
|
||||
if doc.doctype not in get_patient_history_doctypes():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_module(doc):
|
||||
module = doc.meta.module
|
||||
if not module:
|
||||
module = frappe.db.get_value('DocType', doc.doctype, 'module')
|
||||
|
||||
return module
|
@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
import json
|
||||
from frappe.utils import getdate
|
||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
|
||||
|
||||
class TestPatientHistorySettings(unittest.TestCase):
|
||||
def setUp(self):
|
||||
dt = create_custom_doctype()
|
||||
settings = frappe.get_single("Patient History Settings")
|
||||
settings.append("custom_doctypes", {
|
||||
"document_type": dt.name,
|
||||
"date_fieldname": "date",
|
||||
"selected_fields": json.dumps([{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"label": "Rating",
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Rating"
|
||||
},
|
||||
{
|
||||
"label": "Feedback",
|
||||
"fieldname": "feedback",
|
||||
"fieldtype": "Small Text"
|
||||
}])
|
||||
})
|
||||
settings.save()
|
||||
|
||||
def test_custom_doctype_medical_record(self):
|
||||
# tests for medical record creation of standard doctypes in test_patient_medical_record.py
|
||||
patient = create_patient()
|
||||
doc = create_doc(patient)
|
||||
|
||||
# check for medical record
|
||||
medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name})
|
||||
self.assertTrue(medical_rec)
|
||||
|
||||
medical_rec = frappe.get_doc("Patient Medical Record", medical_rec)
|
||||
expected_subject = "<b>Date: </b>{0}<br><b>Rating: </b>3<br><b>Feedback: </b>Test Patient History Settings<br>".format(
|
||||
frappe.utils.format_date(getdate()))
|
||||
self.assertEqual(medical_rec.subject, expected_subject)
|
||||
self.assertEqual(medical_rec.patient, patient)
|
||||
self.assertEqual(medical_rec.communication_date, getdate())
|
||||
|
||||
|
||||
def create_custom_doctype():
|
||||
if not frappe.db.exists("DocType", "Test Patient Feedback"):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Healthcare",
|
||||
"custom": 1,
|
||||
"is_submittable": 1,
|
||||
"fields": [{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"label": "Patient",
|
||||
"fieldname": "patient",
|
||||
"fieldtype": "Link",
|
||||
"options": "Patient"
|
||||
},
|
||||
{
|
||||
"label": "Rating",
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Rating"
|
||||
},
|
||||
{
|
||||
"label": "Feedback",
|
||||
"fieldname": "feedback",
|
||||
"fieldtype": "Small Text"
|
||||
}],
|
||||
"permissions": [{
|
||||
"role": "System Manager",
|
||||
"read": 1
|
||||
}],
|
||||
"name": "Test Patient Feedback",
|
||||
})
|
||||
doc.insert()
|
||||
return doc
|
||||
else:
|
||||
return frappe.get_doc("DocType", "Test Patient Feedback")
|
||||
|
||||
|
||||
def create_doc(patient):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Test Patient Feedback",
|
||||
"patient": patient,
|
||||
"date": getdate(),
|
||||
"rating": 3,
|
||||
"feedback": "Test Patient History Settings"
|
||||
}).insert()
|
||||
doc.submit()
|
||||
|
||||
return doc
|
@ -0,0 +1,57 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-25 13:39:36.014814",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"date_fieldname",
|
||||
"add_edit_fields",
|
||||
"selected_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "selected_fields",
|
||||
"fieldtype": "Code",
|
||||
"label": "Selected Fields",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "add_edit_fields",
|
||||
"fieldtype": "Button",
|
||||
"in_list_view": 1,
|
||||
"label": "Add / Edit Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "date_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Date Fieldname",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-30 13:54:56.773325",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient History Standard Document Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class PatientHistoryStandardDocumentType(Document):
|
||||
pass
|
@ -18,6 +18,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
|
||||
encounter = create_encounter(appointment)
|
||||
|
||||
# check for encounter
|
||||
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name})
|
||||
self.assertTrue(medical_rec)
|
||||
|
@ -41,7 +41,6 @@ class TherapySession(Document):
|
||||
|
||||
def on_submit(self):
|
||||
self.update_sessions_count_in_therapy_plan()
|
||||
insert_session_medical_record(self)
|
||||
|
||||
def on_update(self):
|
||||
if self.appointment:
|
||||
@ -142,23 +141,3 @@ def get_therapy_item(therapy, item):
|
||||
item.reference_dt = 'Therapy Session'
|
||||
item.reference_dn = therapy.name
|
||||
return item
|
||||
|
||||
|
||||
def insert_session_medical_record(doc):
|
||||
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
|
||||
if doc.therapy_plan:
|
||||
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
|
||||
if doc.practitioner:
|
||||
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
|
||||
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
|
||||
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
|
||||
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.start_date
|
||||
medical_record.reference_doctype = 'Therapy Session'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
@ -12,47 +12,7 @@ class VitalSigns(Document):
|
||||
def validate(self):
|
||||
self.set_title()
|
||||
|
||||
def on_submit(self):
|
||||
insert_vital_signs_to_medical_record(self)
|
||||
|
||||
def on_cancel(self):
|
||||
delete_vital_signs_from_medical_record(self)
|
||||
|
||||
def set_title(self):
|
||||
self.title = _('{0} on {1}').format(self.patient_name or self.patient,
|
||||
frappe.utils.format_date(self.signs_date))[:100]
|
||||
|
||||
def insert_vital_signs_to_medical_record(doc):
|
||||
subject = set_subject_field(doc)
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.signs_date
|
||||
medical_record.reference_doctype = 'Vital Signs'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.flags.ignore_mandatory = True
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
||||
def delete_vital_signs_from_medical_record(doc):
|
||||
medical_record = frappe.db.get_value('Patient Medical Record', {'reference_name': doc.name})
|
||||
if medical_record:
|
||||
frappe.delete_doc('Patient Medical Record', medical_record)
|
||||
|
||||
def set_subject_field(doc):
|
||||
subject = ''
|
||||
if doc.temperature:
|
||||
subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
|
||||
if doc.pulse:
|
||||
subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
|
||||
if doc.respiratory_rate:
|
||||
subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
|
||||
if doc.bp:
|
||||
subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
|
||||
if doc.bmi:
|
||||
subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
|
||||
if doc.nutrition_note:
|
||||
subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
|
||||
|
||||
return subject
|
||||
|
@ -109,6 +109,11 @@
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.patient-history-filter {
|
||||
margin-left: 35px;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
#page-medical_record .plot-wrapper {
|
||||
padding: 20px 15px;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
|
@ -1,6 +1,5 @@
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-3">
|
||||
<p class="text-center">{%= __("Select Patient") %}</p>
|
||||
<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
|
||||
<div class="patient_details" style="z-index=0"></div>
|
||||
</div>
|
||||
@ -11,6 +10,13 @@
|
||||
<div id="chart" class="col-sm-12 patient_vital_charts">
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 d-flex">
|
||||
<div class="patient-history-filter doctype-filter"></div>
|
||||
<div class="patient-history-filter date-filter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 patient_documents_list">
|
||||
</div>
|
||||
<div class="col-sm-12 text-center py-3">
|
||||
|
@ -1,141 +1,225 @@
|
||||
frappe.provide("frappe.patient_history");
|
||||
frappe.provide('frappe.patient_history');
|
||||
frappe.pages['patient_history'].on_page_load = function(wrapper) {
|
||||
var me = this;
|
||||
var page = frappe.ui.make_app_page({
|
||||
let me = this;
|
||||
let page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Patient History',
|
||||
single_column: true
|
||||
});
|
||||
|
||||
frappe.breadcrumbs.add("Healthcare");
|
||||
frappe.breadcrumbs.add('Healthcare');
|
||||
let pid = '';
|
||||
page.main.html(frappe.render_template("patient_history", {}));
|
||||
var patient = frappe.ui.form.make_control({
|
||||
parent: page.main.find(".patient"),
|
||||
page.main.html(frappe.render_template('patient_history', {}));
|
||||
page.main.find('.header-separator').hide();
|
||||
|
||||
let patient = frappe.ui.form.make_control({
|
||||
parent: page.main.find('.patient'),
|
||||
df: {
|
||||
fieldtype: "Link",
|
||||
options: "Patient",
|
||||
fieldname: "patient",
|
||||
change: function(){
|
||||
if(pid != patient.get_value() && patient.get_value()){
|
||||
fieldtype: 'Link',
|
||||
options: 'Patient',
|
||||
fieldname: 'patient',
|
||||
placeholder: __('Select Patient'),
|
||||
only_select: true,
|
||||
change: function() {
|
||||
let patient_id = patient.get_value();
|
||||
if (pid != patient_id && patient_id) {
|
||||
me.start = 0;
|
||||
me.page.main.find(".patient_documents_list").html("");
|
||||
get_documents(patient.get_value(), me);
|
||||
show_patient_info(patient.get_value(), me);
|
||||
show_patient_vital_charts(patient.get_value(), me, "bp", "mmHg", "Blood Pressure");
|
||||
me.page.main.find('.patient_documents_list').html('');
|
||||
setup_filters(patient_id, me);
|
||||
get_documents(patient_id, me);
|
||||
show_patient_info(patient_id, me);
|
||||
show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure');
|
||||
}
|
||||
pid = patient.get_value();
|
||||
pid = patient_id;
|
||||
}
|
||||
},
|
||||
only_input: true,
|
||||
});
|
||||
patient.refresh();
|
||||
|
||||
if (frappe.route_options){
|
||||
if (frappe.route_options) {
|
||||
patient.set_value(frappe.route_options.patient);
|
||||
}
|
||||
|
||||
this.page.main.on("click", ".btn-show-chart", function() {
|
||||
var btn_show_id = $(this).attr("data-show-chart-id"), pts = $(this).attr("data-pts");
|
||||
var title = $(this).attr("data-title");
|
||||
this.page.main.on('click', '.btn-show-chart', function() {
|
||||
let btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts');
|
||||
let title = $(this).attr('data-title');
|
||||
show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
|
||||
});
|
||||
|
||||
this.page.main.on("click", ".btn-more", function() {
|
||||
var doctype = $(this).attr("data-doctype"), docname = $(this).attr("data-docname");
|
||||
if(me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched') == "1"){
|
||||
me.page.main.find("."+docname).hide();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').show();
|
||||
}else{
|
||||
if(doctype && docname){
|
||||
let exclude = ["patient", "patient_name", 'patient_sex', "encounter_date"];
|
||||
this.page.main.on('click', '.btn-more', function() {
|
||||
let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
|
||||
if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
|
||||
me.page.main.find('.'+docname).hide();
|
||||
me.page.main.find('.'+docname).parent().find('.document-html').show();
|
||||
} else {
|
||||
if (doctype && docname) {
|
||||
let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date'];
|
||||
frappe.call({
|
||||
method: "erpnext.healthcare.utils.render_doc_as_html",
|
||||
method: 'erpnext.healthcare.utils.render_doc_as_html',
|
||||
args:{
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
exclude_fields: exclude
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if (r.message){
|
||||
me.page.main.find("."+docname).hide();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').html(r.message.html+"\
|
||||
<div align='center'><a class='btn octicon octicon-chevron-up btn-default btn-xs\
|
||||
btn-less' data-doctype='"+doctype+"' data-docname='"+docname+"'></a></div>");
|
||||
me.page.main.find("."+docname).parent().find('.document-html').show();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched', "1");
|
||||
if (r.message) {
|
||||
me.page.main.find('.' + docname).hide();
|
||||
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').html(
|
||||
`${r.message.html}
|
||||
<div align='center'>
|
||||
<a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
|
||||
data-doctype='${doctype}'
|
||||
data-docname='${docname}'>
|
||||
</a>
|
||||
</div>
|
||||
`);
|
||||
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').show();
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
|
||||
}
|
||||
},
|
||||
freeze: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.page.main.on("click", ".btn-less", function() {
|
||||
var docname = $(this).attr("data-docname");
|
||||
me.page.main.find("."+docname).parent().find('.document-id').show();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').hide();
|
||||
this.page.main.on('click', '.btn-less', function() {
|
||||
let docname = $(this).attr('data-docname');
|
||||
me.page.main.find('.' + docname).parent().find('.document-id').show();
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').hide();
|
||||
});
|
||||
me.start = 0;
|
||||
me.page.main.on("click", ".btn-get-records", function(){
|
||||
me.page.main.on('click', '.btn-get-records', function() {
|
||||
get_documents(patient.get_value(), me);
|
||||
});
|
||||
};
|
||||
|
||||
var get_documents = function(patient, me){
|
||||
let setup_filters = function(patient, me) {
|
||||
$('.doctype-filter').empty();
|
||||
frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
|
||||
).then(document_types => {
|
||||
let doctype_filter = frappe.ui.form.make_control({
|
||||
parent: $('.doctype-filter'),
|
||||
df: {
|
||||
fieldtype: 'MultiSelectList',
|
||||
fieldname: 'document_type',
|
||||
placeholder: __('Select Document Type'),
|
||||
input_class: 'input-xs',
|
||||
change: () => {
|
||||
me.start = 0;
|
||||
me.page.main.find('.patient_documents_list').html('');
|
||||
get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value());
|
||||
},
|
||||
get_data: () => {
|
||||
return document_types.map(document_type => {
|
||||
return {
|
||||
description: document_type,
|
||||
value: document_type
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
doctype_filter.refresh();
|
||||
|
||||
$('.date-filter').empty();
|
||||
let date_range_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: 'DateRange',
|
||||
fieldname: 'date_range',
|
||||
placeholder: __('Date Range'),
|
||||
input_class: 'input-xs',
|
||||
change: () => {
|
||||
let selected_date_range = date_range_field.get_value();
|
||||
if (selected_date_range && selected_date_range.length === 2) {
|
||||
me.start = 0;
|
||||
me.page.main.find('.patient_documents_list').html('');
|
||||
get_documents(patient, me, doctype_filter.get_value(), selected_date_range);
|
||||
}
|
||||
}
|
||||
},
|
||||
parent: $('.date-filter')
|
||||
});
|
||||
date_range_field.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
let get_documents = function(patient, me, document_types="", selected_date_range="") {
|
||||
let filters = {
|
||||
name: patient,
|
||||
start: me.start,
|
||||
page_length: 20
|
||||
};
|
||||
if (document_types)
|
||||
filters['document_types'] = document_types;
|
||||
if (selected_date_range)
|
||||
filters['date_range'] = selected_date_range;
|
||||
|
||||
frappe.call({
|
||||
"method": "erpnext.healthcare.page.patient_history.patient_history.get_feed",
|
||||
args: {
|
||||
name: patient,
|
||||
start: me.start,
|
||||
page_length: 20
|
||||
},
|
||||
callback: function (r) {
|
||||
var data = r.message;
|
||||
if(data.length){
|
||||
'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
|
||||
args: filters,
|
||||
callback: function(r) {
|
||||
let data = r.message;
|
||||
if (data.length) {
|
||||
add_to_records(me, data);
|
||||
}else{
|
||||
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
|
||||
me.page.main.find(".btn-get-records").hide();
|
||||
} else {
|
||||
me.page.main.find('.patient_documents_list').append(`
|
||||
<div class='text-muted' align='center'>
|
||||
<br><br>${__('No more records..')}<br><br>
|
||||
</div>`);
|
||||
me.page.main.find('.btn-get-records').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var add_to_records = function(me, data){
|
||||
var details = "<ul class='nav nav-pills nav-stacked'>";
|
||||
var i;
|
||||
for(i=0; i<data.length; i++){
|
||||
if(data[i].reference_doctype){
|
||||
let add_to_records = function(me, data) {
|
||||
let details = "<ul class='nav nav-pills nav-stacked'>";
|
||||
let i;
|
||||
for (i=0; i<data.length; i++) {
|
||||
if (data[i].reference_doctype) {
|
||||
let label = '';
|
||||
if(data[i].subject){
|
||||
label += "<br/>"+data[i].subject;
|
||||
if (data[i].subject) {
|
||||
label += "<br/>" + data[i].subject;
|
||||
}
|
||||
data[i] = add_date_separator(data[i]);
|
||||
if(frappe.user_info(data[i].owner).image){
|
||||
|
||||
if (frappe.user_info(data[i].owner).image) {
|
||||
data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
data[i].imgsrc = false;
|
||||
}
|
||||
var time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
|
||||
time_line_heading += data[i].reference_doctype + " - "+ data[i].reference_name;
|
||||
details += `<li data-toggle='pill' class='patient_doc_menu'
|
||||
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
|
||||
<div class='col-sm-12 d-flex border-bottom py-3'>`;
|
||||
if (data[i].imgsrc){
|
||||
details += `<span class='mr-3'>
|
||||
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'>
|
||||
</img>
|
||||
</span>`;
|
||||
}else{
|
||||
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'><div align='center' class='standard-image'
|
||||
style='background-color: #fafbfc;'>${data[i].practitioner ? data[i].practitioner.charAt(0) : "U"}</div></span>`;
|
||||
|
||||
let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
|
||||
time_line_heading += data[i].reference_doctype + " - " +
|
||||
`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
|
||||
${data[i].reference_name}
|
||||
</a>`;
|
||||
|
||||
details += `
|
||||
<li data-toggle='pill' class='patient_doc_menu'
|
||||
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
|
||||
<div class='col-sm-12 d-flex border-bottom py-3'>`;
|
||||
|
||||
if (data[i].imgsrc) {
|
||||
details += `
|
||||
<span class='mr-3'>
|
||||
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'></img>
|
||||
</span>`;
|
||||
} else {
|
||||
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
|
||||
<div align='center' class='standard-image' style='background-color: #fafbfc;'>
|
||||
${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
|
||||
</div>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
details += `<div class='d-flex flex-column width-full'>
|
||||
<div>
|
||||
`+time_line_heading+` on
|
||||
`+time_line_heading+`
|
||||
<span>
|
||||
${data[i].date_sep}
|
||||
</span>
|
||||
@ -156,133 +240,150 @@ var add_to_records = function(me, data){
|
||||
</li>`;
|
||||
}
|
||||
}
|
||||
details += "</ul>";
|
||||
me.page.main.find(".patient_documents_list").append(details);
|
||||
|
||||
details += '</ul>';
|
||||
me.page.main.find('.patient_documents_list').append(details);
|
||||
me.start += data.length;
|
||||
if(data.length===20){
|
||||
|
||||
if (data.length === 20) {
|
||||
me.page.main.find(".btn-get-records").show();
|
||||
}else{
|
||||
} else {
|
||||
me.page.main.find(".btn-get-records").hide();
|
||||
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
|
||||
me.page.main.find(".patient_documents_list").append(`
|
||||
<div class='text-muted' align='center'>
|
||||
<br><br>${__('No more records..')}<br><br>
|
||||
</div>`);
|
||||
}
|
||||
};
|
||||
|
||||
var add_date_separator = function(data) {
|
||||
var date = frappe.datetime.str_to_obj(data.creation);
|
||||
let add_date_separator = function(data) {
|
||||
let date = frappe.datetime.str_to_obj(data.communication_date);
|
||||
let pdate = '';
|
||||
let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
|
||||
|
||||
var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
|
||||
if(diff < 1) {
|
||||
var pdate = 'Today';
|
||||
} else if(diff < 2) {
|
||||
pdate = 'Yesterday';
|
||||
if (diff < 1) {
|
||||
pdate = __('Today');
|
||||
} else if (diff < 2) {
|
||||
pdate = __('Yesterday');
|
||||
} else {
|
||||
pdate = frappe.datetime.global_date_format(date);
|
||||
pdate = __('on ') + frappe.datetime.global_date_format(date);
|
||||
}
|
||||
data.date_sep = pdate;
|
||||
return data;
|
||||
};
|
||||
|
||||
var show_patient_info = function(patient, me){
|
||||
let show_patient_info = function(patient, me) {
|
||||
frappe.call({
|
||||
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
|
||||
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
|
||||
args: {
|
||||
patient: patient
|
||||
},
|
||||
callback: function (r) {
|
||||
var data = r.message;
|
||||
var details = "";
|
||||
if(data.image){
|
||||
details += "<div><img class='thumbnail' width=75% src='"+data.image+"'></div>";
|
||||
callback: function(r) {
|
||||
let data = r.message;
|
||||
let details = '';
|
||||
if (data.image) {
|
||||
details += `<div><img class='thumbnail' width=75% src='${data.image}'></div>`;
|
||||
}
|
||||
details += "<b>" + data.patient_name +"</b><br>" + data.sex;
|
||||
if(data.email) details += "<br>" + data.email;
|
||||
if(data.mobile) details += "<br>" + data.mobile;
|
||||
if(data.occupation) details += "<br><br><b>Occupation :</b> " + data.occupation;
|
||||
if(data.blood_group) details += "<br><b>Blood group : </b> " + data.blood_group;
|
||||
if(data.allergies) details += "<br><br><b>Allergies : </b> "+ data.allergies.replace("\n", "<br>");
|
||||
if(data.medication) details += "<br><b>Medication : </b> "+ data.medication.replace("\n", "<br>");
|
||||
if(data.alcohol_current_use) details += "<br><br><b>Alcohol use : </b> "+ data.alcohol_current_use;
|
||||
if(data.alcohol_past_use) details += "<br><b>Alcohol past use : </b> "+ data.alcohol_past_use;
|
||||
if(data.tobacco_current_use) details += "<br><b>Tobacco use : </b> "+ data.tobacco_current_use;
|
||||
if(data.tobacco_past_use) details += "<br><b>Tobacco past use : </b> "+ data.tobacco_past_use;
|
||||
if(data.medical_history) details += "<br><br><b>Medical history : </b> "+ data.medical_history.replace("\n", "<br>");
|
||||
if(data.surgical_history) details += "<br><b>Surgical history : </b> "+ data.surgical_history.replace("\n", "<br>");
|
||||
if(data.surrounding_factors) details += "<br><br><b>Occupational hazards : </b> "+ data.surrounding_factors.replace("\n", "<br>");
|
||||
if(data.other_risk_factors) details += "<br><b>Other risk factors : </b> " + data.other_risk_factors.replace("\n", "<br>");
|
||||
if(data.patient_details) details += "<br><br><b>More info : </b> " + data.patient_details.replace("\n", "<br>");
|
||||
|
||||
if(details){
|
||||
details = "<div style='padding-left:10px; font-size:13px;' align='center'>" + details + "</div>";
|
||||
details += `<b> ${data.patient_name} </b><br> ${data.sex}`;
|
||||
if (data.email) details += `<br> ${data.email}`;
|
||||
if (data.mobile) details += `<br> ${data.mobile}`;
|
||||
if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
|
||||
if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
|
||||
if (data.allergies) details += `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
|
||||
if (data.medication) details += `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
|
||||
if (data.alcohol_current_use) details += `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
|
||||
if (data.alcohol_past_use) details += `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
|
||||
if (data.tobacco_current_use) details += `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
|
||||
if (data.tobacco_past_use) details += `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
|
||||
if (data.medical_history) details += `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
|
||||
if (data.surgical_history) details += `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
|
||||
if (data.surrounding_factors) details += `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
|
||||
if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
|
||||
if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
|
||||
|
||||
if (details) {
|
||||
details = `<div style='padding-left:10px; font-size:13px;' align='left'>` + details + `</div>`;
|
||||
}
|
||||
me.page.main.find(".patient_details").html(details);
|
||||
me.page.main.find('.patient_details').html(details);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
||||
let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
||||
frappe.call({
|
||||
method: "erpnext.healthcare.utils.get_patient_vitals",
|
||||
method: 'erpnext.healthcare.utils.get_patient_vitals',
|
||||
args:{
|
||||
patient: patient
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message){
|
||||
var show_chart_btns_html = "<div style='padding-top:5px;'><a class='btn btn-default btn-xs btn-show-chart' \
|
||||
data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>Blood Pressure</a>\
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' \
|
||||
data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>Respiratory/Pulse Rate</a>\
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' \
|
||||
data-pts='°C or °F' data-title='Temperature'>Temperature</a>\
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' \
|
||||
data-pts='' data-title='BMI'>BMI</a></div>";
|
||||
me.page.main.find(".show_chart_btns").html(show_chart_btns_html);
|
||||
var data = r.message;
|
||||
if (r.message) {
|
||||
let show_chart_btns_html = `
|
||||
<div style='padding-top:10px;'>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
|
||||
${__('Blood Pressure')}
|
||||
</a>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
|
||||
${__('Respiratory/Pulse Rate')}
|
||||
</a>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
|
||||
${__('Temperature')}
|
||||
</a>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
|
||||
${__('BMI')}
|
||||
</a>
|
||||
</div>`;
|
||||
|
||||
me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
|
||||
let data = r.message;
|
||||
let labels = [], datasets = [];
|
||||
let bp_systolic = [], bp_diastolic = [], temperature = [];
|
||||
let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
|
||||
for(var i=0; i<data.length; i++){
|
||||
labels.push(data[i].signs_date+"||"+data[i].signs_time);
|
||||
if(btn_show_id=="bp"){
|
||||
|
||||
for (let i=0; i<data.length; i++) {
|
||||
labels.push(data[i].signs_date+'||'+data[i].signs_time);
|
||||
|
||||
if (btn_show_id === 'bp') {
|
||||
bp_systolic.push(data[i].bp_systolic);
|
||||
bp_diastolic.push(data[i].bp_diastolic);
|
||||
}
|
||||
if(btn_show_id=="temperature"){
|
||||
if (btn_show_id === 'temperature') {
|
||||
temperature.push(data[i].temperature);
|
||||
}
|
||||
if(btn_show_id=="pulse_rate"){
|
||||
if (btn_show_id === 'pulse_rate') {
|
||||
pulse.push(data[i].pulse);
|
||||
respiratory_rate.push(data[i].respiratory_rate);
|
||||
}
|
||||
if(btn_show_id=="bmi"){
|
||||
if (btn_show_id === 'bmi') {
|
||||
bmi.push(data[i].bmi);
|
||||
height.push(data[i].height);
|
||||
weight.push(data[i].weight);
|
||||
}
|
||||
}
|
||||
if(btn_show_id=="temperature"){
|
||||
datasets.push({name: "Temperature", values: temperature, chartType:'line'});
|
||||
if (btn_show_id === 'temperature') {
|
||||
datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
|
||||
}
|
||||
if(btn_show_id=="bmi"){
|
||||
datasets.push({name: "BMI", values: bmi, chartType:'line'});
|
||||
datasets.push({name: "Height", values: height, chartType:'line'});
|
||||
datasets.push({name: "Weight", values: weight, chartType:'line'});
|
||||
if (btn_show_id === 'bmi') {
|
||||
datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
|
||||
datasets.push({name: 'Height', values: height, chartType: 'line'});
|
||||
datasets.push({name: 'Weight', values: weight, chartType: 'line'});
|
||||
}
|
||||
if(btn_show_id=="bp"){
|
||||
datasets.push({name: "BP Systolic", values: bp_systolic, chartType:'line'});
|
||||
datasets.push({name: "BP Diastolic", values: bp_diastolic, chartType:'line'});
|
||||
if (btn_show_id === 'bp') {
|
||||
datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
|
||||
datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
|
||||
}
|
||||
if(btn_show_id=="pulse_rate"){
|
||||
datasets.push({name: "Heart Rate / Pulse", values: pulse, chartType:'line'});
|
||||
datasets.push({name: "Respiratory Rate", values: respiratory_rate, chartType:'line'});
|
||||
if (btn_show_id === 'pulse_rate') {
|
||||
datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
|
||||
datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
|
||||
}
|
||||
new frappe.Chart( ".patient_vital_charts", {
|
||||
new frappe.Chart('.patient_vital_charts', {
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets
|
||||
},
|
||||
|
||||
title: title,
|
||||
type: 'axis-mixed', // 'axis-mixed', 'bar', 'line', 'pie', 'percentage'
|
||||
type: 'axis-mixed',
|
||||
height: 200,
|
||||
colors: ['purple', '#ffa3ef', 'light-blue'],
|
||||
|
||||
@ -291,9 +392,11 @@ var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
||||
formatTooltipY: d => d + ' ' + pts,
|
||||
}
|
||||
});
|
||||
}else{
|
||||
me.page.main.find(".patient_vital_charts").html("");
|
||||
me.page.main.find(".show_chart_btns").html("");
|
||||
me.page.main.find('.header-separator').show();
|
||||
} else {
|
||||
me.page.main.find('.patient_vital_charts').html('');
|
||||
me.page.main.find('.show_chart_btns').html('');
|
||||
me.page.main.find('.header-separator').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -4,36 +4,70 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe.utils import cint
|
||||
from erpnext.healthcare.utils import render_docs_as_html
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_feed(name, start=0, page_length=20):
|
||||
def get_feed(name, document_types=None, date_range=None, start=0, page_length=20):
|
||||
"""get feed"""
|
||||
result = frappe.db.sql("""select name, owner, creation,
|
||||
reference_doctype, reference_name, subject
|
||||
from `tabPatient Medical Record`
|
||||
where patient=%(patient)s
|
||||
order by creation desc
|
||||
limit %(start)s, %(page_length)s""",
|
||||
{
|
||||
"patient": name,
|
||||
"start": cint(start),
|
||||
"page_length": cint(page_length)
|
||||
}, as_dict=True)
|
||||
filters = get_filters(name, document_types, date_range)
|
||||
|
||||
result = frappe.db.get_all('Patient Medical Record',
|
||||
fields=['name', 'owner', 'communication_date',
|
||||
'reference_doctype', 'reference_name', 'subject'],
|
||||
filters=filters,
|
||||
order_by='communication_date DESC',
|
||||
limit=cint(page_length),
|
||||
start=cint(start)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_filters(name, document_types=None, date_range=None):
|
||||
filters = {'patient': name}
|
||||
if document_types:
|
||||
document_types = json.loads(document_types)
|
||||
if len(document_types):
|
||||
filters['reference_doctype'] = ['IN', document_types]
|
||||
|
||||
if date_range:
|
||||
try:
|
||||
date_range = json.loads(date_range)
|
||||
if date_range:
|
||||
filters['communication_date'] = ['between', [date_range[0], date_range[1]]]
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_feed_for_dt(doctype, docname):
|
||||
"""get feed"""
|
||||
result = frappe.db.sql("""select name, owner, modified, creation,
|
||||
reference_doctype, reference_name, subject
|
||||
from `tabPatient Medical Record`
|
||||
where reference_name=%(docname)s and reference_doctype=%(doctype)s
|
||||
order by creation desc""",
|
||||
{
|
||||
"docname": docname,
|
||||
"doctype": doctype
|
||||
}, as_dict=True)
|
||||
result = frappe.db.get_all('Patient Medical Record',
|
||||
fields=['name', 'owner', 'communication_date',
|
||||
'reference_doctype', 'reference_name', 'subject'],
|
||||
filters={
|
||||
'reference_doctype': doctype,
|
||||
'reference_name': docname
|
||||
},
|
||||
order_by='communication_date DESC'
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_patient_history_doctypes():
|
||||
document_types = []
|
||||
settings = frappe.get_single("Patient History Settings")
|
||||
|
||||
for entry in settings.standard_doctypes:
|
||||
document_types.append(entry.document_type)
|
||||
|
||||
for entry in settings.custom_doctypes:
|
||||
document_types.append(entry.document_type)
|
||||
|
||||
return document_types
|
||||
|
@ -16,6 +16,7 @@ def setup_healthcare():
|
||||
create_healthcare_item_groups()
|
||||
create_sensitivity()
|
||||
add_healthcare_service_unit_tree_root()
|
||||
setup_patient_history_settings()
|
||||
|
||||
def create_medical_departments():
|
||||
departments = [
|
||||
@ -213,3 +214,82 @@ def get_company():
|
||||
if company:
|
||||
return company[0].name
|
||||
return None
|
||||
|
||||
def setup_patient_history_settings():
|
||||
import json
|
||||
|
||||
settings = frappe.get_single('Patient History Settings')
|
||||
configuration = get_patient_history_config()
|
||||
for dt, config in configuration.items():
|
||||
settings.append("standard_doctypes", {
|
||||
"document_type": dt,
|
||||
"date_fieldname": config[0],
|
||||
"selected_fields": json.dumps(config[1])
|
||||
})
|
||||
settings.save()
|
||||
|
||||
def get_patient_history_config():
|
||||
return {
|
||||
"Patient Encounter": ("encounter_date", [
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"},
|
||||
{"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"},
|
||||
{"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"},
|
||||
{"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"},
|
||||
{"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"},
|
||||
{"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"},
|
||||
{"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"}
|
||||
]),
|
||||
"Clinical Procedure": ("start_date", [
|
||||
{"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"},
|
||||
{"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"},
|
||||
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
|
||||
{"label": "Sample", "fieldname": "sample", "fieldtype": "Link"}
|
||||
]),
|
||||
"Lab Test": ("result_date", [
|
||||
{"label": "Test Template", "fieldname": "template", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"},
|
||||
{"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"},
|
||||
{"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"},
|
||||
{"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"},
|
||||
{"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"},
|
||||
{"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"},
|
||||
{"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"},
|
||||
{"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"}
|
||||
]),
|
||||
"Therapy Session": ("start_date", [
|
||||
{"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"},
|
||||
{"label": "Duration", "fieldname": "duration", "fieldtype": "Int"},
|
||||
{"label": "Location", "fieldname": "location", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"},
|
||||
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
|
||||
{"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"},
|
||||
{"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"},
|
||||
{"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"}
|
||||
]),
|
||||
"Vital Signs": ("signs_date", [
|
||||
{"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"},
|
||||
{"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"},
|
||||
{"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"},
|
||||
{"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"},
|
||||
{"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"},
|
||||
{"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"},
|
||||
{"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"},
|
||||
{"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"},
|
||||
{"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"},
|
||||
{"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"},
|
||||
{"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"}
|
||||
]),
|
||||
"Inpatient Medication Order": ("start_date", [
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"},
|
||||
{"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"},
|
||||
{"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"},
|
||||
{"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"}
|
||||
])
|
||||
}
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import math
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils.formatters import format_value
|
||||
from frappe.utils import time_diff_in_hours, rounded
|
||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
|
||||
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
|
||||
@ -648,11 +649,15 @@ def render_doc_as_html(doctype, docname, exclude_fields = []):
|
||||
html += "<table class='table table-condensed table-bordered'>" \
|
||||
+ table_head + table_row + "</table>"
|
||||
continue
|
||||
|
||||
#on other field types add label and value to html
|
||||
if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
|
||||
html += '<br>{0} : {1}'.format(df.label or df.fieldname, \
|
||||
doc.get(df.fieldname))
|
||||
if doc.get(df.fieldname):
|
||||
formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
|
||||
html += '<br>{0} : {1}'.format(df.label or df.fieldname, formatted_value)
|
||||
|
||||
if not has_data : has_data = True
|
||||
|
||||
if sec_on and col_on and has_data:
|
||||
doc_html += section_html + html + '</div></div>'
|
||||
elif sec_on and not col_on and has_data:
|
||||
|
@ -222,6 +222,11 @@ standard_queries = {
|
||||
}
|
||||
|
||||
doc_events = {
|
||||
"*": {
|
||||
"on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record",
|
||||
"on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record",
|
||||
"on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record"
|
||||
},
|
||||
"Stock Entry": {
|
||||
"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
|
||||
"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty"
|
||||
@ -339,7 +344,8 @@ scheduler_events = {
|
||||
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
||||
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
|
||||
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
|
||||
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email"
|
||||
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
|
||||
"erpnext.non_profit.doctype.membership.membership.set_expired_status"
|
||||
],
|
||||
"daily_long": [
|
||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||
@ -408,9 +414,6 @@ regional_overrides = {
|
||||
'Italy': {
|
||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data',
|
||||
'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate',
|
||||
},
|
||||
'Germany': {
|
||||
'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.germany.accounts_controller.validate_regional',
|
||||
}
|
||||
}
|
||||
user_privacy_documents = [
|
||||
|
@ -88,7 +88,7 @@ def get_events(start, end, filters=None):
|
||||
|
||||
def add_assignments(events, start, end, conditions=None):
|
||||
query = """select name, start_date, end_date, employee_name,
|
||||
employee, docstatus
|
||||
employee, docstatus, shift_type
|
||||
from `tabShift Assignment` where
|
||||
start_date >= %(start_date)s
|
||||
or end_date <= %(end_date)s
|
||||
@ -97,18 +97,40 @@ def add_assignments(events, start, end, conditions=None):
|
||||
if conditions:
|
||||
query += conditions
|
||||
|
||||
for d in frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True):
|
||||
e = {
|
||||
"name": d.name,
|
||||
"doctype": "Shift Assignment",
|
||||
"start_date": d.start_date,
|
||||
"end_date": d.end_date if d.end_date else nowdate(),
|
||||
"title": cstr(d.employee_name) + ": "+ \
|
||||
cstr(d.shift_type),
|
||||
"docstatus": d.docstatus
|
||||
}
|
||||
if e not in events:
|
||||
events.append(e)
|
||||
records = frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True)
|
||||
shift_timing_map = get_shift_type_timing([d.shift_type for d in records])
|
||||
|
||||
for d in records:
|
||||
daily_event_start = d.start_date
|
||||
daily_event_end = d.end_date if d.end_date else getdate()
|
||||
delta = timedelta(days=1)
|
||||
while daily_event_start <= daily_event_end:
|
||||
start_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['start_time']
|
||||
end_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['end_time']
|
||||
daily_event_start += delta
|
||||
e = {
|
||||
"name": d.name,
|
||||
"doctype": "Shift Assignment",
|
||||
"start_date": start_timing,
|
||||
"end_date": end_timing,
|
||||
"title": cstr(d.employee_name) + ": "+ \
|
||||
cstr(d.shift_type),
|
||||
"docstatus": d.docstatus,
|
||||
"allDay": 0
|
||||
}
|
||||
if e not in events:
|
||||
events.append(e)
|
||||
|
||||
return events
|
||||
|
||||
def get_shift_type_timing(shift_types):
|
||||
shift_timing_map = {}
|
||||
data = frappe.get_all("Shift Type", filters = {"name": ("IN", shift_types)}, fields = ['name', 'start_time', 'end_time'])
|
||||
|
||||
for d in data:
|
||||
shift_timing_map[d.name] = d
|
||||
|
||||
return shift_timing_map
|
||||
|
||||
|
||||
def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=False, next_shift_direction=None):
|
||||
|
@ -6,14 +6,8 @@ frappe.views.calendar["Shift Assignment"] = {
|
||||
"start": "start_date",
|
||||
"end": "end_date",
|
||||
"id": "name",
|
||||
"docstatus": 1
|
||||
},
|
||||
options: {
|
||||
header: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'month'
|
||||
}
|
||||
"docstatus": 1,
|
||||
"allDay": "allDay",
|
||||
},
|
||||
get_events_method: "erpnext.hr.doctype.shift_assignment.shift_assignment.get_events"
|
||||
}
|
@ -26,6 +26,7 @@ def get_columns(filters):
|
||||
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
||||
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
||||
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100},
|
||||
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
||||
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||
@ -42,38 +43,53 @@ def get_data(filters):
|
||||
currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
for key, qty in iteritems(pledge_values):
|
||||
row = {}
|
||||
current_value = flt(qty * loan_security_details.get(key[1])['latest_price'])
|
||||
row.update(loan_security_details.get(key[1]))
|
||||
row.update({
|
||||
'applicant_type': applicant_type_map.get(key[0]),
|
||||
'applicant_name': key[0],
|
||||
'total_qty': qty,
|
||||
'current_value': current_value,
|
||||
'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2),
|
||||
'currency': currency
|
||||
})
|
||||
if qty:
|
||||
row = {}
|
||||
current_value = flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
|
||||
valid_upto = loan_security_details.get(key[1], {}).get('valid_upto')
|
||||
|
||||
data.append(row)
|
||||
row.update(loan_security_details.get(key[1]))
|
||||
row.update({
|
||||
'applicant_type': applicant_type_map.get(key[0]),
|
||||
'applicant_name': key[0],
|
||||
'total_qty': qty,
|
||||
'current_value': current_value,
|
||||
'price_valid_upto': valid_upto,
|
||||
'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2) if total_value_map.get(key[0]) \
|
||||
else 0.0,
|
||||
'currency': currency
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
def get_loan_security_details(filters):
|
||||
security_detail_map = {}
|
||||
loan_security_price_map = {}
|
||||
lsp_validity_map = {}
|
||||
|
||||
loan_security_price_map = frappe._dict(frappe.db.sql("""
|
||||
SELECT loan_security, loan_security_price
|
||||
loan_security_prices = frappe.db.sql("""
|
||||
SELECT loan_security, loan_security_price, valid_upto
|
||||
FROM `tabLoan Security Price` t1
|
||||
WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
|
||||
WHERE t1.loan_security = t2.loan_security)
|
||||
""", as_list=1))
|
||||
""", as_dict=1)
|
||||
|
||||
for security in loan_security_prices:
|
||||
loan_security_price_map.setdefault(security.loan_security, security.loan_security_price)
|
||||
lsp_validity_map.setdefault(security.loan_security, security.valid_upto)
|
||||
|
||||
loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security',
|
||||
'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type',
|
||||
'disabled'])
|
||||
|
||||
for security in loan_security_details:
|
||||
security.update({'latest_price': flt(loan_security_price_map.get(security.loan_security))})
|
||||
security.update({
|
||||
'latest_price': flt(loan_security_price_map.get(security.loan_security)),
|
||||
'valid_upto': lsp_validity_map.get(security.loan_security)
|
||||
})
|
||||
|
||||
security_detail_map.setdefault(security.loan_security, security)
|
||||
|
||||
return security_detail_map
|
||||
@ -118,6 +134,6 @@ def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
|
||||
applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0)
|
||||
|
||||
total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \
|
||||
* loan_security_details.get(security.loan_security)['latest_price']
|
||||
* loan_security_details.get(security.loan_security, {}).get('latest_price', 0)
|
||||
|
||||
return current_pledges, total_value_map, applicant_type_map
|
@ -6,6 +6,8 @@ import frappe
|
||||
import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import flt, getdate, add_days
|
||||
from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \
|
||||
import get_loan_security_details
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@ -31,6 +33,7 @@ def get_columns(filters):
|
||||
{"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100},
|
||||
{"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100},
|
||||
{"label": _("Loan To Value Ratio"), "fieldname": "loan_to_value", "fieldtype": "Percent", "width": 100},
|
||||
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||
]
|
||||
|
||||
@ -50,6 +53,9 @@ def get_active_loan_details(filters):
|
||||
|
||||
loan_list = [d.loan for d in loan_details]
|
||||
|
||||
current_pledges = get_loan_wise_pledges(filters)
|
||||
loan_wise_security_value = get_loan_wise_security_value(filters, current_pledges)
|
||||
|
||||
sanctioned_amount_map = get_sanctioned_amount_map()
|
||||
penal_interest_rate_map = get_penal_interest_rate_map()
|
||||
payments = get_payments(loan_list)
|
||||
@ -67,12 +73,16 @@ def get_active_loan_details(filters):
|
||||
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
|
||||
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
|
||||
"undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
|
||||
"loan_to_value": 0.0,
|
||||
"currency": currency
|
||||
})
|
||||
|
||||
loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
|
||||
+ loan['penalty']
|
||||
|
||||
if loan_wise_security_value.get(loan.loan):
|
||||
loan['loan_to_value'] = flt((loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan))
|
||||
|
||||
return loan_details
|
||||
|
||||
def get_sanctioned_amount_map():
|
||||
@ -121,4 +131,53 @@ def get_interest_accruals(loans):
|
||||
return accrual_map
|
||||
|
||||
def get_penal_interest_rate_map():
|
||||
return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
|
||||
return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
|
||||
|
||||
def get_loan_wise_pledges(filters):
|
||||
loan_wise_unpledges = {}
|
||||
current_pledges = {}
|
||||
|
||||
conditions = ""
|
||||
|
||||
if filters.get('company'):
|
||||
conditions = "AND company = %(company)s"
|
||||
|
||||
unpledges = frappe.db.sql("""
|
||||
SELECT up.loan, u.loan_security, sum(u.qty) as qty
|
||||
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
|
||||
WHERE u.parent = up.name
|
||||
AND up.status = 'Approved'
|
||||
{conditions}
|
||||
GROUP BY up.loan, u.loan_security
|
||||
""".format(conditions=conditions), filters, as_dict=1)
|
||||
|
||||
for unpledge in unpledges:
|
||||
loan_wise_unpledges.setdefault((unpledge.loan, unpledge.loan_security), unpledge.qty)
|
||||
|
||||
pledges = frappe.db.sql("""
|
||||
SELECT lp.loan, p.loan_security, sum(p.qty) as qty
|
||||
FROM `tabLoan Security Pledge` lp, `tabPledge`p
|
||||
WHERE p.parent = lp.name
|
||||
AND lp.status = 'Pledged'
|
||||
{conditions}
|
||||
GROUP BY lp.loan, p.loan_security
|
||||
""".format(conditions=conditions), filters, as_dict=1)
|
||||
|
||||
for security in pledges:
|
||||
current_pledges.setdefault((security.loan, security.loan_security), security.qty)
|
||||
current_pledges[(security.loan, security.loan_security)] -= \
|
||||
loan_wise_unpledges.get((security.loan, security.loan_security), 0.0)
|
||||
|
||||
return current_pledges
|
||||
|
||||
def get_loan_wise_security_value(filters, current_pledges):
|
||||
loan_security_details = get_loan_security_details(filters)
|
||||
loan_wise_security_value = {}
|
||||
|
||||
for key in current_pledges:
|
||||
qty = current_pledges.get(key)
|
||||
loan_wise_security_value.setdefault(key[0], 0.0)
|
||||
loan_wise_security_value[key[0]] += \
|
||||
flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
|
||||
|
||||
return loan_wise_security_value
|
@ -24,6 +24,7 @@ def get_columns(filters):
|
||||
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
||||
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
||||
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100},
|
||||
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
||||
{"label": _("Pledged Applicant Count"), "fieldname": "pledged_applicant_count", "fieldtype": "Percentage", "width": 100},
|
||||
@ -39,18 +40,22 @@ def get_data(filters):
|
||||
currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
for security, value in iteritems(current_pledges):
|
||||
row = {}
|
||||
current_value = flt(value['qty'] * loan_security_details.get(security)['latest_price'])
|
||||
row.update(loan_security_details.get(security))
|
||||
row.update({
|
||||
'total_qty': value['qty'],
|
||||
'current_value': current_value,
|
||||
'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
|
||||
'pledged_applicant_count': value['applicant_count'],
|
||||
'currency': currency
|
||||
})
|
||||
if value.get('qty'):
|
||||
row = {}
|
||||
current_value = flt(value.get('qty', 0) * loan_security_details.get(security, {}).get('latest_price', 0))
|
||||
valid_upto = loan_security_details.get(security, {}).get('valid_upto')
|
||||
|
||||
data.append(row)
|
||||
row.update(loan_security_details.get(security))
|
||||
row.update({
|
||||
'total_qty': value.get('qty'),
|
||||
'current_value': current_value,
|
||||
'price_valid_upto': valid_upto,
|
||||
'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
|
||||
'pledged_applicant_count': value.get('applicant_count'),
|
||||
'currency': currency
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
@ -12,7 +12,6 @@
|
||||
"membership_expiry_date",
|
||||
"column_break_5",
|
||||
"membership_type",
|
||||
"email",
|
||||
"email_id",
|
||||
"image",
|
||||
"customer_section",
|
||||
@ -64,13 +63,6 @@
|
||||
"options": "Membership Type",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "User",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
@ -178,7 +170,7 @@
|
||||
],
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-09-16 23:44:13.596948",
|
||||
"modified": "2020-11-09 12:12:10.174647",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Member",
|
||||
|
@ -18,8 +18,6 @@ class Member(Document):
|
||||
|
||||
|
||||
def validate(self):
|
||||
if self.email:
|
||||
self.validate_email_type(self.email)
|
||||
if self.email_id:
|
||||
self.validate_email_type(self.email_id)
|
||||
|
||||
@ -57,14 +55,16 @@ class Member(Document):
|
||||
def make_customer_and_link(self):
|
||||
if self.customer:
|
||||
frappe.msgprint(_("A customer is already linked to this Member"))
|
||||
cust = create_customer(frappe._dict({
|
||||
|
||||
customer = create_customer(frappe._dict({
|
||||
'fullname': self.member_name,
|
||||
'email': self.email_id or self.email,
|
||||
'email': self.email_id,
|
||||
'phone': None
|
||||
}))
|
||||
|
||||
self.customer = cust
|
||||
self.customer = customer
|
||||
self.save()
|
||||
frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
|
||||
|
||||
|
||||
def get_or_create_member(user_details):
|
||||
|
@ -4,16 +4,25 @@
|
||||
frappe.ui.form.on('Membership', {
|
||||
setup: function(frm) {
|
||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
||||
if (val) frm.set_df_property('razorpay_details_section', 'hidden', false);
|
||||
if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
|
||||
})
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.__islocal)
|
||||
return;
|
||||
|
||||
!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
|
||||
frm.call("generate_invoice", {
|
||||
save: true
|
||||
}).then(() => {
|
||||
frm.reload_doc();
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "generate_invoice",
|
||||
args: {save: true},
|
||||
freeze: true,
|
||||
freeze_message: __("Creating Membership Invoice"),
|
||||
callback: function(r) {
|
||||
if (r.invoice)
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -27,6 +36,6 @@ frappe.ui.form.on('Membership', {
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.add_fetch('membership_type', 'amount', 'amount');
|
||||
frm.add_fetch("membership_type", "amount", "amount");
|
||||
}
|
||||
});
|
||||
|
@ -7,6 +7,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"member",
|
||||
"member_name",
|
||||
"membership_type",
|
||||
"column_break_3",
|
||||
"membership_status",
|
||||
@ -46,6 +47,8 @@
|
||||
{
|
||||
"fieldname": "membership_status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Membership Status",
|
||||
"options": "New\nCurrent\nExpired\nPending\nCancelled"
|
||||
},
|
||||
@ -122,11 +125,18 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Invoice",
|
||||
"options": "Sales Invoice"
|
||||
},
|
||||
{
|
||||
"fetch_from": "member.member_name",
|
||||
"fieldname": "member_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Member Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-19 14:28:11.532696",
|
||||
"modified": "2021-01-21 16:31:20.032656",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Membership",
|
||||
@ -158,7 +168,9 @@
|
||||
}
|
||||
],
|
||||
"restrict_to_domain": "Non Profit",
|
||||
"search_fields": "member, member_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "member_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -14,33 +14,43 @@ from erpnext.non_profit.doctype.member.member import create_member
|
||||
from frappe import _
|
||||
import erpnext
|
||||
|
||||
|
||||
class Membership(Document):
|
||||
def validate(self):
|
||||
if not self.member or not frappe.db.exists("Member", self.member):
|
||||
member_name = frappe.get_value('Member', dict(email=frappe.session.user))
|
||||
# for web forms
|
||||
user_type = frappe.db.get_value("User", frappe.session.user, "user_type")
|
||||
if user_type == "Website User":
|
||||
self.create_member_from_website_user()
|
||||
else:
|
||||
frappe.throw(_("Please select a Member"))
|
||||
|
||||
if not member_name:
|
||||
user = frappe.get_doc('User', frappe.session.user)
|
||||
member = frappe.get_doc(dict(
|
||||
doctype='Member',
|
||||
email=frappe.session.user,
|
||||
membership_type=self.membership_type,
|
||||
member_name=user.get_fullname()
|
||||
)).insert(ignore_permissions=True)
|
||||
member_name = member.name
|
||||
self.validate_membership_period()
|
||||
|
||||
if self.get("__islocal"):
|
||||
self.member = member_name
|
||||
def create_member_from_website_user(self):
|
||||
member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
|
||||
|
||||
if not member_name:
|
||||
user = frappe.get_doc("User", frappe.session.user)
|
||||
member = frappe.get_doc(dict(
|
||||
doctype="Member",
|
||||
email_id=frappe.session.user,
|
||||
membership_type=self.membership_type,
|
||||
member_name=user.get_fullname()
|
||||
)).insert(ignore_permissions=True)
|
||||
member_name = member.name
|
||||
|
||||
if self.get("__islocal"):
|
||||
self.member = member_name
|
||||
|
||||
def validate_membership_period(self):
|
||||
# get last membership (if active)
|
||||
last_membership = erpnext.get_last_membership()
|
||||
last_membership = erpnext.get_last_membership(self.member)
|
||||
|
||||
# if person applied for offline membership
|
||||
if last_membership and not frappe.session.user == "Administrator":
|
||||
# if last membership does not expire in 30 days, then do not allow to renew
|
||||
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
|
||||
frappe.throw(_('You can only renew if your membership expires within 30 days'))
|
||||
frappe.throw(_("You can only renew if your membership expires within 30 days"))
|
||||
|
||||
self.from_date = add_days(last_membership.to_date, 1)
|
||||
elif frappe.session.user == "Administrator":
|
||||
@ -54,11 +64,16 @@ class Membership(Document):
|
||||
self.to_date = add_months(self.from_date, 1)
|
||||
|
||||
def on_payment_authorized(self, status_changed_to=None):
|
||||
if status_changed_to in ("Completed", "Authorized"):
|
||||
self.load_from_db()
|
||||
self.db_set('paid', 1)
|
||||
if status_changed_to not in ("Completed", "Authorized"):
|
||||
return
|
||||
self.load_from_db()
|
||||
self.db_set("paid", 1)
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
if settings.enable_invoicing and settings.create_for_web_forms:
|
||||
self.generate_invoice(with_payment_entry=settings.make_payment_entry, save=True)
|
||||
|
||||
def generate_invoice(self, save=True):
|
||||
|
||||
def generate_invoice(self, save=True, with_payment_entry=False):
|
||||
if not (self.paid or self.currency or self.amount):
|
||||
frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
|
||||
|
||||
@ -66,34 +81,64 @@ class Membership(Document):
|
||||
frappe.throw(_("An invoice is already linked to this document"))
|
||||
|
||||
member = frappe.get_doc("Member", self.member)
|
||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
|
||||
if not member.customer:
|
||||
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
|
||||
|
||||
if not settings.debit_account:
|
||||
frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings"))
|
||||
|
||||
if not settings.company:
|
||||
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in Membership Settings"))
|
||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
self.validate_membership_type_and_settings(plan, settings)
|
||||
|
||||
invoice = make_invoice(self, member, plan, settings)
|
||||
self.invoice = invoice.name
|
||||
|
||||
if with_payment_entry:
|
||||
self.make_payment_entry(settings, invoice)
|
||||
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
return invoice
|
||||
|
||||
def validate_membership_type_and_settings(self, plan, settings):
|
||||
settings_link = get_link_to_form("Membership Type", self.membership_type)
|
||||
|
||||
if not settings.debit_account:
|
||||
frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
|
||||
|
||||
if not settings.company:
|
||||
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in {0}").format(settings_link))
|
||||
|
||||
if not plan.linked_item:
|
||||
frappe.throw(_("Please set a Linked Item for the Membership Type {0}").format(
|
||||
get_link_to_form("Membership Type", self.membership_type)))
|
||||
|
||||
def make_payment_entry(self, settings, invoice):
|
||||
if not settings.payment_account:
|
||||
frappe.throw(_("You need to set <b>Payment Account</b> in {0}").format(
|
||||
get_link_to_form("Membership Type", self.membership_type)))
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
frappe.flags.ignore_account_permission = True
|
||||
pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
|
||||
frappe.flags.ignore_account_permission=False
|
||||
pe.paid_to = settings.payment_account
|
||||
pe.reference_no = self.name
|
||||
pe.reference_date = getdate()
|
||||
pe.save(ignore_permissions=True)
|
||||
pe.submit()
|
||||
|
||||
def send_acknowlement(self):
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
if not settings.send_email:
|
||||
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in Membership Settings"))
|
||||
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
|
||||
get_link_to_form("Membership Settings", "Membership Settings")))
|
||||
|
||||
member = frappe.get_doc("Member", self.member)
|
||||
if not member.email_id:
|
||||
frappe.throw(_("Email address of member {0} is missing").format(frappe.utils.get_link_to_form("Member", self.member)))
|
||||
|
||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||
email = member.email_id if member.email_id else member.email
|
||||
email = member.email_id
|
||||
attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
|
||||
|
||||
if self.invoice and settings.send_invoice:
|
||||
@ -112,48 +157,56 @@ class Membership(Document):
|
||||
}
|
||||
|
||||
if not frappe.flags.in_test:
|
||||
frappe.enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args)
|
||||
frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
|
||||
else:
|
||||
frappe.sendmail(**email_args)
|
||||
|
||||
def generate_and_send_invoice(self):
|
||||
invoice = self.generate_invoice(False)
|
||||
self.generate_invoice(save=False)
|
||||
self.send_acknowlement()
|
||||
|
||||
|
||||
def make_invoice(membership, member, plan, settings):
|
||||
invoice = frappe.get_doc({
|
||||
'doctype': 'Sales Invoice',
|
||||
'customer': member.customer,
|
||||
'debit_to': settings.debit_account,
|
||||
'currency': membership.currency,
|
||||
'is_pos': 0,
|
||||
'items': [
|
||||
"doctype": "Sales Invoice",
|
||||
"customer": member.customer,
|
||||
"debit_to": settings.debit_account,
|
||||
"currency": membership.currency,
|
||||
"company": settings.company,
|
||||
"is_pos": 0,
|
||||
"items": [
|
||||
{
|
||||
'item_code': plan.linked_item,
|
||||
'rate': membership.amount,
|
||||
'qty': 1
|
||||
"item_code": plan.linked_item,
|
||||
"rate": membership.amount,
|
||||
"qty": 1
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
invoice.set_missing_values()
|
||||
invoice.insert(ignore_permissions=True)
|
||||
invoice.submit()
|
||||
|
||||
frappe.msgprint(_("Sales Invoice created successfully"))
|
||||
|
||||
return invoice
|
||||
|
||||
|
||||
def get_member_based_on_subscription(subscription_id, email):
|
||||
members = frappe.get_all("Member", filters={
|
||||
'subscription_id': subscription_id,
|
||||
'email_id': email
|
||||
"subscription_id": subscription_id,
|
||||
"email_id": email
|
||||
}, order_by="creation desc")
|
||||
|
||||
try:
|
||||
return frappe.get_doc("Member", members[0]['name'])
|
||||
return frappe.get_doc("Member", members[0]["name"])
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def verify_signature(data):
|
||||
signature = frappe.request.headers.get('X-Razorpay-Signature')
|
||||
if frappe.flags.in_test:
|
||||
return True
|
||||
signature = frappe.request.headers.get("X-Razorpay-Signature")
|
||||
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
key = settings.get_webhook_secret()
|
||||
@ -162,6 +215,7 @@ def verify_signature(data):
|
||||
|
||||
controller.verify_signature(data, signature, key)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def trigger_razorpay_subscription(*args, **kwargs):
|
||||
data = frappe.request.get_data(as_text=True)
|
||||
@ -170,16 +224,16 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
except Exception as e:
|
||||
log = frappe.log_error(e, "Webhook Verification Error")
|
||||
notify_failure(log)
|
||||
return { 'status': 'Failed', 'reason': e}
|
||||
return { "status": "Failed", "reason": e}
|
||||
|
||||
if isinstance(data, six.string_types):
|
||||
data = json.loads(data)
|
||||
data = frappe._dict(data)
|
||||
|
||||
subscription = data.payload.get("subscription", {}).get('entity', {})
|
||||
subscription = data.payload.get("subscription", {}).get("entity", {})
|
||||
subscription = frappe._dict(subscription)
|
||||
|
||||
payment = data.payload.get("payment", {}).get('entity', {})
|
||||
payment = data.payload.get("payment", {}).get("entity", {})
|
||||
payment = frappe._dict(payment)
|
||||
|
||||
try:
|
||||
@ -189,15 +243,15 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
member = get_member_based_on_subscription(subscription.id, payment.email)
|
||||
if not member:
|
||||
member = create_member(frappe._dict({
|
||||
'fullname': payment.email,
|
||||
'email': payment.email,
|
||||
'plan_id': get_plan_from_razorpay_id(subscription.plan_id)
|
||||
"fullname": payment.email,
|
||||
"email": payment.email,
|
||||
"plan_id": get_plan_from_razorpay_id(subscription.plan_id)
|
||||
}))
|
||||
|
||||
member.subscription_id = subscription.id
|
||||
member.customer_id = payment.customer_id
|
||||
if subscription.notes and type(subscription.notes) == dict:
|
||||
notes = '\n'.join("{}: {}".format(k, v) for k, v in subscription.notes.items())
|
||||
notes = "\n".join("{}: {}".format(k, v) for k, v in subscription.notes.items())
|
||||
member.add_comment("Comment", notes)
|
||||
elif subscription.notes and type(subscription.notes) == str:
|
||||
member.add_comment("Comment", subscription.notes)
|
||||
@ -227,28 +281,39 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
|
||||
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
||||
notify_failure(log)
|
||||
return { 'status': 'Failed', 'reason': e}
|
||||
return { "status": "Failed", "reason": e}
|
||||
|
||||
return { 'status': 'Success' }
|
||||
return { "status": "Success" }
|
||||
|
||||
|
||||
def notify_failure(log):
|
||||
try:
|
||||
content = """Dear System Manager,
|
||||
Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below
|
||||
content = """
|
||||
Dear System Manager,
|
||||
Razorpay webhook for creating renewing membership subscription failed due to some reason.
|
||||
Please check the following error log linked below
|
||||
Error Log: {0}
|
||||
Regards, Administrator
|
||||
""".format(get_link_to_form("Error Log", log.name))
|
||||
|
||||
Error Log: {0}
|
||||
|
||||
Regards,
|
||||
Administrator""".format(get_link_to_form("Error Log", log.name))
|
||||
sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def get_plan_from_razorpay_id(plan_id):
|
||||
plan = frappe.get_all("Membership Type", filters={'razorpay_plan_id': plan_id}, order_by="creation desc")
|
||||
plan = frappe.get_all("Membership Type", filters={"razorpay_plan_id": plan_id}, order_by="creation desc")
|
||||
|
||||
try:
|
||||
return plan[0]['name']
|
||||
return plan[0]["name"]
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def set_expired_status():
|
||||
frappe.db.sql("""
|
||||
UPDATE
|
||||
`tabMembership` SET `status` = 'Expired'
|
||||
WHERE
|
||||
`status` not in ('Cancelled') AND `to_date` < %s
|
||||
""", (nowdate()))
|
15
erpnext/non_profit/doctype/membership/membership_list.js
Normal file
15
erpnext/non_profit/doctype/membership/membership_list.js
Normal file
@ -0,0 +1,15 @@
|
||||
frappe.listview_settings['Membership'] = {
|
||||
get_indicator: function(doc) {
|
||||
if (doc.membership_status == 'New') {
|
||||
return [__('New'), 'blue', 'membership_status,=,New'];
|
||||
} else if (doc.membership_status === 'Current') {
|
||||
return [__('Current'), 'green', 'membership_status,=,Current'];
|
||||
} else if (doc.membership_status === 'Pending') {
|
||||
return [__('Pending'), 'yellow', 'membership_status,=,Pending'];
|
||||
} else if (doc.membership_status === 'Expired') {
|
||||
return [__('Expired'), 'grey', 'membership_status,=,Expired'];
|
||||
} else {
|
||||
return [__('Cancelled'), 'red', 'membership_status,=,Cancelled'];
|
||||
}
|
||||
}
|
||||
};
|
@ -2,8 +2,110 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
import erpnext
|
||||
from erpnext.non_profit.doctype.member.member import create_member
|
||||
from frappe.utils import nowdate, add_months
|
||||
|
||||
class TestMembership(unittest.TestCase):
|
||||
pass
|
||||
def setUp(self):
|
||||
# Get default company
|
||||
company = frappe.get_doc("Company", erpnext.get_default_company())
|
||||
|
||||
# update membership settings
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
# Enable razorpay
|
||||
settings.enable_razorpay = 1
|
||||
settings.billing_cycle = "Monthly"
|
||||
settings.billing_frequency = 24
|
||||
# Enable invoicing
|
||||
settings.enable_invoicing = 1
|
||||
settings.make_payment_entry = 1
|
||||
settings.company = company.name
|
||||
settings.payment_account = company.default_cash_account
|
||||
settings.debit_account = company.default_receivable_account
|
||||
settings.save()
|
||||
|
||||
# make test plan
|
||||
if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
|
||||
plan = frappe.new_doc("Membership Type")
|
||||
plan.membership_type = "_rzpy_test_milythm"
|
||||
plan.amount = 100
|
||||
plan.razorpay_plan_id = "_rzpy_test_milythm"
|
||||
plan.linked_item = create_item("_Test Item for Non Profit Membership").name
|
||||
plan.insert()
|
||||
else:
|
||||
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
||||
|
||||
# make test member
|
||||
self.member_doc = create_member(frappe._dict({
|
||||
'fullname': "_Test_Member",
|
||||
'email': "_test_member_erpnext@example.com",
|
||||
'plan_id': plan.name
|
||||
}))
|
||||
self.member_doc.make_customer_and_link()
|
||||
self.member = self.member_doc.name
|
||||
|
||||
def test_auto_generate_invoice_and_payment_entry(self):
|
||||
entry = make_membership(self.member)
|
||||
|
||||
# Naive test to see if at all invoice was generated and attached to member
|
||||
# In any case if details were missing, the invoicing would throw an error
|
||||
invoice = entry.generate_invoice(save=True)
|
||||
self.assertEqual(invoice.name, entry.invoice)
|
||||
|
||||
def test_renew_within_30_days(self):
|
||||
# create a membership for two months
|
||||
# Should work fine
|
||||
make_membership(self.member, { "from_date": nowdate() })
|
||||
make_membership(self.member, { "from_date": add_months(nowdate(), 1) })
|
||||
|
||||
from frappe.utils.user import add_role
|
||||
add_role("test@example.com", "Non Profit Manager")
|
||||
frappe.set_user("test@example.com")
|
||||
|
||||
# create next membership with expiry not within 30 days
|
||||
self.assertRaises(frappe.ValidationError, make_membership, self.member, {
|
||||
"from_date": add_months(nowdate(), 2),
|
||||
})
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
# create the same membership but as administrator
|
||||
make_membership(self.member, {
|
||||
"from_date": add_months(nowdate(), 2),
|
||||
"to_date": add_months(nowdate(), 3),
|
||||
})
|
||||
|
||||
def set_config(key, value):
|
||||
frappe.db.set_value("Membership Settings", None, key, value)
|
||||
|
||||
def make_membership(member, payload={}):
|
||||
data = {
|
||||
"doctype": "Membership",
|
||||
"member": member,
|
||||
"membership_status": "Current",
|
||||
"membership_type": "_rzpy_test_milythm",
|
||||
"currency": "INR",
|
||||
"paid": 1,
|
||||
"from_date": nowdate(),
|
||||
"amount": 100
|
||||
}
|
||||
data.update(payload)
|
||||
membership = frappe.get_doc(data)
|
||||
membership.insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
return membership
|
||||
|
||||
def create_item(item_code):
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
item = frappe.new_doc("Item")
|
||||
item.item_code = item_code
|
||||
item.item_name = item_code
|
||||
item.stock_uom = "Nos"
|
||||
item.description = item_code
|
||||
item.item_group = "All Item Groups"
|
||||
item.is_stock_item = 0
|
||||
item.save()
|
||||
else:
|
||||
item = frappe.get_doc("Item", item_code)
|
||||
return item
|
||||
|
@ -11,7 +11,7 @@ frappe.ui.form.on("Membership Settings", {
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_query('inv_print_format', function(doc) {
|
||||
frm.set_query("inv_print_format", function() {
|
||||
return {
|
||||
filters: {
|
||||
"doc_type": "Sales Invoice"
|
||||
@ -19,7 +19,7 @@ frappe.ui.form.on("Membership Settings", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('membership_print_format', function(doc) {
|
||||
frm.set_query("membership_print_format", function() {
|
||||
return {
|
||||
filters: {
|
||||
"doc_type": "Membership"
|
||||
@ -27,12 +27,23 @@ frappe.ui.form.on("Membership Settings", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('debit_account', function(doc) {
|
||||
frm.set_query("debit_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
'account_type': 'Receivable',
|
||||
'is_group': 0,
|
||||
'company': frm.doc.company
|
||||
"account_type": "Receivable",
|
||||
"is_group": 0,
|
||||
"company": frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("payment_account", function () {
|
||||
var account_types = ["Bank", "Cash"];
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ["in", account_types],
|
||||
"is_group": 0,
|
||||
"company": frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -11,9 +11,12 @@
|
||||
"billing_frequency",
|
||||
"webhook_secret",
|
||||
"column_break_6",
|
||||
"enable_auto_invoicing",
|
||||
"enable_invoicing",
|
||||
"create_for_web_forms",
|
||||
"make_payment_entry",
|
||||
"company",
|
||||
"debit_account",
|
||||
"payment_account",
|
||||
"column_break_9",
|
||||
"send_email",
|
||||
"send_invoice",
|
||||
@ -58,14 +61,7 @@
|
||||
"label": "Invoicing"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_auto_invoicing",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Auto Invoicing",
|
||||
"mandatory_depends_on": "eval:doc.send_invoice"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_invoicing",
|
||||
"depends_on": "eval:doc.enable_invoicing",
|
||||
"fieldname": "debit_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Debit Account",
|
||||
@ -77,7 +73,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_invoicing",
|
||||
"depends_on": "eval:doc.enable_invoicing",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
@ -86,7 +82,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.enable_auto_invoicing && doc.send_email",
|
||||
"depends_on": "eval:doc.enable_invoicing && doc.send_email",
|
||||
"fieldname": "send_invoice",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Invoice with Email"
|
||||
@ -119,11 +115,43 @@
|
||||
"label": "Email Template",
|
||||
"mandatory_depends_on": "eval:doc.send_email",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_invoicing",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Invoicing",
|
||||
"mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.enable_invoicing",
|
||||
"description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.",
|
||||
"fieldname": "make_payment_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Make Payment Entry"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.make_payment_entry",
|
||||
"fieldname": "payment_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment To",
|
||||
"mandatory_depends_on": "eval:doc.make_payment_entry",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.enable_invoicing",
|
||||
"description": "Automatically create an invoice when payment is authorized from a web form entry",
|
||||
"fieldname": "create_for_web_forms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Create Invoice for Web Forms"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-05 17:26:37.287395",
|
||||
"modified": "2021-01-21 19:57:53.213286",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Membership Settings",
|
||||
|
@ -2,13 +2,21 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Membership Type', {
|
||||
refresh: function(frm) {
|
||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
||||
refresh: function (frm) {
|
||||
frappe.db.get_single_value('Membership Settings', 'enable_razorpay').then(val => {
|
||||
if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
|
||||
});
|
||||
|
||||
frappe.db.get_single_value("Membership Settings", "enable_auto_invoicing").then(val => {
|
||||
frappe.db.get_single_value('Membership Settings', 'enable_invoicing').then(val => {
|
||||
if (val) frm.set_df_property('linked_item', 'hidden', false);
|
||||
});
|
||||
|
||||
frm.set_query('linked_item', () => {
|
||||
return {
|
||||
filters: {
|
||||
is_stock_item: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -5,9 +5,14 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
class MembershipType(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
if self.linked_item:
|
||||
is_stock_item = frappe.db.get_value("Item", self.linked_item, "is_stock_item")
|
||||
if is_stock_item:
|
||||
frappe.throw(_("The Linked Item should be a service item"))
|
||||
|
||||
def get_membership_type(razorpay_id):
|
||||
return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})
|
@ -677,7 +677,7 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
|
||||
erpnext.patches.v12_0.fix_quotation_expired_status
|
||||
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
|
||||
erpnext.patches.v12_0.rename_pos_closing_doctype
|
||||
erpnext.patches.v13_0.replace_pos_payment_mode_table
|
||||
erpnext.patches.v13_0.replace_pos_payment_mode_table #2020-12-29
|
||||
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
|
||||
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
|
||||
execute:frappe.reload_doc("HR", "doctype", "Employee Advance")
|
||||
@ -737,13 +737,18 @@ erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
||||
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
|
||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||
erpnext.patches.v13_0.update_member_email_address
|
||||
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||
erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
|
||||
erpnext.patches.v13_0.add_po_to_global_search
|
||||
erpnext.patches.v13_0.update_returned_qty_in_pr_dn
|
||||
execute:frappe.rename_doc("Workspace", "Loan", "Loan Management", ignore_if_exists=True, force=True)
|
||||
erpnext.patches.v13_0.create_uae_pos_invoice_fields
|
||||
erpnext.patches.v13_0.update_project_template_tasks
|
||||
erpnext.patches.v13_0.set_company_in_leave_ledger_entry
|
||||
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
|
||||
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
|
||||
erpnext.patches.v13_0.add_naming_series_to_old_projects
|
||||
|
26
erpnext/patches/v13_0/add_naming_series_to_old_projects.py
Normal file
26
erpnext/patches/v13_0/add_naming_series_to_old_projects.py
Normal file
@ -0,0 +1,26 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter, delete_property_setter
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("projects", "doctype", "project")
|
||||
projects = frappe.db.get_all("Project",
|
||||
fields=["name", "naming_series", "modified"],
|
||||
filters={
|
||||
"naming_series": ["is", "not set"]
|
||||
},
|
||||
order_by="timestamp(modified) asc")
|
||||
|
||||
# disable set only once as the old docs must be saved
|
||||
# (to bypass 'Cant change naming series' validation on save)
|
||||
make_property_setter("Project", "naming_series", "set_only_once", 0, "Check")
|
||||
|
||||
for entry in projects:
|
||||
# need to save the doc so that users can edit old projects
|
||||
doc = frappe.get_doc("Project", entry.name)
|
||||
if not doc.naming_series:
|
||||
doc.naming_series = "PROJ-.####"
|
||||
doc.save()
|
||||
|
||||
delete_property_setter("Project", "set_only_once", "naming_series")
|
||||
frappe.db.commit()
|
14
erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
Normal file
14
erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from erpnext.regional.united_arab_emirates.setup import make_custom_fields
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': ['in', ['Saudi Arabia', 'United Arab Emirates']]})
|
||||
if not company:
|
||||
return
|
||||
|
||||
make_custom_fields()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user