From 91b307e7f8bc2ccc9865f2650777e49475becae7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 11 Mar 2020 13:59:15 +0530 Subject: [PATCH 001/101] fix: project link not set in accounts other than profilt and loss accounts --- .../accounts_settings/accounts_settings.json | 416 +++++++++--------- erpnext/accounts/doctype/gl_entry/gl_entry.py | 6 - .../purchase_invoice/purchase_invoice.json | 9 +- .../purchase_invoice/purchase_invoice.py | 39 +- .../doctype/sales_invoice/sales_invoice.py | 12 +- .../sales_invoice_item.json | 9 +- erpnext/accounts/utils.py | 9 +- erpnext/controllers/stock_controller.py | 2 + .../delivery_note_item.json | 10 +- .../purchase_receipt/purchase_receipt.json | 10 +- 10 files changed, 276 insertions(+), 246 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 4ff4212920..d2f7de3be5 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -1,210 +1,210 @@ { - "creation": "2013-06-24 15:49:57", - "description": "Settings for Accounts", - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "auto_accounting_for_stock", - "acc_frozen_upto", - "frozen_accounts_modifier", - "determine_address_tax_category_from", - "over_billing_allowance", - "column_break_4", - "credit_controller", - "check_supplier_invoice_uniqueness", - "make_payment_via_journal_entry", - "unlink_payment_on_cancellation_of_invoice", - "unlink_advance_payment_on_cancelation_of_order", - "book_asset_depreciation_entry_automatically", - "allow_cost_center_in_entry_of_bs_account", - "add_taxes_from_item_tax_template", - "automatically_fetch_payment_terms", - "print_settings", - "show_inclusive_tax_in_print", - "column_break_12", - "show_payment_schedule_in_print", - "currency_exchange_section", - "allow_stale", - "stale_days", - "report_settings_sb", - "use_custom_cash_flow" - ], - "fields": [ - { - "default": "1", - "description": "If enabled, the system will post accounting entries for inventory automatically.", - "fieldname": "auto_accounting_for_stock", - "fieldtype": "Check", - "hidden": 1, - "in_list_view": 1, - "label": "Make Accounting Entry For Every Stock Movement" - }, - { - "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", - "fieldname": "acc_frozen_upto", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Accounts Frozen Upto" - }, - { - "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", - "fieldname": "frozen_accounts_modifier", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", - "options": "Role" - }, - { - "default": "Billing Address", - "description": "Address used to determine Tax Category in transactions.", - "fieldname": "determine_address_tax_category_from", - "fieldtype": "Select", - "label": "Determine Address Tax Category From", - "options": "Billing Address\nShipping Address" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "description": "Role that is allowed to submit transactions that exceed credit limits set.", - "fieldname": "credit_controller", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Credit Controller", - "options": "Role" - }, - { - "fieldname": "check_supplier_invoice_uniqueness", - "fieldtype": "Check", - "label": "Check Supplier Invoice Number Uniqueness" - }, - { - "fieldname": "make_payment_via_journal_entry", - "fieldtype": "Check", - "label": "Make Payment via Journal Entry" - }, - { - "default": "1", - "fieldname": "unlink_payment_on_cancellation_of_invoice", - "fieldtype": "Check", - "label": "Unlink Payment on Cancellation of Invoice" - }, - { - "default": "1", - "fieldname": "unlink_advance_payment_on_cancelation_of_order", - "fieldtype": "Check", - "label": "Unlink Advance Payment on Cancelation of Order" - }, - { - "default": "1", - "fieldname": "book_asset_depreciation_entry_automatically", - "fieldtype": "Check", - "label": "Book Asset Depreciation Entry Automatically" - }, - { - "fieldname": "allow_cost_center_in_entry_of_bs_account", - "fieldtype": "Check", - "label": "Allow Cost Center In Entry of Balance Sheet Account" - }, - { - "default": "1", - "fieldname": "add_taxes_from_item_tax_template", - "fieldtype": "Check", - "label": "Automatically Add Taxes and Charges from Item Tax Template" - }, - { - "fieldname": "print_settings", - "fieldtype": "Section Break", - "label": "Print Settings" - }, - { - "fieldname": "show_inclusive_tax_in_print", - "fieldtype": "Check", - "label": "Show Inclusive Tax In Print" - }, - { - "fieldname": "column_break_12", - "fieldtype": "Column Break" - }, - { - "fieldname": "show_payment_schedule_in_print", - "fieldtype": "Check", - "label": "Show Payment Schedule in Print" - }, - { - "fieldname": "currency_exchange_section", - "fieldtype": "Section Break", - "label": "Currency Exchange Settings" - }, - { - "default": "1", - "fieldname": "allow_stale", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Allow Stale Exchange Rates" - }, - { - "default": "1", - "depends_on": "eval:doc.allow_stale==0", - "fieldname": "stale_days", - "fieldtype": "Int", - "label": "Stale Days" - }, - { - "fieldname": "report_settings_sb", - "fieldtype": "Section Break", - "label": "Report Settings" - }, - { - "default": "0", - "description": "Only select if you have setup Cash Flow Mapper documents", - "fieldname": "use_custom_cash_flow", - "fieldtype": "Check", - "label": "Use Custom Cash Flow Format" - }, - { - "fieldname": "automatically_fetch_payment_terms", - "fieldtype": "Check", - "label": "Automatically Fetch Payment Terms" - }, - { - "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", - "fieldname": "over_billing_allowance", - "fieldtype": "Currency", - "label": "Over Billing Allowance (%)" - } - ], - "icon": "icon-cog", - "idx": 1, - "issingle": 1, - "modified": "2019-07-04 18:20:55.789946", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Accounts Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Accounts Manager", - "share": 1, - "write": 1 - }, - { - "read": 1, - "role": "Sales User" - }, - { - "read": 1, - "role": "Purchase User" - } - ], - "quick_entry": 1, - "sort_order": "ASC", - "track_changes": 1 + "creation": "2013-06-24 15:49:57", + "description": "Settings for Accounts", + "doctype": "DocType", + "document_type": "Other", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "auto_accounting_for_stock", + "acc_frozen_upto", + "frozen_accounts_modifier", + "determine_address_tax_category_from", + "over_billing_allowance", + "column_break_4", + "credit_controller", + "check_supplier_invoice_uniqueness", + "make_payment_via_journal_entry", + "unlink_payment_on_cancellation_of_invoice", + "unlink_advance_payment_on_cancelation_of_order", + "book_asset_depreciation_entry_automatically", + "add_taxes_from_item_tax_template", + "automatically_fetch_payment_terms", + "print_settings", + "show_inclusive_tax_in_print", + "column_break_12", + "show_payment_schedule_in_print", + "currency_exchange_section", + "allow_stale", + "stale_days", + "report_settings_sb", + "use_custom_cash_flow" + ], + "fields": [ + { + "default": "1", + "description": "If enabled, the system will post accounting entries for inventory automatically.", + "fieldname": "auto_accounting_for_stock", + "fieldtype": "Check", + "hidden": 1, + "in_list_view": 1, + "label": "Make Accounting Entry For Every Stock Movement" + }, + { + "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", + "fieldname": "acc_frozen_upto", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Accounts Frozen Upto" + }, + { + "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", + "fieldname": "frozen_accounts_modifier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", + "options": "Role" + }, + { + "default": "Billing Address", + "description": "Address used to determine Tax Category in transactions.", + "fieldname": "determine_address_tax_category_from", + "fieldtype": "Select", + "label": "Determine Address Tax Category From", + "options": "Billing Address\nShipping Address" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "description": "Role that is allowed to submit transactions that exceed credit limits set.", + "fieldname": "credit_controller", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Credit Controller", + "options": "Role" + }, + { + "default": "0", + "fieldname": "check_supplier_invoice_uniqueness", + "fieldtype": "Check", + "label": "Check Supplier Invoice Number Uniqueness" + }, + { + "default": "0", + "fieldname": "make_payment_via_journal_entry", + "fieldtype": "Check", + "label": "Make Payment via Journal Entry" + }, + { + "default": "1", + "fieldname": "unlink_payment_on_cancellation_of_invoice", + "fieldtype": "Check", + "label": "Unlink Payment on Cancellation of Invoice" + }, + { + "default": "1", + "fieldname": "unlink_advance_payment_on_cancelation_of_order", + "fieldtype": "Check", + "label": "Unlink Advance Payment on Cancelation of Order" + }, + { + "default": "1", + "fieldname": "book_asset_depreciation_entry_automatically", + "fieldtype": "Check", + "label": "Book Asset Depreciation Entry Automatically" + }, + { + "default": "1", + "fieldname": "add_taxes_from_item_tax_template", + "fieldtype": "Check", + "label": "Automatically Add Taxes and Charges from Item Tax Template" + }, + { + "fieldname": "print_settings", + "fieldtype": "Section Break", + "label": "Print Settings" + }, + { + "default": "0", + "fieldname": "show_inclusive_tax_in_print", + "fieldtype": "Check", + "label": "Show Inclusive Tax In Print" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "show_payment_schedule_in_print", + "fieldtype": "Check", + "label": "Show Payment Schedule in Print" + }, + { + "fieldname": "currency_exchange_section", + "fieldtype": "Section Break", + "label": "Currency Exchange Settings" + }, + { + "default": "1", + "fieldname": "allow_stale", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Stale Exchange Rates" + }, + { + "default": "1", + "depends_on": "eval:doc.allow_stale==0", + "fieldname": "stale_days", + "fieldtype": "Int", + "label": "Stale Days" + }, + { + "fieldname": "report_settings_sb", + "fieldtype": "Section Break", + "label": "Report Settings" + }, + { + "default": "0", + "description": "Only select if you have setup Cash Flow Mapper documents", + "fieldname": "use_custom_cash_flow", + "fieldtype": "Check", + "label": "Use Custom Cash Flow Format" + }, + { + "default": "0", + "fieldname": "automatically_fetch_payment_terms", + "fieldtype": "Check", + "label": "Automatically Fetch Payment Terms" + }, + { + "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", + "fieldname": "over_billing_allowance", + "fieldtype": "Currency", + "label": "Over Billing Allowance (%)" } + ], + "icon": "icon-cog", + "idx": 1, + "issingle": 1, + "modified": "2020-03-11 13:09:26.235848", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "Sales User" + }, + { + "read": 1, + "role": "Purchase User" + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index f9e4fd7714..38b8876341 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -75,12 +75,6 @@ class GLEntry(Document): if not self.cost_center and self.voucher_type != 'Period Closing Voucher': frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.") .format(self.voucher_type, self.voucher_no, self.account)) - else: - from erpnext.accounts.utils import get_allow_cost_center_in_entry_of_bs_account - if not get_allow_cost_center_in_entry_of_bs_account() and self.cost_center: - self.cost_center = None - if self.project: - self.project = None def validate_dimensions_for_pl_and_bs(self): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3cd988ccd2..8c4fad5cb6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -25,6 +25,7 @@ "accounting_dimensions_section", "cost_center", "dimension_col_break", + "project", "sb_14", "on_hold", "release_date", @@ -1292,13 +1293,19 @@ "fieldtype": "Check", "label": "Is Internal Supplier", "read_only": 1 + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2019-12-30 19:13:49.610538", + "modified": "2020-03-11 12:28:45.711416", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index cc992cec44..14bf174804 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -466,7 +466,8 @@ class PurchaseInvoice(BuyingController): if self.party_account_currency==self.company_currency else grand_total, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency) ) @@ -506,6 +507,7 @@ class PurchaseInvoice(BuyingController): "account": warehouse_account[item.warehouse]['account'], "against": warehouse_account[item.from_warehouse]["account"], "cost_center": item.cost_center, + "project": item_row.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": warehouse_debit_amount, }, warehouse_account[item.warehouse]["account_currency"], item=item)) @@ -515,6 +517,7 @@ class PurchaseInvoice(BuyingController): "account": warehouse_account[item.from_warehouse]['account'], "against": warehouse_account[item.warehouse]["account"], "cost_center": item.cost_center, + "project": item_row.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")), }, warehouse_account[item.from_warehouse]["account_currency"], item=item)) @@ -538,7 +541,7 @@ class PurchaseInvoice(BuyingController): "debit": warehouse_debit_amount, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "cost_center": item.cost_center, - "project": item.project + "project": item.project or self.project }, account_currency, item=item) ) @@ -551,7 +554,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(amount), - "project": item.project + "project": item.project or self.project }, item=item)) # sub-contracting warehouse @@ -564,6 +567,7 @@ class PurchaseInvoice(BuyingController): "account": supplier_warehouse_account, "against": item.expense_account, "cost_center": item.cost_center, + "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.rm_supp_cost) }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) @@ -582,7 +586,7 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "debit": amount, "cost_center": item.cost_center, - "project": item.project + "project": item.project or self.project }, account_currency, item=item)) # If asset is bought through this document and not linked to PR @@ -595,7 +599,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) gl_entries.append(self.get_gl_dict({ @@ -604,7 +608,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) # update gross amount of asset bought through this document @@ -630,7 +634,8 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or "Accounting Entry for Stock", - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": item.project or self.project }, item=item) ) @@ -659,7 +664,8 @@ class PurchaseInvoice(BuyingController): "debit": base_asset_amount, "debit_in_account_currency": (base_asset_amount if arbnb_currency == self.company_currency else asset_amount), - "cost_center": item.cost_center + "cost_center": item.cost_center, + "project": item.project or self.project }, item=item)) if item.item_tax_amount: @@ -669,6 +675,7 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "cost_center": item.cost_center, + "project": item.project or self.project, "credit": item.item_tax_amount, "credit_in_account_currency": (item.item_tax_amount if asset_eiiav_currency == self.company_currency else @@ -685,7 +692,8 @@ class PurchaseInvoice(BuyingController): "debit": base_asset_amount, "debit_in_account_currency": (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount), - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": item.project or self.project }, item=item)) if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)): @@ -696,6 +704,7 @@ class PurchaseInvoice(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "cost_center": item.cost_center, "credit": item.item_tax_amount, + "project": item.project or self.project, "credit_in_account_currency": (item.item_tax_amount if asset_eiiav_currency == self.company_currency else item.item_tax_amount / self.conversion_rate) @@ -711,7 +720,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) gl_entries.append(self.get_gl_dict({ @@ -720,7 +729,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) # update gross amount of assets bought through this document @@ -755,7 +764,7 @@ class PurchaseInvoice(BuyingController): "debit": stock_adjustment_amt, "remarks": self.get("remarks") or _("Stock Adjustment"), "cost_center": item.cost_center, - "project": item.project + "project": item.project or self.project }, account_currency, item=item) ) @@ -847,7 +856,8 @@ class PurchaseInvoice(BuyingController): if self.party_account_currency==self.company_currency else self.paid_amount, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency) ) @@ -879,7 +889,8 @@ class PurchaseInvoice(BuyingController): if self.party_account_currency==self.company_currency else self.write_off_amount, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency) ) gl_entries.append( diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7f7938db24..6fbd630c1e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -777,7 +777,8 @@ class SalesInvoice(SellingController): if self.party_account_currency==self.company_currency else grand_total, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency) ) @@ -832,7 +833,8 @@ class SalesInvoice(SellingController): "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) if account_currency==self.company_currency else flt(item.net_amount, item.precision("net_amount"))), - "cost_center": item.cost_center + "cost_center": item.cost_center, + "project": item.project or self.project }, account_currency, item=item) ) @@ -913,7 +915,8 @@ class SalesInvoice(SellingController): if self.party_account_currency==self.company_currency else flt(self.change_amount), "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency) ) @@ -946,7 +949,8 @@ class SalesInvoice(SellingController): else flt(self.write_off_amount, self.precision("write_off_amount"))), "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency) ) gl_entries.append( diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index b2294e4318..9bc24664d1 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -94,6 +94,7 @@ "accounting_dimensions_section", "cost_center", "dimension_col_break", + "project", "section_break_54", "page_break" ], @@ -783,12 +784,18 @@ "fieldtype": "Link", "label": "Finance Book", "options": "Finance Book" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2019-12-04 12:22:38.517710", + "modified": "2020-03-11 12:24:41.749986", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 4789063ba5..4f4c0860b0 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -124,14 +124,12 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company # hence, assuming balance as 0.0 return 0.0 - allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account() - if account: report_type = acc.report_type else: report_type = "" - if cost_center and (allow_cost_center_in_entry_of_bs_account or report_type =='Profit and Loss'): + if cost_center and report_type == 'Profit and Loss': cc = frappe.get_doc("Cost Center", cost_center) if cc.is_group: cond.append(""" exists ( @@ -888,11 +886,6 @@ def get_coa(doctype, parent, is_root, chart=None): return accounts -def get_allow_cost_center_in_entry_of_bs_account(): - def generator(): - return cint(frappe.db.get_value('Accounts Settings', None, 'allow_cost_center_in_entry_of_bs_account')) - return frappe.local_cache("get_allow_cost_center_in_entry_of_bs_account", (), generator, regenerate_if_none=True) - def get_stock_accounts(company): return frappe.get_all("Account", filters = { "account_type": "Stock", diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d452fe4ac0..37cea28fad 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -91,6 +91,7 @@ class StockController(AccountsController): "account": warehouse_account[sle.warehouse]["account"], "against": item_row.expense_account, "cost_center": item_row.cost_center, + "project": item_row.project or self.project if hasattr(self, 'project') else None, "remarks": self.get("remarks") or "Accounting Entry for Stock", "debit": flt(sle.stock_value_difference, precision), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", @@ -101,6 +102,7 @@ class StockController(AccountsController): "account": item_row.expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, + "project": item_row.project or self.project if hasattr(self, 'project') else None, "remarks": self.get("remarks") or "Accounting Entry for Stock", "credit": flt(sle.stock_value_difference, precision), "project": item_row.get("project") or self.get("project"), diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 782ac84e57..475a8198dc 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-04-22 13:15:44", "doctype": "DocType", @@ -81,6 +80,7 @@ "accounting_dimensions_section", "cost_center", "dimension_col_break", + "project", "section_break_72", "page_break" ], @@ -699,12 +699,18 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-05 14:18:33.131672", + "modified": "2020-03-11 12:25:06.177894", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 35446ecb1f..bc2f09e6e2 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -105,6 +104,7 @@ "amended_from", "range", "column_break4", + "project", "per_billed", "is_internal_supplier", "inter_company_reference", @@ -925,6 +925,12 @@ "print_width": "50%", "width": "50%" }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, { "fieldname": "per_billed", "fieldtype": "Percent", @@ -1076,7 +1082,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2019-12-30 19:12:49.709711", + "modified": "2020-03-11 12:58:46.515404", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 0a42d819934d9c1732a135bded575641ea9cc73b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 11 Mar 2020 14:27:40 +0530 Subject: [PATCH 002/101] fix: cannot find get_allow_cost_center_in_entry_of_bs_account --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 55d275831e..ef1261baf1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, erpnext, json from frappe import _, scrub, ValidationError from frappe.utils import flt, comma_or, nowdate, getdate -from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on, get_allow_cost_center_in_entry_of_bs_account +from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on from erpnext.accounts.party import get_party_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.setup.utils import get_exchange_rate @@ -597,7 +597,7 @@ def get_outstanding_reference_documents(args): .format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])) # Add cost center condition - if args.get("cost_center") and get_allow_cost_center_in_entry_of_bs_account(): + if args.get("cost_center"): condition += " and cost_center='%s'" % args.get("cost_center") date_fields_dict = { From 706c239cf608b75afcde837cd449747c0a60828c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 11 Mar 2020 15:01:02 +0530 Subject: [PATCH 003/101] fix: remove enable_allow_cost_center_in_entry_of_bs_account --- .../accounts_settings/accounts_settings.py | 6 -- .../journal_entry/test_journal_entry.py | 16 +--- .../payment_entry/test_payment_entry.py | 76 +------------------ .../purchase_invoice/test_purchase_invoice.py | 13 +--- .../sales_invoice/test_sales_invoice.py | 16 +--- .../delivery_note/test_delivery_note.py | 12 +-- .../purchase_receipt/test_purchase_receipt.py | 14 +--- 7 files changed, 14 insertions(+), 139 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 2473d715d0..5593466fc2 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -20,7 +20,6 @@ class AccountsSettings(Document): self.validate_stale_days() self.enable_payment_schedule_in_print() - self.enable_fields_for_cost_center_settings() def validate_stale_days(self): if not self.allow_stale and cint(self.stale_days) <= 0: @@ -33,8 +32,3 @@ class AccountsSettings(Document): for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check") make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check") - - def enable_fields_for_cost_center_settings(self): - show_field = 0 if cint(self.allow_cost_center_in_entry_of_bs_account) else 1 - for doctype in ("Sales Invoice", "Purchase Invoice", "Payment Entry"): - make_property_setter(doctype, "cost_center", "hidden", show_field, "Check") diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 6996c775b3..0b30f9f5c9 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -204,11 +204,8 @@ class TestJournalEntry(unittest.TestCase): self.assertEqual(jv.inter_company_journal_entry_reference, "") self.assertEqual(jv1.inter_company_journal_entry_reference, "") - def test_jv_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_jv_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False) @@ -237,15 +234,9 @@ class TestJournalEntry(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_jv_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_jv_account_and_party_balance_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.utils import get_balance_on - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False) @@ -261,9 +252,6 @@ class TestJournalEntry(unittest.TestCase): account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) self.assertEqual(expected_account_balance, account_balance) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None): if not cost_center: cost_center = "_Test Cost Center - _TC" diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 5303743d42..3ae5b2d5c4 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -438,11 +438,8 @@ class TestPaymentEntry(unittest.TestCase): outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) self.assertEqual(outstanding_amount, 0) - def test_payment_entry_against_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_payment_entry_against_sales_invoice_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -477,39 +474,8 @@ class TestPaymentEntry(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_payment_entry_against_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - si = create_sales_invoice(debit_to="Debtors - _TC") - - pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") - - pe.reference_no = "112211-2" - pe.reference_date = nowdate() - pe.paid_to = "_Test Bank - _TC" - pe.paid_amount = si.grand_total - pe.insert() - pe.submit() - - gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", pe.name, as_dict=1) - - self.assertTrue(gl_entries) - - for gle in gl_entries: - self.assertEqual(gle.cost_center, None) - - def test_payment_entry_against_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_payment_entry_against_purchase_invoice_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -544,40 +510,9 @@ class TestPaymentEntry(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_payment_entry_against_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - pi = make_purchase_invoice(credit_to="Creditors - _TC") - - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") - - pe.reference_no = "112222-2" - pe.reference_date = nowdate() - pe.paid_from = "_Test Bank - _TC" - pe.paid_amount = pi.grand_total - pe.insert() - pe.submit() - - gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", pe.name, as_dict=1) - - self.assertTrue(gl_entries) - - for gle in gl_entries: - self.assertEqual(gle.cost_center, None) - - def test_payment_entry_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_payment_entry_account_and_party_balance_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.utils import get_balance_on - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -606,7 +541,4 @@ class TestPaymentEntry(unittest.TestCase): self.assertEqual(pe.cost_center, si.cost_center) self.assertEqual(expected_account_balance, account_balance) self.assertEqual(expected_party_balance, party_balance) - self.assertEqual(expected_party_account_balance, party_account_balance) - - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() \ No newline at end of file + self.assertEqual(expected_party_account_balance, party_account_balance) \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index e41ad42846..c2343319ab 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -791,11 +791,8 @@ class TestPurchaseInvoice(unittest.TestCase): pi_doc = frappe.get_doc('Purchase Invoice', pi.name) self.assertEqual(pi_doc.outstanding_amount, 0) - def test_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_purchase_invoice_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -821,13 +818,7 @@ class TestPurchaseInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() + def test_purchase_invoice_without_cost_center(self): cost_center = "_Test Cost Center - _TC" pi = make_purchase_invoice(credit_to="Creditors - _TC") diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e48e6c95a3..6636025910 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1633,11 +1633,8 @@ class TestSalesInvoice(unittest.TestCase): si_doc = frappe.get_doc('Sales Invoice', si.name) self.assertEqual(si_doc.outstanding_amount, 0) - def test_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_sales_invoice_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -1663,13 +1660,7 @@ class TestSalesInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() + def test_sales_invoice_without_cost_center(self): cost_center = "_Test Cost Center - _TC" si = create_sales_invoice(debit_to="Debtors - _TC") @@ -1692,9 +1683,6 @@ class TestSalesInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - def test_deferred_revenue(self): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index dc92c5c9ff..2c8414ab70 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -547,11 +547,8 @@ class TestDeliveryNote(unittest.TestCase): dt = make_delivery_trip(dn.name) self.assertEqual(dn.name, dt.delivery_stops[0].delivery_note) - def test_delivery_note_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_delivery_note_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - TCP1" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory") @@ -577,13 +574,8 @@ class TestDeliveryNote(unittest.TestCase): } for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - def test_delivery_note_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() + def test_delivery_note_without_cost_center(self): cost_center = "Main - TCP1" company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d80e8f211b..07f89ee9ab 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -372,11 +372,8 @@ class TestPurchaseReceipt(unittest.TestCase): location = frappe.db.get_value('Asset', assets[0].name, 'location') self.assertEquals(location, "Test Location") - def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_purchase_receipt_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - TCP1" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory") @@ -404,14 +401,7 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_purchase_receipt_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - + def test_purchase_receipt_without_cost_center(self): if not frappe.db.exists('Location', 'Test Location'): frappe.get_doc({ 'doctype': 'Location', From af1221bcbd9045b18fb37a0e1865eafa8c5356fe Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 12 Mar 2020 17:04:17 +0530 Subject: [PATCH 004/101] chore: add tests and remove test based on allow_cost_center_for_bs_accounts --- .../journal_entry/test_journal_entry.py | 36 +++++++++++++++++ .../purchase_invoice/test_purchase_invoice.py | 38 ++++++++++++++++++ .../sales_invoice/test_sales_invoice.py | 39 +++++++++++++++++++ .../projects/doctype/project/test_project.py | 22 ++++++++++- .../project_template/test_project_template.py | 21 +++++++++- 5 files changed, 154 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 0b30f9f5c9..23ad1eef14 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -234,6 +234,42 @@ class TestJournalEntry(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + def test_jv_with_project(self): + from erpnext.projects.doctype.project.test_project import make_project + project = make_project({ + 'project_name': 'Journal Entry Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2020-01-01' + }) + + jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) + for d in jv.accounts: + d.project = project.project_name + jv.voucher_type = "Bank Entry" + jv.multi_currency = 0 + jv.cheque_no = "112233" + jv.cheque_date = nowdate() + jv.insert() + jv.submit() + + expected_values = { + "_Test Cash - _TC": { + "project": project.project_name + }, + "_Test Bank - _TC": { + "project": project.project_name + } + } + + gl_entries = frappe.db.sql("""select account, project, debit, credit + from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s + order by account asc""", jv.name, as_dict=1) + + self.assertTrue(gl_entries) + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account]["project"], gle.project) + def test_jv_account_and_party_balance_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.utils import get_balance_on diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c2343319ab..af0eddf3de 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -840,7 +840,45 @@ class TestPurchaseInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + + def test_purchase_invoice_with_project_link(self): + from erpnext.projects.doctype.project.test_project import make_project + project = make_project({ + 'project_name': 'Purchase Invoice Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2020-01-01' + }) + item_project = make_project({ + 'project_name': 'Purchase Invoice Item Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2019-06-01' + }) + + pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1) + pi.items[0].project = item_project.project_name + pi.project = project.project_name + + pi.submit() + + expected_values = { + "Creditors - _TC": { + "project": project.project_name + }, + "_Test Account Cost for Goods Sold - _TC": { + "project": item_project.project_name + } + } + + gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", pi.name, as_dict=1) + + self.assertTrue(gl_entries) + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account]["project"], gle.project) def unlink_payment_on_cancel_of_invoice(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6636025910..cb2d8c35bd 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1659,6 +1659,45 @@ class TestSalesInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + + def test_sales_invoice_with_project_link(self): + from erpnext.projects.doctype.project.test_project import make_project + + project = make_project({ + 'project_name': 'Sales Invoice Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2020-01-01' + }) + item_project = make_project({ + 'project_name': 'Sales Invoice Item Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2019-06-01' + }) + + sales_invoice = create_sales_invoice(do_not_save=1) + sales_invoice.items[0].project = item_project.project_name + sales_invoice.project = project.project_name + + sales_invoice.submit() + + expected_values = { + "Debtors - _TC": { + "project": project.project_name + }, + "Sales - _TC": { + "project": item_project.project_name + } + } + + gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s + order by account asc""", sales_invoice.name, as_dict=1) + + self.assertTrue(gl_entries) + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account]["project"], gle.project) def test_sales_invoice_without_cost_center(self): cost_center = "_Test Cost Center - _TC" diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 06c62b62d2..0c4f6f1bdf 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -7,7 +7,7 @@ import frappe, unittest test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] -from erpnext.projects.doctype.project_template.test_project_template import get_project_template +from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template from erpnext.projects.doctype.project.project import set_project_status from frappe.utils import getdate @@ -43,4 +43,24 @@ def get_project(name): expected_start_date = '2019-01-01' )).insert() + return project + +def make_project(args): + args = frappe._dict(args) + if args.project_template_name: + template = make_project_template(args.project_template_name) + else: + template = get_project_template() + + project = frappe.get_doc(dict( + doctype = 'Project', + project_name = args.project_name, + status = 'Open', + project_template = template.name, + expected_start_date = args.start_date + )) + + if not frappe.db.exists("Project", args.project_name): + project.insert() + return project \ No newline at end of file diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index efcb2eab68..2c5831a5dc 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -26,4 +26,23 @@ def get_project_template(): ] )).insert() - return frappe.get_doc('Project Template', 'Test Project Template') \ No newline at end of file + return frappe.get_doc('Project Template', 'Test Project Template') + +def make_project_template(project_template_name, project_tasks=[]): + if not frappe.db.exists('Project Template', project_template_name): + frappe.get_doc(dict( + doctype = 'Project Template', + name = project_template_name, + tasks = project_tasks or [ + dict(subject='Task 1', description='Task 1 description', + start=0, duration=3), + dict(subject='Task 2', description='Task 2 description', + start=0, duration=2), + dict(subject='Task 3', description='Task 3 description', + start=2, duration=4), + dict(subject='Task 4', description='Task 4 description', + start=3, duration=2), + ] + )).insert() + + return frappe.get_doc('Project Template', project_template_name) \ No newline at end of file From 4a85b42da0838817396503b71af4831204adec3e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 3 Apr 2020 14:31:00 +0530 Subject: [PATCH 005/101] fix: travis --- erpnext/assets/doctype/asset/depreciation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 522c1fef67..c50211e6ab 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -10,7 +10,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g def post_depreciation_entries(date=None): # Return if automatic booking of asset depreciation is disabled - if not cint(frappe.db.get_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically")): + if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")): return if not date: @@ -58,7 +58,8 @@ def make_depreciation_entry(asset_name, date=None): "account": accumulated_depreciation_account, "credit_in_account_currency": d.depreciation_amount, "reference_type": "Asset", - "reference_name": asset.name + "reference_name": asset.name, + "cost_center": "" } debit_entry = { From aa196c0a667ee7c3f2400fc7bd65319ccbb25a5a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 14 Apr 2020 14:03:02 +0530 Subject: [PATCH 006/101] fix: travis --- .../purchase_invoice_item/purchase_invoice_item.json | 4 ++-- erpnext/assets/doctype/asset/depreciation.py | 3 +-- erpnext/crm/doctype/opportunity/test_records.json | 1 + erpnext/healthcare/setup.py | 3 ++- erpnext/manufacturing/doctype/bom/test_records.json | 9 ++++++--- .../setup/setup_wizard/operations/install_fixtures.py | 1 - erpnext/www/book-appointment/__init__.py | 0 erpnext/www/book-appointment/verify/__init__.py | 0 8 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 erpnext/www/book-appointment/__init__.py create mode 100644 erpnext/www/book-appointment/verify/__init__.py diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index ef90b942b5..5e073f8e32 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -761,7 +761,7 @@ "depends_on": "is_fixed_asset", "fetch_from": "item_code.asset_category", "fieldname": "asset_category", - "fieldtype": "Data", + "fieldtype": "Link", "label": "Asset Category", "options": "Asset Category", "read_only": 1 @@ -777,7 +777,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-11 14:20:17.297284", + "modified": "2020-04-14 03:33:32.981331", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index c50211e6ab..ad671ba0f2 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -58,8 +58,7 @@ def make_depreciation_entry(asset_name, date=None): "account": accumulated_depreciation_account, "credit_in_account_currency": d.depreciation_amount, "reference_type": "Asset", - "reference_name": asset.name, - "cost_center": "" + "reference_name": asset.name } debit_entry = { diff --git a/erpnext/crm/doctype/opportunity/test_records.json b/erpnext/crm/doctype/opportunity/test_records.json index a1e0ad921b..0a6c29b637 100644 --- a/erpnext/crm/doctype/opportunity/test_records.json +++ b/erpnext/crm/doctype/opportunity/test_records.json @@ -2,6 +2,7 @@ { "doctype": "Opportunity", "name": "_Test Opportunity 1", + "company": "Wind Power LLC", "opportunity_from": "Lead", "enquiry_type": "Sales", "party_name": "_T-Lead-00001", diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py index 2087f49f32..224dc8d3ad 100644 --- a/erpnext/healthcare/setup.py +++ b/erpnext/healthcare/setup.py @@ -198,7 +198,8 @@ def add_healthcare_service_unit_tree_root(): { "doctype": "Healthcare Service Unit", "healthcare_service_unit_name": "All Healthcare Service Units", - "is_group": 1 + "is_group": 1, + "company": "Wind Power LLC" } ] insert_record(record) diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 25730f9b9f..3913268e1d 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -32,7 +32,8 @@ "is_active": 1, "is_default": 1, "item": "_Test Item Home Desktop Manufactured", - "quantity": 1.0 + "quantity": 1.0, + "company": "_Test Company" }, { "scrap_items":[ @@ -78,7 +79,8 @@ "is_default": 1, "currency": "USD", "item": "_Test FG Item", - "quantity": 1.0 + "quantity": 1.0, + "company":"_Test Company" }, { "operations": [ @@ -160,6 +162,7 @@ "currency": "USD", "item": "_Test Variant Item", "quantity": 1.0, - "with_operations": 1 + "with_operations": 1, + "company": "_Test Company" } ] diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index ebd7b50939..432df5b901 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -435,7 +435,6 @@ def install_defaults(args=None): global_defaults.update({ 'current_fiscal_year': current_fiscal_year.name, 'default_currency': args.get('currency'), - 'default_company':args.get('company_name') , "country": args.get("country"), }) diff --git a/erpnext/www/book-appointment/__init__.py b/erpnext/www/book-appointment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/www/book-appointment/verify/__init__.py b/erpnext/www/book-appointment/verify/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 1f20c99ecfb51675d7182588001bb3c1d581f415 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 14 Apr 2020 19:40:25 +0530 Subject: [PATCH 007/101] fix: tests depending on global default company --- .../sales_invoice/test_sales_invoice.py | 1 + .../doctype/subscription/test_subscription.py | 43 +++++++++++++++++++ erpnext/assets/doctype/asset/test_asset.py | 3 +- .../test_procurement_tracker.py | 6 ++- erpnext/controllers/tests/test_mapper.py | 2 + .../doctype/opportunity/test_opportunity.py | 1 + .../plaid_settings/test_plaid_settings.py | 2 +- .../inpatient_record/test_inpatient_record.py | 3 +- .../test_compensatory_leave_request.py | 3 +- .../hr/doctype/department/test_department.py | 2 +- erpnext/hr/doctype/employee/test_employee.py | 2 +- ...test_employee_tax_exemption_declaration.py | 2 +- .../expense_claim/test_expense_claim.py | 1 + .../hr/doctype/job_offer/test_job_offer.py | 4 +- .../doctype/salary_slip/test_salary_slip.py | 2 +- .../salary_structure/test_salary_structure.py | 2 +- .../training_event/test_training_event.py | 3 +- .../delivery_note/test_delivery_note.py | 27 +----------- .../delivery_trip/test_delivery_trip.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 24 ----------- .../stock/doctype/serial_no/test_serial_no.py | 1 + 21 files changed, 72 insertions(+), 64 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 2692465051..df2cc29a16 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1058,6 +1058,7 @@ class TestSalesInvoice(unittest.TestCase): serial_no = frappe.get_doc({ "doctype": "Serial No", "item_code": "_Test Serialized Item With Series", + "company": "Wind Power LLC", "serial_no": make_autoname("SR", "Serial No") }) serial_no.save() diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 3d96f233b4..a0784e6719 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -101,6 +101,7 @@ class TestSubscription(unittest.TestCase): subscription.delete() def test_invoice_is_generated_at_end_of_billing_period(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.start = '2018-01-01' @@ -116,8 +117,10 @@ class TestSubscription(unittest.TestCase): self.assertEqual(subscription.current_invoice_start, '2018-01-01') self.assertEqual(subscription.status, 'Past Due Date') subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_status_goes_back_to_active_after_invoice_is_paid(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -141,8 +144,10 @@ class TestSubscription(unittest.TestCase): self.assertEqual(len(subscription.invoices), 1) subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancel_after_grace_period(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 1 @@ -164,8 +169,11 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_unpaid_after_grace_period(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 0 @@ -187,8 +195,11 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_invoice_days_until_due(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -200,8 +211,11 @@ class TestSubscription(unittest.TestCase): self.assertEqual(subscription.status, 'Active') subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_is_past_due_doesnt_change_within_grace_period(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + settings = frappe.get_single('Subscription Settings') grace_period = settings.grace_period settings.grace_period = 1000 @@ -229,6 +243,7 @@ class TestSubscription(unittest.TestCase): settings.grace_period = grace_period settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_remains_active_during_invoice_period(self): subscription = frappe.new_doc('Subscription') @@ -257,6 +272,7 @@ class TestSubscription(unittest.TestCase): subscription.delete() def test_subscription_cancelation(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -266,8 +282,10 @@ class TestSubscription(unittest.TestCase): self.assertEqual(subscription.status, 'Cancelled') subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancellation_invoices(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 1 @@ -301,8 +319,11 @@ class TestSubscription(unittest.TestCase): subscription.delete() settings.prorate = to_prorate settings.save() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancellation_invoices_with_prorata_false(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 0 @@ -321,8 +342,11 @@ class TestSubscription(unittest.TestCase): settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancellation_invoices_with_prorata_true(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 1 @@ -345,8 +369,10 @@ class TestSubscription(unittest.TestCase): settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subcription_cancellation_and_process(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 1 @@ -378,8 +404,11 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_restart_and_process(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.grace_period = 0 @@ -416,8 +445,11 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_unpaid_back_to_active(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 0 @@ -450,6 +482,7 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_restart_active_subscription(self): subscription = frappe.new_doc('Subscription') @@ -462,6 +495,8 @@ class TestSubscription(unittest.TestCase): subscription.delete() def test_subscription_invoice_discount_percentage(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.additional_discount_percentage = 10 @@ -475,8 +510,11 @@ class TestSubscription(unittest.TestCase): self.assertEqual(invoice.apply_discount_on, 'Grand Total') subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_invoice_discount_amount(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") + subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.additional_discount_amount = 11 @@ -490,10 +528,12 @@ class TestSubscription(unittest.TestCase): self.assertEqual(invoice.apply_discount_on, 'Grand Total') subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_prepaid_subscriptions(self): # Create a non pre-billed subscription, processing should not create # invoices. + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -509,8 +549,10 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(len(subscription.invoices), 1) + frappe.db.set_value("Global Defaults", None, "default_company", None) def test_prepaid_subscriptions_with_prorate_true(self): + frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 1 @@ -538,3 +580,4 @@ class TestSubscription(unittest.TestCase): settings.save() subscription.delete() + frappe.db.set_value("Global Defaults", None, "default_company", None) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a56440de3d..d2a70375c2 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -78,6 +78,7 @@ class TestAsset(unittest.TestCase): 'qty': 1, 'asset': asset.name }) + doc.company = 'Wind Power LLC' doc.set_missing_values() self.assertEquals(doc.items[0].is_fixed_asset, 1) @@ -595,7 +596,7 @@ def create_asset(**args): "asset_name": args.asset_name or "Macbook Pro 1", "asset_category": "Computers", "item_code": args.item_code or "Macbook Pro", - "company": args.company or"_Test Company", + "company": args.company or "_Test Company", "purchase_date": "2015-01-01", "calculate_depreciation": 0, "gross_purchase_amount": 100000, diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index bebf0ccec5..0c840ecbd8 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -36,16 +36,18 @@ class TestProcurementTracker(unittest.TestCase): mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse) po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" - po.get("items")[0].cost_center = "_Test Cost Center - _TC" + po.company = "_Test Procurement Company" + po.get("items")[0].cost_center = "_Test Cost Center - _TPC" po.submit() pr = make_purchase_receipt(po.name) + pr.company = "_Test Procurement Company" pr.submit() frappe.db.commit() date_obj = datetime.date(datetime.now()) expected_data = { "material_request_date": date_obj, - "cost_center": "_Test Cost Center - _TC", + "cost_center": "_Test Cost Center - _TPC", "project": None, "requesting_site": "_Test Procurement Warehouse - _TPC", "requestor": "Administrator", diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py index d02308d8f2..93d1b321b4 100644 --- a/erpnext/controllers/tests/test_mapper.py +++ b/erpnext/controllers/tests/test_mapper.py @@ -43,6 +43,7 @@ class TestMapper(unittest.TestCase): qtn = frappe.get_doc({ "doctype": "Quotation", "quotation_to": "Customer", + "company": "_Test Company", "party_name": customer, "order_type": "Sales", "transaction_date" : nowdate(), @@ -59,6 +60,7 @@ class TestMapper(unittest.TestCase): "base_amount": 1000.0, "base_rate": 100.0, "description": "CPU", + "company": "_Test Company", "doctype": "Sales Order Item", "item_code": "_Test Item Home Desktop 100", "item_name": "CPU", diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 33d90076c4..fc852b19a1 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -30,6 +30,7 @@ class TestOpportunity(unittest.TestCase): new_lead_email_id = "new{}@example.com".format(random_string(5)) args = { "doctype": "Opportunity", + "company": "_Test Company", "contact_email": new_lead_email_id, "opportunity_type": "Sales", "with_items": 0, diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 29e8fa4fec..75184d989a 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -108,7 +108,7 @@ class TestPlaidSettings(unittest.TestCase): } bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler) - company = frappe.db.get_single_value('Global Defaults', 'default_company') + company = frappe.db.get_single_value('Global Defaults', 'default_company') or '_Test Company' if frappe.db.get_value("Company", company, "default_bank_account") is None: frappe.db.set_value("Company", company, "default_bank_account", get_default_bank_cash_account(company, "Cash").get("account")) diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index e15324c55b..7a7936a0f8 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -84,7 +84,8 @@ def get_healthcare_service_unit(): service_unit.service_unit_type = get_service_unit_type() service_unit.inpatient_occupancy = 1 service_unit.occupancy_status = "Vacant" - service_unit.is_group = 0 + service_unit.is_group = 0, + service_unit.company = "_Test Company" service_unit_parent_name = frappe.db.exists({ "doctype": "Healthcare Service Unit", "healthcare_service_unit_name": "All Healthcare Service Units", diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index 1615ab30f1..1181192cbe 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -104,7 +104,8 @@ def mark_attendance(employee, date=today(), status='Present'): "doctype": "Attendance", "employee": employee.name, "attendance_date": date, - "status": status + "status": status, + "company": "_Test Company" }) attendance.save() attendance.submit() diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py index 2eeca26e30..a6e8aae625 100644 --- a/erpnext/hr/doctype/department/test_department.py +++ b/erpnext/hr/doctype/department/test_department.py @@ -16,7 +16,7 @@ def create_department(department_name, parent_department=None): 'is_group': 0, 'parent_department': parent_department, 'department_name': department_name, - 'company': frappe.defaults.get_defaults().company + 'company': frappe.defaults.get_defaults().company or 'Wind Power LLC' }).insert() return doc diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index d3410de2eb..eff382d973 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -60,7 +60,7 @@ def make_employee(user, company=None): "doctype": "Employee", "naming_series": "EMP-", "first_name": user, - "company": company or erpnext.get_default_company(), + "company": company or erpnext.get_default_company() or 'Wind Power LLC', "user_id": user, "date_of_birth": "1990-05-08", "date_of_joining": "2013-01-01", diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index 9c87bbd1f3..d53f6a706d 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -88,7 +88,7 @@ def create_payroll_period(): payroll_period = frappe.get_doc(dict( doctype = 'Payroll Period', name = "_Test Payroll Period", - company = erpnext.get_default_company(), + company = erpnext.get_default_company() or 'Wind Power LLC', start_date = date(date.today().year, 1, 1), end_date = date(date.today().year, 12, 31) )).insert() diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 6e97f0513d..e7ee108142 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -94,6 +94,7 @@ class TestExpenseClaim(unittest.TestCase): payable_account = get_payable_account(company_name) expense_claim = frappe.get_doc({ "doctype": "Expense Claim", + "company": "_Test Company", "employee": "_T-Employee-00001", "payable_account": payable_account, "approval_status": "Rejected", diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index 8886596450..1da107bfe7 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -55,7 +55,8 @@ def create_job_offer(**args): "job_applicant": args.job_applicant or job_applicant.name, "offer_date": args.offer_date or nowdate(), "designation": args.designation or "Researcher", - "status": args.status or "Accepted" + "status": args.status or "Accepted", + "company": "_Test Company" }) return job_offer @@ -68,6 +69,7 @@ def create_staffing_plan(**args): staffing_plan = frappe.get_doc({ "doctype": "Staffing Plan", "name": args.name or "Test", + "company": "_Test Company", "from_date": args.from_date or nowdate(), "to_date": args.to_date or add_days(nowdate(), 10), "staffing_details": args.staffing_details or [{ diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index ecccac7d41..cd73b94719 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -359,7 +359,7 @@ def make_salary_component(salary_components, test_tax, company_list=None): get_salary_component_account(salary_component["salary_component"], company_list) def get_salary_component_account(sal_comp, company_list=None): - company = erpnext.get_default_company() + company = erpnext.get_default_company() or 'Wind Power LLC' if company_list and company not in company_list: company_list.append(company) diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index 6ca6dfd2c0..e465d04f4d 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -116,7 +116,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do details = { "doctype": "Salary Structure", "name": salary_structure, - "company": company or erpnext.get_default_company(), + "company": company or erpnext.get_default_company() or "_Test Company", "earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]), "deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]), "payroll_frequency": payroll_frequency, diff --git a/erpnext/hr/doctype/training_event/test_training_event.py b/erpnext/hr/doctype/training_event/test_training_event.py index 57123e304f..5ddb99b8ea 100644 --- a/erpnext/hr/doctype/training_event/test_training_event.py +++ b/erpnext/hr/doctype/training_event/test_training_event.py @@ -32,7 +32,8 @@ def create_training_program(training_program): frappe.get_doc({ "doctype": "Training Program", "training_program": training_program, - "description": training_program + "description": training_program, + "company": "Wind Power LLC" }).insert() def get_attendees(employee, employee2): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index c15fada525..a0c69c3518 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -192,6 +192,7 @@ class TestDeliveryNote(unittest.TestCase): serial_no = frappe.get_doc({ "doctype": "Serial No", "item_code": "_Test Serialized Item With Series", + "company": "Wind Power LLC", "serial_no": make_autoname("SR", "Serial No") }) serial_no.save() @@ -585,32 +586,6 @@ class TestDeliveryNote(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - def test_delivery_note_without_cost_center(self): - cost_center = "Main - TCP1" - - company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - - set_valuation_method("_Test Item", "FIFO") - - make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) - - stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') - dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") - - gl_entries = get_gl_entries("Delivery Note", dn.name) - - self.assertTrue(gl_entries) - expected_values = { - "Cost of Goods Sold - TCP1": { - "cost_center": cost_center - }, - stock_in_hand_account: { - "cost_center": None - } - } - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - def test_make_sales_invoice_from_dn_for_returned_qty(self): from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index eeea6da7a4..da2c97cb8c 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -172,7 +172,7 @@ def create_delivery_trip(driver, address, contact=None): delivery_trip = frappe.get_doc({ "doctype": "Delivery Trip", - "company": erpnext.get_default_company(), + "company": erpnext.get_default_company() or 'Wind Power LLC', "departure_time": add_days(now_datetime(), 5), "driver": driver.name, "driver_address": address.name, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 9d41b45686..a45fb304d1 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -432,30 +432,6 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - def test_purchase_receipt_without_cost_center(self): - if not frappe.db.exists('Location', 'Test Location'): - frappe.get_doc({ - 'doctype': 'Location', - 'location_name': 'Test Location' - }).insert() - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") - - stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) - gl_entries = get_gl_entries("Purchase Receipt", pr.name) - - self.assertTrue(gl_entries) - - expected_values = { - "Stock Received But Not Billed - TCP1": { - "cost_center": None - }, - stock_in_hand_account: { - "cost_center": None - } - } - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - def test_make_purchase_invoice_from_pr_for_returned_qty(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, create_pr_against_po diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index ab061076e5..7b06ef96ce 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -24,6 +24,7 @@ class TestSerialNo(unittest.TestCase): frappe.delete_doc_if_exists("Serial No", "_TCSER0001") sr = frappe.new_doc("Serial No") + sr.company = '_Test Company' sr.item_code = "_Test Serialized Item" sr.warehouse = "_Test Warehouse - _TC" sr.serial_no = "_TCSER0001" From 55410e03e41f8859faf18d6ac4af319a3a0ae6b4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 15 Apr 2020 12:57:55 +0530 Subject: [PATCH 008/101] fix: tests depending on global default company --- erpnext/accounts/doctype/subscription/subscription.py | 4 ++++ .../clinical_procedure/test_clinical_procedure.py | 2 ++ erpnext/healthcare/doctype/patient/test_patient.py | 2 ++ .../test_patient_medical_record.py | 2 ++ erpnext/hr/doctype/employee/test_employee.py | 2 +- .../test_employee_tax_exemption_declaration.py | 8 ++++---- erpnext/hr/doctype/leave_period/test_leave_period.py | 4 ++-- erpnext/hr/doctype/payroll_entry/test_payroll_entry.py | 6 +++--- erpnext/hr/doctype/salary_slip/test_salary_slip.py | 10 +++++----- .../doctype/salary_structure/test_salary_structure.py | 4 ++-- 10 files changed, 27 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 0933c7e8b8..fbdaf1bc95 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -238,6 +238,10 @@ class Subscription(Document): Creates a `Sales Invoice`, submits it and returns it """ invoice = frappe.new_doc('Sales Invoice') + + if not invoice.company: + invoice.company = frappe.db.get_value('Global Defaults', None, 'default_company') + invoice.set_posting_time = 1 invoice.posting_date = self.current_invoice_start invoice.customer = self.customer diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index 207351ff20..100addc9ba 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -18,6 +18,7 @@ class TestClinicalProcedure(unittest.TestCase): self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) def test_consumables(self): + frappe.db.set_value('Global Defaults', None, 'default_company', '_Test Company') patient, medical_department, practitioner = create_healthcare_docs() procedure_template = create_clinical_procedure_template() procedure_template.allow_stock_consumption = 1 @@ -38,6 +39,7 @@ class TestClinicalProcedure(unittest.TestCase): result = procedure.complete_procedure() # check consumption self.assertTrue(frappe.db.exists('Stock Entry', result)) + frappe.db.set_value('Global Defaults', None, 'default_company', None) def create_consumable(): diff --git a/erpnext/healthcare/doctype/patient/test_patient.py b/erpnext/healthcare/doctype/patient/test_patient.py index 9274b6f5e8..dfe61bd6c1 100644 --- a/erpnext/healthcare/doctype/patient/test_patient.py +++ b/erpnext/healthcare/doctype/patient/test_patient.py @@ -15,6 +15,7 @@ class TestPatient(unittest.TestCase): self.assertTrue(frappe.db.get_value('Patient', patient, 'customer')) def test_patient_registration(self): + frappe.db.set_value('Global Defaults', None, 'default_company', '_Test Company') frappe.db.sql("""delete from `tabPatient`""") settings = frappe.get_single('Healthcare Settings') settings.collect_registration_fee = 1 @@ -32,3 +33,4 @@ class TestPatient(unittest.TestCase): settings.collect_registration_fee = 0 settings.save() + frappe.db.set_value('Global Defaults', None, 'default_company', None) diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py index e5a5e4c010..4de90bc09f 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py +++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py @@ -13,6 +13,7 @@ class TestPatientMedicalRecord(unittest.TestCase): frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) def test_medical_record(self): + frappe.db.set_value('Global Defaults', None, 'default_company', '_Test Company') patient, medical_department, practitioner = create_healthcare_docs() appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) encounter = create_encounter(appointment) @@ -38,6 +39,7 @@ class TestPatientMedicalRecord(unittest.TestCase): # check for lab test medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': lab_test.name}) self.assertTrue(medical_rec) + frappe.db.set_value('Global Defaults', None, 'default_company', None) def create_procedure(appointment): diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index eff382d973..755393ea04 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -55,7 +55,7 @@ def make_employee(user, company=None): "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() - if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }): + if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() or 'Wind Power LLC' }): employee = frappe.get_doc({ "doctype": "Employee", "naming_series": "EMP-", diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index d53f6a706d..4dc77ccc51 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -20,7 +20,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), + "company": erpnext.get_default_company() or 'Wind Power LLC', "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", @@ -37,7 +37,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), + "company": erpnext.get_default_company() or 'Wind Power LLC', "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", @@ -52,7 +52,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): duplicate_declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), + "company": erpnext.get_default_company() or 'Wind Power LLC', "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", @@ -68,7 +68,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company(), + "company": erpnext.get_default_company() or 'Wind Power LLC', "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py index 1762cf917a..06e5836738 100644 --- a/erpnext/hr/doctype/leave_period/test_leave_period.py +++ b/erpnext/hr/doctype/leave_period/test_leave_period.py @@ -45,7 +45,7 @@ class TestLeavePeriod(unittest.TestCase): def create_leave_period(from_date, to_date, company=None): leave_period = frappe.db.get_value('Leave Period', - dict(company=company or erpnext.get_default_company(), + dict(company=company or erpnext.get_default_company() or 'Wind Power LLC', from_date=from_date, to_date=to_date, is_active=1), 'name') @@ -54,7 +54,7 @@ def create_leave_period(from_date, to_date, company=None): leave_period = frappe.get_doc({ "doctype": "Leave Period", - "company": company or erpnext.get_default_company(), + "company": company or erpnext.get_default_company() or 'Wind Power LLC', "from_date": from_date, "to_date": to_date, "is_active": 1 diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index 49671d5e22..52fb69f1a4 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -26,7 +26,7 @@ class TestPayrollEntry(unittest.TestCase): frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0) def test_payroll_entry(self): # pylint: disable=no-self-use - company = erpnext.get_default_company() + company = erpnext.get_default_company() or 'Wind Power LLC' for data in frappe.get_all('Salary Component', fields = ["name"]): if not frappe.db.get_value('Salary Component Account', {'parent': data.name, 'company': company}, 'name'): @@ -109,7 +109,7 @@ def make_payroll_entry(**args): args = frappe._dict(args) payroll_entry = frappe.new_doc("Payroll Entry") - payroll_entry.company = args.company or erpnext.get_default_company() + payroll_entry.company = args.company or erpnext.get_default_company() or 'Wind Power LLC' payroll_entry.start_date = args.start_date or "2016-11-01" payroll_entry.end_date = args.end_date or "2016-11-30" payroll_entry.payment_account = get_payment_account() @@ -133,7 +133,7 @@ def make_payroll_entry(**args): def get_payment_account(): return frappe.get_value('Account', - {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") + {'account_type': 'Cash', 'company': erpnext.get_default_company() or 'Wind Power LLC', 'is_group':0}, "name") def make_holiday(holiday_list_name): if not frappe.db.exists('Holiday List', holiday_list_name): diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index cd73b94719..ac91aec282 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -26,7 +26,7 @@ class TestSalarySlip(unittest.TestCase): self.make_holiday_list() - frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") + frappe.db.set_value("Company", erpnext.get_default_company() or 'Wind Power LLC', "default_holiday_list", "Salary Slip Test Holiday List") frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0) def tearDown(self): @@ -175,7 +175,7 @@ class TestSalarySlip(unittest.TestCase): self.assertEqual(ss.net_pay, (flt(ss.gross_pay) - (flt(ss.total_deduction) + flt(ss.total_loan_repayment)))) def test_payroll_frequency(self): - fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())[0] + fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company() or 'Wind Power LLC')[0] month = "%02d" % getdate(nowdate()).month m = get_month_details(fiscal_year, month) @@ -295,7 +295,7 @@ class TestSalarySlip(unittest.TestCase): frappe.db.rollback() def make_holiday_list(self): - fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company()) + fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company() or 'Wind Power LLC') if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"): holiday_list = frappe.get_doc({ "doctype": "Holiday List", @@ -499,7 +499,7 @@ def create_exemption_declaration(employee, payroll_period): "doctype": "Employee Tax Exemption Declaration", "employee": employee, "payroll_period": payroll_period, - "company": erpnext.get_default_company() + "company": erpnext.get_default_company() or 'Wind Power LLC' }) declaration.append("declarations", { "exemption_sub_category": "_Test Sub Category", @@ -588,7 +588,7 @@ def create_additional_salary(employee, payroll_period, amount): frappe.get_doc({ "doctype": "Additional Salary", "employee": employee, - "company": erpnext.get_default_company(), + "company": erpnext.get_default_company() or 'Wind Power LLC', "salary_component": "Performance Bonus", "payroll_date": salary_date, "amount": amount, diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index e465d04f4d..17087d14cf 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -21,7 +21,7 @@ class TestSalaryStructure(unittest.TestCase): frappe.db.sql("delete from `tab%s`" % dt) self.make_holiday_list() - frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List") + frappe.db.set_value("Company", erpnext.get_default_company() or 'Wind Power LLC', "default_holiday_list", "Salary Structure Test Holiday List") make_employee("test_employee@salary.com") make_employee("test_employee_2@salary.com") @@ -145,7 +145,7 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non salary_structure_assignment.variable = 5000 salary_structure_assignment.from_date = from_date or add_months(nowdate(), -1) salary_structure_assignment.salary_structure = salary_structure - salary_structure_assignment.company = company or erpnext.get_default_company() + salary_structure_assignment.company = company or erpnext.get_default_company() or 'Wind Power LLC' salary_structure_assignment.save(ignore_permissions=True) salary_structure_assignment.submit() return salary_structure_assignment From 52baf8f863539b5ad5063c640374dd31ee7e6408 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 May 2020 17:20:03 +0530 Subject: [PATCH 009/101] fix: Test Cases --- .../purchase_invoice/purchase_invoice.json | 2 +- .../purchase_invoice/test_purchase_invoice.py | 13 +++++++------ .../doctype/sales_invoice/test_records.json | 16 ++++++++++++---- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index b8d173bcc8..a80637e7a4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1316,7 +1316,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2020-04-18 13:05:25.199832", + "modified": "2020-05-29 13:05:25.199832", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 5cbfad2051..f61d27aee3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -15,6 +15,7 @@ from erpnext.controllers.accounts_controller import get_payment_terms from erpnext.exceptions import InvalidCurrency from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction from erpnext.accounts.doctype.account.test_account import get_inventory_account +from erpnext.projects.doctype.project.test_project import make_project test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_ignore = ["Serial No"] @@ -434,6 +435,8 @@ class TestPurchaseInvoice(unittest.TestCase): ) def test_total_purchase_cost_for_project(self): + make_project({'project_name':'_Test Project'}) + existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount) from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""") existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0 @@ -819,7 +822,7 @@ class TestPurchaseInvoice(unittest.TestCase): "Creditors - _TC": { "cost_center": cost_center }, - "_Test Account Cost for Goods Sold - _TC": { + "Cost of Goods Sold - _TC": { "cost_center": cost_center } } @@ -856,10 +859,8 @@ class TestPurchaseInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - - def test_purchase_invoice_with_project_link(self): - from erpnext.projects.doctype.project.test_project import make_project + def test_purchase_invoice_with_project_link(self): project = make_project({ 'project_name': 'Purchase Invoice Project', 'project_template_name': 'Test Project Template', @@ -890,9 +891,9 @@ class TestPurchaseInvoice(unittest.TestCase): debit_in_account_currency, credit_in_account_currency from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s order by account asc""", pi.name, as_dict=1) - + self.assertTrue(gl_entries) - + for gle in gl_entries: self.assertEqual(expected_values[gle.account]["project"], gle.project) diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index ebe6e3da8d..11ebe6a573 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -3,6 +3,7 @@ "company": "_Test Company", "conversion_rate": 1.0, "currency": "INR", + "cost_center": "_Test Cost Center - _TC", "customer": "_Test Customer", "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", @@ -37,7 +38,8 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 6 }, { @@ -45,7 +47,8 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 6.36 } ], @@ -76,6 +79,7 @@ "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", "items": [ { "amount": 500.0, @@ -107,7 +111,8 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 16 }, { @@ -115,7 +120,8 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 10 } ], @@ -132,6 +138,7 @@ "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", "items": [ { "cost_center": "_Test Cost Center - _TC", @@ -259,6 +266,7 @@ "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", "items": [ { "cost_center": "_Test Cost Center - _TC", From dfa32a77c1bdfc23ab2722682b3f0412986fe7e5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 May 2020 17:20:33 +0530 Subject: [PATCH 010/101] fix: Patch to remove Property Setter --- erpnext/patches.txt | 3 ++- erpnext/patches/v12_0/unhide_cost_center_field.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/unhide_cost_center_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b0421f43c6..7125abfb69 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -694,4 +694,5 @@ execute:frappe.delete_doc("Report", "Department Analytics") execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True) erpnext.patches.v12_0.update_uom_conversion_factor erpnext.patches.v13_0.delete_old_purchase_reports -erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions \ No newline at end of file +erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions +erpnext.patches.v12_0.unhide_cost_center_field \ No newline at end of file diff --git a/erpnext/patches/v12_0/unhide_cost_center_field.py b/erpnext/patches/v12_0/unhide_cost_center_field.py new file mode 100644 index 0000000000..6005ab7072 --- /dev/null +++ b/erpnext/patches/v12_0/unhide_cost_center_field.py @@ -0,0 +1,13 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.db.sql(""" + DELETE FROM `tabProperty Setter` + WHERE doc_type in ('Sales Invoice', 'Purchase Invoice', 'Payment Entry') + AND field_name = 'cost_center' + AND property = 'hidden' + """) \ No newline at end of file From f8550790fa8850c73ab1489379aa17e9d771091a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 May 2020 11:37:36 +0530 Subject: [PATCH 011/101] This reverts commit aa196c0a667ee7c3f2400fc7bd65319ccbb25a5a --- .../purchase_invoice_item/purchase_invoice_item.json | 2 +- erpnext/assets/doctype/asset/depreciation.py | 3 ++- erpnext/crm/doctype/opportunity/test_records.json | 1 - erpnext/manufacturing/doctype/bom/test_records.json | 9 +++------ .../setup/setup_wizard/operations/install_fixtures.py | 1 + erpnext/www/book-appointment/__init__.py | 0 erpnext/www/book-appointment/verify/__init__.py | 0 7 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 erpnext/www/book-appointment/__init__.py delete mode 100644 erpnext/www/book-appointment/verify/__init__.py diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 52a5be0984..cfbe06ad02 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -753,7 +753,7 @@ "depends_on": "is_fixed_asset", "fetch_from": "item_code.asset_category", "fieldname": "asset_category", - "fieldtype": "Link", + "fieldtype": "Data", "label": "Asset Category", "options": "Asset Category", "read_only": 1 diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index ad671ba0f2..c50211e6ab 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -58,7 +58,8 @@ def make_depreciation_entry(asset_name, date=None): "account": accumulated_depreciation_account, "credit_in_account_currency": d.depreciation_amount, "reference_type": "Asset", - "reference_name": asset.name + "reference_name": asset.name, + "cost_center": "" } debit_entry = { diff --git a/erpnext/crm/doctype/opportunity/test_records.json b/erpnext/crm/doctype/opportunity/test_records.json index 0a6c29b637..a1e0ad921b 100644 --- a/erpnext/crm/doctype/opportunity/test_records.json +++ b/erpnext/crm/doctype/opportunity/test_records.json @@ -2,7 +2,6 @@ { "doctype": "Opportunity", "name": "_Test Opportunity 1", - "company": "Wind Power LLC", "opportunity_from": "Lead", "enquiry_type": "Sales", "party_name": "_T-Lead-00001", diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 3913268e1d..25730f9b9f 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -32,8 +32,7 @@ "is_active": 1, "is_default": 1, "item": "_Test Item Home Desktop Manufactured", - "quantity": 1.0, - "company": "_Test Company" + "quantity": 1.0 }, { "scrap_items":[ @@ -79,8 +78,7 @@ "is_default": 1, "currency": "USD", "item": "_Test FG Item", - "quantity": 1.0, - "company":"_Test Company" + "quantity": 1.0 }, { "operations": [ @@ -162,7 +160,6 @@ "currency": "USD", "item": "_Test Variant Item", "quantity": 1.0, - "with_operations": 1, - "company": "_Test Company" + "with_operations": 1 } ] diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 56899d5fbb..0d70d91f73 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -437,6 +437,7 @@ def install_defaults(args=None): global_defaults.update({ 'current_fiscal_year': current_fiscal_year.name, 'default_currency': args.get('currency'), + 'default_company':args.get('company_name') , "country": args.get("country"), }) diff --git a/erpnext/www/book-appointment/__init__.py b/erpnext/www/book-appointment/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/www/book-appointment/verify/__init__.py b/erpnext/www/book-appointment/verify/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 36f7710356a2fa55db8c5401b4db48bb098b2a28 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 May 2020 11:38:04 +0530 Subject: [PATCH 012/101] Revert "fix: tests depending on global default company" This reverts commit 1f20c99ecfb51675d7182588001bb3c1d581f415. --- .../sales_invoice/test_sales_invoice.py | 1 - .../doctype/subscription/test_subscription.py | 43 ------------------- erpnext/assets/doctype/asset/test_asset.py | 3 +- .../test_procurement_tracker.py | 6 +-- erpnext/controllers/tests/test_mapper.py | 2 - .../doctype/opportunity/test_opportunity.py | 1 - .../plaid_settings/test_plaid_settings.py | 2 +- .../inpatient_record/test_inpatient_record.py | 3 +- .../test_compensatory_leave_request.py | 3 +- .../hr/doctype/department/test_department.py | 2 +- erpnext/hr/doctype/employee/test_employee.py | 2 +- ...test_employee_tax_exemption_declaration.py | 2 +- .../expense_claim/test_expense_claim.py | 1 - .../hr/doctype/job_offer/test_job_offer.py | 4 +- .../doctype/salary_slip/test_salary_slip.py | 2 +- .../salary_structure/test_salary_structure.py | 2 +- .../training_event/test_training_event.py | 3 +- .../delivery_note/test_delivery_note.py | 27 +++++++++++- .../delivery_trip/test_delivery_trip.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 24 +++++++++++ .../stock/doctype/serial_no/test_serial_no.py | 1 - 21 files changed, 64 insertions(+), 72 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index fc53f8818a..a2f9040670 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1065,7 +1065,6 @@ class TestSalesInvoice(unittest.TestCase): serial_no = frappe.get_doc({ "doctype": "Serial No", "item_code": "_Test Serialized Item With Series", - "company": "Wind Power LLC", "serial_no": make_autoname("SR", "Serial No") }) serial_no.save() diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index a0784e6719..3d96f233b4 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -101,7 +101,6 @@ class TestSubscription(unittest.TestCase): subscription.delete() def test_invoice_is_generated_at_end_of_billing_period(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.start = '2018-01-01' @@ -117,10 +116,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual(subscription.current_invoice_start, '2018-01-01') self.assertEqual(subscription.status, 'Past Due Date') subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_status_goes_back_to_active_after_invoice_is_paid(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -144,10 +141,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual(len(subscription.invoices), 1) subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancel_after_grace_period(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 1 @@ -169,11 +164,8 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_unpaid_after_grace_period(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 0 @@ -195,11 +187,8 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_invoice_days_until_due(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -211,11 +200,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual(subscription.status, 'Active') subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_is_past_due_doesnt_change_within_grace_period(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - settings = frappe.get_single('Subscription Settings') grace_period = settings.grace_period settings.grace_period = 1000 @@ -243,7 +229,6 @@ class TestSubscription(unittest.TestCase): settings.grace_period = grace_period settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_remains_active_during_invoice_period(self): subscription = frappe.new_doc('Subscription') @@ -272,7 +257,6 @@ class TestSubscription(unittest.TestCase): subscription.delete() def test_subscription_cancelation(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -282,10 +266,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual(subscription.status, 'Cancelled') subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancellation_invoices(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 1 @@ -319,11 +301,8 @@ class TestSubscription(unittest.TestCase): subscription.delete() settings.prorate = to_prorate settings.save() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancellation_invoices_with_prorata_false(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 0 @@ -342,11 +321,8 @@ class TestSubscription(unittest.TestCase): settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_cancellation_invoices_with_prorata_true(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 1 @@ -369,10 +345,8 @@ class TestSubscription(unittest.TestCase): settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subcription_cancellation_and_process(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 1 @@ -404,11 +378,8 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_restart_and_process(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.grace_period = 0 @@ -445,11 +416,8 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_unpaid_back_to_active(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - settings = frappe.get_single('Subscription Settings') default_grace_period_action = settings.cancel_after_grace settings.cancel_after_grace = 0 @@ -482,7 +450,6 @@ class TestSubscription(unittest.TestCase): settings.cancel_after_grace = default_grace_period_action settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_restart_active_subscription(self): subscription = frappe.new_doc('Subscription') @@ -495,8 +462,6 @@ class TestSubscription(unittest.TestCase): subscription.delete() def test_subscription_invoice_discount_percentage(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.additional_discount_percentage = 10 @@ -510,11 +475,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual(invoice.apply_discount_on, 'Grand Total') subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_subscription_invoice_discount_amount(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") - subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.additional_discount_amount = 11 @@ -528,12 +490,10 @@ class TestSubscription(unittest.TestCase): self.assertEqual(invoice.apply_discount_on, 'Grand Total') subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_prepaid_subscriptions(self): # Create a non pre-billed subscription, processing should not create # invoices. - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") subscription = frappe.new_doc('Subscription') subscription.customer = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -549,10 +509,8 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(len(subscription.invoices), 1) - frappe.db.set_value("Global Defaults", None, "default_company", None) def test_prepaid_subscriptions_with_prorate_true(self): - frappe.db.set_value("Global Defaults", None, "default_company", "_Test Company") settings = frappe.get_single('Subscription Settings') to_prorate = settings.prorate settings.prorate = 1 @@ -580,4 +538,3 @@ class TestSubscription(unittest.TestCase): settings.save() subscription.delete() - frappe.db.set_value("Global Defaults", None, "default_company", None) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 9490c8cbc7..aed78e7746 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -75,7 +75,6 @@ class TestAsset(unittest.TestCase): 'qty': 1, 'asset': asset.name }) - doc.company = 'Wind Power LLC' doc.set_missing_values() self.assertEquals(doc.items[0].is_fixed_asset, 1) @@ -667,7 +666,7 @@ def create_asset(**args): "asset_name": args.asset_name or "Macbook Pro 1", "asset_category": "Computers", "item_code": args.item_code or "Macbook Pro", - "company": args.company or "_Test Company", + "company": args.company or"_Test Company", "purchase_date": "2015-01-01", "calculate_depreciation": 0, "gross_purchase_amount": 100000, diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 0c840ecbd8..bebf0ccec5 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -36,18 +36,16 @@ class TestProcurementTracker(unittest.TestCase): mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse) po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" - po.company = "_Test Procurement Company" - po.get("items")[0].cost_center = "_Test Cost Center - _TPC" + po.get("items")[0].cost_center = "_Test Cost Center - _TC" po.submit() pr = make_purchase_receipt(po.name) - pr.company = "_Test Procurement Company" pr.submit() frappe.db.commit() date_obj = datetime.date(datetime.now()) expected_data = { "material_request_date": date_obj, - "cost_center": "_Test Cost Center - _TPC", + "cost_center": "_Test Cost Center - _TC", "project": None, "requesting_site": "_Test Procurement Warehouse - _TPC", "requestor": "Administrator", diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py index 08248be0e9..8839e002a4 100644 --- a/erpnext/controllers/tests/test_mapper.py +++ b/erpnext/controllers/tests/test_mapper.py @@ -43,7 +43,6 @@ class TestMapper(unittest.TestCase): qtn = frappe.get_doc({ "doctype": "Quotation", "quotation_to": "Customer", - "company": "_Test Company", "party_name": customer, "order_type": "Sales", "transaction_date" : nowdate(), @@ -60,7 +59,6 @@ class TestMapper(unittest.TestCase): "base_amount": 1000.0, "base_rate": 100.0, "description": "CPU", - "company": "_Test Company", "doctype": "Sales Order Item", "item_code": "_Test Item Home Desktop 100", "item_name": "CPU", diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index fc852b19a1..33d90076c4 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -30,7 +30,6 @@ class TestOpportunity(unittest.TestCase): new_lead_email_id = "new{}@example.com".format(random_string(5)) args = { "doctype": "Opportunity", - "company": "_Test Company", "contact_email": new_lead_email_id, "opportunity_type": "Sales", "with_items": 0, diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 8be2510b11..1a063d6b6f 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -108,7 +108,7 @@ class TestPlaidSettings(unittest.TestCase): } bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler) - company = frappe.db.get_single_value('Global Defaults', 'default_company') or '_Test Company' + company = frappe.db.get_single_value('Global Defaults', 'default_company') if frappe.db.get_value("Company", company, "default_bank_account") is None: frappe.db.set_value("Company", company, "default_bank_account", get_default_bank_cash_account(company, "Cash").get("account")) diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index c4826c94c3..4c2d3f692a 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -83,8 +83,7 @@ def get_healthcare_service_unit(): service_unit.service_unit_type = get_service_unit_type() service_unit.inpatient_occupancy = 1 service_unit.occupancy_status = "Vacant" - service_unit.is_group = 0, - service_unit.company = "_Test Company" + service_unit.is_group = 0 service_unit_parent_name = frappe.db.exists({ "doctype": "Healthcare Service Unit", "healthcare_service_unit_name": "All Healthcare Service Units", diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index 1181192cbe..1615ab30f1 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -104,8 +104,7 @@ def mark_attendance(employee, date=today(), status='Present'): "doctype": "Attendance", "employee": employee.name, "attendance_date": date, - "status": status, - "company": "_Test Company" + "status": status }) attendance.save() attendance.submit() diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py index a6e8aae625..2eeca26e30 100644 --- a/erpnext/hr/doctype/department/test_department.py +++ b/erpnext/hr/doctype/department/test_department.py @@ -16,7 +16,7 @@ def create_department(department_name, parent_department=None): 'is_group': 0, 'parent_department': parent_department, 'department_name': department_name, - 'company': frappe.defaults.get_defaults().company or 'Wind Power LLC' + 'company': frappe.defaults.get_defaults().company }).insert() return doc diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index b92b8e7e4e..f4b214adc3 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -60,7 +60,7 @@ def make_employee(user, company=None, **kwargs): "doctype": "Employee", "naming_series": "EMP-", "first_name": user, - "company": company or erpnext.get_default_company() or 'Wind Power LLC', + "company": company or erpnext.get_default_company(), "user_id": user, "date_of_birth": "1990-05-08", "date_of_joining": "2013-01-01", diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index e22948c655..867c35b1b8 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -88,7 +88,7 @@ def create_payroll_period(): payroll_period = frappe.get_doc(dict( doctype = 'Payroll Period', name = "_Test Payroll Period", - company = erpnext.get_default_company() or 'Wind Power LLC', + company = erpnext.get_default_company(), start_date = date(date.today().year, 1, 1), end_date = date(date.today().year, 12, 31) )).insert() diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index e7ee108142..6e97f0513d 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -94,7 +94,6 @@ class TestExpenseClaim(unittest.TestCase): payable_account = get_payable_account(company_name) expense_claim = frappe.get_doc({ "doctype": "Expense Claim", - "company": "_Test Company", "employee": "_T-Employee-00001", "payable_account": payable_account, "approval_status": "Rejected", diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index 1da107bfe7..8886596450 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -55,8 +55,7 @@ def create_job_offer(**args): "job_applicant": args.job_applicant or job_applicant.name, "offer_date": args.offer_date or nowdate(), "designation": args.designation or "Researcher", - "status": args.status or "Accepted", - "company": "_Test Company" + "status": args.status or "Accepted" }) return job_offer @@ -69,7 +68,6 @@ def create_staffing_plan(**args): staffing_plan = frappe.get_doc({ "doctype": "Staffing Plan", "name": args.name or "Test", - "company": "_Test Company", "from_date": args.from_date or nowdate(), "to_date": args.to_date or add_days(nowdate(), 10), "staffing_details": args.staffing_details or [{ diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index d002e5408a..a61ce79d0f 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -414,7 +414,7 @@ def make_salary_component(salary_components, test_tax, company_list=None): get_salary_component_account(salary_component["salary_component"], company_list) def get_salary_component_account(sal_comp, company_list=None): - company = erpnext.get_default_company() or 'Wind Power LLC' + company = erpnext.get_default_company() if company_list and company not in company_list: company_list.append(company) diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index ba3a968af0..d350031df7 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -116,7 +116,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do details = { "doctype": "Salary Structure", "name": salary_structure, - "company": company or erpnext.get_default_company() or "_Test Company", + "company": company or erpnext.get_default_company(), "earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]), "deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]), "payroll_frequency": payroll_frequency, diff --git a/erpnext/hr/doctype/training_event/test_training_event.py b/erpnext/hr/doctype/training_event/test_training_event.py index 5ddb99b8ea..57123e304f 100644 --- a/erpnext/hr/doctype/training_event/test_training_event.py +++ b/erpnext/hr/doctype/training_event/test_training_event.py @@ -32,8 +32,7 @@ def create_training_program(training_program): frappe.get_doc({ "doctype": "Training Program", "training_program": training_program, - "description": training_program, - "company": "Wind Power LLC" + "description": training_program }).insert() def get_attendees(employee, employee2): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 13bd02f416..2e38e8ff9e 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -192,7 +192,6 @@ class TestDeliveryNote(unittest.TestCase): serial_no = frappe.get_doc({ "doctype": "Serial No", "item_code": "_Test Serialized Item With Series", - "company": "Wind Power LLC", "serial_no": make_autoname("SR", "Serial No") }) serial_no.save() @@ -566,6 +565,32 @@ class TestDeliveryNote(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + def test_delivery_note_without_cost_center(self): + cost_center = "Main - TCP1" + + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') + + set_valuation_method("_Test Item", "FIFO") + + make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) + + stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') + dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") + + gl_entries = get_gl_entries("Delivery Note", dn.name) + + self.assertTrue(gl_entries) + expected_values = { + "Cost of Goods Sold - TCP1": { + "cost_center": cost_center + }, + stock_in_hand_account: { + "cost_center": None + } + } + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + def test_make_sales_invoice_from_dn_for_returned_qty(self): from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index da2c97cb8c..eeea6da7a4 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -172,7 +172,7 @@ def create_delivery_trip(driver, address, contact=None): delivery_trip = frappe.get_doc({ "doctype": "Delivery Trip", - "company": erpnext.get_default_company() or 'Wind Power LLC', + "company": erpnext.get_default_company(), "departure_time": add_days(now_datetime(), 5), "driver": driver.name, "driver_address": address.name, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index af25ba68ea..53f8b47f68 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -432,6 +432,30 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + def test_purchase_receipt_without_cost_center(self): + if not frappe.db.exists('Location', 'Test Location'): + frappe.get_doc({ + 'doctype': 'Location', + 'location_name': 'Test Location' + }).insert() + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") + + stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) + gl_entries = get_gl_entries("Purchase Receipt", pr.name) + + self.assertTrue(gl_entries) + + expected_values = { + "Stock Received But Not Billed - TCP1": { + "cost_center": None + }, + stock_in_hand_account: { + "cost_center": None + } + } + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + def test_make_purchase_invoice_from_pr_for_returned_qty(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, create_pr_against_po diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 7b06ef96ce..ab061076e5 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -24,7 +24,6 @@ class TestSerialNo(unittest.TestCase): frappe.delete_doc_if_exists("Serial No", "_TCSER0001") sr = frappe.new_doc("Serial No") - sr.company = '_Test Company' sr.item_code = "_Test Serialized Item" sr.warehouse = "_Test Warehouse - _TC" sr.serial_no = "_TCSER0001" From 8a4dc521cdc721d75bb0078787c332d3599b3af4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 May 2020 11:40:16 +0530 Subject: [PATCH 013/101] Revert 'fix: tests depending on global default company' --- erpnext/accounts/doctype/subscription/subscription.py | 4 ---- .../doctype/clinical_procedure/test_clinical_procedure.py | 2 -- erpnext/healthcare/doctype/patient/test_patient.py | 2 -- .../patient_medical_record/test_patient_medical_record.py | 2 -- .../test_employee_tax_exemption_declaration.py | 8 ++++---- erpnext/hr/doctype/leave_period/test_leave_period.py | 4 ++-- erpnext/hr/doctype/payroll_entry/test_payroll_entry.py | 6 +++--- erpnext/hr/doctype/salary_slip/test_salary_slip.py | 6 +++--- .../hr/doctype/salary_structure/test_salary_structure.py | 4 ++-- 9 files changed, 14 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index fbdaf1bc95..0933c7e8b8 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -238,10 +238,6 @@ class Subscription(Document): Creates a `Sales Invoice`, submits it and returns it """ invoice = frappe.new_doc('Sales Invoice') - - if not invoice.company: - invoice.company = frappe.db.get_value('Global Defaults', None, 'default_company') - invoice.set_posting_time = 1 invoice.posting_date = self.current_invoice_start invoice.customer = self.customer diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index 100addc9ba..207351ff20 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -18,7 +18,6 @@ class TestClinicalProcedure(unittest.TestCase): self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) def test_consumables(self): - frappe.db.set_value('Global Defaults', None, 'default_company', '_Test Company') patient, medical_department, practitioner = create_healthcare_docs() procedure_template = create_clinical_procedure_template() procedure_template.allow_stock_consumption = 1 @@ -39,7 +38,6 @@ class TestClinicalProcedure(unittest.TestCase): result = procedure.complete_procedure() # check consumption self.assertTrue(frappe.db.exists('Stock Entry', result)) - frappe.db.set_value('Global Defaults', None, 'default_company', None) def create_consumable(): diff --git a/erpnext/healthcare/doctype/patient/test_patient.py b/erpnext/healthcare/doctype/patient/test_patient.py index dfe61bd6c1..9274b6f5e8 100644 --- a/erpnext/healthcare/doctype/patient/test_patient.py +++ b/erpnext/healthcare/doctype/patient/test_patient.py @@ -15,7 +15,6 @@ class TestPatient(unittest.TestCase): self.assertTrue(frappe.db.get_value('Patient', patient, 'customer')) def test_patient_registration(self): - frappe.db.set_value('Global Defaults', None, 'default_company', '_Test Company') frappe.db.sql("""delete from `tabPatient`""") settings = frappe.get_single('Healthcare Settings') settings.collect_registration_fee = 1 @@ -33,4 +32,3 @@ class TestPatient(unittest.TestCase): settings.collect_registration_fee = 0 settings.save() - frappe.db.set_value('Global Defaults', None, 'default_company', None) diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py index 4de90bc09f..e5a5e4c010 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py +++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py @@ -13,7 +13,6 @@ class TestPatientMedicalRecord(unittest.TestCase): frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) def test_medical_record(self): - frappe.db.set_value('Global Defaults', None, 'default_company', '_Test Company') patient, medical_department, practitioner = create_healthcare_docs() appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) encounter = create_encounter(appointment) @@ -39,7 +38,6 @@ class TestPatientMedicalRecord(unittest.TestCase): # check for lab test medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': lab_test.name}) self.assertTrue(medical_rec) - frappe.db.set_value('Global Defaults', None, 'default_company', None) def create_procedure(appointment): diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index 867c35b1b8..9549fd1b75 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -20,7 +20,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company() or 'Wind Power LLC', + "company": erpnext.get_default_company(), "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", @@ -37,7 +37,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company() or 'Wind Power LLC', + "company": erpnext.get_default_company(), "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", @@ -52,7 +52,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): duplicate_declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company() or 'Wind Power LLC', + "company": erpnext.get_default_company(), "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", @@ -68,7 +68,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": erpnext.get_default_company() or 'Wind Power LLC', + "company": erpnext.get_default_company(), "payroll_period": "_Test Payroll Period", "declarations": [ dict(exemption_sub_category = "_Test Sub Category", diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py index 06e5836738..1762cf917a 100644 --- a/erpnext/hr/doctype/leave_period/test_leave_period.py +++ b/erpnext/hr/doctype/leave_period/test_leave_period.py @@ -45,7 +45,7 @@ class TestLeavePeriod(unittest.TestCase): def create_leave_period(from_date, to_date, company=None): leave_period = frappe.db.get_value('Leave Period', - dict(company=company or erpnext.get_default_company() or 'Wind Power LLC', + dict(company=company or erpnext.get_default_company(), from_date=from_date, to_date=to_date, is_active=1), 'name') @@ -54,7 +54,7 @@ def create_leave_period(from_date, to_date, company=None): leave_period = frappe.get_doc({ "doctype": "Leave Period", - "company": company or erpnext.get_default_company() or 'Wind Power LLC', + "company": company or erpnext.get_default_company(), "from_date": from_date, "to_date": to_date, "is_active": 1 diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index f1393b168d..3c318e78a2 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -27,7 +27,7 @@ class TestPayrollEntry(unittest.TestCase): frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0) def test_payroll_entry(self): # pylint: disable=no-self-use - company = erpnext.get_default_company() or 'Wind Power LLC' + company = erpnext.get_default_company() for data in frappe.get_all('Salary Component', fields = ["name"]): if not frappe.db.get_value('Salary Component Account', {'parent': data.name, 'company': company}, 'name'): @@ -157,7 +157,7 @@ def make_payroll_entry(**args): args = frappe._dict(args) payroll_entry = frappe.new_doc("Payroll Entry") - payroll_entry.company = args.company or erpnext.get_default_company() or 'Wind Power LLC' + payroll_entry.company = args.company or erpnext.get_default_company() payroll_entry.start_date = args.start_date or "2016-11-01" payroll_entry.end_date = args.end_date or "2016-11-30" payroll_entry.payment_account = get_payment_account() @@ -183,7 +183,7 @@ def make_payroll_entry(**args): def get_payment_account(): return frappe.get_value('Account', - {'account_type': 'Cash', 'company': erpnext.get_default_company() or 'Wind Power LLC', 'is_group':0}, "name") + {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") def make_holiday(holiday_list_name): if not frappe.db.exists('Holiday List', holiday_list_name): diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index a61ce79d0f..9584866f67 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -242,7 +242,7 @@ class TestSalarySlip(unittest.TestCase): self.assertEqual(ss.net_pay, (flt(ss.gross_pay) - (flt(ss.total_deduction) + flt(ss.total_loan_repayment)))) def test_payroll_frequency(self): - fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company() or 'Wind Power LLC')[0] + fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())[0] month = "%02d" % getdate(nowdate()).month m = get_month_details(fiscal_year, month) @@ -560,7 +560,7 @@ def create_exemption_declaration(employee, payroll_period): "doctype": "Employee Tax Exemption Declaration", "employee": employee, "payroll_period": payroll_period, - "company": erpnext.get_default_company() or 'Wind Power LLC' + "company": erpnext.get_default_company() }) declaration.append("declarations", { "exemption_sub_category": "_Test Sub Category", @@ -667,7 +667,7 @@ def create_additional_salary(employee, payroll_period, amount): frappe.get_doc({ "doctype": "Additional Salary", "employee": employee, - "company": erpnext.get_default_company() or 'Wind Power LLC', + "company": erpnext.get_default_company(), "salary_component": "Performance Bonus", "payroll_date": salary_date, "amount": amount, diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index d350031df7..eb5311e3b8 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -22,7 +22,7 @@ class TestSalaryStructure(unittest.TestCase): frappe.db.sql("delete from `tab%s`" % dt) self.make_holiday_list() - frappe.db.set_value("Company", erpnext.get_default_company() or 'Wind Power LLC', "default_holiday_list", "Salary Structure Test Holiday List") + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List") make_employee("test_employee@salary.com") make_employee("test_employee_2@salary.com") @@ -151,7 +151,7 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non salary_structure_assignment.variable = 5000 salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1) salary_structure_assignment.salary_structure = salary_structure - salary_structure_assignment.company = company or erpnext.get_default_company() or 'Wind Power LLC' + salary_structure_assignment.company = company or erpnext.get_default_company() salary_structure_assignment.save(ignore_permissions=True) salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period" salary_structure_assignment.submit() From c96bf870e4c3f08f258098639a71008d5b68078c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 May 2020 23:50:04 +0530 Subject: [PATCH 014/101] fix: Test Cases --- .../invoice_discounting/invoice_discounting.py | 15 ++++++++++++--- .../purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/assets/doctype/asset/depreciation.py | 8 +++++--- .../test_procurement_tracker.py | 1 + .../doctype/employee_advance/employee_advance.py | 2 ++ erpnext/hr/doctype/expense_claim/expense_claim.py | 4 +++- .../doctype/delivery_note/test_delivery_note.py | 10 +++++++--- .../purchase_receipt/test_purchase_receipt.py | 7 ++++--- 8 files changed, 35 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 594b4d4a22..8083b21f75 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json +import frappe, json, erpnext from frappe import _ from frappe.utils import flt, getdate, nowdate, add_days from erpnext.controllers.accounts_controller import AccountsController @@ -134,16 +134,19 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.bank_account, "debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges), + "cost_center": erpnext.get_default_cost_center(self.company) }) je.append("accounts", { "account": self.bank_charges_account, - "debit_in_account_currency": flt(self.bank_charges) + "debit_in_account_currency": flt(self.bank_charges), + "cost_center": erpnext.get_default_cost_center(self.company) }) je.append("accounts", { "account": self.short_term_loan, "credit_in_account_currency": flt(self.total_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name }) @@ -151,6 +154,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_discounted, "debit_in_account_currency": flt(d.outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", @@ -160,6 +164,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_credit, "credit_in_account_currency": flt(d.outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", @@ -177,13 +182,15 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.short_term_loan, "debit_in_account_currency": flt(self.total_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, }) je.append("accounts", { "account": self.bank_account, - "credit_in_account_currency": flt(self.total_amount) + "credit_in_account_currency": flt(self.total_amount), + "cost_center": erpnext.get_default_cost_center(self.company) }) if getdate(self.loan_end_date) > getdate(nowdate()): @@ -193,6 +200,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_discounted, "credit_in_account_currency": flt(outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", @@ -202,6 +210,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_unpaid, "debit_in_account_currency": flt(outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index f61d27aee3..4019815e19 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -822,7 +822,7 @@ class TestPurchaseInvoice(unittest.TestCase): "Creditors - _TC": { "cost_center": cost_center }, - "Cost of Goods Sold - _TC": { + "_Test Account Cost for Goods Sold - _TC": { "cost_center": cost_center } } diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index c50211e6ab..b7ebb4767e 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt, today, getdate, cint from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts @@ -197,12 +197,14 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) { "account": fixed_asset_account, "credit_in_account_currency": asset.gross_purchase_amount, - "credit": asset.gross_purchase_amount + "credit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center }, { "account": accumulated_depr_account, "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center } ] diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index bebf0ccec5..acec4c8502 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -39,6 +39,7 @@ class TestProcurementTracker(unittest.TestCase): po.get("items")[0].cost_center = "_Test Cost Center - _TC" po.submit() pr = make_purchase_receipt(po.name) + pr.get("items")[0].cost_center = "_Test Cost Center - _TC" pr.submit() frappe.db.commit() date_obj = datetime.date(datetime.now()) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index db39eff0e4..c5a9fcec1d 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -119,12 +119,14 @@ def make_bank_entry(dt, dn): "reference_type": "Employee Advance", "reference_name": doc.name, "party_type": "Employee", + "cost_center": erpnext.get_default_cost_center(doc.company), "party": doc.employee, "is_advance": "Yes" }) je.append("accounts", { "account": payment_account.account, + "cost_center": erpnext.get_default_cost_center(doc.company), "credit_in_account_currency": flt(doc.advance_amount), "account_currency": payment_account.account_currency, "account_type": payment_account.account_type diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index ea469b82c9..87bfbd18f4 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -295,7 +295,7 @@ def make_bank_entry(dt, dn): je = frappe.new_doc("Journal Entry") je.voucher_type = 'Bank Entry' je.company = expense_claim.company - je.remark = 'Payment against Expense Claim: ' + dn; + je.remark = 'Payment against Expense Claim: ' + dn je.append("accounts", { "account": expense_claim.payable_account, @@ -303,6 +303,7 @@ def make_bank_entry(dt, dn): "reference_type": "Expense Claim", "party_type": "Employee", "party": expense_claim.employee, + "cost_center": erpnext.get_default_cost_center(expense_claim.company), "reference_name": expense_claim.name }) @@ -313,6 +314,7 @@ def make_bank_entry(dt, dn): "reference_name": expense_claim.name, "balance": default_bank_cash_account.balance, "account_currency": default_bank_cash_account.account_currency, + "cost_center": erpnext.get_default_cost_center(expense_claim.company), "account_type": default_bank_cash_account.account_type }) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 2e38e8ff9e..4b04a0a8c3 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -565,7 +565,7 @@ class TestDeliveryNote(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - def test_delivery_note_without_cost_center(self): + def test_delivery_note_cost_center_with_balance_sheet_account(self): cost_center = "Main - TCP1" company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') @@ -575,7 +575,11 @@ class TestDeliveryNote(unittest.TestCase): make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') - dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") + dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1", + do_not_submit=1) + + dn.get('items')[0].cost_center = None + dn.submit() gl_entries = get_gl_entries("Delivery Note", dn.name) @@ -585,7 +589,7 @@ class TestDeliveryNote(unittest.TestCase): "cost_center": cost_center }, stock_in_hand_account: { - "cost_center": None + "cost_center": cost_center } } for i, gle in enumerate(gl_entries): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 53f8b47f68..d97b9e82c3 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -432,7 +432,7 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - def test_purchase_receipt_without_cost_center(self): + def test_purchase_receipt_cost_center_with_balance_sheet_account(self): if not frappe.db.exists('Location', 'Test Location'): frappe.get_doc({ 'doctype': 'Location', @@ -444,13 +444,14 @@ class TestPurchaseReceipt(unittest.TestCase): gl_entries = get_gl_entries("Purchase Receipt", pr.name) self.assertTrue(gl_entries) + cost_center = pr.get('items')[0].cost_center expected_values = { "Stock Received But Not Billed - TCP1": { - "cost_center": None + "cost_center": cost_center }, stock_in_hand_account: { - "cost_center": None + "cost_center": cost_center } } for i, gle in enumerate(gl_entries): From 1aec30d849fd302726a694070c684d878830d152 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 May 2020 00:51:19 +0530 Subject: [PATCH 015/101] fix: Procurement Tracker test case --- .../report/procurement_tracker/test_procurement_tracker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index acec4c8502..ad355598a0 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -36,10 +36,10 @@ class TestProcurementTracker(unittest.TestCase): mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse) po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" - po.get("items")[0].cost_center = "_Test Cost Center - _TC" + po.get("items")[0].cost_center = "Main - _TPC" po.submit() pr = make_purchase_receipt(po.name) - pr.get("items")[0].cost_center = "_Test Cost Center - _TC" + pr.get("items")[0].cost_center = "Main - _TPC" pr.submit() frappe.db.commit() date_obj = datetime.date(datetime.now()) From 159d0734ef836c627b6acc142de750d05a732b4b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 May 2020 10:08:32 +0530 Subject: [PATCH 016/101] fix: Proccurement tracker report test --- .../test_procurement_tracker.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index ad355598a0..06da336892 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -15,7 +15,7 @@ class TestProcurementTracker(unittest.TestCase): def test_result_for_procurement_tracker(self): filters = { 'company': '_Test Procurement Company', - 'cost_center': '_Test Cost Center - _TC' + 'cost_center': 'Main - _TPC' } expected_data = self.generate_expected_data() report = execute(filters) @@ -33,10 +33,12 @@ class TestProcurementTracker(unittest.TestCase): country="Pakistan" )).insert() warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company") - mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse) + mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC") po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" po.get("items")[0].cost_center = "Main - _TPC" + po.items[0].rate = 500.0 + po.save() po.submit() pr = make_purchase_receipt(po.name) pr.get("items")[0].cost_center = "Main - _TPC" @@ -44,9 +46,11 @@ class TestProcurementTracker(unittest.TestCase): frappe.db.commit() date_obj = datetime.date(datetime.now()) + po.load_from_db() + expected_data = { "material_request_date": date_obj, - "cost_center": "_Test Cost Center - _TC", + "cost_center": "Main - _TPC", "project": None, "requesting_site": "_Test Procurement Warehouse - _TPC", "requestor": "Administrator", @@ -60,9 +64,10 @@ class TestProcurementTracker(unittest.TestCase): "supplier": "_Test Supplier", "estimated_cost": 0.0, "actual_cost": None, - "purchase_order_amt": 5000.0, - "purchase_order_amt_in_company_currency": 300000.0, + "purchase_order_amt": po.net_total, + "purchase_order_amt_in_company_currency": po.base_net_total, "expected_delivery_date": date_obj, "actual_delivery_date": date_obj } + return expected_data \ No newline at end of file From 3609ddedbb49e15700c83064edc8be2ff8a71eb5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 May 2020 12:03:37 +0530 Subject: [PATCH 017/101] fix: Purchase receipt item json --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index cfbe06ad02..52a5be0984 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -753,7 +753,7 @@ "depends_on": "is_fixed_asset", "fetch_from": "item_code.asset_category", "fieldname": "asset_category", - "fieldtype": "Data", + "fieldtype": "Link", "label": "Asset Category", "options": "Asset Category", "read_only": 1 From 1b30ca6a36e2f088439641ba5bbd370475defa85 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 19 Jun 2020 12:12:08 +0530 Subject: [PATCH 018/101] fix: Codacy --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 4 ++-- erpnext/assets/doctype/asset/depreciation.py | 2 +- erpnext/controllers/stock_controller.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c77e5002dd..f0585ad4d8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -500,7 +500,7 @@ class PurchaseInvoice(BuyingController): "account": warehouse_account[item.warehouse]['account'], "against": warehouse_account[item.from_warehouse]["account"], "cost_center": item.cost_center, - "project": item_row.project or self.project, + "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": warehouse_debit_amount, }, warehouse_account[item.warehouse]["account_currency"], item=item)) @@ -510,7 +510,7 @@ class PurchaseInvoice(BuyingController): "account": warehouse_account[item.from_warehouse]['account'], "against": warehouse_account[item.warehouse]["account"], "cost_center": item.cost_center, - "project": item_row.project or self.project, + "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")), }, warehouse_account[item.from_warehouse]["account_currency"], item=item)) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index b7ebb4767e..8f0afb42b2 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, erpnext +import frappe from frappe import _ from frappe.utils import flt, today, getdate, cint from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d098c1cfd5..e8483da544 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -96,7 +96,7 @@ class StockController(AccountsController): "account": warehouse_account[sle.warehouse]["account"], "against": item_row.expense_account, "cost_center": item_row.cost_center, - "project": item_row.project or self.project if hasattr(self, 'project') else None, + "project": item_row.project or self.get('project'), "remarks": self.get("remarks") or "Accounting Entry for Stock", "debit": flt(sle.stock_value_difference, precision), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", @@ -107,7 +107,7 @@ class StockController(AccountsController): "account": item_row.expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, - "project": item_row.project or self.project if hasattr(self, 'project') else None, + "project": item_row.project or self.get('project'), "remarks": self.get("remarks") or "Accounting Entry for Stock", "credit": flt(sle.stock_value_difference, precision), "project": item_row.get("project") or self.get("project"), From d79e8e82cb198c3a191617c8933dbd3d9accafe5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Jun 2020 10:00:12 +0530 Subject: [PATCH 019/101] fix: Test cases --- erpnext/regional/report/datev/test_datev.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py index 3cc65fe9d3..eed62a8690 100644 --- a/erpnext/regional/report/datev/test_datev.py +++ b/erpnext/regional/report/datev/test_datev.py @@ -90,7 +90,7 @@ def make_customer_with_account(customer_name, company): if not frappe.db.exists("Customer", customer_name): customer = frappe.get_doc({ - "doctype": "Customer", + "doctype": "Customer", "customer_name": customer_name, "customer_type": "Company", "accounts": [{ @@ -155,17 +155,17 @@ class TestDatev(TestCase): setup_fiscal_year() warehouse = frappe.db.get_value("Item Default", { - "parent": item.name, + "parent": item.name, "company": self.company.name }, "default_warehouse") income_account = frappe.db.get_value("Account", { - "account_number": "4200", + "account_number": "4200", "company": self.company.name }, "name") tax_account = frappe.db.get_value("Account", { - "account_number": "3806", + "account_number": "3806", "company": self.company.name }, "name") @@ -186,9 +186,12 @@ class TestDatev(TestCase): "charge_type": "On Net Total", "account_head": tax_account, "description": "Umsatzsteuer 19 %", - "rate": 19 + "rate": 19, + "cost_center": self.company.cost_center }) + si.cost_center = self.company.cost_center + si.save() si.submit() @@ -196,7 +199,7 @@ class TestDatev(TestCase): def is_subset(get_data, allowed_keys): """ Validate that the dict contains only allowed keys. - + Params: get_data -- Function that returns a list of dicts. allowed_keys -- List of allowed keys From 3c004ad79880f023917017ec715da9b0e07b0b7b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 2 Jul 2020 21:18:29 +0530 Subject: [PATCH 020/101] fix(GST): Do not add tax amount in grand total for reverse charge invoices --- .../purchase_invoice/purchase_invoice.py | 6 ++ .../purchase_invoice/regional/india.js | 30 ++++++++++ erpnext/accounts/general_ledger.py | 1 + erpnext/hooks.py | 5 +- erpnext/regional/india/utils.py | 59 +++++++++++++------ 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3cd57d403a..c701a7c9dd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -438,6 +438,8 @@ class PurchaseInvoice(BuyingController): self.make_tax_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) @@ -1097,6 +1099,10 @@ def get_list_context(context=None): }) return list_context +@erpnext.allow_regional +def make_regional_gl_entries(gl_entries, doc): + return gl_entries + @frappe.whitelist() def make_debit_note(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc diff --git a/erpnext/accounts/doctype/purchase_invoice/regional/india.js b/erpnext/accounts/doctype/purchase_invoice/regional/india.js index 81488a2c52..83cb03a77c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/regional/india.js +++ b/erpnext/accounts/doctype/purchase_invoice/regional/india.js @@ -1,3 +1,33 @@ {% include "erpnext/regional/india/taxes.js" %} erpnext.setup_auto_gst_taxation('Purchase Invoice'); + + +frappe.ui.form.on('Purchase Taxes and Charges', { + taxes_add: function(frm) { + if (frm.doc.reverse_charge === 'Y') { + frappe.call({ + 'method': 'erpnext.regional.india.utils.get_gst_accounts', + 'args': { + company: frm.doc.company + }, + 'callback': function(r) { + let accounts = r.message; + let account_list = accounts['cgst_account'] + accounts['sgst_account'] + + accounts['igst_account'] + + let gst_tax = 0; + + $.each(frm.doc.taxes || [], function(i, row) { + if (account_list.includes(row.account_head)) { + gst_tax += row.base_tax_amount_after_discount_amount; + } + }); + + frm.doc.taxes_and_charges_added -= flt(gst_tax); + frm.refresh_field('taxes_and_charges_added'); + } + }) + } + } +}); diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index bfe35ab006..a1f8c037b3 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -20,6 +20,7 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd if not cancel: validate_accounting_period(gl_map) gl_map = process_gl_map(gl_map, merge_entries) + print(gl_map, "$$$$$$$$$") if gl_map and len(gl_map) > 1: save_entries(gl_map, adv_adj, update_outstanding) else: diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 835d92ef5c..89c5b7409d 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -246,7 +246,7 @@ doc_events = { "on_trash": "erpnext.regional.check_deletion_permission" }, "Purchase Invoice": { - "on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries" + "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm" }, "Payment Entry": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"], @@ -374,7 +374,8 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data', 'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', - 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period' + 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', + 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 05ffa87f14..c6df1366d1 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import frappe, re, json from frappe import _ -from frappe.utils import cstr, flt, date_diff, nowdate +from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words from erpnext.regional.india import states, state_numbers from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -644,6 +644,7 @@ def validate_state_code(state_code, address): else: return int(state_code) +@frappe.whitelist() def get_gst_accounts(company, account_wise=False): gst_accounts = frappe._dict() gst_settings_accounts = frappe.get_all("GST Account", @@ -662,14 +663,49 @@ def get_gst_accounts(company, account_wise=False): return gst_accounts -def make_reverse_charge_entries(doc, method): +def update_grand_total_for_rcm(doc, method): + if doc.reverse_charge == 'Y': + gst_accounts = get_gst_accounts(doc.company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + gst_tax = 0 + for tax in doc.get('taxes'): + if tax.category not in ("Total", "Valuation and Total"): + continue + + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: + gst_tax += tax.base_tax_amount_after_discount_amount + + doc.taxes_and_charges_added -= gst_tax + doc.total_taxes_and_charges -= gst_tax + + update_totals(gst_tax, doc) + +def update_totals(gst_tax, doc): + doc.grand_total -= gst_tax + + if doc.meta.get_field("rounded_total"): + if doc.is_rounded_total_disabled(): + doc.outstanding_amount = doc.grand_total + else: + doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total, + doc.currency, doc.precision("rounded_total")) + + doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total, + doc.precision("rounding_adjustment")) + + doc.outstanding_amount = doc.base_rounded_total + + doc.in_words = money_in_words(doc.grand_total, doc.currency) + +def make_regional_gl_entries(gl_entries, doc): country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'India': return if doc.reverse_charge == 'Y': - gl_entries = [] gst_accounts = get_gst_accounts(doc.company) gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + gst_accounts.get('igst_account') @@ -694,19 +730,4 @@ def make_reverse_charge_entries(doc, method): }, account_currency, item=tax) ) - gl_entries.append(doc.get_gl_dict( - { - "account": doc.credit_to if doc.doctype == 'Purchase Invoice' else doc.debit_to, - "cost_center": doc.cost_center, - "posting_date": doc.posting_date, - "party_type": 'Supplier', - "party": doc.supplier, - "against": tax.account_head, - "debit": tax.base_tax_amount_after_discount_amount, - "debit_in_account_currency": tax.base_tax_amount_after_discount_amount \ - if account_currency==doc.company_currency \ - else tax.tax_amount_after_discount_amount - }, account_currency, item=doc) - ) - - make_gl_entries(gl_entries) \ No newline at end of file + return gl_entries \ No newline at end of file From 9ed0384bea9d34b2eded39c336965281c4d127ea Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 Jul 2020 14:54:41 +0530 Subject: [PATCH 021/101] fix: Code cleanup --- .../purchase_invoice/regional/india.js | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/regional/india.js b/erpnext/accounts/doctype/purchase_invoice/regional/india.js index 83cb03a77c..81488a2c52 100644 --- a/erpnext/accounts/doctype/purchase_invoice/regional/india.js +++ b/erpnext/accounts/doctype/purchase_invoice/regional/india.js @@ -1,33 +1,3 @@ {% include "erpnext/regional/india/taxes.js" %} erpnext.setup_auto_gst_taxation('Purchase Invoice'); - - -frappe.ui.form.on('Purchase Taxes and Charges', { - taxes_add: function(frm) { - if (frm.doc.reverse_charge === 'Y') { - frappe.call({ - 'method': 'erpnext.regional.india.utils.get_gst_accounts', - 'args': { - company: frm.doc.company - }, - 'callback': function(r) { - let accounts = r.message; - let account_list = accounts['cgst_account'] + accounts['sgst_account'] - + accounts['igst_account'] - - let gst_tax = 0; - - $.each(frm.doc.taxes || [], function(i, row) { - if (account_list.includes(row.account_head)) { - gst_tax += row.base_tax_amount_after_discount_amount; - } - }); - - frm.doc.taxes_and_charges_added -= flt(gst_tax); - frm.refresh_field('taxes_and_charges_added'); - } - }) - } - } -}); From 05738bd29aa7b46f86a3f3b7da5d06d3fb571bc4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 6 Jul 2020 13:40:48 +0530 Subject: [PATCH 022/101] fix: Remove print statements --- erpnext/accounts/general_ledger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a1f8c037b3..bfe35ab006 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -20,7 +20,6 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd if not cancel: validate_accounting_period(gl_map) gl_map = process_gl_map(gl_map, merge_entries) - print(gl_map, "$$$$$$$$$") if gl_map and len(gl_map) > 1: save_entries(gl_map, adv_adj, update_outstanding) else: From 4355d3cf0fc3de447d653775f2ca79f9812b98e5 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 6 Jul 2020 23:15:08 +0530 Subject: [PATCH 023/101] fix: Exploded Item Rate --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 7d31a1cd15..58b79d1cac 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -494,7 +494,7 @@ class BOM(WebsiteGenerator): 'image' : d.image, 'stock_uom' : d.stock_uom, 'stock_qty' : flt(d.stock_qty), - 'rate' : d.base_rate, + 'rate' : flt(d.base_rate) / flt(d.conversion_factor), 'include_item_in_manufacturing': d.include_item_in_manufacturing })) From e3269039d3e14ed030a65fe8d7bf7e96f4396955 Mon Sep 17 00:00:00 2001 From: Afshan Date: Tue, 7 Jul 2020 18:01:37 +0530 Subject: [PATCH 024/101] feat: added columns to get complete analysis for material request --- .../requested_items_to_order.py | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py index cca01b104a..e0cd63237d 100644 --- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py +++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py @@ -59,8 +59,11 @@ def get_data(filters, conditions): sum(ifnull(mr_item.stock_qty, 0)) as qty, ifnull(mr_item.stock_uom, '') as uom, sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty, - (sum(mr_item.stock_qty) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order, + sum(ifnull(mr_item.received_qty, 0)) as received_qty, + (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.received_qty, 0))) as pending_qty, + (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order, mr_item.item_name as item_name, + mr_item.description as "description", mr.company as company from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item @@ -78,7 +81,7 @@ def get_data(filters, conditions): return data def update_qty_columns(row_to_update, data_row): - fields = ["qty", "ordered_qty", "qty_to_order"] + fields = ["qty", "ordered_qty", "received_qty", "pending_qty", "qty_to_order"] for field in fields: row_to_update[field] += flt(data_row[field]) @@ -92,7 +95,9 @@ def prepare_data(data, filters): item_qty_map[row["item_code"]] = { "qty" : row["qty"], "ordered_qty" : row["ordered_qty"], - "qty_to_order" : row["qty_to_order"] + "received_qty": row["received_qty"], + "pending_qty": row["pending_qty"], + "qty_to_order" : row["qty_to_order"], } else: item_entry = item_qty_map[row["item_code"]] @@ -122,7 +127,7 @@ def prepare_data(data, filters): return data, chart_data def prepare_chart_data(item_data): - labels, qty_to_order, ordered_qty = [], [], [] + labels, qty_to_order, ordered_qty, received_qty, pending_qty = [], [], [], [], [] if len(item_data) > 30: item_data = dict(list(item_data.items())[:30]) @@ -132,6 +137,8 @@ def prepare_chart_data(item_data): labels.append(row) qty_to_order.append(mr_row["qty_to_order"]) ordered_qty.append(mr_row["ordered_qty"]) + received_qty.append(mr_row["received_qty"]) + pending_qty.append(mr_row["pending_qty"]) chart_data = { "data" : { @@ -144,6 +151,14 @@ def prepare_chart_data(item_data): { 'name': _('Ordered Qty'), 'values': ordered_qty + }, + { + 'name': _('Received Qty'), + 'values': received_qty + }, + { + 'name': _('Pending Qty'), + 'values': pending_qty } ] }, @@ -193,7 +208,13 @@ def get_columns(filters): "width": 100 }, { - "label": _("UOM"), + "label": _("Description"), + "fieldname": "description", + "fieldtype": "Data", + "width": 200 + }, + { + "label": _("Stock UOM"), "fieldname": "uom", "fieldtype": "Data", "width": 100, @@ -201,7 +222,7 @@ def get_columns(filters): columns.extend([ { - "label": _("Qty"), + "label": _("Stock Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 120, @@ -214,6 +235,20 @@ def get_columns(filters): "width": 120, "convertible": "qty" }, + { + "label": _("Received Qty"), + "fieldname": "received_qty", + "fieldtype": "Float", + "width": 120, + "convertible": "qty" + }, + { + "label": _("Pending Qty"), + "fieldname": "pending_qty", + "fieldtype": "Float", + "width": 120, + "convertible": "qty" + }, { "label": _("Qty to Order"), "fieldname": "qty_to_order", From 3d14011d23dd7285f92d4cd4e2250b735fd510aa Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 7 Jul 2020 18:30:54 +0530 Subject: [PATCH 025/101] fix: allow creating SLA documents even if SLA tracking is not enabled --- .../service_level_agreement/service_level_agreement.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index c692315706..24ddbf8b28 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import getdate, get_weekdays +from frappe.utils import getdate, get_weekdays, get_link_to_form class ServiceLevelAgreement(Document): @@ -73,8 +73,9 @@ class ServiceLevelAgreement(Document): frappe.throw(_("Workday {0} has been repeated.").format(repeated_days)) def validate_doc(self): - if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): - frappe.throw(_("Service Level Agreement tracking is not enabled.")) + if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement") and self.enable: + frappe.throw(_("{0} is not enabled in {1}").format(frappe.bold("Track Service Level Agreement"), + get_link_to_form("Support Settings", "Support Settings"))) if self.default_service_level_agreement: if frappe.db.exists("Service Level Agreement", {"default_service_level_agreement": "1", "name": ["!=", self.name]}): From 923ed8a7412e022c52c7d0f9215cb18bc4d75107 Mon Sep 17 00:00:00 2001 From: Kaviya Periyasamy Date: Wed, 8 Jul 2020 11:15:04 +0530 Subject: [PATCH 026/101] fix: log type mandatory error while exposing api call to employee checkin --- erpnext/hr/doctype/employee_checkin/employee_checkin.js | 4 ++++ erpnext/hr/doctype/employee_checkin/employee_checkin.json | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.js b/erpnext/hr/doctype/employee_checkin/employee_checkin.js index c2403ca2bd..43023b6034 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.js +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.js @@ -6,5 +6,9 @@ frappe.ui.form.on('Employee Checkin', { if(!frm.doc.time) { frm.set_value("time", frappe.datetime.now_datetime()); } + }, + refresh: (frm) => { + // make log type mandatory + frm.set_df_property('log_type', 'reqd', frm.doc.log_type ? 0 : 1); } }); diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.json b/erpnext/hr/doctype/employee_checkin/employee_checkin.json index 75f699751b..d34316dc0f 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.json +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.json @@ -41,8 +41,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Log Type", - "options": "\nIN\nOUT", - "reqd": 1 + "options": "\nIN\nOUT" }, { "fieldname": "shift", @@ -108,7 +107,7 @@ } ], "links": [], - "modified": "2020-01-23 04:57:42.551355", + "modified": "2020-07-08 11:02:32.660986", "modified_by": "Administrator", "module": "HR", "name": "Employee Checkin", From 9071a7de82f1e3e663f3ec7bb998e566598853cf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 8 Jul 2020 11:27:25 +0530 Subject: [PATCH 027/101] fix: Add default cost center in payment reconciliation JV --- .../payment_reconciliation/payment_reconciliation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 8eaad7acd4..35d8d34c51 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -281,7 +281,8 @@ def reconcile_dr_cr_note(dr_cr_notes, company): 'party_type': d.party_type, d.dr_or_cr: abs(d.allocated_amount), 'reference_type': d.against_voucher_type, - 'reference_name': d.against_voucher + 'reference_name': d.against_voucher, + 'cost_center': erpnext.get_default_cost_center(company) }, { 'account': d.account, @@ -290,7 +291,8 @@ def reconcile_dr_cr_note(dr_cr_notes, company): reconcile_dr_or_cr: (abs(d.allocated_amount) if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)), 'reference_type': d.voucher_type, - 'reference_name': d.voucher_no + 'reference_name': d.voucher_no, + 'cost_center': erpnext.get_default_cost_center(company) } ] }) From dca8a1d69db1c26d2ca70e198c152201f14f4f58 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 10 Jul 2020 14:26:21 +0530 Subject: [PATCH 028/101] renamed "pending qty" to "qty to recieved" --- .../requested_items_to_order.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py index e0cd63237d..faf67c9f7f 100644 --- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py +++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py @@ -60,7 +60,7 @@ def get_data(filters, conditions): ifnull(mr_item.stock_uom, '') as uom, sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty, sum(ifnull(mr_item.received_qty, 0)) as received_qty, - (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.received_qty, 0))) as pending_qty, + (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.received_qty, 0))) as qty_to_receive, (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order, mr_item.item_name as item_name, mr_item.description as "description", @@ -81,7 +81,7 @@ def get_data(filters, conditions): return data def update_qty_columns(row_to_update, data_row): - fields = ["qty", "ordered_qty", "received_qty", "pending_qty", "qty_to_order"] + fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"] for field in fields: row_to_update[field] += flt(data_row[field]) @@ -96,7 +96,7 @@ def prepare_data(data, filters): "qty" : row["qty"], "ordered_qty" : row["ordered_qty"], "received_qty": row["received_qty"], - "pending_qty": row["pending_qty"], + "qty_to_receive": row["qty_to_receive"], "qty_to_order" : row["qty_to_order"], } else: @@ -127,7 +127,7 @@ def prepare_data(data, filters): return data, chart_data def prepare_chart_data(item_data): - labels, qty_to_order, ordered_qty, received_qty, pending_qty = [], [], [], [], [] + labels, qty_to_order, ordered_qty, received_qty, qty_to_receive = [], [], [], [], [] if len(item_data) > 30: item_data = dict(list(item_data.items())[:30]) @@ -138,7 +138,7 @@ def prepare_chart_data(item_data): qty_to_order.append(mr_row["qty_to_order"]) ordered_qty.append(mr_row["ordered_qty"]) received_qty.append(mr_row["received_qty"]) - pending_qty.append(mr_row["pending_qty"]) + qty_to_receive.append(mr_row["qty_to_receive"]) chart_data = { "data" : { @@ -157,8 +157,8 @@ def prepare_chart_data(item_data): 'values': received_qty }, { - 'name': _('Pending Qty'), - 'values': pending_qty + 'name': _('Qty to Receive'), + 'values': qty_to_receive } ] }, @@ -243,8 +243,8 @@ def get_columns(filters): "convertible": "qty" }, { - "label": _("Pending Qty"), - "fieldname": "pending_qty", + "label": _("Qty to Receive"), + "fieldname": "qty_to_receive", "fieldtype": "Float", "width": 120, "convertible": "qty" From 0863beec92340e3606fa9ba7ca5b0abb56b97216 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 10 Jul 2020 15:14:01 +0530 Subject: [PATCH 029/101] created new report named "requested_items_to_order_and_receive" removed report "requested_items_to_order" also wrote a patch for deleting old report from table "tabReport" --- .../__init__.py | 0 .../requested_items_to_order_and_receive.js} | 2 +- .../requested_items_to_order_and_receive.json} | 8 ++++---- .../requested_items_to_order_and_receive.py} | 0 erpnext/patches.txt | 1 + .../v12_0/delete_report_requested_items_to_order.py | 7 +++++++ 6 files changed, 13 insertions(+), 5 deletions(-) rename erpnext/buying/report/{requested_items_to_order => requested_items_to_order_and_receive}/__init__.py (100%) rename erpnext/buying/report/{requested_items_to_order/requested_items_to_order.js => requested_items_to_order_and_receive/requested_items_to_order_and_receive.js} (96%) rename erpnext/buying/report/{requested_items_to_order/requested_items_to_order.json => requested_items_to_order_and_receive/requested_items_to_order_and_receive.json} (71%) rename erpnext/buying/report/{requested_items_to_order/requested_items_to_order.py => requested_items_to_order_and_receive/requested_items_to_order_and_receive.py} (100%) create mode 100644 erpnext/patches/v12_0/delete_report_requested_items_to_order.py diff --git a/erpnext/buying/report/requested_items_to_order/__init__.py b/erpnext/buying/report/requested_items_to_order_and_receive/__init__.py similarity index 100% rename from erpnext/buying/report/requested_items_to_order/__init__.py rename to erpnext/buying/report/requested_items_to_order_and_receive/__init__.py diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js similarity index 96% rename from erpnext/buying/report/requested_items_to_order/requested_items_to_order.js rename to erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js index 9555e8252a..d727584d0a 100644 --- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Requested Items to Order"] = { +frappe.query_reports["Requested Items to Order and Receive"] = { "filters": [ { "fieldname": "company", diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.json similarity index 71% rename from erpnext/buying/report/requested_items_to_order/requested_items_to_order.json rename to erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.json index 4a0578be4b..cb158f50e2 100644 --- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.json @@ -1,21 +1,21 @@ { "add_total_row": 1, - "creation": "2020-05-04 20:23:57.750719", + "creation": "2020-07-10 14:28:21.041310", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", "idx": 0, "is_standard": "Yes", - "modified": "2020-05-05 13:05:51.723951", + "modified": "2020-07-10 14:28:21.041310", "modified_by": "Administrator", "module": "Buying", - "name": "Requested Items to Order", + "name": "Requested Items to Order and Receive", "owner": "Administrator", "prepared_report": 0, "query": "", "ref_doctype": "Material Request", - "report_name": "Requested Items to Order", + "report_name": "Requested Items to Order and Receive", "report_type": "Script Report", "roles": [ { diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py similarity index 100% rename from erpnext/buying/report/requested_items_to_order/requested_items_to_order.py rename to erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c7a7abf819..9cd586a220 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -707,3 +707,4 @@ erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll # erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v12_0.add_taxjar_integration_field +erpnext.patches.v12_0.delete_report_requested_items_to_order diff --git a/erpnext/patches/v12_0/delete_report_requested_items_to_order.py b/erpnext/patches/v12_0/delete_report_requested_items_to_order.py new file mode 100644 index 0000000000..0296d47bde --- /dev/null +++ b/erpnext/patches/v12_0/delete_report_requested_items_to_order.py @@ -0,0 +1,7 @@ +import frappe + +def execute(): + frappe.db.sql(""" + DELETE FROM `tabReport` + WHERE name = 'Requested Items to Order' + """) \ No newline at end of file From cd445786fe1f01965f8f54191557d8c7c1a9a087 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Sun, 12 Jul 2020 20:03:15 +0530 Subject: [PATCH 030/101] making owner fieldtype to link --- .../crm/report/lead_owner_efficiency/lead_owner_efficiency.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py index 6172a75fdd..53fc8cd810 100644 --- a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py +++ b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py @@ -17,7 +17,8 @@ def get_columns(): { "fieldname": "lead_owner", "label": _("Lead Owner"), - "fieldtype": "Data", + "fieldtype": "Link", + "options":"User", "width": "130" }, { From 2e8d8b9aebdd2b7b372517167508ae030f995d05 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 13 Jul 2020 12:08:58 +0530 Subject: [PATCH 031/101] fix: '>' not supported between instances of 'str' and 'int' --- erpnext/projects/doctype/task/task.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 3b75cf4215..4bdda68b69 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -9,7 +9,7 @@ import frappe from frappe import _, throw from frappe.desk.form.assign_to import clear, close_all_assignments from frappe.model.mapper import get_mapped_doc -from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today +from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today, flt from frappe.utils.nestedset import NestedSet @@ -63,10 +63,10 @@ class Task(NestedSet): close_all_assignments(self.doctype, self.name) def validate_progress(self): - if (self.progress or 0) > 100: + if flt(self.progress or 0) > 100: frappe.throw(_("Progress % for a task cannot be more than 100.")) - if self.progress == 100: + if flt(self.progress) == 100: self.status = 'Completed' if self.status == 'Completed': From 1791bc187b384ebd840d563e70b84099e1e75b84 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 13 Jul 2020 12:32:09 +0530 Subject: [PATCH 032/101] fix: Block Invalid Serial No updates in Maintenance Schedule --- .../maintenance_schedule/maintenance_schedule.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index b58f999cfb..add7bbfa57 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -44,7 +44,7 @@ class MaintenanceSchedule(TransactionBase): for d in self.get('items'): if d.serial_no: serial_nos = get_valid_serial_nos(d.serial_no) - self.validate_serial_no(serial_nos, d.start_date) + self.validate_serial_no(d.item_code, serial_nos, d.start_date) self.update_amc_date(serial_nos, d.end_date) no_email_sp = [] @@ -178,14 +178,18 @@ class MaintenanceSchedule(TransactionBase): serial_no_doc.amc_expiry_date = amc_expiry_date serial_no_doc.save() - def validate_serial_no(self, serial_nos, amc_start_date): + def validate_serial_no(self, item_code, serial_nos, amc_start_date): for serial_no in serial_nos: sr_details = frappe.db.get_value("Serial No", serial_no, - ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date"], as_dict=1) + ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"], as_dict=1) if not sr_details: frappe.throw(_("Serial No {0} not found").format(serial_no)) + if sr_details.get("item_code") != item_code: + frappe.throw(_("Serial No {0} does not belong to Item {1}") + .format(frappe.bold(serial_no), frappe.bold(item_code)), title="Invalid") + if sr_details.warranty_expiry_date \ and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under warranty upto {1}") From eb69859d3ef0c4ec1189c4b77a3932cdc0b125c6 Mon Sep 17 00:00:00 2001 From: bhavesh95863 <34086262+bhavesh95863@users.noreply.github.com> Date: Mon, 13 Jul 2020 23:15:31 +0530 Subject: [PATCH 033/101] fix: Quotation list view blank if quotation_to field not set as a standard filter --- .../selling/doctype/quotation/quotation_list.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js index 802c0ba641..f425acf180 100644 --- a/erpnext/selling/doctype/quotation/quotation_list.js +++ b/erpnext/selling/doctype/quotation/quotation_list.js @@ -3,13 +3,15 @@ frappe.listview_settings['Quotation'] = { "company", "currency", 'valid_till'], onload: function(listview) { - listview.page.fields_dict.quotation_to.get_query = function() { - return { - "filters": { - "name": ["in", ["Customer", "Lead"]], - } + if (listview.page.fields_dict.quotation_to) { + listview.page.fields_dict.quotation_to.get_query = function() { + return { + "filters": { + "name": ["in", ["Customer", "Lead"]], + } + }; }; - }; + } }, get_indicator: function(doc) { From 72320afb0797d72426ed31827febbc49045da51f Mon Sep 17 00:00:00 2001 From: John Veness Date: Tue, 14 Jul 2020 06:14:15 +0100 Subject: [PATCH 034/101] Correct help link for Address (#22673) --- erpnext/public/js/help_links.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 17b726ee18..66ff46405d 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -450,7 +450,7 @@ frappe.help.help_links['Form/Opportunity'] = [ ] frappe.help.help_links['Form/Address'] = [ - { label: 'Address', url: docsUrl + 'user/manual/en/CRM/contact' }, + { label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' }, ] frappe.help.help_links['Form/Contact'] = [ From 88931f0677c4cb8a79f4fdf1055767a1db14616b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 14 Jul 2020 11:46:11 +0530 Subject: [PATCH 035/101] fix(Support): TypeError while saving Service Level Agreement --- .../service_level_agreement/service_level_agreement.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index c692315706..61b66a9b13 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -21,8 +21,8 @@ class ServiceLevelAgreement(Document): for priority in self.priorities: # Check if response and resolution time is set for every priority - if not (priority.response_time or priority.resolution_time): - frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").format(priority.priority, priority.idx)) + if not priority.response_time or not priority.resolution_time: + frappe.throw(_("Set Response Time and Resolution Time for Priority {0} in row {1}.").format(priority.priority, priority.idx)) priorities.append(priority.priority) @@ -33,7 +33,7 @@ class ServiceLevelAgreement(Document): resolution = priority.resolution_time if response > resolution: - frappe.throw(_("Response Time for {0} at index {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx)) + frappe.throw(_("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx)) # Check if repeated priority if not len(set(priorities)) == len(priorities): From 82a606d04bd7568aec2206967d3cf11fddb0930b Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 14 Jul 2020 13:16:28 +0530 Subject: [PATCH 036/101] fix: Added Project Field in Purchase Receipt for Stock Ledger Tagging (#22666) --- .../purchase_receipt/purchase_receipt.json | 452 +++++------------- 1 file changed, 111 insertions(+), 341 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 3b92dac220..0cb85d32a6 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -103,6 +103,7 @@ "bill_no", "bill_date", "more_info", + "project", "status", "amended_from", "range", @@ -132,17 +133,13 @@ { "fieldname": "supplier_section", "fieldtype": "Section Break", - "options": "fa fa-user", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-user" }, { "fieldname": "column_break0", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_width": "50%", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -153,9 +150,7 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "naming_series", @@ -167,9 +162,7 @@ "options": "MAT-PRE-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "bold": 1, @@ -184,8 +177,6 @@ "print_width": "150px", "reqd": 1, "search_index": 1, - "show_days": 1, - "show_seconds": 1, "width": "150px" }, { @@ -196,24 +187,18 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Supplier Name", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "supplier_delivery_note", "fieldtype": "Data", - "label": "Supplier Delivery Note", - "show_days": 1, - "show_seconds": 1 + "label": "Supplier Delivery Note" }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_width": "50%", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -228,8 +213,6 @@ "print_width": "100px", "reqd": 1, "search_index": 1, - "show_days": 1, - "show_seconds": 1, "width": "100px" }, { @@ -243,8 +226,6 @@ "print_hide": 1, "print_width": "100px", "reqd": 1, - "show_days": 1, - "show_seconds": 1, "width": "100px" }, { @@ -253,9 +234,7 @@ "fieldname": "set_posting_time", "fieldtype": "Check", "label": "Edit Posting Date and Time", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "company", @@ -269,8 +248,6 @@ "print_width": "150px", "remember_last_selected_value": 1, "reqd": 1, - "show_days": 1, - "show_seconds": 1, "width": "150px" }, { @@ -280,9 +257,7 @@ "label": "Is Return", "no_copy": 1, "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "is_return", @@ -292,60 +267,46 @@ "no_copy": 1, "options": "Purchase Receipt", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact", - "show_days": 1, - "show_seconds": 1 + "label": "Address and Contact" }, { "fieldname": "supplier_address", "fieldtype": "Link", "label": "Select Supplier Address", "options": "Address", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "contact_person", "fieldtype": "Link", "label": "Contact Person", "options": "Contact", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "address_display", "fieldtype": "Small Text", "label": "Address", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "in_global_search": 1, "label": "Contact", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_email", @@ -353,42 +314,32 @@ "label": "Contact Email", "options": "Email", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "col_break_address", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shipping_address", "fieldtype": "Link", "label": "Select Shipping Address", "options": "Address", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", "label": "Shipping Address", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "currency_and_price_list", "fieldtype": "Section Break", "label": "Currency and Price List", - "options": "fa fa-tag", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-tag" }, { "fieldname": "currency", @@ -398,9 +349,7 @@ "oldfieldtype": "Select", "options": "Currency", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "description": "Rate at which supplier's currency is converted to company's base currency", @@ -411,17 +360,13 @@ "oldfieldtype": "Currency", "precision": "9", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "column_break2", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_width": "50%", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -429,9 +374,7 @@ "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "buying_price_list", @@ -440,9 +383,7 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "buying_price_list", @@ -450,9 +391,7 @@ "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", @@ -461,15 +400,11 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "description": "Sets 'Accepted Warehouse' in each row of the items table.", @@ -477,9 +412,7 @@ "fieldtype": "Link", "label": "Accepted Warehouse", "options": "Warehouse", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "description": "Sets 'Rejected Warehouse' in each row of the items table.", @@ -490,15 +423,11 @@ "oldfieldname": "rejected_warehouse", "oldfieldtype": "Link", "options": "Warehouse", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "col_break_warehouse", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "No", @@ -508,9 +437,7 @@ "oldfieldname": "is_subcontracted", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "eval:doc.is_subcontracted==\"Yes\"", @@ -523,17 +450,13 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { "fieldname": "items_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-shopping-cart" }, { "allow_bulk_edit": 1, @@ -543,26 +466,20 @@ "oldfieldname": "purchase_receipt_details", "oldfieldtype": "Table", "options": "Purchase Receipt Item", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "collapsible": 1, "fieldname": "pricing_rule_details", "fieldtype": "Section Break", - "label": "Pricing Rules", - "show_days": 1, - "show_seconds": 1 + "label": "Pricing Rules" }, { "fieldname": "pricing_rules", "fieldtype": "Table", "label": "Pricing Rule Detail", "options": "Pricing Rule Detail", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "supplied_items", @@ -571,9 +488,7 @@ "label": "Get Current Stock", "oldfieldtype": "Button", "options": "get_current_stock", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -584,9 +499,7 @@ "oldfieldtype": "Section Break", "options": "fa fa-table", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "supplied_items", @@ -597,24 +510,18 @@ "oldfieldtype": "Table", "options": "Purchase Receipt Item Supplied", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "section_break0", "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Section Break" }, { "fieldname": "total_qty", "fieldtype": "Float", "label": "Total Quantity", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total", @@ -622,9 +529,7 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_net_total", @@ -637,24 +542,18 @@ "print_width": "150px", "read_only": 1, "reqd": 1, - "show_days": 1, - "show_seconds": 1, "width": "150px" }, { "fieldname": "column_break_27", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "total", "fieldtype": "Currency", "label": "Total", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "net_total", @@ -664,56 +563,42 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "description": "Add / Edit Taxes and Charges", "fieldname": "taxes_charges_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-money" }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", "options": "Tax Category", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "shipping_col", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", - "options": "Shipping Rule", - "show_days": 1, - "show_seconds": 1 + "options": "Shipping Rule" }, { "fieldname": "taxes_section", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "taxes_and_charges", @@ -722,9 +607,7 @@ "oldfieldname": "purchase_other_charges", "oldfieldtype": "Link", "options": "Purchase Taxes and Charges Template", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "taxes", @@ -732,17 +615,13 @@ "label": "Purchase Taxes and Charges", "oldfieldname": "purchase_tax_details", "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Taxes and Charges" }, { "collapsible": 1, "fieldname": "sec_tax_breakup", "fieldtype": "Section Break", - "label": "Tax Breakup", - "show_days": 1, - "show_seconds": 1 + "label": "Tax Breakup" }, { "fieldname": "other_charges_calculation", @@ -751,17 +630,13 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "totals", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-money" }, { "fieldname": "base_taxes_and_charges_added", @@ -771,9 +646,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_taxes_and_charges_deducted", @@ -783,9 +656,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total_taxes_and_charges", @@ -795,16 +666,12 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break3", "fieldtype": "Column Break", "print_width": "50%", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -815,9 +682,7 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "taxes_and_charges_deducted", @@ -827,9 +692,7 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_taxes_and_charges", @@ -837,18 +700,14 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "collapsible_depends_on": "discount_amount", "fieldname": "section_break_42", "fieldtype": "Section Break", - "label": "Additional Discount", - "show_days": 1, - "show_seconds": 1 + "label": "Additional Discount" }, { "default": "Grand Total", @@ -856,9 +715,7 @@ "fieldtype": "Select", "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_discount_amount", @@ -866,38 +723,28 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_44", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "additional_discount_percentage", "fieldtype": "Float", "label": "Additional Discount Percentage", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_46", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "base_grand_total", @@ -907,9 +754,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_rounding_adjustment", @@ -918,9 +763,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_in_words", @@ -929,9 +772,7 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_rounded_total", @@ -941,15 +782,11 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_50", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "grand_total", @@ -959,9 +796,7 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "rounding_adjustment", @@ -970,9 +805,7 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -982,9 +815,7 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "in_words", @@ -993,17 +824,13 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total", - "show_days": 1, - "show_seconds": 1 + "label": "Disable Rounded Total" }, { "collapsible": 1, @@ -1012,9 +839,7 @@ "fieldtype": "Section Break", "label": "Terms and Conditions", "oldfieldtype": "Section Break", - "options": "fa fa-legal", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-legal" }, { "fieldname": "tc_name", @@ -1023,18 +848,14 @@ "oldfieldname": "tc_name", "oldfieldtype": "Link", "options": "Terms and Conditions", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", "label": "Terms and Conditions", "oldfieldname": "terms", - "oldfieldtype": "Text Editor", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Text Editor" }, { "fieldname": "bill_no", @@ -1043,9 +864,7 @@ "label": "Bill No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "bill_date", @@ -1054,9 +873,7 @@ "label": "Bill Date", "oldfieldname": "bill_date", "oldfieldtype": "Date", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1064,9 +881,7 @@ "fieldtype": "Section Break", "label": "More Information", "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-file-text" }, { "default": "Draft", @@ -1083,8 +898,6 @@ "read_only": 1, "reqd": 1, "search_index": 1, - "show_days": 1, - "show_seconds": 1, "width": "150px" }, { @@ -1100,8 +913,6 @@ "print_hide": 1, "print_width": "150px", "read_only": 1, - "show_days": 1, - "show_seconds": 1, "width": "150px" }, { @@ -1111,9 +922,7 @@ "label": "Range", "oldfieldname": "range", "oldfieldtype": "Data", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break4", @@ -1121,8 +930,6 @@ "oldfieldtype": "Column Break", "print_hide": 1, "print_width": "50%", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -1137,16 +944,12 @@ "label": "% Amount Billed", "no_copy": 1, "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "subscription_detail", "fieldtype": "Section Break", - "label": "Auto Repeat Detail", - "show_days": 1, - "show_seconds": 1 + "label": "Auto Repeat Detail" }, { "fieldname": "auto_repeat", @@ -1155,17 +958,13 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Printing Settings" }, { "allow_on_submit": 1, @@ -1173,9 +972,7 @@ "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1187,17 +984,13 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1, - "show_days": 1, - "show_seconds": 1 + "report_hide": 1 }, { "fieldname": "language", "fieldtype": "Data", "label": "Print Language", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "allow_on_submit": 1, @@ -1205,15 +998,11 @@ "fieldname": "group_same_items", "fieldtype": "Check", "label": "Group same items", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_97", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "other_details", @@ -1224,8 +1013,6 @@ "options": "
Other Details
", "print_hide": 1, "print_width": "30%", - "show_days": 1, - "show_seconds": 1, "width": "30%" }, { @@ -1233,17 +1020,13 @@ "fieldtype": "Small Text", "label": "Instructions", "oldfieldname": "instructions", - "oldfieldtype": "Text", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Text" }, { "fieldname": "remarks", "fieldtype": "Small Text", "label": "Remarks", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1251,25 +1034,19 @@ "fieldname": "transporter_info", "fieldtype": "Section Break", "label": "Transporter Details", - "options": "fa fa-truck", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-truck" }, { "fieldname": "transporter_name", "fieldtype": "Data", "label": "Transporter Name", "oldfieldname": "transporter_name", - "oldfieldtype": "Data", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Data" }, { "fieldname": "column_break5", "fieldtype": "Column Break", "print_width": "50%", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -1280,8 +1057,6 @@ "oldfieldname": "lr_no", "oldfieldtype": "Data", "print_width": "100px", - "show_days": 1, - "show_seconds": 1, "width": "100px" }, { @@ -1292,8 +1067,6 @@ "oldfieldname": "lr_date", "oldfieldtype": "Date", "print_width": "100px", - "show_days": 1, - "show_seconds": 1, "width": "100px" }, { @@ -1302,48 +1075,45 @@ "fieldname": "is_internal_supplier", "fieldtype": "Check", "label": "Is Internal Supplier", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "inter_company_reference", "fieldtype": "Link", "label": "Inter Company Reference", "options": "Delivery Note", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode", - "show_days": 1, - "show_seconds": 1 + "label": "Scan Barcode" }, { "fieldname": "billing_address", "fieldtype": "Link", "label": "Select Billing Address", - "options": "Address", - "show_days": 1, - "show_seconds": 1 + "options": "Address" }, { "fieldname": "billing_address_display", "fieldtype": "Small Text", "label": "Billing Address", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 + }, + { + "description": "Track this Purchase Receipt against any Project", + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-06-13 22:26:03.600092", + "modified": "2020-07-13 14:01:39.302238", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 392f323d2de2a847e24d00ae000a9eca0174c8af Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 14 Jul 2020 17:03:17 +0530 Subject: [PATCH 037/101] fix: Dont overwrite default warehouse in Material Request --- .../doctype/material_request/material_request.js | 16 ++++++++-------- erpnext/stock/get_item_details.py | 6 ++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 3a8deb6d25..60f5ff3629 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -180,9 +180,8 @@ frappe.ui.form.on('Material Request', { }); }, - get_item_data: function(frm, item) { + get_item_data: function(frm, item, overwrite_warehouse=false) { if (item && !item.item_code) { return; } - frm.call({ method: "erpnext.stock.get_item_details.get_item_details", child: item, @@ -203,7 +202,8 @@ frappe.ui.form.on('Material Request', { plc_conversion_rate: 1, rate: item.rate, conversion_factor: item.conversion_factor - } + }, + overwrite_warehouse: overwrite_warehouse }, callback: function(r) { const d = item; @@ -354,29 +354,29 @@ frappe.ui.form.on("Material Request Item", { } const item = locals[doctype][name]; - frm.events.get_item_data(frm, item); + frm.events.get_item_data(frm, item, false); }, from_warehouse: function(frm, doctype, name) { const item = locals[doctype][name]; - frm.events.get_item_data(frm, item); + frm.events.get_item_data(frm, item, false); }, warehouse: function(frm, doctype, name) { const item = locals[doctype][name]; - frm.events.get_item_data(frm, item); + frm.events.get_item_data(frm, item, false); }, rate: function(frm, doctype, name) { const item = locals[doctype][name]; - frm.events.get_item_data(frm, item); + frm.events.get_item_data(frm, item, false); }, item_code: function(frm, doctype, name) { const item = locals[doctype][name]; item.rate = 0; set_schedule_date(frm); - frm.events.get_item_data(frm, item); + frm.events.get_item_data(frm, item, true); }, schedule_date: function(frm, cdt, cdn) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index b1a16149b2..b8554c83e2 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -47,6 +47,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru """ args = process_args(args) + for_validate = process_string_args(for_validate) + overwrite_warehouse = process_string_args(overwrite_warehouse) item = frappe.get_cached_doc("Item", args.item_code) validate_item_details(args, item) @@ -166,6 +168,10 @@ def process_args(args): set_transaction_type(args) return args +def process_string_args(args): + if isinstance(args, string_types): + args = json.loads(args) + return args @frappe.whitelist() def get_item_code(barcode=None, serial_no=None): From c1636f8fab62472034169bccc24ac6ed9202421e Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 14 Jul 2020 18:58:02 +0530 Subject: [PATCH 038/101] fix: conversion rate as 1 if no conversion rate --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 59611bc74c..cd2313ab8f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1029,14 +1029,14 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= if bank_amount: received_amount = bank_amount else: - received_amount = paid_amount * doc.conversion_rate + received_amount = paid_amount * (doc.get('conversion_rate') or 1) else: received_amount = abs(outstanding_amount) if bank_amount: paid_amount = bank_amount else: # if party account currency and bank currency is different then populate paid amount as well - paid_amount = received_amount * doc.conversion_rate + paid_amount = received_amount * (doc.get('conversion_rate') or 1) pe = frappe.new_doc("Payment Entry") pe.payment_type = payment_type From 5beac7a0cca91d4dccff578f23363ba86c90e54c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 15 Jul 2020 12:17:43 +0530 Subject: [PATCH 039/101] fix: Period list fixes in financial statements (#22677) --- erpnext/accounts/report/financial_statements.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index ee1d54a92e..3785ebf215 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -8,6 +8,7 @@ from __future__ import unicode_literals import re from past.builtins import cmp import functools +import math import frappe, erpnext from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency @@ -45,10 +46,7 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_ start_date = year_start_date months = get_months(year_start_date, year_end_date) - if (months // months_to_add) != (months / months_to_add): - months += months_to_add - - for i in range(months // months_to_add): + for i in range(math.ceil(months / months_to_add)): period = frappe._dict({ "from_date": start_date }) From e1b3f16abd9484d59ba54072f21758ada46a8b65 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 15 Jul 2020 12:28:19 +0530 Subject: [PATCH 040/101] fix: project field added two time in purchase receipt --- .../doctype/purchase_receipt/purchase_receipt.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 0cb85d32a6..df9eb50843 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -108,7 +108,6 @@ "amended_from", "range", "column_break4", - "project", "per_billed", "is_internal_supplier", "inter_company_reference", @@ -933,6 +932,7 @@ "width": "50%" }, { + "description": "Track this Purchase Receipt against any Project", "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -1100,20 +1100,13 @@ "fieldtype": "Small Text", "label": "Billing Address", "read_only": 1 - }, - { - "description": "Track this Purchase Receipt against any Project", - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-07-13 14:01:39.302238", + "modified": "2020-07-15 10:01:39.302238", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 34617744e1f4cd2fac9a6271639c4df7c00eb482 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 15 Jul 2020 16:37:21 +0530 Subject: [PATCH 041/101] fix: Show total row in print format of financial statement --- erpnext/accounts/report/financial_statements.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html index 50947ecf5e..2bb09cf0dc 100644 --- a/erpnext/accounts/report/financial_statements.html +++ b/erpnext/accounts/report/financial_statements.html @@ -44,7 +44,7 @@ - {% for(let j=0, k=data.length-1; j Date: Wed, 15 Jul 2020 16:41:32 +0530 Subject: [PATCH 042/101] fix: patch update_actual_start_and_end_date_in_wo --- erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py index 331c5590e5..adfa20e368 100644 --- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py +++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import frappe from frappe.utils import add_to_date -from frappe.utils.dashboard import get_config, make_records def execute(): frappe.reload_doc("manufacturing", "doctype", "work_order") From e0de0ac61751216632ebbefdb666ffcf430e8712 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 15 Jul 2020 17:06:42 +0530 Subject: [PATCH 043/101] fix: not able to submit sales invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index b2b4cb1ea7..bab5208370 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1113,7 +1113,10 @@ class SalesInvoice(SellingController): expiry_date=self.posting_date, include_expired_entry=True) if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \ (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)): - points_earned = cint(eligible_amount/lp_details.collection_factor) + + collection_factor = lp_details.collection_factor if lp_details.collection_factor else 1.0 + points_earned = cint(eligible_amount/collection_factor) + doc = frappe.get_doc({ "doctype": "Loyalty Point Entry", "company": self.company, From 132376750c965b7d371f8c90e04d1e7a6dc9a670 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 10 Jul 2020 18:11:04 +0530 Subject: [PATCH 044/101] fix: incorrect balance qty in stock ledger report --- erpnext/stock/report/stock_ledger/stock_ledger.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index abf959eb0b..a5f92e2c76 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -4,10 +4,10 @@ from __future__ import unicode_literals import frappe +from frappe.utils import cint, flt from erpnext.stock.utils import update_included_uom_in_report from frappe import _ - def execute(filters=None): include_uom = filters.get("include_uom") columns = get_columns() @@ -15,6 +15,7 @@ def execute(filters=None): sl_entries = get_stock_ledger_entries(filters, items) item_details = get_item_details(items, sl_entries, include_uom) opening_row = get_opening_balance(filters, columns) + precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) data = [] conversion_factors = [] @@ -29,7 +30,7 @@ def execute(filters=None): sle.update(item_detail) if filters.get("batch_no"): - actual_qty += sle.actual_qty + actual_qty += flt(sle.actual_qty, precision) stock_value += sle.stock_value_difference if sle.voucher_type == 'Stock Reconciliation': From 0fa4143bac50fcca97505c2d4d8a545843273ff0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 15 Jul 2020 18:17:38 +0530 Subject: [PATCH 045/101] fix: Cess amount in GSTR 3B report --- .../doctype/gstr_3b_report/gstr_3b_report.js | 4 + .../doctype/gstr_3b_report/gstr_3b_report.py | 78 ++++++++++--------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js index a1cea8f609..5170185158 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js @@ -45,6 +45,10 @@ frappe.ui.form.on('GSTR 3B Report', { frm.set_df_property('year', 'options', options); }, + validate: function(frm) { + frm.dirty(); + }, + setup: function(frm) { frm.set_query('company_address', function(doc) { if(!doc.company) { diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 619734ff26..6d9b8dbe28 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -243,20 +243,15 @@ class GSTR3BReport(Document): osup_det = self.report_dict["sup_details"]["osup_det"] - for d in inter_state_supply.get("Unregistered", []): - self.report_dict["inter_sup"]["unreg_details"].append(d) - osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2) - osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2) + for key, value in iteritems(inter_state_supply): + if key[0] == "Unregistered": + self.report_dict["inter_sup"]["unreg_details"].append(value) - for d in inter_state_supply.get("Registered Composition", []): - self.report_dict["inter_sup"]["comp_details"].append(d) - osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2) - osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2) + if key[0] == "Registered Composition": + self.report_dict["inter_sup"]["comp_details"].append(value) - for d in inter_state_supply.get("UIN Holders", []): - self.report_dict["inter_sup"]["uin_details"].append(d) - osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2) - osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2) + if key[0] == "UIN Holders": + self.report_dict["inter_sup"]["uin_details"].append(value) def get_total_taxable_value(self, doctype, reverse_charge): @@ -301,41 +296,54 @@ class GSTR3BReport(Document): (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total def get_inter_state_supplies(self, state_number): - - inter_state_supply_taxable_value = frappe.db.sql(""" select sum(s.net_total) as total, s.place_of_supply, s.gst_category - from `tabSales Invoice` s where s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s - and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders') - group by s.gst_category, s.place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - - inter_state_supply_tax = frappe.db.sql(""" select sum(t.tax_amount_after_discount_amount) as tax_amount, s.place_of_supply, s.gst_category - from `tabSales Invoice` s, `tabSales Taxes and Charges` t + inter_state_supply_tax = frappe.db.sql(""" select t.account_head, t.tax_amount_after_discount_amount as tax_amount, + s.name, s.net_total, s.place_of_supply, s.gst_category from `tabSales Invoice` s, `tabSales Taxes and Charges` t where t.parent = s.name and s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders') - group by s.gst_category, s.place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - inter_state_supply_tax_mapping={} + inter_state_supply_tax_mapping = {} inter_state_supply_details = {} for d in inter_state_supply_tax: - inter_state_supply_tax_mapping.setdefault(d.place_of_supply, d.tax_amount) + inter_state_supply_tax_mapping.setdefault(d.name, { + 'place_of_supply': d.place_of_supply, + 'taxable_value': d.net_total, + 'camt': 0.0, + 'samt': 0.0, + 'iamt': 0.0, + 'csamt': 0.0 + }) - for d in inter_state_supply_taxable_value: - inter_state_supply_details.setdefault( - d.gst_category, [] - ) + if d.account_head in [d.cgst_account for d in self.account_heads]: + inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount + if d.account_head in [d.sgst_account for d in self.account_heads]: + inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount + + if d.account_head in [d.igst_account for d in self.account_heads]: + inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount + + if d.account_head in [d.cess_account for d in self.account_heads]: + inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount + + for key, value in iteritems(inter_state_supply_tax_mapping): if d.place_of_supply: + osup_det = self.report_dict["sup_details"]["osup_det"] + osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2) + osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2) + osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2) + osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2) + if state_number != d.place_of_supply.split("-")[0]: - inter_state_supply_details[d.gst_category].append({ + inter_state_supply_details.setdefault((d.gst_category, d.place_of_supply), { + "txval": 0.0, "pos": d.place_of_supply.split("-")[0], - "txval": flt(d.total, 2), - "iamt": flt(inter_state_supply_tax_mapping.get(d.place_of_supply), 2) + "iamt": 0.0 }) - else: - osup_det = self.report_dict["sup_details"]["osup_det"] - osup_det["txval"] = flt(osup_det["txval"] + d.total, 2) - osup_det["camt"] = flt(osup_det["camt"] + inter_state_supply_tax_mapping.get(d.place_of_supply)/2, 2) - osup_det["samt"] = flt(osup_det["samt"] + inter_state_supply_tax_mapping.get(d.place_of_supply)/2, 2) + + inter_state_supply_details[(d.gst_category, d.place_of_supply)]['txval'] += value['taxable_value'] + inter_state_supply_details[(d.gst_category, d.place_of_supply)]['iamt'] += value['iamt'] return inter_state_supply_details From 2af43b3df60701e7545b95bb040e6e2f05a3fbff Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 15 Jul 2020 21:54:28 +0530 Subject: [PATCH 046/101] fix: linting --- .../crm/report/lead_owner_efficiency/lead_owner_efficiency.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py index 53fc8cd810..8fe16a2f4c 100644 --- a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py +++ b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py @@ -18,7 +18,7 @@ def get_columns(): "fieldname": "lead_owner", "label": _("Lead Owner"), "fieldtype": "Link", - "options":"User", + "options": "User", "width": "130" }, { @@ -69,4 +69,4 @@ def get_columns(): "fieldtype": "Float", "width": "100" } - ] \ No newline at end of file + ] From 52c319cfa793108162acd6046b000ffd71293b1b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 15 Jul 2020 23:57:03 +0530 Subject: [PATCH 047/101] fix: Update RCM only for indian countries --- erpnext/regional/india/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 24bb137e68..7d10892c4a 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -668,6 +668,11 @@ def get_gst_accounts(company, account_wise=False): return gst_accounts def update_grand_total_for_rcm(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'India': + return + if doc.reverse_charge == 'Y': gst_accounts = get_gst_accounts(doc.company) gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ From 50902cf30ca34802c24ebd1616ab68492cca55f4 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 16 Jul 2020 12:21:46 +0530 Subject: [PATCH 048/101] Fix: Changes Requested --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index cd2313ab8f..962006553e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1029,14 +1029,14 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= if bank_amount: received_amount = bank_amount else: - received_amount = paid_amount * (doc.get('conversion_rate') or 1) + received_amount = paid_amount * doc.get('conversion_rate', 1) else: received_amount = abs(outstanding_amount) if bank_amount: paid_amount = bank_amount else: # if party account currency and bank currency is different then populate paid amount as well - paid_amount = received_amount * (doc.get('conversion_rate') or 1) + paid_amount = received_amount * doc.get('conversion_rate', 1) pe = frappe.new_doc("Payment Entry") pe.payment_type = payment_type From ac23ac6a025018e6e707fcb904256ae55f7cd876 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 16 Jul 2020 13:10:43 +0530 Subject: [PATCH 049/101] fix: validation for additional salary (#22645) * fix: validation for additional salary * fix:changes requested Co-authored-by: Rucha Mahabal Co-authored-by: Rucha Mahabal --- .../doctype/additional_salary/additional_salary.js | 3 +-- .../doctype/additional_salary/additional_salary.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js index fb42b6f410..d56cd4e967 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.js +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js @@ -8,8 +8,7 @@ frappe.ui.form.on('Additional Salary', { frm.set_query("employee", function() { return { filters: { - company: frm.doc.company, - status: "Active" + company: frm.doc.company } }; }); diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index e369ba7cef..ef174bdea2 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -33,12 +33,16 @@ class AdditionalSalary(Document): frappe.throw(_("From Date can not be greater than To Date.")) if date_of_joining: - if getdate(self.payroll_date) < getdate(date_of_joining): + if self.payroll_date and getdate(self.payroll_date) < getdate(date_of_joining): frappe.throw(_("Payroll date can not be less than employee's joining date.")) - elif getdate(self.from_date) < getdate(date_of_joining): + elif self.from_date and getdate(self.from_date) < getdate(date_of_joining): frappe.throw(_("From date can not be less than employee's joining date.")) - elif relieving_date and getdate(self.to_date) > getdate(relieving_date): + + if relieving_date: + if self.to_date and getdate(self.to_date) > getdate(relieving_date): frappe.throw(_("To date can not be greater than employee's relieving date.")) + if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date): + frappe.throw(_("Payroll date can not be greater than employee's relieving date.")) def get_amount(self, sal_start_date, sal_end_date): start_date = getdate(sal_start_date) @@ -107,4 +111,4 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty existing_salary_components.append(d.salary_component) - return salary_components_details, additional_salary_details \ No newline at end of file + return salary_components_details, additional_salary_details From ff6ba25386609781d72f8d02c359cc93e9c4bbe8 Mon Sep 17 00:00:00 2001 From: Karthikeyan S Date: Thu, 16 Jul 2020 13:33:38 +0530 Subject: [PATCH 050/101] fix: remove FE validation for log_type field --- erpnext/hr/doctype/employee_checkin/employee_checkin.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.js b/erpnext/hr/doctype/employee_checkin/employee_checkin.js index 43023b6034..c2403ca2bd 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.js +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.js @@ -6,9 +6,5 @@ frappe.ui.form.on('Employee Checkin', { if(!frm.doc.time) { frm.set_value("time", frappe.datetime.now_datetime()); } - }, - refresh: (frm) => { - // make log type mandatory - frm.set_df_property('log_type', 'reqd', frm.doc.log_type ? 0 : 1); } }); From 6319073d819e44d607e32c4d44a423e7603f5259 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 16 Jul 2020 15:38:51 +0530 Subject: [PATCH 051/101] fix: Move Issue List actions under 'Actions' dropdown --- erpnext/support/doctype/issue/issue_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue_list.js b/erpnext/support/doctype/issue/issue_list.js index 6d702f6bdf..513a8dca22 100644 --- a/erpnext/support/doctype/issue/issue_list.js +++ b/erpnext/support/doctype/issue/issue_list.js @@ -8,11 +8,11 @@ frappe.listview_settings['Issue'] = { var method = "erpnext.support.doctype.issue.issue.set_multiple_status"; - listview.page.add_menu_item(__("Set as Open"), function() { + listview.page.add_action_item(__("Set as Open"), function() { listview.call_for_selected_items(method, {"status": "Open"}); }); - listview.page.add_menu_item(__("Set as Closed"), function() { + listview.page.add_action_item(__("Set as Closed"), function() { listview.call_for_selected_items(method, {"status": "Closed"}); }); }, From d02465a68f788f33c3fbe6bdd122b1b841a9fcf1 Mon Sep 17 00:00:00 2001 From: michellealva Date: Fri, 17 Jul 2020 10:35:16 +0530 Subject: [PATCH 052/101] fix: Update modified timestamp for Delivery Note Item --- .../stock/doctype/delivery_note_item/delivery_note_item.json | 2 +- erpnext/www/book-appointment/__init__.py | 0 erpnext/www/book-appointment/verify/__init__.py | 0 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 erpnext/www/book-appointment/__init__.py create mode 100644 erpnext/www/book-appointment/verify/__init__.py diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 7c92ac7dbb..3d57f47601 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -720,7 +720,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-11 12:25:06.177894", + "modified": "2020-07-20 12:25:06.177894", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/www/book-appointment/__init__.py b/erpnext/www/book-appointment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/www/book-appointment/verify/__init__.py b/erpnext/www/book-appointment/verify/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 188273564cc593d3bf991901a1606b18e8a5c8af Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 17 Jul 2020 11:31:15 +0530 Subject: [PATCH 053/101] fix: Multiple fixes in GST --- .../doctype/gstr_3b_report/gstr_3b_report.js | 5 +- .../doctype/gstr_3b_report/gstr_3b_report.py | 3 +- erpnext/regional/india/utils.py | 3 +- erpnext/regional/report/gstr_1/gstr_1.py | 10 ++-- erpnext/regional/report/gstr_2/gstr_2.py | 46 +++++++++---------- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js index 5170185158..c7442667c2 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js @@ -3,6 +3,7 @@ frappe.ui.form.on('GSTR 3B Report', { refresh : function(frm) { + frm.doc.__unsaved = 1; if(!frm.is_new()) { frm.set_intro(__("Please save the report again to rebuild or update")); frm.add_custom_button(__('Download JSON'), function() { @@ -45,10 +46,6 @@ frappe.ui.form.on('GSTR 3B Report', { frm.set_df_property('year', 'options', options); }, - validate: function(frm) { - frm.dirty(); - }, - setup: function(frm) { frm.set_query('company_address', function(doc) { if(!doc.company) { diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 6d9b8dbe28..2d306ba172 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -322,7 +322,7 @@ class GSTR3BReport(Document): inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount if d.account_head in [d.igst_account for d in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount + inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount if d.account_head in [d.cess_account for d in self.account_heads]: inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount @@ -331,6 +331,7 @@ class GSTR3BReport(Document): if d.place_of_supply: osup_det = self.report_dict["sup_details"]["osup_det"] osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2) + osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2) osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2) osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2) osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 7d10892c4a..fe7e0c807c 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -704,9 +704,10 @@ def update_totals(gst_tax, doc): doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total, doc.precision("rounding_adjustment")) - doc.outstanding_amount = doc.base_rounded_total + doc.outstanding_amount = doc.rounded_total or doc.grand_total doc.in_words = money_in_words(doc.grand_total, doc.currency) + doc.set_payment_schedule() def make_regional_gl_entries(gl_entries, doc): country = frappe.get_cached_value('Company', doc.company, 'country') diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 43b1ea83eb..8885b88c2a 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -118,7 +118,7 @@ class Gstr1Report(object): row.append(invoice_details.get(fieldname)) taxable_value = 0 - if invoice in self.cgst_igst_invoices: + if invoice in self.cgst_sgst_invoices: division_factor = 2 else: division_factor = 1 @@ -129,6 +129,8 @@ class Gstr1Report(object): taxable_value += abs(net_amount) elif not self.item_tax_rate.get(invoice): taxable_value += abs(net_amount) + elif tax_rate: + taxable_value += abs(net_amount) row += [tax_rate or 0, taxable_value] @@ -227,7 +229,7 @@ class Gstr1Report(object): self.items_based_on_tax_rate = {} self.invoice_cess = frappe._dict() - self.cgst_igst_invoices = [] + self.cgst_sgst_invoices = [] unidentified_gst_accounts = [] for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: @@ -251,8 +253,8 @@ class Gstr1Report(object): tax_rate = tax_amounts[0] if cgst_or_sgst: tax_rate *= 2 - if parent not in self.cgst_igst_invoices: - self.cgst_igst_invoices.append(parent) + if parent not in self.cgst_sgst_invoices: + self.cgst_sgst_invoices.append(parent) rate_based_dict = self.items_based_on_tax_rate\ .setdefault(parent, {}).setdefault(tax_rate, []) diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py index f326fe07ca..f899349ccc 100644 --- a/erpnext/regional/report/gstr_2/gstr_2.py +++ b/erpnext/regional/report/gstr_2/gstr_2.py @@ -44,30 +44,30 @@ class Gstr2Report(Gstr1Report): for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) for rate, items in items_based_on_rate.items(): - if inv not in self.igst_invoices: - rate = rate / 2 - row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - tax_amount = taxable_value * rate / 100 - row += [0, tax_amount, tax_amount] - else: - row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - tax_amount = taxable_value * rate / 100 - row += [tax_amount, 0, 0] + if rate: + if inv not in self.igst_invoices: + rate = rate / 2 + row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) + tax_amount = taxable_value * rate / 100 + row += [0, tax_amount, tax_amount] + else: + row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) + tax_amount = taxable_value * rate / 100 + row += [tax_amount, 0, 0] + row += [ + self.invoice_cess.get(inv), + invoice_details.get('eligibility_for_itc'), + invoice_details.get('itc_integrated_tax'), + invoice_details.get('itc_central_tax'), + invoice_details.get('itc_state_tax'), + invoice_details.get('itc_cess_amount') + ] + if self.filters.get("type_of_business") == "CDNR": + row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") + row.append("C" if invoice_details.return_against else "R") - row += [ - self.invoice_cess.get(inv), - invoice_details.get('eligibility_for_itc'), - invoice_details.get('itc_integrated_tax'), - invoice_details.get('itc_central_tax'), - invoice_details.get('itc_state_tax'), - invoice_details.get('itc_cess_amount') - ] - if self.filters.get("type_of_business") == "CDNR": - row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") - row.append("C" if invoice_details.return_against else "R") - - self.data.append(row) + self.data.append(row) def get_igst_invoices(self): self.igst_invoices = [] @@ -86,7 +86,7 @@ class Gstr2Report(Gstr1Report): conditions += opts[1] if self.filters.get("type_of_business") == "B2B": - conditions += "and ifnull(gst_category, '') != 'Overseas' and is_return != 1 " + conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1 " elif self.filters.get("type_of_business") == "CDNR": conditions += """ and is_return = 1 """ From 9be6787b80ca51318e22024d385c8004feba13d4 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Jul 2020 12:51:49 +0530 Subject: [PATCH 054/101] fix(maintenance-visit): change fieldtype of status to select --- .../doctype/maintenance_visit/maintenance_visit.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index c797b7ea77..11925681df 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -701,7 +701,7 @@ "columns": 0, "default": "Draft", "fieldname": "status", - "fieldtype": "Data", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -1001,7 +1001,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:44.911402", + "modified": "2020-07-15 14:44:44.911402", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit", From 848568d023a0af96b3916d9600380f99c1b7fc4d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 17 Jul 2020 11:24:40 +0530 Subject: [PATCH 055/101] fix: currency symbol not showing as per company currency in stock balance --- .../stock/report/stock_balance/stock_balance.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 74a4f6ef14..042087a4a7 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt, cint, getdate, now, date_diff from erpnext.stock.utils import add_additional_uom_columns @@ -20,6 +20,11 @@ def execute(filters=None): from_date = filters.get('from_date') to_date = filters.get('to_date') + if filters.get("company"): + company_currency = erpnext.get_company_currency(filters.get("company")) + else: + company_currency = frappe.db.get_single_value("Global Defaults", "default_currency") + include_uom = filters.get("include_uom") columns = get_columns(filters) items = get_items(filters) @@ -52,6 +57,7 @@ def execute(filters=None): item_reorder_qty = item_reorder_detail_map[item + warehouse]["warehouse_reorder_qty"] report_data = { + 'currency': company_currency, 'item_code': item, 'warehouse': warehouse, 'company': company, @@ -89,7 +95,6 @@ def execute(filters=None): def get_columns(filters): """return columns""" - columns = [ {"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100}, {"label": _("Item Name"), "fieldname": "item_name", "width": 150}, @@ -97,14 +102,14 @@ def get_columns(filters): {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100}, {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90}, {"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100}, + {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, "options": "currency"}, {"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Float", "width": 110}, + {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, "options": "currency"}, {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80}, {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80}, - {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate"}, + {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate", "options": "currency"}, {"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 100} From 2347e69cb973112153acbc8336e42d89c29f6924 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 16 Jul 2020 22:13:00 +0530 Subject: [PATCH 056/101] fix: for past dated stock reco, batched item showing the current available qty instead of quantity as per posting date --- erpnext/stock/doctype/batch/batch.py | 9 +++++++-- .../stock_reconciliation/stock_reconciliation.js | 14 ++++++++++++++ .../stock_reconciliation/stock_reconciliation.py | 12 ++++++++++-- erpnext/stock/report/stock_ledger/stock_ledger.py | 2 +- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index a091ac7fae..c8424f13e1 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -143,7 +143,7 @@ class Batch(Document): @frappe.whitelist() -def get_batch_qty(batch_no=None, warehouse=None, item_code=None): +def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None): """Returns batch actual qty if warehouse is passed, or returns dict of qty by warehouse if warehouse is None @@ -155,9 +155,14 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None): out = 0 if batch_no and warehouse: + cond = "" + if posting_date and posting_time: + cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(posting_date, + posting_time) + out = float(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and batch_no=%s""", + where warehouse=%s and batch_no=%s {0}""".format(cond), (warehouse, batch_no))[0][0] or 0) if batch_no and not warehouse: diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index dd284e4a96..ecee97ce86 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -74,6 +74,20 @@ frappe.ui.form.on("Stock Reconciliation", { , __("Get Items"), __("Update")); }, + posting_date: function(frm) { + frm.trigger("set_valuation_rate_and_qty_for_all_items"); + }, + + posting_time: function(frm) { + frm.trigger("set_valuation_rate_and_qty_for_all_items"); + }, + + set_valuation_rate_and_qty_for_all_items: function(frm) { + frm.doc.items.forEach(row => { + frm.events.set_valuation_rate_and_qty(frm, row.doctype, row.name); + }); + }, + set_valuation_rate_and_qty: function(frm, cdt, cdn) { var d = frappe.model.get_doc(cdt, cdn); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 5e469c24d7..43fbc00466 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -184,8 +184,12 @@ class StockReconciliation(StockController): sl_entries = [] has_serial_no = False + has_batch_no = False for row in self.items: item = frappe.get_doc("Item", row.item_code) + if item.has_batch_no: + has_batch_no = True + if item.has_serial_no or item.has_batch_no: has_serial_no = True self.get_sle_for_serialized_items(row, sl_entries) @@ -222,7 +226,11 @@ class StockReconciliation(StockController): if has_serial_no: sl_entries = self.merge_similar_item_serial_nos(sl_entries) - self.make_sl_entries(sl_entries) + allow_negative_stock = False + if has_batch_no: + allow_negative_stock = True + + self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) if has_serial_no and sl_entries: self.update_valuation_rate_for_serial_no() @@ -493,7 +501,7 @@ def get_stock_balance_for(item_code, warehouse, qty, rate = data if item_dict.get("has_batch_no"): - qty = get_batch_qty(batch_no, warehouse) or 0 + qty = get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0 return { 'qty': qty, diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index a5f92e2c76..fe8ad71b73 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -33,7 +33,7 @@ def execute(filters=None): actual_qty += flt(sle.actual_qty, precision) stock_value += sle.stock_value_difference - if sle.voucher_type == 'Stock Reconciliation': + if sle.voucher_type == 'Stock Reconciliation' and not sle.actual_qty: actual_qty = sle.qty_after_transaction stock_value = sle.stock_value From 07f8e749be0ad5562f9944991c45dea30ebf4582 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 18 Jul 2020 19:47:33 +0530 Subject: [PATCH 057/101] fix: Period list api changes for custom cash flow report --- erpnext/accounts/report/cash_flow/custom_cash_flow.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py index 8566f5375a..fe2bc725e0 100644 --- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py +++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py @@ -334,10 +334,9 @@ def compute_data(filters, company_currency, profit_data, period_list, light_mapp def execute(filters=None): if not filters.periodicity: filters.periodicity = "Monthly" - period_list = get_period_list( - filters.from_fiscal_year, filters.to_fiscal_year, filters.periodicity, - filters.accumulated_values, filters.company - ) + period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, + filters.period_start_date, filters.period_end_date, filters.filter_based_on, + filters.periodicity, company=filters.company) mappers = get_mappers_from_db() @@ -396,7 +395,7 @@ def _get_account_type_based_data(filters, account_names, period_list, accumulate gl_sum = frappe.db.sql_list(""" select sum(credit) - sum(debit) from `tabGL Entry` - where company=%s and posting_date >= %s and posting_date <= %s + where company=%s and posting_date >= %s and posting_date <= %s and voucher_type != 'Period Closing Voucher' and account in ( SELECT name FROM tabAccount WHERE name IN (%s) OR parent_account IN (%s)) @@ -405,7 +404,7 @@ def _get_account_type_based_data(filters, account_names, period_list, accumulate gl_sum = frappe.db.sql_list(""" select sum(credit) - sum(debit) from `tabGL Entry` - where company=%s and posting_date >= %s and posting_date <= %s + where company=%s and posting_date >= %s and posting_date <= %s and voucher_type != 'Period Closing Voucher' and account in ( SELECT name FROM tabAccount WHERE name IN (%s) OR parent_account IN (%s)) From 37cd1b8021fc82f982871df8c150f625b936c524 Mon Sep 17 00:00:00 2001 From: michellealva Date: Mon, 20 Jul 2020 13:04:46 +0530 Subject: [PATCH 058/101] fix: Heatmap in Vehicle --- erpnext/hr/doctype/vehicle/vehicle.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/vehicle/vehicle.py b/erpnext/hr/doctype/vehicle/vehicle.py index a75cfa6125..57e2594d1b 100644 --- a/erpnext/hr/doctype/vehicle/vehicle.py +++ b/erpnext/hr/doctype/vehicle/vehicle.py @@ -13,4 +13,11 @@ class Vehicle(Document): if getdate(self.start_date) > getdate(self.end_date): frappe.throw(_("Insurance Start date should be less than Insurance End date")) if getdate(self.carbon_check_date) > getdate(): - frappe.throw(_("Last carbon check date cannot be a future date")) \ No newline at end of file + frappe.throw(_("Last carbon check date cannot be a future date")) + +def get_timeline_data(doctype, name): + '''Return timeline for vehicle log''' + return dict(frappe.db.sql('''select unix_timestamp(date), count(*) + from `tabVehicle Log` where license_plate=%s + and date > date_sub(curdate(), interval 1 year) + group by date''', name)) From 8c7d8ba917ffc623e75e099330cdd09d035a7eec Mon Sep 17 00:00:00 2001 From: michellealva Date: Mon, 20 Jul 2020 13:12:17 +0530 Subject: [PATCH 059/101] fix: remove tab in code --- erpnext/hr/doctype/vehicle/vehicle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/vehicle/vehicle.py b/erpnext/hr/doctype/vehicle/vehicle.py index 57e2594d1b..1df5068268 100644 --- a/erpnext/hr/doctype/vehicle/vehicle.py +++ b/erpnext/hr/doctype/vehicle/vehicle.py @@ -15,7 +15,7 @@ class Vehicle(Document): if getdate(self.carbon_check_date) > getdate(): frappe.throw(_("Last carbon check date cannot be a future date")) -def get_timeline_data(doctype, name): +def get_timeline_data(doctype, name): '''Return timeline for vehicle log''' return dict(frappe.db.sql('''select unix_timestamp(date), count(*) from `tabVehicle Log` where license_plate=%s From 493b581862d0a767016cbcf90ecc0366a1ca1dba Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 21 Jul 2020 12:47:16 +0530 Subject: [PATCH 060/101] fix: Ignore cancelled entries in trial balance opening --- erpnext/accounts/report/trial_balance/trial_balance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 5a699b6580..3cf08703f8 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -152,6 +152,7 @@ def get_rootwise_opening_balances(filters, report_type): {additional_conditions} and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') and account in (select name from `tabAccount` where report_type=%(report_type)s) + and is_cancelled = 0 group by account""".format(additional_conditions=additional_conditions), query_filters , as_dict=True) opening = frappe._dict() From a5b83e85c3290c95423458277092948bf502a28f Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Tue, 21 Jul 2020 12:54:59 +0530 Subject: [PATCH 061/101] feat: dashboard for timesheet (#22750) --- .../doctype/timesheet/timesheet_dashboard.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 erpnext/projects/doctype/timesheet/timesheet_dashboard.py diff --git a/erpnext/projects/doctype/timesheet/timesheet_dashboard.py b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py new file mode 100644 index 0000000000..acff97a226 --- /dev/null +++ b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'time_sheet', + 'transactions': [ + { + 'label': _('References'), + 'items': ['Sales Invoice', 'Salary Slip'] + } + ] + } \ No newline at end of file From 09b8caf866b3d68e3dc832955464f91a98ae41eb Mon Sep 17 00:00:00 2001 From: Diksha Jadhav Date: Tue, 21 Jul 2020 17:35:10 +0530 Subject: [PATCH 062/101] fix(report): fix alignment in script report that extends financial_statements.js --- erpnext/public/js/financial_statements.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index d89d4712e6..459c01b269 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -3,7 +3,7 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), "formatter": function(value, row, column, data, default_formatter) { - if (column.fieldname=="account") { + if (data && column.fieldname=="account") { value = data.account_name || value; column.link_onclick = @@ -13,7 +13,7 @@ erpnext.financial_statements = { value = default_formatter(value, row, column, data); - if (!data.parent_account) { + if (data && !data.parent_account) { value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); From ffbd7b40879a092ca66fcfb5a4829ffcff0f9f8a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 21 Jul 2020 19:40:18 +0530 Subject: [PATCH 063/101] fix: move Video DocType from Core to Utilities module --- erpnext/utilities/doctype/video/__init__.py | 0 erpnext/utilities/doctype/video/test_video.py | 10 ++ erpnext/utilities/doctype/video/video.js | 8 ++ erpnext/utilities/doctype/video/video.json | 106 ++++++++++++++++++ erpnext/utilities/doctype/video/video.py | 10 ++ 5 files changed, 134 insertions(+) create mode 100644 erpnext/utilities/doctype/video/__init__.py create mode 100644 erpnext/utilities/doctype/video/test_video.py create mode 100644 erpnext/utilities/doctype/video/video.js create mode 100644 erpnext/utilities/doctype/video/video.json create mode 100644 erpnext/utilities/doctype/video/video.py diff --git a/erpnext/utilities/doctype/video/__init__.py b/erpnext/utilities/doctype/video/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/utilities/doctype/video/test_video.py b/erpnext/utilities/doctype/video/test_video.py new file mode 100644 index 0000000000..33ea31c919 --- /dev/null +++ b/erpnext/utilities/doctype/video/test_video.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestVideo(unittest.TestCase): + pass diff --git a/erpnext/utilities/doctype/video/video.js b/erpnext/utilities/doctype/video/video.js new file mode 100644 index 0000000000..056bd3ccd6 --- /dev/null +++ b/erpnext/utilities/doctype/video/video.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Video', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/utilities/doctype/video/video.json b/erpnext/utilities/doctype/video/video.json new file mode 100644 index 0000000000..5d2cc13348 --- /dev/null +++ b/erpnext/utilities/doctype/video/video.json @@ -0,0 +1,106 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:title", + "creation": "2018-10-17 05:47:13.087395", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "provider", + "url", + "column_break_4", + "publish_date", + "duration", + "section_break_7", + "description" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "provider", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Provider", + "options": "YouTube\nVimeo", + "reqd": 1 + }, + { + "fieldname": "url", + "fieldtype": "Data", + "in_list_view": 1, + "label": "URL", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "publish_date", + "fieldtype": "Date", + "label": "Publish Date" + }, + { + "fieldname": "duration", + "fieldtype": "Data", + "label": "Duration" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "in_list_view": 1, + "label": "Description", + "reqd": 1 + } + ], + "links": [], + "modified": "2020-07-21 19:29:46.603734", + "modified_by": "Administrator", + "module": "Utilities", + "name": "Video", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/utilities/doctype/video/video.py b/erpnext/utilities/doctype/video/video.py new file mode 100644 index 0000000000..3c17b560f3 --- /dev/null +++ b/erpnext/utilities/doctype/video/video.py @@ -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 Video(Document): + pass From 66d4b42df0f9807ff42f508526289ca30e1c0204 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 21 Jul 2020 19:59:30 +0530 Subject: [PATCH 064/101] fix: update set query for warehouse after changing the company (#22632) * fix: update set query for warehouse after changing the company * fix: changed set_query to get_query * fix: variable filter doesn't get updated once set on load hence removed Co-authored-by: Marica --- .../doctype/material_request/material_request.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 60f5ff3629..3c4e35349e 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -49,17 +49,21 @@ frappe.ui.form.on('Material Request', { // set schedule_date set_schedule_date(frm); - let filters = {'company': frm.doc.company} - - frm.set_query("warehouse", "items", function() { + frm.set_query("warehouse", "items", function(doc) { return { - filters: filters + filters: {'company': doc.company} }; }); - frm.set_query("set_warehouse", function(){ + frm.set_query("set_warehouse", function(doc){ return { - filters: filters + filters: {'company': doc.company} + }; + }); + + frm.set_query("set_from_warehouse", function(doc){ + return { + filters: {'company': doc.company} }; }); }, From 871889220b31994d06bac90f87b33354b2066acf Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 21 Jul 2020 20:39:00 +0530 Subject: [PATCH 065/101] =?UTF-8?q?style:=20moved=20parent=20warehouse=20t?= =?UTF-8?q?o=20top=20section=20also=20added=20a=20section=20bre=E2=80=A6?= =?UTF-8?q?=20(#22708)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: moved parent warehouse to top section also added a section break for better look * style: changes display depends on for HTML field Co-authored-by: Marica --- erpnext/stock/doctype/warehouse/warehouse.json | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index 5d534af157..3b49c4ca52 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.json @@ -10,12 +10,14 @@ "field_order": [ "warehouse_detail", "warehouse_name", + "column_break_3", + "warehouse_type", + "parent_warehouse", "is_group", - "company", - "disabled", "column_break_4", "account", - "warehouse_type", + "company", + "disabled", "address_and_contact", "address_html", "column_break_10", @@ -31,7 +33,6 @@ "state", "pin", "tree_details", - "parent_warehouse", "lft", "rgt", "old_parent" @@ -91,6 +92,7 @@ "options": "Account" }, { + "depends_on": "eval:!doc.__islocal", "fieldname": "address_and_contact", "fieldtype": "Section Break", "label": "Address and Contact" @@ -224,13 +226,17 @@ "fieldtype": "Link", "label": "Warehouse Type", "options": "Warehouse Type" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Section Break" } ], "icon": "fa fa-building", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 18:11:53.282358", + "modified": "2020-07-16 15:43:50.653256", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse", From de417d2774fdca03dee38dcb67ef14a8a5cb58f9 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 22 Jul 2020 00:58:13 +0530 Subject: [PATCH 066/101] feat: Provision to make RFQ against Opportunity --- erpnext/crm/doctype/opportunity/opportunity.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index f1b8171349..28aedde122 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -76,6 +76,11 @@ frappe.ui.form.on("Opportunity", { function() { frm.trigger("make_supplier_quotation") }, __('Create')); + + frm.add_custom_button(__('Request For Quotation'), + function() { + frm.trigger("make_request_for_quotation") + }, __('Create')); } frm.add_custom_button(__('Quotation'), @@ -126,6 +131,13 @@ frappe.ui.form.on("Opportunity", { }) }, + make_request_for_quotation: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation", + frm: cur_frm + }) + }, + toggle_mandatory: function(frm) { frm.toggle_reqd("items", frm.doc.with_items ? 1:0); } From 8d28eacb0978667e3317a74f59f20e87ab2d5914 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 22 Jul 2020 15:57:33 +0530 Subject: [PATCH 067/101] chore: Exported Healthcare Module Dashboard (#22775) --- .../clinical_procedures.json | 26 ++ .../clinical_procedures_status.json | 26 ++ .../department_wise_patient_appointments.json | 25 ++ .../dashboard_chart/diagnoses/diagnoses.json | 25 ++ .../in_patient_status/in_patient_status.json | 26 ++ .../dashboard_chart/lab_tests/lab_tests.json | 26 ++ .../patient_appointments.json | 27 ++ .../dashboard_chart/symptoms/symptoms.json | 26 ++ erpnext/healthcare/dashboard_fixtures.py | 245 ------------------ .../healthcare/healthcare.json | 62 +++++ .../appointments_to_bill.json | 21 ++ .../open_appointments/open_appointments.json | 21 ++ .../total_patients/total_patients.json | 20 ++ .../total_patients_admitted.json | 20 ++ 14 files changed, 351 insertions(+), 245 deletions(-) create mode 100644 erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json create mode 100644 erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json create mode 100644 erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json create mode 100644 erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json create mode 100644 erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json create mode 100644 erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json create mode 100644 erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json create mode 100644 erpnext/healthcare/dashboard_chart/symptoms/symptoms.json delete mode 100644 erpnext/healthcare/dashboard_fixtures.py create mode 100644 erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json create mode 100644 erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json create mode 100644 erpnext/healthcare/number_card/open_appointments/open_appointments.json create mode 100644 erpnext/healthcare/number_card/total_patients/total_patients.json create mode 100644 erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json new file mode 100644 index 0000000000..a59f149ee5 --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Clinical Procedures", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.601236", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Clinical Procedure", + "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_based_on": "procedure_template", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:47.008622", + "modified": "2020-07-22 13:36:48.114479", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Clinical Procedures", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Percentage", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json new file mode 100644 index 0000000000..6d560f74bf --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Clinical Procedure Status", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.654325", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Clinical Procedure", + "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_based_on": "status", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:46.691764", + "modified": "2020-07-22 13:40:17.215775", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Clinical Procedures Status", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Pie", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json b/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json new file mode 100644 index 0000000000..b24bb345ac --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json @@ -0,0 +1,25 @@ +{ + "chart_name": "Department wise Patient Appointments", + "chart_type": "Custom", + "creation": "2020-07-17 11:25:37.190130", + "custom_options": "{\"colors\": [\"#7CD5FA\", \"#5F62F6\", \"#7544E2\", \"#EE5555\"], \"barOptions\": {\"stacked\": 1}, \"height\": 300}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", + "filters_json": "{}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:32:05.827566", + "modified": "2020-07-22 15:35:12.798035", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Department wise Patient Appointments", + "number_of_groups": 0, + "owner": "Administrator", + "source": "Department wise Patient Appointments", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json new file mode 100644 index 0000000000..0195aac8b7 --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json @@ -0,0 +1,25 @@ +{ + "chart_name": "Diagnoses", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.705698", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Patient Encounter Diagnosis", + "filters_json": "[]", + "group_by_based_on": "diagnosis", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:47.895521", + "modified": "2020-07-22 13:43:32.369481", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Diagnoses", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Percentage", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json b/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json new file mode 100644 index 0000000000..77b47c9e15 --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json @@ -0,0 +1,26 @@ +{ + "chart_name": "In-Patient Status", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.629199", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Inpatient Record", + "dynamic_filters_json": "[[\"Inpatient Record\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[]", + "group_by_based_on": "status", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:46.792131", + "modified": "2020-07-22 13:33:16.008150", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "In-Patient Status", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json new file mode 100644 index 0000000000..052483533e --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Lab Tests", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.574903", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Lab Test", + "dynamic_filters_json": "[[\"Lab Test\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Lab Test\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_based_on": "template", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:47.344055", + "modified": "2020-07-22 13:37:34.490129", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Tests", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Percentage", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json b/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json new file mode 100644 index 0000000000..19bfb7256f --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json @@ -0,0 +1,27 @@ +{ + "based_on": "appointment_datetime", + "chart_name": "Patient Appointments", + "chart_type": "Count", + "creation": "2020-07-14 18:17:54.525082", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Patient Appointment", + "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Patient Appointment\",\"status\",\"!=\",\"Cancelled\",false]]", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:46.830491", + "modified": "2020-07-22 13:38:02.254190", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Appointments", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Daily", + "timeseries": 1, + "timespan": "Last Month", + "type": "Line", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json new file mode 100644 index 0000000000..8fc86a1c59 --- /dev/null +++ b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Symptoms", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.680852", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Patient Encounter Symptom", + "dynamic_filters_json": "", + "filters_json": "[]", + "group_by_based_on": "complaint", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:47.296748", + "modified": "2020-07-22 13:40:59.655129", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Symptoms", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Percentage", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py deleted file mode 100644 index 94668a16d9..0000000000 --- a/erpnext/healthcare/dashboard_fixtures.py +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import json -from frappe import _ - -def get_data(): - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(), - "number_cards": get_number_cards(), - }) - -def get_company(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company = frappe.get_list("Company", limit=1) - if company: - return company[0].name - return None - -def get_dashboards(): - return [{ - "name": "Healthcare", - "dashboard_name": "Healthcare", - "charts": [ - { "chart": "Patient Appointments", "width": "Full"}, - { "chart": "In-Patient Status", "width": "Half"}, - { "chart": "Clinical Procedures Status", "width": "Half"}, - { "chart": "Lab Tests", "width": "Half"}, - { "chart": "Clinical Procedures", "width": "Half"}, - { "chart": "Symptoms", "width": "Half"}, - { "chart": "Diagnoses", "width": "Half"}, - { "chart": "Department wise Patient Appointments", "width": "Full"} - ], - "cards": [ - { "card": "Total Patients" }, - { "card": "Total Patient Admitted" }, - { "card": "Open Appointments" }, - { "card": "Appointments to Bill" } - ] - }] - -def get_charts(): - company = get_company() - return [ - { - "doctype": "Dashboard Chart", - "time_interval": "Daily", - "name": "Patient Appointments", - "chart_name": _("Patient Appointments"), - "timespan": "Last Month", - "filters_json": json.dumps([ - ["Patient Appointment", "company", "=", company, False], - ["Patient Appointment", "status", "!=", "Cancelled"] - ]), - "chart_type": "Count", - "timeseries": 1, - "based_on": "appointment_datetime", - "owner": "Administrator", - "document_type": "Patient Appointment", - "type": "Line", - "width": "Half" - }, - { - "doctype": "Dashboard Chart", - "name": "Department wise Patient Appointments", - "chart_name": _("Department wise Patient Appointments"), - "chart_type": "Custom", - "source": "Department wise Patient Appointments", - "filters_json": json.dumps([]), - 'is_public': 1, - "owner": "Administrator", - "type": "Bar", - "width": "Full", - "custom_options": json.dumps({ - "colors": ["#7CD5FA", "#5F62F6", "#7544E2", "#EE5555"], - "barOptions":{ - "stacked":1 - }, - "height": 300 - }) - }, - { - "doctype": "Dashboard Chart", - "name": "Lab Tests", - "chart_name": _("Lab Tests"), - "chart_type": "Group By", - "document_type": "Lab Test", - "group_by_type": "Count", - "group_by_based_on": "template", - "filters_json": json.dumps([ - ["Lab Test", "company", "=", company, False], - ["Lab Test", "docstatus", "=", 1] - ]), - 'is_public': 1, - "owner": "Administrator", - "type": "Percentage", - "width": "Half", - }, - { - "doctype": "Dashboard Chart", - "name": "Clinical Procedures", - "chart_name": _("Clinical Procedures"), - "chart_type": "Group By", - "document_type": "Clinical Procedure", - "group_by_type": "Count", - "group_by_based_on": "procedure_template", - "filters_json": json.dumps([ - ["Clinical Procedure", "company", "=", company, False], - ["Clinical Procedure", "docstatus", "=", 1] - ]), - 'is_public': 1, - "owner": "Administrator", - "type": "Percentage", - "width": "Half", - }, - { - "doctype": "Dashboard Chart", - "name": "In-Patient Status", - "chart_name": _("In-Patient Status"), - "chart_type": "Group By", - "document_type": "Inpatient Record", - "group_by_type": "Count", - "group_by_based_on": "status", - "filters_json": json.dumps([ - ["Inpatient Record", "company", "=", company, False] - ]), - 'is_public': 1, - "owner": "Administrator", - "type": "Bar", - "width": "Half", - }, - { - "doctype": "Dashboard Chart", - "name": "Clinical Procedures Status", - "chart_name": _("Clinical Procedure Status"), - "chart_type": "Group By", - "document_type": "Clinical Procedure", - "group_by_type": "Count", - "group_by_based_on": "status", - "filters_json": json.dumps([ - ["Clinical Procedure", "company", "=", company, False], - ["Clinical Procedure", "docstatus", "=", 1] - ]), - 'is_public': 1, - "owner": "Administrator", - "type": "Pie", - "width": "Half", - }, - { - "doctype": "Dashboard Chart", - "name": "Symptoms", - "chart_name": _("Symptoms"), - "chart_type": "Group By", - "document_type": "Patient Encounter Symptom", - "group_by_type": "Count", - "group_by_based_on": "complaint", - "filters_json": json.dumps([]), - 'is_public': 1, - "owner": "Administrator", - "type": "Percentage", - "width": "Half", - }, - { - "doctype": "Dashboard Chart", - "name": "Diagnoses", - "chart_name": _("Diagnoses"), - "chart_type": "Group By", - "document_type": "Patient Encounter Diagnosis", - "group_by_type": "Count", - "group_by_based_on": "diagnosis", - "filters_json": json.dumps([]), - 'is_public': 1, - "owner": "Administrator", - "type": "Percentage", - "width": "Half", - } - ] - -def get_number_cards(): - company = get_company() - return [ - { - "name": "Total Patients", - "label": _("Total Patients"), - "function": "Count", - "doctype": "Number Card", - "document_type": "Patient", - "filters_json": json.dumps( - [["Patient","status","=","Active",False]] - ), - "is_public": 1, - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - }, - { - "name": "Total Patients Admitted", - "label": _("Total Patients Admitted"), - "function": "Count", - "doctype": "Number Card", - "document_type": "Patient", - "filters_json": json.dumps( - [["Patient","inpatient_status","=","Admitted",False]] - ), - "is_public": 1, - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - }, - { - "name": "Open Appointments", - "label": _("Open Appointments"), - "function": "Count", - "doctype": "Number Card", - "document_type": "Patient Appointment", - "filters_json": json.dumps( - [["Patient Appointment","company","=",company,False], - ["Patient Appointment","status","=","Open",False]] - ), - "is_public": 1, - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - }, - { - "name": "Appointments to Bill", - "label": _("Appointments To Bill"), - "function": "Count", - "doctype": "Number Card", - "document_type": "Patient Appointment", - "filters_json": json.dumps( - [["Patient Appointment","company","=",company,False], - ["Patient Appointment","invoiced","=",0,False]] - ), - "is_public": 1, - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - } - ] \ No newline at end of file diff --git a/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json b/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json new file mode 100644 index 0000000000..2fea6682ed --- /dev/null +++ b/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json @@ -0,0 +1,62 @@ +{ + "cards": [ + { + "card": "Total Patients" + }, + { + "card": "Total Patients Admitted" + }, + { + "card": "Open Appointments" + }, + { + "card": "Appointments to Bill" + } + ], + "charts": [ + { + "chart": "Patient Appointments", + "width": "Full" + }, + { + "chart": "In-Patient Status", + "width": "Half" + }, + { + "chart": "Clinical Procedures Status", + "width": "Half" + }, + { + "chart": "Lab Tests", + "width": "Half" + }, + { + "chart": "Clinical Procedures", + "width": "Half" + }, + { + "chart": "Symptoms", + "width": "Half" + }, + { + "chart": "Diagnoses", + "width": "Half" + }, + { + "chart": "Department wise Patient Appointments", + "width": "Full" + } + ], + "creation": "2020-07-14 18:17:54.823311", + "dashboard_name": "Healthcare", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-22 15:36:34.220387", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare", + "owner": "Administrator" +} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json b/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json new file mode 100644 index 0000000000..3e4d4e27df --- /dev/null +++ b/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-14 18:17:54.792773", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient Appointment", + "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Patient Appointment\",\"invoiced\",\"=\",0,false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Appointments To Bill", + "modified": "2020-07-22 13:27:58.038577", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Appointments to Bill", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/open_appointments/open_appointments.json b/erpnext/healthcare/number_card/open_appointments/open_appointments.json new file mode 100644 index 0000000000..8d121cc58a --- /dev/null +++ b/erpnext/healthcare/number_card/open_appointments/open_appointments.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-14 18:17:54.771092", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient Appointment", + "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Patient Appointment\",\"status\",\"=\",\"Open\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Open Appointments", + "modified": "2020-07-22 13:27:09.542122", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Open Appointments", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/total_patients/total_patients.json b/erpnext/healthcare/number_card/total_patients/total_patients.json new file mode 100644 index 0000000000..75441a6842 --- /dev/null +++ b/erpnext/healthcare/number_card/total_patients/total_patients.json @@ -0,0 +1,20 @@ +{ + "creation": "2020-07-14 18:17:54.727946", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient", + "filters_json": "[[\"Patient\",\"status\",\"=\",\"Active\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Patients", + "modified": "2020-07-22 13:26:02.643534", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Total Patients", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json b/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json new file mode 100644 index 0000000000..69a967df93 --- /dev/null +++ b/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json @@ -0,0 +1,20 @@ +{ + "creation": "2020-07-14 18:17:54.749754", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient", + "filters_json": "[[\"Patient\",\"inpatient_status\",\"=\",\"Admitted\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Patients Admitted", + "modified": "2020-07-22 13:26:20.027788", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Total Patients Admitted", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file From 6b9acf4476c23673b840cc48ef338a7dd9123a8b Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 22 Jul 2020 16:00:18 +0530 Subject: [PATCH 068/101] updated patch to delete auto email reports and moved patch to v13 from v12 --- erpnext/patches.txt | 2 +- .../v12_0/delete_report_requested_items_to_order.py | 7 ------- .../v13_0/delete_report_requested_items_to_order.py | 12 ++++++++++++ 3 files changed, 13 insertions(+), 8 deletions(-) delete mode 100644 erpnext/patches/v12_0/delete_report_requested_items_to_order.py create mode 100644 erpnext/patches/v13_0/delete_report_requested_items_to_order.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 782234069a..da390f54fd 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -708,5 +708,5 @@ erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll # erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v12_0.add_taxjar_integration_field -erpnext.patches.v12_0.delete_report_requested_items_to_order +erpnext.patches.v13_0.delete_report_requested_items_to_order erpnext.patches.v12_0.update_item_tax_template_company diff --git a/erpnext/patches/v12_0/delete_report_requested_items_to_order.py b/erpnext/patches/v12_0/delete_report_requested_items_to_order.py deleted file mode 100644 index 0296d47bde..0000000000 --- a/erpnext/patches/v12_0/delete_report_requested_items_to_order.py +++ /dev/null @@ -1,7 +0,0 @@ -import frappe - -def execute(): - frappe.db.sql(""" - DELETE FROM `tabReport` - WHERE name = 'Requested Items to Order' - """) \ No newline at end of file diff --git a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py new file mode 100644 index 0000000000..94a9fa85a8 --- /dev/null +++ b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py @@ -0,0 +1,12 @@ +import frappe + +def execute(): + """ Check for one or multiple Auto Email Reports and delete """ + auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": "Requested Items to Order"}, ["name"]) + for auto_email_report in auto_email_reports: + frappe.delete_doc("Auto Email Report", auto_email_report[0]) + + frappe.db.sql(""" + DELETE FROM `tabReport` + WHERE name = 'Requested Items to Order' + """) \ No newline at end of file From 1fddc6d126a6b0233408b87f8f0d73ae79336813 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 22 Jul 2020 16:11:06 +0530 Subject: [PATCH 069/101] chore: Exported Accounts Module Dashboard (#22769) * chore: Exported Accounts Module Dashboards * chore: deleted dashboard fixtures for Accounts module --- .../accounts_dashboard/accounts/accounts.json | 58 ++++ .../accounts_payable_ageing.json | 23 ++ .../accounts_receivable_ageing.json | 23 ++ .../bank_balance/bank_balance.json | 26 ++ .../budget_variance/budget_variance.json | 23 ++ .../incoming_bills_(purchase_invoice).json | 29 ++ .../outgoing_bills_(sales_invoice).json | 28 ++ .../profit_and_loss/profit_and_loss.json | 23 ++ erpnext/accounts/dashboard_fixtures.py | 284 ------------------ .../total_incoming_bills.json | 21 ++ .../total_incoming_payment.json | 21 ++ .../total_outgoing_bills.json | 21 ++ .../total_outgoing_payment.json | 21 ++ 13 files changed, 317 insertions(+), 284 deletions(-) create mode 100644 erpnext/accounts/accounts_dashboard/accounts/accounts.json create mode 100644 erpnext/accounts/dashboard_chart/accounts_payable_ageing/accounts_payable_ageing.json create mode 100644 erpnext/accounts/dashboard_chart/accounts_receivable_ageing/accounts_receivable_ageing.json create mode 100644 erpnext/accounts/dashboard_chart/bank_balance/bank_balance.json create mode 100644 erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json create mode 100644 erpnext/accounts/dashboard_chart/incoming_bills_(purchase_invoice)/incoming_bills_(purchase_invoice).json create mode 100644 erpnext/accounts/dashboard_chart/outgoing_bills_(sales_invoice)/outgoing_bills_(sales_invoice).json create mode 100644 erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json delete mode 100644 erpnext/accounts/dashboard_fixtures.py create mode 100644 erpnext/accounts/number_card/total_incoming_bills/total_incoming_bills.json create mode 100644 erpnext/accounts/number_card/total_incoming_payment/total_incoming_payment.json create mode 100644 erpnext/accounts/number_card/total_outgoing_bills/total_outgoing_bills.json create mode 100644 erpnext/accounts/number_card/total_outgoing_payment/total_outgoing_payment.json diff --git a/erpnext/accounts/accounts_dashboard/accounts/accounts.json b/erpnext/accounts/accounts_dashboard/accounts/accounts.json new file mode 100644 index 0000000000..2fab50e917 --- /dev/null +++ b/erpnext/accounts/accounts_dashboard/accounts/accounts.json @@ -0,0 +1,58 @@ +{ + "cards": [ + { + "card": "Total Outgoing Bills" + }, + { + "card": "Total Incoming Bills" + }, + { + "card": "Total Incoming Payment" + }, + { + "card": "Total Outgoing Payment" + } + ], + "charts": [ + { + "chart": "Profit and Loss", + "width": "Full" + }, + { + "chart": "Incoming Bills (Purchase Invoice)", + "width": "Half" + }, + { + "chart": "Outgoing Bills (Sales Invoice)", + "width": "Half" + }, + { + "chart": "Accounts Receivable Ageing", + "width": "Half" + }, + { + "chart": "Accounts Payable Ageing", + "width": "Half" + }, + { + "chart": "Budget Variance", + "width": "Full" + }, + { + "chart": "Bank Balance", + "width": "Full" + } + ], + "creation": "2020-07-17 11:25:34.796608", + "dashboard_name": "Accounts", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-22 13:07:34.540574", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts", + "owner": "Administrator" +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_chart/accounts_payable_ageing/accounts_payable_ageing.json b/erpnext/accounts/dashboard_chart/accounts_payable_ageing/accounts_payable_ageing.json new file mode 100644 index 0000000000..fb5ee64545 --- /dev/null +++ b/erpnext/accounts/dashboard_chart/accounts_payable_ageing/accounts_payable_ageing.json @@ -0,0 +1,23 @@ +{ + "chart_name": "Accounts Payable Ageing", + "chart_type": "Report", + "creation": "2020-07-17 11:25:34.564015", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}", + "filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 12:29:33.584419", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts Payable Ageing", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Accounts Payable", + "timeseries": 0, + "type": "Donut", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_chart/accounts_receivable_ageing/accounts_receivable_ageing.json b/erpnext/accounts/dashboard_chart/accounts_receivable_ageing/accounts_receivable_ageing.json new file mode 100644 index 0000000000..48ec781f68 --- /dev/null +++ b/erpnext/accounts/dashboard_chart/accounts_receivable_ageing/accounts_receivable_ageing.json @@ -0,0 +1,23 @@ +{ + "chart_name": "Accounts Receivable Ageing", + "chart_type": "Report", + "creation": "2020-07-17 11:25:34.535388", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}", + "filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0,\"show_future_payments\":0,\"show_delivery_notes\":0,\"show_sales_person\":0}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 12:28:42.743551", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts Receivable Ageing", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Accounts Receivable", + "timeseries": 0, + "type": "Donut", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_chart/bank_balance/bank_balance.json b/erpnext/accounts/dashboard_chart/bank_balance/bank_balance.json new file mode 100644 index 0000000000..6442c022c7 --- /dev/null +++ b/erpnext/accounts/dashboard_chart/bank_balance/bank_balance.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Bank Balance", + "chart_type": "Custom", + "creation": "2020-07-17 11:25:34.620221", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"account\":\"locals[\\\":Company\\\"][frappe.defaults.get_user_default(\\\"Company\\\")][\\\"default_bank_account\\\"]\"}", + "filters_json": "{}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 12:19:59.879476", + "modified": "2020-07-22 12:21:48.780513", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Balance", + "number_of_groups": 0, + "owner": "Administrator", + "source": "Account Balance Timeline", + "time_interval": "Quarterly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json new file mode 100644 index 0000000000..8631d3dc2a --- /dev/null +++ b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json @@ -0,0 +1,23 @@ +{ + "chart_name": "Budget Variance", + "chart_type": "Report", + "creation": "2020-07-17 11:25:34.593061", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 12:24:49.144210", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Budget Variance", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Budget Variance Report", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_chart/incoming_bills_(purchase_invoice)/incoming_bills_(purchase_invoice).json b/erpnext/accounts/dashboard_chart/incoming_bills_(purchase_invoice)/incoming_bills_(purchase_invoice).json new file mode 100644 index 0000000000..55f0d77f72 --- /dev/null +++ b/erpnext/accounts/dashboard_chart/incoming_bills_(purchase_invoice)/incoming_bills_(purchase_invoice).json @@ -0,0 +1,29 @@ +{ + "based_on": "posting_date", + "chart_name": "Incoming Bills (Purchase Invoice)", + "chart_type": "Sum", + "color": "#a83333", + "creation": "2020-07-17 11:25:34.479703", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Purchase Invoice", + "dynamic_filters_json": "", + "filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",1]]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-21 17:37:30.727306", + "modified": "2020-07-21 17:51:07.374917", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Incoming Bills (Purchase Invoice)", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "value_based_on": "base_net_total", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_chart/outgoing_bills_(sales_invoice)/outgoing_bills_(sales_invoice).json b/erpnext/accounts/dashboard_chart/outgoing_bills_(sales_invoice)/outgoing_bills_(sales_invoice).json new file mode 100644 index 0000000000..45de667d58 --- /dev/null +++ b/erpnext/accounts/dashboard_chart/outgoing_bills_(sales_invoice)/outgoing_bills_(sales_invoice).json @@ -0,0 +1,28 @@ +{ + "based_on": "posting_date", + "chart_name": "Outgoing Bills (Sales Invoice)", + "chart_type": "Sum", + "color": "#7b933d", + "creation": "2020-07-17 11:25:34.507547", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Sales Invoice", + "filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",1]]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-21 17:37:31.574666", + "modified": "2020-07-21 17:52:03.970530", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Outgoing Bills (Sales Invoice)", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "value_based_on": "base_net_total", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json new file mode 100644 index 0000000000..3fa995bbe1 --- /dev/null +++ b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json @@ -0,0 +1,23 @@ +{ + "chart_name": "Profit and Loss", + "chart_type": "Report", + "creation": "2020-07-17 11:25:34.448572", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 12:33:48.888943", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Profit and Loss", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Profit and Loss Statement", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py deleted file mode 100644 index b2abffc79d..0000000000 --- a/erpnext/accounts/dashboard_fixtures.py +++ /dev/null @@ -1,284 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import json -from frappe.utils import nowdate, add_months, get_date_str -from frappe import _ -from erpnext.accounts.utils import get_fiscal_year, get_account_name, FiscalYearError - -def _get_fiscal_year(date=None): - try: - fiscal_year = get_fiscal_year(date=nowdate(), as_dict=True) - return fiscal_year - - except FiscalYearError: - #if no fiscal year for current date then get default fiscal year - try: - fiscal_year = get_fiscal_year(as_dict=True) - return fiscal_year - - except FiscalYearError: - #if still no fiscal year found then no accounting data created, return - return None - -def get_company_for_dashboards(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company_list = frappe.get_list("Company") - if company_list: - return company_list[0].name - return None - -def get_data(): - - fiscal_year = _get_fiscal_year(nowdate()) - - if not fiscal_year: - return frappe._dict() - - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(fiscal_year), - "number_cards": get_number_cards(fiscal_year) - }) - -def get_dashboards(): - return [{ - "name": "Accounts", - "dashboard_name": "Accounts", - "doctype": "Dashboard", - "charts": [ - { "chart": "Profit and Loss" , "width": "Full"}, - { "chart": "Incoming Bills (Purchase Invoice)", "width": "Half"}, - { "chart": "Outgoing Bills (Sales Invoice)", "width": "Half"}, - { "chart": "Accounts Receivable Ageing", "width": "Half"}, - { "chart": "Accounts Payable Ageing", "width": "Half"}, - { "chart": "Budget Variance", "width": "Full"}, - { "chart": "Bank Balance", "width": "Full"} - ], - "cards": [ - {"card": "Total Outgoing Bills"}, - {"card": "Total Incoming Bills"}, - {"card": "Total Incoming Payment"}, - {"card": "Total Outgoing Payment"} - ] - }] - -def get_charts(fiscal_year): - company = frappe.get_doc("Company", get_company_for_dashboards()) - bank_account = company.default_bank_account or get_account_name("Bank", company=company.name) - default_cost_center = company.cost_center - - return [ - { - "doctype": "Dashboard Charts", - "name": "Profit and Loss", - "owner": "Administrator", - "report_name": "Profit and Loss Statement", - "filters_json": json.dumps({ - "company": company.name, - "filter_based_on": "Fiscal Year", - "from_fiscal_year": fiscal_year.get('name'), - "to_fiscal_year": fiscal_year.get('name'), - "periodicity": "Monthly", - "include_default_book_entries": 1 - }), - "type": "Bar", - 'timeseries': 0, - "chart_type": "Report", - "chart_name": _("Profit and Loss"), - "is_custom": 1, - "is_public": 1 - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Monthly", - "name": "Incoming Bills (Purchase Invoice)", - "chart_name": _("Incoming Bills (Purchase Invoice)"), - "timespan": "Last Year", - "color": "#a83333", - "value_based_on": "base_net_total", - "filters_json": json.dumps([["Purchase Invoice", "docstatus", "=", 1]]), - "chart_type": "Sum", - "timeseries": 1, - "based_on": "posting_date", - "owner": "Administrator", - "document_type": "Purchase Invoice", - "type": "Bar", - "width": "Half", - "is_public": 1 - }, - { - "doctype": "Dashboard Chart", - "name": "Outgoing Bills (Sales Invoice)", - "time_interval": "Monthly", - "chart_name": _("Outgoing Bills (Sales Invoice)"), - "timespan": "Last Year", - "color": "#7b933d", - "value_based_on": "base_net_total", - "filters_json": json.dumps([["Sales Invoice", "docstatus", "=", 1]]), - "chart_type": "Sum", - "timeseries": 1, - "based_on": "posting_date", - "owner": "Administrator", - "document_type": "Sales Invoice", - "type": "Bar", - "width": "Half", - "is_public": 1 - }, - { - "doctype": "Dashboard Charts", - "name": "Accounts Receivable Ageing", - "owner": "Administrator", - "report_name": "Accounts Receivable", - "filters_json": json.dumps({ - "company": company.name, - "report_date": nowdate(), - "ageing_based_on": "Due Date", - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120 - }), - "type": "Donut", - 'timeseries': 0, - "chart_type": "Report", - "chart_name": _("Accounts Receivable Ageing"), - "is_custom": 1, - "is_public": 1 - }, - { - "doctype": "Dashboard Charts", - "name": "Accounts Payable Ageing", - "owner": "Administrator", - "report_name": "Accounts Payable", - "filters_json": json.dumps({ - "company": company.name, - "report_date": nowdate(), - "ageing_based_on": "Due Date", - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120 - }), - "type": "Donut", - 'timeseries': 0, - "chart_type": "Report", - "chart_name": _("Accounts Payable Ageing"), - "is_custom": 1, - "is_public": 1 - }, - { - "doctype": "Dashboard Charts", - "name": "Budget Variance", - "owner": "Administrator", - "report_name": "Budget Variance Report", - "filters_json": json.dumps({ - "company": company.name, - "from_fiscal_year": fiscal_year.get('name'), - "to_fiscal_year": fiscal_year.get('name'), - "period": "Monthly", - "budget_against": "Cost Center" - }), - "type": "Bar", - "timeseries": 0, - "chart_type": "Report", - "chart_name": _("Budget Variance"), - "is_custom": 1, - "is_public": 1 - }, - { - "doctype": "Dashboard Charts", - "name": "Bank Balance", - "time_interval": "Quarterly", - "chart_name": "Bank Balance", - "timespan": "Last Year", - "filters_json": json.dumps({ - "company": company.name, - "account": bank_account - }), - "source": "Account Balance Timeline", - "chart_type": "Custom", - "timeseries": 1, - "owner": "Administrator", - "type": "Line", - "width": "Half", - "is_public": 1 - }, - ] - -def get_number_cards(fiscal_year): - - year_start_date = get_date_str(fiscal_year.get("year_start_date")) - year_end_date = get_date_str(fiscal_year.get("year_end_date")) - return [ - { - "doctype": "Number Card", - "document_type": "Payment Entry", - "name": "Total Incoming Payment", - "filters_json": json.dumps([ - ['Payment Entry', 'docstatus', '=', 1], - ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]], - ['Payment Entry', 'payment_type', '=', 'Receive'] - ]), - "label": _("Total Incoming Payment"), - "function": "Sum", - "aggregate_function_based_on": "base_received_amount", - "is_public": 1, - "is_custom": 1, - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - }, - { - "doctype": "Number Card", - "document_type": "Payment Entry", - "name": "Total Outgoing Payment", - "filters_json": json.dumps([ - ['Payment Entry', 'docstatus', '=', 1], - ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]], - ['Payment Entry', 'payment_type', '=', 'Pay'] - ]), - "label": _("Total Outgoing Payment"), - "function": "Sum", - "aggregate_function_based_on": "base_paid_amount", - "is_public": 1, - "is_custom": 1, - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - }, - { - "doctype": "Number Card", - "document_type": "Sales Invoice", - "name": "Total Outgoing Bills", - "filters_json": json.dumps([ - ['Sales Invoice', 'docstatus', '=', 1], - ['Sales Invoice', 'posting_date', 'between', [year_start_date, year_end_date]] - ]), - "label": _("Total Outgoing Bills"), - "function": "Sum", - "aggregate_function_based_on": "base_net_total", - "is_public": 1, - "is_custom": 1, - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - }, - { - "doctype": "Number Card", - "document_type": "Purchase Invoice", - "name": "Total Incoming Bills", - "filters_json": json.dumps([ - ['Purchase Invoice', 'docstatus', '=', 1], - ['Purchase Invoice', 'posting_date', 'between', [year_start_date, year_end_date]] - ]), - "label": _("Total Incoming Bills"), - "function": "Sum", - "aggregate_function_based_on": "base_net_total", - "is_public": 1, - "is_custom": 1, - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - } - ] diff --git a/erpnext/accounts/number_card/total_incoming_bills/total_incoming_bills.json b/erpnext/accounts/number_card/total_incoming_bills/total_incoming_bills.json new file mode 100644 index 0000000000..283e187b54 --- /dev/null +++ b/erpnext/accounts/number_card/total_incoming_bills/total_incoming_bills.json @@ -0,0 +1,21 @@ +{ + "aggregate_function_based_on": "base_net_total", + "creation": "2020-07-17 11:25:34.748329", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Purchase Invoice", + "filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Purchase Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Incoming Bills", + "modified": "2020-07-22 13:06:46.045344", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Total Incoming Bills", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/accounts/number_card/total_incoming_payment/total_incoming_payment.json b/erpnext/accounts/number_card/total_incoming_payment/total_incoming_payment.json new file mode 100644 index 0000000000..bc23c15b6a --- /dev/null +++ b/erpnext/accounts/number_card/total_incoming_payment/total_incoming_payment.json @@ -0,0 +1,21 @@ +{ + "aggregate_function_based_on": "base_received_amount", + "creation": "2020-07-17 11:25:34.673195", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Payment Entry", + "filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Receive\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Incoming Payment", + "modified": "2020-07-22 13:06:20.237689", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Total Incoming Payment", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/accounts/number_card/total_outgoing_bills/total_outgoing_bills.json b/erpnext/accounts/number_card/total_outgoing_bills/total_outgoing_bills.json new file mode 100644 index 0000000000..fe91618210 --- /dev/null +++ b/erpnext/accounts/number_card/total_outgoing_bills/total_outgoing_bills.json @@ -0,0 +1,21 @@ +{ + "aggregate_function_based_on": "base_net_total", + "creation": "2020-07-17 11:25:34.725416", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Sales Invoice", + "filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Sales Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Outgoing Bills", + "modified": "2020-07-22 13:07:19.633101", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Total Outgoing Bills", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/accounts/number_card/total_outgoing_payment/total_outgoing_payment.json b/erpnext/accounts/number_card/total_outgoing_payment/total_outgoing_payment.json new file mode 100644 index 0000000000..d27be88350 --- /dev/null +++ b/erpnext/accounts/number_card/total_outgoing_payment/total_outgoing_payment.json @@ -0,0 +1,21 @@ +{ + "aggregate_function_based_on": "base_paid_amount", + "creation": "2020-07-17 11:25:34.700137", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Payment Entry", + "filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Pay\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Outgoing Payment", + "modified": "2020-07-22 12:49:34.942896", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Total Outgoing Payment", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file From f50312b6d249ae68b743d75b060e852e732267d7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 22 Jul 2020 16:17:11 +0530 Subject: [PATCH 070/101] fix: button sizing (#22777) --- erpnext/templates/includes/footer/footer_extension.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/footer/footer_extension.html b/erpnext/templates/includes/footer/footer_extension.html index 8cf3081dec..6171b61e38 100644 --- a/erpnext/templates/includes/footer/footer_extension.html +++ b/erpnext/templates/includes/footer/footer_extension.html @@ -6,7 +6,7 @@ aria-label="{{ _('Your email address...') }}" aria-describedby="footer-subscribe-button">
-
From 53646cb360f239948dd80cfd9ffe5a64926dda05 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 22 Jul 2020 16:17:40 +0530 Subject: [PATCH 071/101] chore: Export Stock Dashboard (#22768) --- .../delivery_trends/delivery_trends.json | 27 +++ .../item_shortage_summary.json | 23 +++ .../oldest_items/oldest_items.json | 24 +++ .../purchase_receipt_trends.json | 27 +++ .../warehouse_wise_stock_value.json | 22 +++ erpnext/stock/dashboard_fixtures.py | 170 ------------------ .../total_active_items.json | 20 +++ .../total_stock_value/total_stock_value.json | 21 +++ .../total_warehouses/total_warehouses.json | 20 +++ .../stock/stock_dashboard/stock/stock.json | 47 +++++ 10 files changed, 231 insertions(+), 170 deletions(-) create mode 100644 erpnext/stock/dashboard_chart/delivery_trends/delivery_trends.json create mode 100644 erpnext/stock/dashboard_chart/item_shortage_summary/item_shortage_summary.json create mode 100644 erpnext/stock/dashboard_chart/oldest_items/oldest_items.json create mode 100644 erpnext/stock/dashboard_chart/purchase_receipt_trends/purchase_receipt_trends.json create mode 100644 erpnext/stock/dashboard_chart/warehouse_wise_stock_value/warehouse_wise_stock_value.json delete mode 100644 erpnext/stock/dashboard_fixtures.py create mode 100644 erpnext/stock/number_card/total_active_items/total_active_items.json create mode 100644 erpnext/stock/number_card/total_stock_value/total_stock_value.json create mode 100644 erpnext/stock/number_card/total_warehouses/total_warehouses.json create mode 100644 erpnext/stock/stock_dashboard/stock/stock.json diff --git a/erpnext/stock/dashboard_chart/delivery_trends/delivery_trends.json b/erpnext/stock/dashboard_chart/delivery_trends/delivery_trends.json new file mode 100644 index 0000000000..b3f6e35012 --- /dev/null +++ b/erpnext/stock/dashboard_chart/delivery_trends/delivery_trends.json @@ -0,0 +1,27 @@ +{ + "based_on": "posting_date", + "chart_name": "Delivery Trends", + "chart_type": "Sum", + "color": "#4d4da8", + "creation": "2020-07-20 21:01:04.255291", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Delivery Note", + "filters_json": "[[\"Delivery Note\",\"docstatus\",\"=\",1]]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 13:03:24.937045", + "modified_by": "Administrator", + "module": "Stock", + "name": "Delivery Trends", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "value_based_on": "base_net_total", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/stock/dashboard_chart/item_shortage_summary/item_shortage_summary.json b/erpnext/stock/dashboard_chart/item_shortage_summary/item_shortage_summary.json new file mode 100644 index 0000000000..ce711247e7 --- /dev/null +++ b/erpnext/stock/dashboard_chart/item_shortage_summary/item_shortage_summary.json @@ -0,0 +1,23 @@ +{ + "chart_name": "Item Shortage Summary", + "chart_type": "Report", + "creation": "2020-07-20 21:01:04.383451", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", + "filters_json": "{}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 13:07:01.905334", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item Shortage Summary", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Item Shortage Report", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json new file mode 100644 index 0000000000..6da3b28baf --- /dev/null +++ b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Oldest Items", + "chart_type": "Report", + "creation": "2020-07-20 21:01:04.336845", + "custom_options": "{\"colors\": [\"#5e64ff\"]}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\"}", + "filters_json": "{\"show_warehouse_wise_stock\":0}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 13:04:36.271198", + "modified_by": "Administrator", + "module": "Stock", + "name": "Oldest Items", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Stock Ageing", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/stock/dashboard_chart/purchase_receipt_trends/purchase_receipt_trends.json b/erpnext/stock/dashboard_chart/purchase_receipt_trends/purchase_receipt_trends.json new file mode 100644 index 0000000000..584a6cc867 --- /dev/null +++ b/erpnext/stock/dashboard_chart/purchase_receipt_trends/purchase_receipt_trends.json @@ -0,0 +1,27 @@ +{ + "based_on": "posting_date", + "chart_name": "Purchase Receipt Trends", + "chart_type": "Sum", + "color": "#78d6ff", + "creation": "2020-07-20 21:01:04.205230", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Purchase Receipt", + "filters_json": "[[\"Purchase Receipt\",\"docstatus\",\"=\",1]]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 13:05:25.923130", + "modified_by": "Administrator", + "module": "Stock", + "name": "Purchase Receipt Trends", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "value_based_on": "base_net_total", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/stock/dashboard_chart/warehouse_wise_stock_value/warehouse_wise_stock_value.json b/erpnext/stock/dashboard_chart/warehouse_wise_stock_value/warehouse_wise_stock_value.json new file mode 100644 index 0000000000..a07b55382c --- /dev/null +++ b/erpnext/stock/dashboard_chart/warehouse_wise_stock_value/warehouse_wise_stock_value.json @@ -0,0 +1,22 @@ +{ + "chart_name": "Warehouse wise Stock Value", + "chart_type": "Custom", + "creation": "2020-07-20 21:01:04.296157", + "docstatus": 0, + "doctype": "Dashboard Chart", + "filters_json": "{}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 13:01:01.815123", + "modified_by": "Administrator", + "module": "Stock", + "name": "Warehouse wise Stock Value", + "number_of_groups": 0, + "owner": "Administrator", + "source": "Warehouse wise Stock Value", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/stock/dashboard_fixtures.py b/erpnext/stock/dashboard_fixtures.py deleted file mode 100644 index 7625b1ad28..0000000000 --- a/erpnext/stock/dashboard_fixtures.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import json -from frappe import _ -from frappe.utils import nowdate -from erpnext.accounts.dashboard_fixtures import _get_fiscal_year -from erpnext.buying.dashboard_fixtures import get_company_for_dashboards - -def get_data(): - fiscal_year = _get_fiscal_year(nowdate()) - - if not fiscal_year: - return frappe._dict() - - company = frappe.get_doc("Company", get_company_for_dashboards()) - fiscal_year_name = fiscal_year.get("name") - start_date = str(fiscal_year.get("year_start_date")) - end_date = str(fiscal_year.get("year_end_date")) - - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(company, fiscal_year_name, start_date, end_date), - "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date), - }) - -def get_dashboards(): - return [{ - "name": "Stock", - "dashboard_name": "Stock", - "charts": [ - { "chart": "Warehouse wise Stock Value", "width": "Full"}, - { "chart": "Purchase Receipt Trends", "width": "Half"}, - { "chart": "Delivery Trends", "width": "Half"}, - { "chart": "Oldest Items", "width": "Half"}, - { "chart": "Item Shortage Summary", "width": "Half"} - ], - "cards": [ - { "card": "Total Active Items"}, - { "card": "Total Warehouses"}, - { "card": "Total Stock Value"} - ] - }] - -def get_charts(company, fiscal_year_name, start_date, end_date): - return [ - { - "doctype": "Dashboard Chart", - "name": "Purchase Receipt Trends", - "time_interval": "Monthly", - "chart_name": _("Purchase Receipt Trends"), - "timespan": "Last Year", - "color": "#7b933d", - "value_based_on": "base_net_total", - "filters_json": json.dumps([["Purchase Receipt", "docstatus", "=", 1]]), - "chart_type": "Sum", - "timeseries": 1, - "based_on": "posting_date", - "owner": "Administrator", - "document_type": "Purchase Receipt", - "type": "Bar", - "width": "Half", - "is_public": 1 - }, - { - "doctype": "Dashboard Chart", - "name": "Delivery Trends", - "time_interval": "Monthly", - "chart_name": _("Delivery Trends"), - "timespan": "Last Year", - "color": "#7b933d", - "value_based_on": "base_net_total", - "filters_json": json.dumps([["Delivery Note", "docstatus", "=", 1]]), - "chart_type": "Sum", - "timeseries": 1, - "based_on": "posting_date", - "owner": "Administrator", - "document_type": "Delivery Note", - "type": "Bar", - "width": "Half", - "is_public": 1 - }, - { - "name": "Warehouse wise Stock Value", - "chart_name": _("Warehouse wise Stock Value"), - "chart_type": "Custom", - "doctype": "Dashboard Chart", - "filters_json": json.dumps({}), - "is_custom": 0, - "is_public": 1, - "owner": "Administrator", - "source": "Warehouse wise Stock Value", - "type": "Bar" - }, - { - "name": "Oldest Items", - "chart_name": _("Oldest Items"), - "chart_type": "Report", - "custom_options": json.dumps({ - "colors": ["#5e64ff"] - }), - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "to_date": nowdate(), - "show_warehouse_wise_stock": 0 - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Stock Ageing", - "type": "Bar" - }, - { - "name": "Item Shortage Summary", - "chart_name": _("Item Shortage Summary"), - "chart_type": "Report", - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Item Shortage Report", - "type": "Bar" - } - ] - -def get_number_cards(company, fiscal_year_name, start_date, end_date): - return [ - { - "name": "Total Active Items", - "label": _("Total Active Items"), - "function": "Count", - "doctype": "Number Card", - "document_type": "Item", - "filters_json": json.dumps([["Item", "disabled", "=", 0]]), - "is_public": 1, - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - }, - { - "name": "Total Warehouses", - "label": _("Total Warehouses"), - "function": "Count", - "doctype": "Number Card", - "document_type": "Warehouse", - "filters_json": json.dumps([["Warehouse", "disabled", "=", 0]]), - "is_public": 1, - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - }, - { - "name": "Total Stock Value", - "label": _("Total Stock Value"), - "function": "Sum", - "aggregate_function_based_on": "stock_value", - "doctype": "Number Card", - "document_type": "Bin", - "filters_json": json.dumps([]), - "is_public": 1, - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - } - ] \ No newline at end of file diff --git a/erpnext/stock/number_card/total_active_items/total_active_items.json b/erpnext/stock/number_card/total_active_items/total_active_items.json new file mode 100644 index 0000000000..f6863b96d7 --- /dev/null +++ b/erpnext/stock/number_card/total_active_items/total_active_items.json @@ -0,0 +1,20 @@ +{ + "creation": "2020-07-20 21:01:04.422436", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Item", + "filters_json": "[[\"Item\",\"disabled\",\"=\",0]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Active Items", + "modified": "2020-07-22 13:08:30.430677", + "modified_by": "Administrator", + "module": "Stock", + "name": "Total Active Items", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/stock/number_card/total_stock_value/total_stock_value.json b/erpnext/stock/number_card/total_stock_value/total_stock_value.json new file mode 100644 index 0000000000..8e480a6b3e --- /dev/null +++ b/erpnext/stock/number_card/total_stock_value/total_stock_value.json @@ -0,0 +1,21 @@ +{ + "aggregate_function_based_on": "stock_value", + "creation": "2020-07-20 21:01:04.495481", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Bin", + "filters_json": "[]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Stock Value", + "modified": "2020-07-22 13:08:48.412001", + "modified_by": "Administrator", + "module": "Stock", + "name": "Total Stock Value", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/stock/number_card/total_warehouses/total_warehouses.json b/erpnext/stock/number_card/total_warehouses/total_warehouses.json new file mode 100644 index 0000000000..ab0836a3af --- /dev/null +++ b/erpnext/stock/number_card/total_warehouses/total_warehouses.json @@ -0,0 +1,20 @@ +{ + "creation": "2020-07-20 21:01:04.457598", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Warehouse", + "filters_json": "[[\"Warehouse\",\"disabled\",\"=\",0]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Warehouses", + "modified": "2020-07-22 13:08:40.258927", + "modified_by": "Administrator", + "module": "Stock", + "name": "Total Warehouses", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/stock/stock_dashboard/stock/stock.json b/erpnext/stock/stock_dashboard/stock/stock.json new file mode 100644 index 0000000000..dee7fed6c2 --- /dev/null +++ b/erpnext/stock/stock_dashboard/stock/stock.json @@ -0,0 +1,47 @@ +{ + "cards": [ + { + "card": "Total Active Items" + }, + { + "card": "Total Warehouses" + }, + { + "card": "Total Stock Value" + } + ], + "charts": [ + { + "chart": "Warehouse wise Stock Value", + "width": "Full" + }, + { + "chart": "Purchase Receipt Trends", + "width": "Half" + }, + { + "chart": "Delivery Trends", + "width": "Half" + }, + { + "chart": "Oldest Items", + "width": "Half" + }, + { + "chart": "Item Shortage Summary", + "width": "Half" + } + ], + "creation": "2020-07-20 21:01:04.549136", + "dashboard_name": "Stock", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 1, + "is_standard": 1, + "modified": "2020-07-22 13:09:33.096694", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock", + "owner": "Administrator" +} \ No newline at end of file From 514366654ecae413b7d72afabef3bff58cea3218 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 22 Jul 2020 16:40:24 +0530 Subject: [PATCH 072/101] fix: Serial No Rename does not affect Stock Ledger Entry (#22746) * Revert "fix: Remove rename related code from Serial No (#22627)" This reverts commit 1ec2d962dbcc7ccee30e0d0a35727aa890910203. * fix: Rename fails on Stock Ledger Entry * fix: Allow Rename in Serial No --- erpnext/stock/doctype/serial_no/serial_no.json | 3 ++- erpnext/stock/doctype/serial_no/serial_no.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index 2be14c8006..3acf3a9316 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -1,6 +1,7 @@ { "actions": [], "allow_import": 1, + "allow_rename": 1, "autoname": "field:serial_no", "creation": "2013-05-16 10:59:15", "description": "Distinct unit of an Item", @@ -426,7 +427,7 @@ "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2020-06-25 15:53:50.900855", + "modified": "2020-07-20 20:50:16.660433", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 90f0f5881d..153ce2fb67 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -190,6 +190,23 @@ class SerialNo(StockController): if sle_exists: frappe.throw(_("Cannot delete Serial No {0}, as it is used in stock transactions").format(self.name)) + def before_rename(self, old, new, merge=False): + if merge: + frappe.throw(_("Sorry, Serial Nos cannot be merged")) + + def after_rename(self, old, new, merge=False): + """rename serial_no text fields""" + for dt in frappe.db.sql("""select parent from tabDocField + where fieldname='serial_no' and fieldtype in ('Text', 'Small Text', 'Long Text')"""): + + for item in frappe.db.sql("""select name, serial_no from `tab%s` + where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))): + + serial_nos = map(lambda i: new if i.upper()==old.upper() else i, item[1].split('\n')) + frappe.db.sql("""update `tab%s` set serial_no = %s + where name=%s""" % (dt[0], '%s', '%s'), + ('\n'.join(list(serial_nos)), item[0])) + def update_serial_no_reference(self, serial_no=None): last_sle = self.get_last_sle(serial_no) self.set_purchase_details(last_sle.get("purchase_sle")) From 3d3c922d7fdd7a5e91b13e9ba87f74402ea9499d Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Wed, 22 Jul 2020 16:42:31 +0530 Subject: [PATCH 073/101] fix(patch): handle duplicate entry error while inserting account (#22745) --- .../v12_0/move_item_tax_to_item_tax_template.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 8889056e2d..06331d7ff7 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -100,8 +100,10 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp tax_type = None else: company = get_company(parts[-1], parenttype, parent) - parent_account = frappe.db.get_value("Account", - filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account") + parent_account = frappe.get_value("Account", {"account_name": account_name, "company": company}, "parent_account") + if not parent_account: + parent_account = frappe.db.get_value("Account", + filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account") if not parent_account: parent_account = frappe.db.get_value("Account", filters={"account_type": "Tax", "root_type": "Liability", "is_group": 1, "company": company}) @@ -115,8 +117,11 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp if not tax_type: account = frappe.new_doc("Account") account.update(filters) - account.insert() - tax_type = account.name + try: + account.insert() + tax_type = account.name + except frappe.DuplicateEntryError: + tax_type = frappe.db.get_value("Account", {"account_name": account_name, "company": company}, "name") account_type = frappe.get_cached_value("Account", tax_type, "account_type") From f098221550bdcf6a6d329022a2a22219f396bd6d Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Wed, 22 Jul 2020 16:58:17 +0530 Subject: [PATCH 074/101] feat: Exported manufacturing dashboard (#22626) * feat: exported Manufacturing Dashboard * fix: update use_report_chart fieldname * fix: add dynamic filter for company in Produced Quantity chart --- .../completed_operation.json | 28 +++++++++ .../job_card_analysis/job_card_analysis.json | 26 ++++++++ .../last_month_downtime_analysis.json | 26 ++++++++ .../pending_work_order.json | 26 ++++++++ .../produced_quantity/produced_quantity.json | 30 +++++++++ .../quality_inspection_analysis.json | 25 ++++++++ .../work_order_analysis.json | 26 ++++++++ .../work_order_qty_analysis.json | 26 ++++++++ .../manufacturing/manufacturing.json | 62 +++++++++++++++++++ .../monthly_completed_work_order.json | 19 ++++++ .../monthly_quality_inspection.json | 19 ++++++ .../monthly_total_work_order.json | 19 ++++++ .../ongoing_job_card/ongoing_job_card.json | 19 ++++++ 13 files changed, 351 insertions(+) create mode 100644 erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json create mode 100644 erpnext/manufacturing/dashboard_chart/job_card_analysis/job_card_analysis.json create mode 100644 erpnext/manufacturing/dashboard_chart/last_month_downtime_analysis/last_month_downtime_analysis.json create mode 100644 erpnext/manufacturing/dashboard_chart/pending_work_order/pending_work_order.json create mode 100644 erpnext/manufacturing/dashboard_chart/produced_quantity/produced_quantity.json create mode 100644 erpnext/manufacturing/dashboard_chart/quality_inspection_analysis/quality_inspection_analysis.json create mode 100644 erpnext/manufacturing/dashboard_chart/work_order_analysis/work_order_analysis.json create mode 100644 erpnext/manufacturing/dashboard_chart/work_order_qty_analysis/work_order_qty_analysis.json create mode 100644 erpnext/manufacturing/manufacturing_dashboard/manufacturing/manufacturing.json create mode 100644 erpnext/manufacturing/number_card/monthly_completed_work_order/monthly_completed_work_order.json create mode 100644 erpnext/manufacturing/number_card/monthly_quality_inspection/monthly_quality_inspection.json create mode 100644 erpnext/manufacturing/number_card/monthly_total_work_order/monthly_total_work_order.json create mode 100644 erpnext/manufacturing/number_card/ongoing_job_card/ongoing_job_card.json diff --git a/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json b/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json new file mode 100644 index 0000000000..d74ae2faf4 --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json @@ -0,0 +1,28 @@ +{ + "based_on": "creation", + "chart_name": "Completed Operation", + "chart_type": "Sum", + "creation": "2020-07-08 22:40:22.441658", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Work Order Operation", + "filters_json": "[[\"Work Order Operation\",\"docstatus\",\"=\",1,false]]", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-21 16:57:09.767009", + "modified": "2020-07-21 16:57:55.719802", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Completed Operation", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Quarterly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "completed_qty", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/dashboard_chart/job_card_analysis/job_card_analysis.json b/erpnext/manufacturing/dashboard_chart/job_card_analysis/job_card_analysis.json new file mode 100644 index 0000000000..e3cbba6e81 --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/job_card_analysis/job_card_analysis.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Job Card Analysis", + "chart_type": "Report", + "creation": "2020-07-08 22:40:22.549096", + "custom_options": "{\"barOptions\": {\"stacked\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}", + "filters_json": "{\"docstatus\":1,\"range\":\"Monthly\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-21 17:47:06.537924", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Job Card Summary", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/dashboard_chart/last_month_downtime_analysis/last_month_downtime_analysis.json b/erpnext/manufacturing/dashboard_chart/last_month_downtime_analysis/last_month_downtime_analysis.json new file mode 100644 index 0000000000..46d2215a00 --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/last_month_downtime_analysis/last_month_downtime_analysis.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Last Month Downtime Analysis", + "chart_type": "Report", + "creation": "2020-07-08 22:40:22.516460", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{}", + "filters_json": "{\"from_date\":\"2020-06-21 00:00:00\",\"to_date\":\"2020-07-21 18:46:45\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-21 18:46:50.767333", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Last Month Downtime Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Downtime Analysis", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/dashboard_chart/pending_work_order/pending_work_order.json b/erpnext/manufacturing/dashboard_chart/pending_work_order/pending_work_order.json new file mode 100644 index 0000000000..91cd12b366 --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/pending_work_order/pending_work_order.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Pending Work Order", + "chart_type": "Report", + "creation": "2020-07-08 22:40:22.499217", + "custom_options": "{\"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"height\": 300}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}", + "filters_json": "{\"charts_based_on\":\"Age\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-21 17:46:42.917598", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Pending Work Order", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Work Order Summary", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Donut", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/dashboard_chart/produced_quantity/produced_quantity.json b/erpnext/manufacturing/dashboard_chart/produced_quantity/produced_quantity.json new file mode 100644 index 0000000000..ba1a29d25b --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/produced_quantity/produced_quantity.json @@ -0,0 +1,30 @@ +{ + "based_on": "modified", + "chart_name": "Produced Quantity", + "chart_type": "Sum", + "creation": "2020-07-08 22:40:22.416285", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Work Order", + "dynamic_filters_json": "[[\"Work Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Work Order\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-21 17:46:34.058882", + "modified": "2020-07-21 17:54:11.233531", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Produced Quantity", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "produced_qty", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/dashboard_chart/quality_inspection_analysis/quality_inspection_analysis.json b/erpnext/manufacturing/dashboard_chart/quality_inspection_analysis/quality_inspection_analysis.json new file mode 100644 index 0000000000..8388f3d72b --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/quality_inspection_analysis/quality_inspection_analysis.json @@ -0,0 +1,25 @@ +{ + "chart_name": "Quality Inspection Analysis", + "chart_type": "Report", + "creation": "2020-07-08 22:40:22.483617", + "custom_options": "{\"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"height\": 300}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "filters_json": "{\"from_date\":\"2019-07-09\",\"to_date\":\"2020-07-09\"}", + "idx": 0, + "use_report_chart": 1, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-09 12:15:51.564487", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Quality Inspection Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Quality Inspection Summary", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Donut", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/dashboard_chart/work_order_analysis/work_order_analysis.json b/erpnext/manufacturing/dashboard_chart/work_order_analysis/work_order_analysis.json new file mode 100644 index 0000000000..879826a7ad --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/work_order_analysis/work_order_analysis.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Work Order Analysis", + "chart_type": "Report", + "creation": "2020-07-08 22:40:22.465459", + "custom_options": "{\"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"height\": 300}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}", + "filters_json": "{\"charts_based_on\":\"Status\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-21 17:50:23.806007", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Work Order Summary", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Donut", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/dashboard_chart/work_order_qty_analysis/work_order_qty_analysis.json b/erpnext/manufacturing/dashboard_chart/work_order_qty_analysis/work_order_qty_analysis.json new file mode 100644 index 0000000000..93572799d6 --- /dev/null +++ b/erpnext/manufacturing/dashboard_chart/work_order_qty_analysis/work_order_qty_analysis.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Work Order Qty Analysis", + "chart_type": "Report", + "creation": "2020-07-08 22:40:22.532889", + "custom_options": "{\"barOptions\": {\"stacked\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}", + "filters_json": "{\"charts_based_on\":\"Quantity\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-21 17:46:59.020709", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Qty Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Work Order Summary", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/manufacturing/manufacturing_dashboard/manufacturing/manufacturing.json b/erpnext/manufacturing/manufacturing_dashboard/manufacturing/manufacturing.json new file mode 100644 index 0000000000..314efe7a80 --- /dev/null +++ b/erpnext/manufacturing/manufacturing_dashboard/manufacturing/manufacturing.json @@ -0,0 +1,62 @@ +{ + "cards": [ + { + "card": "Monthly Total Work Order" + }, + { + "card": "Monthly Completed Work Order" + }, + { + "card": "Ongoing Job Card" + }, + { + "card": "Monthly Quality Inspection" + } + ], + "charts": [ + { + "chart": "Produced Quantity", + "width": "Half" + }, + { + "chart": "Completed Operation", + "width": "Half" + }, + { + "chart": "Work Order Analysis", + "width": "Half" + }, + { + "chart": "Quality Inspection Analysis", + "width": "Half" + }, + { + "chart": "Pending Work Order", + "width": "Half" + }, + { + "chart": "Last Month Downtime Analysis", + "width": "Half" + }, + { + "chart": "Work Order Qty Analysis", + "width": "Full" + }, + { + "chart": "Job Card Analysis", + "width": "Full" + } + ], + "creation": "2020-07-08 22:40:22.626607", + "dashboard_name": "Manufacturing", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-09 12:39:39.455039", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Manufacturing", + "owner": "Administrator" +} \ No newline at end of file diff --git a/erpnext/manufacturing/number_card/monthly_completed_work_order/monthly_completed_work_order.json b/erpnext/manufacturing/number_card/monthly_completed_work_order/monthly_completed_work_order.json new file mode 100644 index 0000000000..36c0b9ae75 --- /dev/null +++ b/erpnext/manufacturing/number_card/monthly_completed_work_order/monthly_completed_work_order.json @@ -0,0 +1,19 @@ +{ + "creation": "2020-07-08 22:40:22.575086", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Work Order", + "filters_json": "[[\"Work Order\",\"status\",\"=\",\"Completed\"],[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Monthly Completed Work Orders", + "modified": "2020-07-09 12:22:54.809813", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Monthly Completed Work Order", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly" +} \ No newline at end of file diff --git a/erpnext/manufacturing/number_card/monthly_quality_inspection/monthly_quality_inspection.json b/erpnext/manufacturing/number_card/monthly_quality_inspection/monthly_quality_inspection.json new file mode 100644 index 0000000000..91a45365c0 --- /dev/null +++ b/erpnext/manufacturing/number_card/monthly_quality_inspection/monthly_quality_inspection.json @@ -0,0 +1,19 @@ +{ + "creation": "2020-07-08 22:40:22.606867", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Quality Inspection", + "filters_json": "[[\"Quality Inspection\",\"docstatus\",\"=\",1],[\"Quality Inspection\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Monthly Quality Inspections", + "modified": "2020-07-09 12:23:34.838154", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Monthly Quality Inspection", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly" +} \ No newline at end of file diff --git a/erpnext/manufacturing/number_card/monthly_total_work_order/monthly_total_work_order.json b/erpnext/manufacturing/number_card/monthly_total_work_order/monthly_total_work_order.json new file mode 100644 index 0000000000..80d3b1520a --- /dev/null +++ b/erpnext/manufacturing/number_card/monthly_total_work_order/monthly_total_work_order.json @@ -0,0 +1,19 @@ +{ + "creation": "2020-07-08 22:40:22.562715", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Work Order", + "filters_json": "[[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Monthly Total Work Orders", + "modified": "2020-07-09 12:22:25.698795", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Monthly Total Work Order", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly" +} \ No newline at end of file diff --git a/erpnext/manufacturing/number_card/ongoing_job_card/ongoing_job_card.json b/erpnext/manufacturing/number_card/ongoing_job_card/ongoing_job_card.json new file mode 100644 index 0000000000..ba23ff3453 --- /dev/null +++ b/erpnext/manufacturing/number_card/ongoing_job_card/ongoing_job_card.json @@ -0,0 +1,19 @@ +{ + "creation": "2020-07-08 22:40:22.592042", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Job Card", + "filters_json": "[[\"Job Card\",\"status\",\"!=\",\"Completed\"],[\"Job Card\",\"docstatus\",\"=\",1]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Ongoing Job Cards", + "modified": "2020-07-09 12:23:18.218233", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Ongoing Job Card", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly" +} \ No newline at end of file From c7eadfceb0e794b070d2b22e4f70d7ac2edd5612 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 22 Jul 2020 17:59:37 +0530 Subject: [PATCH 075/101] fix: Multi currency payment reconciliation (#22738) * fix: Multi currency payment reconciliation * fix: Hide currency link fields --- .../payment_reconciliation.js | 4 + .../payment_reconciliation.py | 10 +- .../payment_reconciliation_invoice.json | 243 +++++------------- .../payment_reconciliation_payment.json | 18 +- .../sales_invoice_item.json | 2 +- erpnext/accounts/utils.py | 6 +- erpnext/controllers/accounts_controller.py | 12 +- 7 files changed, 110 insertions(+), 185 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index d3992d5111..355fe96c96 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -73,6 +73,10 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }; } }); + + this.frm.set_value('party_type', ''); + this.frm.set_value('party', ''); + this.frm.set_value('receivable_payable_account', ''); }, refresh: function() { diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 35d8d34c51..2f8b634664 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -48,7 +48,8 @@ class PaymentReconciliation(Document): select "Journal Entry" as reference_type, t1.name as reference_name, t1.posting_date, t1.remark as remarks, t2.name as reference_row, - {dr_or_cr} as amount, t2.is_advance + {dr_or_cr} as amount, t2.is_advance, + t2.account_currency as currency from `tabJournal Entry` t1, `tabJournal Entry Account` t2 where @@ -88,7 +89,8 @@ class PaymentReconciliation(Document): if self.party_type == 'Customer' else "Purchase Invoice") return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type, - (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount + (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount, + account_currency as currency FROM `tab{doc}`, `tabGL Entry` WHERE (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) @@ -141,6 +143,7 @@ class PaymentReconciliation(Document): ent.invoice_number = e.get('voucher_no') ent.invoice_date = e.get('posting_date') ent.amount = flt(e.get('invoice_amount')) + ent.currency = e.get('currency') ent.outstanding_amount = e.get('outstanding_amount') def reconcile(self, args): @@ -269,11 +272,14 @@ def reconcile_dr_cr_note(dr_cr_notes, company): reconcile_dr_or_cr = ('debit_in_account_currency' if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency') + company_currency = erpnext.get_company_currency(company) + jv = frappe.get_doc({ "doctype": "Journal Entry", "voucher_type": voucher_type, "posting_date": today(), "company": company, + "multi_currency": 1 if d.currency != company_currency else 0, "accounts": [ { 'account': d.account, diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json index ce7ce98edb..6a79a85c34 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json +++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json @@ -1,183 +1,80 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-07-09 16:14:23.672922", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2014-07-09 16:14:23.672922", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "invoice_type", + "invoice_number", + "invoice_date", + "col_break1", + "amount", + "outstanding_amount", + "currency" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "invoice_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Invoice Type", - "length": 0, - "no_copy": 0, - "options": "Sales Invoice\nPurchase Invoice\nJournal Entry", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "invoice_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Invoice Type", + "options": "Sales Invoice\nPurchase Invoice\nJournal Entry", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "invoice_number", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Invoice Number", - "length": 0, - "no_copy": 0, - "options": "invoice_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "invoice_number", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Invoice Number", + "options": "invoice_type", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "invoice_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Invoice Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "invoice_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Invoice Date", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "outstanding_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Outstanding Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Outstanding Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-07-11 03:28:03.588476", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Payment Reconciliation Invoice", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-07-19 18:12:27.964073", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Reconciliation Invoice", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json index 018bfd028a..925a6f10a5 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json @@ -1,7 +1,9 @@ { + "actions": [], "creation": "2014-07-09 16:13:35.452759", "doctype": "DocType", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "reference_type", "reference_name", @@ -16,7 +18,8 @@ "difference_account", "difference_amount", "sec_break1", - "remark" + "remark", + "currency" ], "fields": [ { @@ -73,6 +76,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Amount", + "options": "currency", "read_only": 1 }, { @@ -81,6 +85,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Allocated amount", + "options": "currency", "reqd": 1 }, { @@ -106,16 +111,25 @@ "fieldname": "difference_amount", "fieldtype": "Currency", "label": "Difference Amount", + "options": "currency", "print_hide": 1, "read_only": 1 }, { "fieldname": "section_break_10", "fieldtype": "Section Break" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency" } ], "istable": 1, - "modified": "2019-06-24 00:08:11.150796", + "links": [], + "modified": "2020-07-19 18:12:41.682347", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Payment", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 9bc24664d1..004d358ef9 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -795,7 +795,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-11 12:24:41.749986", + "modified": "2020-07-18 12:24:41.749986", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 013c30d6ff..824b2f2efb 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -676,7 +676,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters invoice_list = frappe.db.sql(""" select voucher_no, voucher_type, posting_date, due_date, - ifnull(sum({dr_or_cr}), 0) as invoice_amount + ifnull(sum({dr_or_cr}), 0) as invoice_amount, + account_currency as currency from `tabGL Entry` where @@ -733,7 +734,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters 'invoice_amount': flt(d.invoice_amount), 'payment_amount': payment_amount, 'outstanding_amount': outstanding_amount, - 'due_date': d.due_date + 'due_date': d.due_date, + 'currency': d.currency }) ) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ead503ed09..89c38c710b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1014,6 +1014,7 @@ def get_advance_journal_entries(party_type, party, party_account, amount_field, def get_advance_payment_entries(party_type, party, party_account, order_doctype, order_list=None, include_unallocated=True, against_all_orders=False, limit=None): party_account_field = "paid_from" if party_type == "Customer" else "paid_to" + currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency" payment_type = "Receive" if party_type == "Customer" else "Pay" payment_entries_against_order, unallocated_payment_entries = [], [] limit_cond = "limit %s" % limit if limit else "" @@ -1030,14 +1031,15 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, select "Payment Entry" as reference_type, t1.name as reference_name, t1.remarks, t2.allocated_amount as amount, t2.name as reference_row, - t2.reference_name as against_order, t1.posting_date + t2.reference_name as against_order, t1.posting_date, + t1.{0} as currency from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 where - t1.name = t2.parent and t1.{0} = %s and t1.payment_type = %s + t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 - and t2.reference_doctype = %s {1} - order by t1.posting_date {2} - """.format(party_account_field, reference_condition, limit_cond), + and t2.reference_doctype = %s {2} + order by t1.posting_date {3} + """.format(currency_field, party_account_field, reference_condition, limit_cond), [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1) From 93afbe143b0bd6f2b678366f5060ef3ffde40333 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 22 Jul 2020 18:07:39 +0530 Subject: [PATCH 076/101] feat: Export Selling Dashboard (#22776) * feat:Export Stock Dashboard * Export Selling Dashboard * Export Selling Dashboard --- .../item_wise_annual_sales.json | 24 +++ .../sales_order_analysis.json | 24 +++ .../sales_order_trends.json | 24 +++ .../top_customers/top_customers.json | 24 +++ erpnext/selling/dashboard_fixtures.py | 198 ------------------ .../active_customers/active_customers.json | 21 ++ .../annual_sales/annual_sales.json | 22 ++ .../sales_orders_to_bill.json | 21 ++ .../sales_orders_to_deliver.json | 21 ++ .../selling_dashboard/selling/selling.json | 46 ++++ 10 files changed, 227 insertions(+), 198 deletions(-) create mode 100644 erpnext/selling/dashboard_chart/item_wise_annual_sales/item_wise_annual_sales.json create mode 100644 erpnext/selling/dashboard_chart/sales_order_analysis/sales_order_analysis.json create mode 100644 erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json create mode 100644 erpnext/selling/dashboard_chart/top_customers/top_customers.json delete mode 100644 erpnext/selling/dashboard_fixtures.py create mode 100644 erpnext/selling/number_card/active_customers/active_customers.json create mode 100644 erpnext/selling/number_card/annual_sales/annual_sales.json create mode 100644 erpnext/selling/number_card/sales_orders_to_bill/sales_orders_to_bill.json create mode 100644 erpnext/selling/number_card/sales_orders_to_deliver/sales_orders_to_deliver.json create mode 100644 erpnext/selling/selling_dashboard/selling/selling.json diff --git a/erpnext/selling/dashboard_chart/item_wise_annual_sales/item_wise_annual_sales.json b/erpnext/selling/dashboard_chart/item_wise_annual_sales/item_wise_annual_sales.json new file mode 100644 index 0000000000..290e526f11 --- /dev/null +++ b/erpnext/selling/dashboard_chart/item_wise_annual_sales/item_wise_annual_sales.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Item-wise Annual Sales", + "chart_type": "Report", + "creation": "2020-07-20 20:17:16.474566", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\"}", + "filters_json": "{\"from_date\":\"2020-06-22\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 14:42:25.512675", + "modified_by": "Administrator", + "module": "Selling", + "name": "Item-wise Annual Sales", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Item-wise Sales History", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/selling/dashboard_chart/sales_order_analysis/sales_order_analysis.json b/erpnext/selling/dashboard_chart/sales_order_analysis/sales_order_analysis.json new file mode 100644 index 0000000000..5e1a0d9258 --- /dev/null +++ b/erpnext/selling/dashboard_chart/sales_order_analysis/sales_order_analysis.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Sales Order Analysis", + "chart_type": "Report", + "creation": "2020-07-20 20:17:16.440393", + "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\"}", + "filters_json": "{\"status\":[\"To Bill\",\"To Deliver\"],\"group_by_so\":0,\"from_date\":\"2020-06-22\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 17:06:05.750660", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Order Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Sales Order Analysis", + "timeseries": 0, + "type": "Donut", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json new file mode 100644 index 0000000000..914d915d94 --- /dev/null +++ b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Sales Order Trends", + "chart_type": "Report", + "creation": "2020-07-20 20:17:16.508240", + "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "filters_json": "{\"period\":\"Monthly\",\"based_on\":\"Item\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 16:24:45.726270", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Order Trends", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Sales Order Trends", + "timeseries": 0, + "type": "Line", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/selling/dashboard_chart/top_customers/top_customers.json b/erpnext/selling/dashboard_chart/top_customers/top_customers.json new file mode 100644 index 0000000000..59a2ba37dd --- /dev/null +++ b/erpnext/selling/dashboard_chart/top_customers/top_customers.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Top Customers", + "chart_type": "Report", + "creation": "2020-07-20 20:17:16.539281", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "filters_json": "{\"period\":\"Yearly\",\"based_on\":\"Customer\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 17:03:10.320147", + "modified_by": "Administrator", + "module": "Selling", + "name": "Top Customers", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Delivery Note Trends", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/selling/dashboard_fixtures.py b/erpnext/selling/dashboard_fixtures.py deleted file mode 100644 index 889cb88dce..0000000000 --- a/erpnext/selling/dashboard_fixtures.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import json -from frappe import _ -from frappe.utils import nowdate -from erpnext.accounts.utils import get_fiscal_year - -def get_data(): - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(), - "number_cards": get_number_cards(), - }) - -def get_company_for_dashboards(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company_list = frappe.get_list("Company") - if company_list: - return company_list[0].name - return None - -company = frappe.get_doc("Company", get_company_for_dashboards()) -fiscal_year = get_fiscal_year(nowdate(), as_dict=1) -fiscal_year_name = fiscal_year.get("name") -start_date = str(fiscal_year.get("year_start_date")) -end_date = str(fiscal_year.get("year_end_date")) - -def get_dashboards(): - return [{ - "name": "Selling", - "dashboard_name": "Selling", - "charts": [ - { "chart": "Sales Order Trends", "width": "Full"}, - { "chart": "Top Customers", "width": "Half"}, - { "chart": "Sales Order Analysis", "width": "Half"}, - { "chart": "Item-wise Annual Sales", "width": "Full"} - ], - "cards": [ - { "card": "Annual Sales"}, - { "card": "Sales Orders to Deliver"}, - { "card": "Sales Orders to Bill"}, - { "card": "Active Customers"} - ] - }] - -def get_charts(): - return [ - { - "name": "Sales Order Analysis", - "chart_name": _("Sales Order Analysis"), - "chart_type": "Report", - "custom_options": json.dumps({ - "type": "donut", - "height": 300, - "axisOptions": {"shortenYAxisNumbers": 1} - }), - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "from_date": start_date, - "to_date": end_date - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Sales Order Analysis", - "type": "Donut" - }, - { - "name": "Item-wise Annual Sales", - "chart_name": _("Item-wise Annual Sales"), - "chart_type": "Report", - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "from_date": start_date, - "to_date": end_date - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Item-wise Sales History", - "type": "Bar" - }, - { - "name": "Sales Order Trends", - "chart_name": _("Sales Order Trends"), - "chart_type": "Report", - "custom_options": json.dumps({ - "type": "line", - "axisOptions": {"shortenYAxisNumbers": 1}, - "tooltipOptions": {}, - "lineOptions": { - "regionFill": 1 - } - }), - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "period": "Monthly", - "fiscal_year": fiscal_year_name, - "based_on": "Item" - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Sales Order Trends", - "type": "Line" - }, - { - "name": "Top Customers", - "chart_name": _("Top Customers"), - "chart_type": "Report", - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "period": "Monthly", - "fiscal_year": fiscal_year_name, - "based_on": "Customer" - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Delivery Note Trends", - "type": "Bar" - } - ] - -def get_number_cards(): - return [ - { - "name": "Annual Sales", - "aggregate_function_based_on": "base_net_total", - "doctype": "Number Card", - "document_type": "Sales Order", - "filters_json": json.dumps([ - ["Sales Order", "transaction_date", "Between", [start_date, end_date], False], - ["Sales Order", "status", "not in", ["Draft", "Cancelled", "Closed", None], False], - ["Sales Order", "docstatus", "=", 1, False], - ["Sales Order", "company", "=", company.name, False] - ]), - "function": "Sum", - "is_public": 1, - "label": _("Annual Sales"), - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - }, - { - "name": "Sales Orders to Deliver", - "doctype": "Number Card", - "document_type": "Sales Order", - "filters_json": json.dumps([ - ["Sales Order", "status", "in", ["To Deliver and Bill", "To Deliver", None], False], - ["Sales Order", "docstatus", "=", 1, False], - ["Sales Order", "company", "=", company.name, False] - ]), - "function": "Count", - "is_public": 1, - "label": _("Sales Orders to Deliver"), - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Weekly" - }, - { - "name": "Sales Orders to Bill", - "doctype": "Number Card", - "document_type": "Sales Order", - "filters_json": json.dumps([ - ["Sales Order", "status", "in", ["To Deliver and Bill", "To Bill", None], False], - ["Sales Order", "docstatus", "=", 1, False], - ["Sales Order", "company", "=", company.name, False] - ]), - "function": "Count", - "is_public": 1, - "label": _("Sales Orders to Bill"), - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Weekly" - }, - { - "name": "Active Customers", - "doctype": "Number Card", - "document_type": "Customer", - "filters_json": json.dumps([["Customer", "disabled", "=", "0"]]), - "function": "Count", - "is_public": 1, - "label": "Active Customers", - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - } - ] \ No newline at end of file diff --git a/erpnext/selling/number_card/active_customers/active_customers.json b/erpnext/selling/number_card/active_customers/active_customers.json new file mode 100644 index 0000000000..3377634847 --- /dev/null +++ b/erpnext/selling/number_card/active_customers/active_customers.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 20:17:16.653866", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Customer", + "dynamic_filters_json": "", + "filters_json": "[[\"Customer\",\"disabled\",\"=\",\"0\"]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Active Customers", + "modified": "2020-07-22 14:20:32.268103", + "modified_by": "Administrator", + "module": "Selling", + "name": "Active Customers", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/selling/number_card/annual_sales/annual_sales.json b/erpnext/selling/number_card/annual_sales/annual_sales.json new file mode 100644 index 0000000000..8746ee4c6a --- /dev/null +++ b/erpnext/selling/number_card/annual_sales/annual_sales.json @@ -0,0 +1,22 @@ +{ + "aggregate_function_based_on": "base_net_total", + "creation": "2020-07-20 20:17:16.568132", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Sales Order", + "dynamic_filters_json": "[[\"Sales Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Sales Order\",\"status\",\"not in\",[\"Draft\",\"Cancelled\",\"Closed\",null],false],[\"Sales Order\",\"docstatus\",\"=\",\"1\",false],[\"Sales Order\",\"modified\",\"Timespan\",\"this year\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Annual Sales", + "modified": "2020-07-22 16:56:33.747156", + "modified_by": "Administrator", + "module": "Selling", + "name": "Annual Sales", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/selling/number_card/sales_orders_to_bill/sales_orders_to_bill.json b/erpnext/selling/number_card/sales_orders_to_bill/sales_orders_to_bill.json new file mode 100644 index 0000000000..27fea45723 --- /dev/null +++ b/erpnext/selling/number_card/sales_orders_to_bill/sales_orders_to_bill.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 20:17:16.625001", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Sales Order", + "dynamic_filters_json": "[[\"Sales Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Sales Order\",\"status\",\"in\",[\"To Deliver and Bill\",\"To Bill\",null],false],[\"Sales Order\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Sales Orders to Bill", + "modified": "2020-07-22 14:20:09.918626", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Orders to Bill", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/selling/number_card/sales_orders_to_deliver/sales_orders_to_deliver.json b/erpnext/selling/number_card/sales_orders_to_deliver/sales_orders_to_deliver.json new file mode 100644 index 0000000000..6e19cf4d3e --- /dev/null +++ b/erpnext/selling/number_card/sales_orders_to_deliver/sales_orders_to_deliver.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 20:17:16.596857", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Sales Order", + "dynamic_filters_json": "[[\"Sales Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Sales Order\",\"status\",\"in\",[\"To Deliver and Bill\",\"To Deliver\",null],false],[\"Sales Order\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Sales Orders to Deliver", + "modified": "2020-07-22 14:19:28.833784", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Orders to Deliver", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/selling/selling_dashboard/selling/selling.json b/erpnext/selling/selling_dashboard/selling/selling.json new file mode 100644 index 0000000000..52e6714965 --- /dev/null +++ b/erpnext/selling/selling_dashboard/selling/selling.json @@ -0,0 +1,46 @@ +{ + "cards": [ + { + "card": "Annual Sales" + }, + { + "card": "Sales Orders to Deliver" + }, + { + "card": "Sales Orders to Bill" + }, + { + "card": "Active Customers" + } + ], + "charts": [ + { + "chart": "Sales Order Trends", + "width": "Full" + }, + { + "chart": "Top Customers", + "width": "Half" + }, + { + "chart": "Sales Order Analysis", + "width": "Half" + }, + { + "chart": "Item-wise Annual Sales", + "width": "Full" + } + ], + "creation": "2020-07-20 20:17:16.688162", + "dashboard_name": "Selling", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-22 15:31:22.299903", + "modified_by": "Administrator", + "module": "Selling", + "name": "Selling", + "owner": "Administrator" +} \ No newline at end of file From 5bd0b93acf632845135762950c809cc496140a54 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 22 Jul 2020 18:10:57 +0530 Subject: [PATCH 077/101] fix: cur_frm -> frm --- erpnext/crm/doctype/opportunity/opportunity.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 28aedde122..7bae803508 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -127,14 +127,14 @@ frappe.ui.form.on("Opportunity", { make_supplier_quotation: function(frm) { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.opportunity.opportunity.make_supplier_quotation", - frm: cur_frm + frm: frm }) }, make_request_for_quotation: function(frm) { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation", - frm: cur_frm + frm: frm }) }, From e519334dfcf7916f25cb0e25bda39c62f9fba78f Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 22 Jul 2020 18:16:57 +0530 Subject: [PATCH 078/101] feat: Export CRM dashboard (#22778) * feat: Exported CRM dashboard * feat: Export CRM Dashboard * fix: adding company in all dashboard and number card --- erpnext/crm/crm_dashboard/crm/crm.json | 58 +++++ .../incoming_leads/incoming_leads.json | 28 +++ .../lead_source/lead_source.json | 27 +++ .../opportunities_via_campaigns.json | 27 +++ .../opportunity_trends.json | 28 +++ .../territory_wise_opportunity_count.json | 27 +++ .../territory_wise_sales.json | 28 +++ .../won_opportunities/won_opportunities.json | 27 +++ erpnext/crm/dashboard_fixtures.py | 221 ------------------ .../new_lead_(last_1_month).json | 21 ++ .../new_opportunity_(last_1_month).json | 21 ++ .../open_opportunity/open_opportunity.json | 21 ++ .../won_opportunity_(last_1_month).json | 21 ++ 13 files changed, 334 insertions(+), 221 deletions(-) create mode 100644 erpnext/crm/crm_dashboard/crm/crm.json create mode 100644 erpnext/crm/dashboard_chart/incoming_leads/incoming_leads.json create mode 100644 erpnext/crm/dashboard_chart/lead_source/lead_source.json create mode 100644 erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json create mode 100644 erpnext/crm/dashboard_chart/opportunity_trends/opportunity_trends.json create mode 100644 erpnext/crm/dashboard_chart/territory_wise_opportunity_count/territory_wise_opportunity_count.json create mode 100644 erpnext/crm/dashboard_chart/territory_wise_sales/territory_wise_sales.json create mode 100644 erpnext/crm/dashboard_chart/won_opportunities/won_opportunities.json delete mode 100644 erpnext/crm/dashboard_fixtures.py create mode 100644 erpnext/crm/number_card/new_lead_(last_1_month)/new_lead_(last_1_month).json create mode 100644 erpnext/crm/number_card/new_opportunity_(last_1_month)/new_opportunity_(last_1_month).json create mode 100644 erpnext/crm/number_card/open_opportunity/open_opportunity.json create mode 100644 erpnext/crm/number_card/won_opportunity_(last_1_month)/won_opportunity_(last_1_month).json diff --git a/erpnext/crm/crm_dashboard/crm/crm.json b/erpnext/crm/crm_dashboard/crm/crm.json new file mode 100644 index 0000000000..69c2c8a351 --- /dev/null +++ b/erpnext/crm/crm_dashboard/crm/crm.json @@ -0,0 +1,58 @@ +{ + "cards": [ + { + "card": "New Lead (Last 1 Month)" + }, + { + "card": "New Opportunity (Last 1 Month)" + }, + { + "card": "Won Opportunity (Last 1 Month)" + }, + { + "card": "Open Opportunity" + } + ], + "charts": [ + { + "chart": "Incoming Leads", + "width": "Full" + }, + { + "chart": "Opportunity Trends", + "width": "Full" + }, + { + "chart": "Won Opportunities", + "width": "Full" + }, + { + "chart": "Territory Wise Opportunity Count", + "width": "Half" + }, + { + "chart": "Opportunities via Campaigns", + "width": "Half" + }, + { + "chart": "Territory Wise Sales", + "width": "Full" + }, + { + "chart": "Lead Source", + "width": "Half" + } + ], + "creation": "2020-07-20 20:17:15.985657", + "dashboard_name": "CRM", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-21 18:56:47.230053", + "modified_by": "Administrator", + "module": "CRM", + "name": "CRM", + "owner": "Administrator" +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_chart/incoming_leads/incoming_leads.json b/erpnext/crm/dashboard_chart/incoming_leads/incoming_leads.json new file mode 100644 index 0000000000..82398ebd99 --- /dev/null +++ b/erpnext/crm/dashboard_chart/incoming_leads/incoming_leads.json @@ -0,0 +1,28 @@ +{ + "based_on": "creation", + "chart_name": "Incoming Leads", + "chart_type": "Count", + "creation": "2020-07-20 20:17:15.639164", + "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Lead", + "dynamic_filters_json": "[[\"Lead\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:49:19.896501", + "modified": "2020-07-22 16:06:34.941729", + "modified_by": "Administrator", + "module": "CRM", + "name": "Incoming Leads", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Weekly", + "timeseries": 1, + "timespan": "Last Quarter", + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_chart/lead_source/lead_source.json b/erpnext/crm/dashboard_chart/lead_source/lead_source.json new file mode 100644 index 0000000000..f25fea5751 --- /dev/null +++ b/erpnext/crm/dashboard_chart/lead_source/lead_source.json @@ -0,0 +1,27 @@ +{ + "chart_name": "Lead Source", + "chart_type": "Group By", + "creation": "2020-07-20 20:17:15.842106", + "custom_options": "{\"truncateLegends\": 1, \"maxSlices\": 8}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Lead", + "dynamic_filters_json": "[[\"Lead\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[]", + "group_by_based_on": "source", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 16:11:14.170636", + "modified": "2020-07-22 16:13:38.696710", + "modified_by": "Administrator", + "module": "CRM", + "name": "Lead Source", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Donut", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json b/erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json new file mode 100644 index 0000000000..4adda9a1c5 --- /dev/null +++ b/erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json @@ -0,0 +1,27 @@ +{ + "chart_name": "Opportunities via Campaigns", + "chart_type": "Group By", + "creation": "2020-07-20 20:17:15.705402", + "custom_options": "{\"truncateLegends\": 1, \"maxSlices\": 8}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[]", + "group_by_based_on": "campaign", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:45:32.572011", + "modified": "2020-07-22 16:10:02.497726", + "modified_by": "Administrator", + "module": "CRM", + "name": "Opportunities via Campaigns", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Pie", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_chart/opportunity_trends/opportunity_trends.json b/erpnext/crm/dashboard_chart/opportunity_trends/opportunity_trends.json new file mode 100644 index 0000000000..08e26cd3e3 --- /dev/null +++ b/erpnext/crm/dashboard_chart/opportunity_trends/opportunity_trends.json @@ -0,0 +1,28 @@ +{ + "based_on": "creation", + "chart_name": "Opportunity Trends", + "chart_type": "Count", + "creation": "2020-07-20 20:17:15.672124", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:45:32.590967", + "modified": "2020-07-22 16:08:33.100532", + "modified_by": "Administrator", + "module": "CRM", + "name": "Opportunity Trends", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Weekly", + "timeseries": 1, + "timespan": "Last Quarter", + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_chart/territory_wise_opportunity_count/territory_wise_opportunity_count.json b/erpnext/crm/dashboard_chart/territory_wise_opportunity_count/territory_wise_opportunity_count.json new file mode 100644 index 0000000000..8b15ec93e2 --- /dev/null +++ b/erpnext/crm/dashboard_chart/territory_wise_opportunity_count/territory_wise_opportunity_count.json @@ -0,0 +1,27 @@ +{ + "chart_name": "Territory Wise Opportunity Count", + "chart_type": "Group By", + "creation": "2020-07-20 20:17:15.774176", + "custom_options": "{\"truncateLegends\": 1, \"maxSlices\": 8}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[]", + "group_by_based_on": "territory", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:45:32.134026", + "modified": "2020-07-22 16:09:42.921547", + "modified_by": "Administrator", + "module": "CRM", + "name": "Territory Wise Opportunity Count", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Donut", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_chart/territory_wise_sales/territory_wise_sales.json b/erpnext/crm/dashboard_chart/territory_wise_sales/territory_wise_sales.json new file mode 100644 index 0000000000..fe142b4344 --- /dev/null +++ b/erpnext/crm/dashboard_chart/territory_wise_sales/territory_wise_sales.json @@ -0,0 +1,28 @@ +{ + "aggregate_function_based_on": "opportunity_amount", + "chart_name": "Territory Wise Sales", + "chart_type": "Group By", + "creation": "2020-07-20 20:17:15.809008", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"status\",\"=\",\"Converted\",false]]", + "group_by_based_on": "territory", + "group_by_type": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:45:32.501313", + "modified": "2020-07-22 16:10:28.308110", + "modified_by": "Administrator", + "module": "CRM", + "name": "Territory Wise Sales", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_chart/won_opportunities/won_opportunities.json b/erpnext/crm/dashboard_chart/won_opportunities/won_opportunities.json new file mode 100644 index 0000000000..2b5576b3a4 --- /dev/null +++ b/erpnext/crm/dashboard_chart/won_opportunities/won_opportunities.json @@ -0,0 +1,27 @@ +{ + "based_on": "modified", + "chart_name": "Won Opportunities", + "chart_type": "Count", + "creation": "2020-07-20 20:17:15.738889", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"status\",\"=\",\"Converted\",false]]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:45:32.575964", + "modified": "2020-07-22 16:09:14.265231", + "modified_by": "Administrator", + "module": "CRM", + "name": "Won Opportunities", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/crm/dashboard_fixtures.py b/erpnext/crm/dashboard_fixtures.py deleted file mode 100644 index 901c0581f4..0000000000 --- a/erpnext/crm/dashboard_fixtures.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe, erpnext, json -from frappe import _ - -def get_data(): - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(), - "number_cards": get_number_cards() - }) - -def get_dashboards(): - return [{ - "doctype": "Dashboard", - "name": "CRM", - "dashboard_name": "CRM", - "charts": [ - { "chart": "Incoming Leads", "width": "Full" }, - { "chart": "Opportunity Trends", "width": "Full"}, - { "chart": "Won Opportunities", "width": "Full" }, - { "chart": "Territory Wise Opportunity Count", "width": "Half"}, - { "chart": "Opportunities via Campaigns", "width": "Half" }, - { "chart": "Territory Wise Sales", "width": "Full"}, - { "chart": "Lead Source", "width": "Half"} - ], - "cards": [ - { "card": "New Lead (Last 1 Month)" }, - { "card": "New Opportunity (Last 1 Month)" }, - { "card": "Won Opportunity (Last 1 Month)" }, - { "card": "Open Opportunity"}, - ] - }] - -def get_company_for_dashboards(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company_list = frappe.get_list("Company") - if company_list: - return company_list[0].name - return None - -def get_charts(): - company = get_company_for_dashboards() - - return [{ - "name": "Incoming Leads", - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Count", - "chart_name": _("Incoming Leads"), - "timespan": "Last Quarter", - "time_interval": "Weekly", - "document_type": "Lead", - "based_on": "creation", - 'is_public': 1, - 'timeseries': 1, - "owner": "Administrator", - "filters_json": json.dumps([]), - "type": "Bar" - }, - { - "name": "Opportunity Trends", - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Count", - "chart_name": _("Opportunity Trends"), - "timespan": "Last Quarter", - "time_interval": "Weekly", - "document_type": "Opportunity", - "based_on": "creation", - 'is_public': 1, - 'timeseries': 1, - "owner": "Administrator", - "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]), - "type": "Bar" - }, - { - "name": "Opportunities via Campaigns", - "chart_name": _("Opportunities via Campaigns"), - "doctype": "Dashboard Chart", - "chart_type": "Group By", - "group_by_type": "Count", - "group_by_based_on": "campaign", - "document_type": "Opportunity", - 'is_public': 1, - 'timeseries': 1, - "owner": "Administrator", - "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]), - "type": "Pie", - "custom_options": json.dumps({ - "truncateLegends": 1, - "maxSlices": 8 - }) - }, - { - "name": "Won Opportunities", - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Count", - "chart_name": _("Won Opportunities"), - "timespan": "Last Year", - "time_interval": "Monthly", - "document_type": "Opportunity", - "based_on": "modified", - 'is_public': 1, - 'timeseries': 1, - "owner": "Administrator", - "filters_json": json.dumps([ - ["Opportunity", "company", "=", company, False], - ["Opportunity", "status", "=", "Converted", False]]), - "type": "Bar" - }, - { - "name": "Territory Wise Opportunity Count", - "doctype": "Dashboard Chart", - "chart_type": "Group By", - "group_by_type": "Count", - "group_by_based_on": "territory", - "chart_name": _("Territory Wise Opportunity Count"), - "document_type": "Opportunity", - 'is_public': 1, - "filters_json": json.dumps([ - ["Opportunity", "company", "=", company, False] - ]), - "owner": "Administrator", - "type": "Donut", - "custom_options": json.dumps({ - "truncateLegends": 1, - "maxSlices": 8 - }) - }, - { - "name": "Territory Wise Sales", - "doctype": "Dashboard Chart", - "chart_type": "Group By", - "group_by_type": "Sum", - "group_by_based_on": "territory", - "chart_name": _("Territory Wise Sales"), - "aggregate_function_based_on": "opportunity_amount", - "document_type": "Opportunity", - 'is_public': 1, - "owner": "Administrator", - "filters_json": json.dumps([ - ["Opportunity", "company", "=", company, False], - ["Opportunity", "status", "=", "Converted", False] - ]), - "type": "Bar" - }, - { - "name": "Lead Source", - "doctype": "Dashboard Chart", - "chart_type": "Group By", - "group_by_type": "Count", - "group_by_based_on": "source", - "chart_name": _("Lead Source"), - "document_type": "Lead", - 'is_public': 1, - "owner": "Administrator", - "type": "Pie", - "custom_options": json.dumps({ - "truncateLegends": 1, - "maxSlices": 8 - }) - }] - -def get_number_cards(): - return [{ - "doctype": "Number Card", - "document_type": "Lead", - "name": "New Lead (Last 1 Month)", - "filters_json": json.dumps([ - ["Lead", "creation", "Timespan", "last month"] - ]), - "function": "Count", - "is_public": 1, - "label": _("New Lead (Last 1 Month)"), - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - }, - { - "doctype": "Number Card", - "document_type": "Opportunity", - "name": "New Opportunity (Last 1 Month)", - "filters_json": json.dumps([ - ["Opportunity", "creation", "Timespan", "last month"] - ]), - "function": "Count", - "is_public": 1, - "label": _("New Opportunity (Last 1 Month)"), - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - }, - { - "doctype": "Number Card", - "document_type": "Opportunity", - "name": "Won Opportunity (Last 1 Month)", - "filters_json": json.dumps([ - ["Opportunity", "status", "=", "Converted",False], - ["Opportunity", "creation", "Timespan", "last month"] - ]), - "function": "Count", - "is_public": 1, - "label": _("Won Opportunity (Last 1 Month)"), - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - }, - { - "doctype": "Number Card", - "document_type": "Opportunity", - "name": "Open Opportunity", - "filters_json": json.dumps([["Opportunity","status","=","Open",False]]), - "function": "Count", - "is_public": 1, - "label": _("Open Opportunity"), - "show_percentage_stats": 1, - "stats_time_interval": "Daily" - }] \ No newline at end of file diff --git a/erpnext/crm/number_card/new_lead_(last_1_month)/new_lead_(last_1_month).json b/erpnext/crm/number_card/new_lead_(last_1_month)/new_lead_(last_1_month).json new file mode 100644 index 0000000000..4511f54007 --- /dev/null +++ b/erpnext/crm/number_card/new_lead_(last_1_month)/new_lead_(last_1_month).json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 20:17:15.870736", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Lead", + "dynamic_filters_json": "[[\"Lead\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Lead\",\"creation\",\"Timespan\",\"last month\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "New Lead (Last 1 Month)", + "modified": "2020-07-22 16:15:17.274972", + "modified_by": "Administrator", + "module": "CRM", + "name": "New Lead (Last 1 Month)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/crm/number_card/new_opportunity_(last_1_month)/new_opportunity_(last_1_month).json b/erpnext/crm/number_card/new_opportunity_(last_1_month)/new_opportunity_(last_1_month).json new file mode 100644 index 0000000000..90997b7033 --- /dev/null +++ b/erpnext/crm/number_card/new_opportunity_(last_1_month)/new_opportunity_(last_1_month).json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 20:17:15.897112", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"creation\",\"Timespan\",\"last month\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "New Opportunity (Last 1 Month)", + "modified": "2020-07-22 16:07:27.910432", + "modified_by": "Administrator", + "module": "CRM", + "name": "New Opportunity (Last 1 Month)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/crm/number_card/open_opportunity/open_opportunity.json b/erpnext/crm/number_card/open_opportunity/open_opportunity.json new file mode 100644 index 0000000000..6e06ed64da --- /dev/null +++ b/erpnext/crm/number_card/open_opportunity/open_opportunity.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 20:17:15.948113", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"status\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"company\",\"=\",null,false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Open Opportunity", + "modified": "2020-07-22 16:16:16.420446", + "modified_by": "Administrator", + "module": "CRM", + "name": "Open Opportunity", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/crm/number_card/won_opportunity_(last_1_month)/won_opportunity_(last_1_month).json b/erpnext/crm/number_card/won_opportunity_(last_1_month)/won_opportunity_(last_1_month).json new file mode 100644 index 0000000000..ba0c07e4b0 --- /dev/null +++ b/erpnext/crm/number_card/won_opportunity_(last_1_month)/won_opportunity_(last_1_month).json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 20:17:15.922486", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"creation\",\"Timespan\",\"last month\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Won Opportunity (Last 1 Month)", + "modified": "2020-07-22 16:15:53.088837", + "modified_by": "Administrator", + "module": "CRM", + "name": "Won Opportunity (Last 1 Month)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file From 3e4b5e5496443b38d5fd9f1058ab38a37791e837 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 22 Jul 2020 18:17:58 +0530 Subject: [PATCH 079/101] fix: item-wise sales history report (#22783) --- .../report/item_wise_sales_history/item_wise_sales_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 1bc4657f29..0a70b97648 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -191,7 +191,7 @@ def get_conditions(filters): conditions += "AND so_item.item_code = '%s'" %frappe.db.escape(filters.item_code) if filters.get("customer"): - conditions += "AND so.customer = '%s'" %frappe.db.escape(filters.customer) + conditions += "AND so.customer = %s" %frappe.db.escape(filters.customer) return conditions From 7c50f421e19992b8a8022b851e6fb566febd2f1f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 22 Jul 2020 18:21:04 +0530 Subject: [PATCH 080/101] fix: Move branch code back to bank account (#22725) * fix: Move branch code back to bank account * fix: Patch --- erpnext/accounts/doctype/bank/bank.json | 12 +----------- .../doctype/bank_account/bank_account.json | 9 ++++++++- .../doctype/bank_guarantee/bank_guarantee.js | 2 +- .../payment_request/payment_request.json | 4 ++-- erpnext/patches.txt | 1 + .../move_bank_account_swift_number_to_bank.py | 3 +-- .../v13_0/move_branch_code_to_bank_account.py | 17 +++++++++++++++++ 7 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 erpnext/patches/v13_0/move_branch_code_to_bank_account.py diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json index 99978e657d..56bae72a15 100644 --- a/erpnext/accounts/doctype/bank/bank.json +++ b/erpnext/accounts/doctype/bank/bank.json @@ -13,7 +13,6 @@ "bank_name", "swift_number", "column_break_1", - "branch_code", "website", "address_and_contact", "address_html", @@ -51,15 +50,6 @@ "fieldtype": "Column Break", "search_index": 1 }, - { - "allow_in_quick_entry": 1, - "fieldname": "branch_code", - "fieldtype": "Data", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Branch Code", - "unique": 1 - }, { "fieldname": "address_and_contact", "fieldtype": "Section Break", @@ -111,7 +101,7 @@ } ], "links": [], - "modified": "2020-03-25 21:22:33.496264", + "modified": "2020-07-17 14:00:13.105433", "modified_by": "Administrator", "module": "Accounts", "name": "Bank", diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 65a0a5138c..b42f1f9d58 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -23,6 +23,7 @@ "account_details_section", "iban", "column_break_12", + "branch_code", "bank_account_no", "address_and_contact", "address_html", @@ -197,10 +198,16 @@ "fieldtype": "Data", "label": "Mask", "read_only": 1 + }, + { + "fieldname": "branch_code", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Branch Code" } ], "links": [], - "modified": "2020-04-06 21:00:45.379804", + "modified": "2020-07-17 13:59:50.795412", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js index 065d25e6c3..febf85ca6c 100644 --- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js +++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js @@ -4,7 +4,7 @@ cur_frm.add_fetch('bank_account','account','account'); cur_frm.add_fetch('bank_account','bank_account_no','bank_account_no'); cur_frm.add_fetch('bank_account','iban','iban'); -cur_frm.add_fetch('bank','branch_code','branch_code'); +cur_frm.add_fetch('bank_account','branch_code','branch_code'); cur_frm.add_fetch('bank','swift_number','swift_number'); frappe.ui.form.on('Bank Guarantee', { diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index eef6be1a7a..8eadfd0b24 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -211,7 +211,7 @@ "label": "IBAN" }, { - "fetch_from": "bank.branch_code", + "fetch_from": "bank_account.branch_code", "fetch_if_empty": 1, "fieldname": "branch_code", "fieldtype": "Read Only", @@ -352,7 +352,7 @@ "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2020-05-29 17:38:49.392713", + "modified": "2020-07-17 14:06:42.185763", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 566b979ade..474c343e1f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -709,3 +709,4 @@ erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-20 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v12_0.add_taxjar_integration_field erpnext.patches.v12_0.update_item_tax_template_company +erpnext.patches.v13_0.move_branch_code_to_bank_account diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py index 1ddbae6cd2..a670adebfd 100644 --- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py +++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py @@ -7,8 +7,7 @@ def execute(): if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'): frappe.db.sql(""" UPDATE `tabBank` b, `tabBank Account` ba - SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code - WHERE b.name = ba.bank + SET b.swift_number = ba.swift_number WHERE b.name = ba.bank """) frappe.reload_doc('accounts', 'doctype', 'bank_account') diff --git a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py new file mode 100644 index 0000000000..833ae2a48f --- /dev/null +++ b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py @@ -0,0 +1,17 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe + +def execute(): + + frappe.reload_doc('accounts', 'doctype', 'bank_account') + frappe.reload_doc('accounts', 'doctype', 'bank') + + if frappe.db.has_column('Bank', 'branch_code') and frappe.db.has_column('Bank Account', 'branch_code'): + frappe.db.sql("""UPDATE `tabBank` b, `tabBank Account` ba + SET ba.branch_code = b.branch_code + WHERE ba.bank = b.name AND + ifnull(b.branch_code, '') != '' AND ifnull(ba.branch_code, '') = ''""") \ No newline at end of file From d45ff31ff08465f19fd263889499bcd993e355ab Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 22 Jul 2020 18:24:01 +0530 Subject: [PATCH 081/101] fix: incorrect stock after merging the items (#22526) * fix: incorrect stock after merging items * fix: Readability fix Co-authored-by: Marica --- erpnext/stock/doctype/item/item.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a75ee67ec4..d5f479ff82 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -13,7 +13,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError, from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for) from frappe import _, msgprint from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate, - now_datetime, random_string, strip) + now_datetime, random_string, strip, get_link_to_form) from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ get_slideshow @@ -634,6 +634,9 @@ class Item(WebsiteGenerator): + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list])) def after_rename(self, old_name, new_name, merge): + if merge: + self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name) + if self.route: invalidate_cache_for_item(self) clear_cache(self.route) @@ -656,6 +659,27 @@ class Item(WebsiteGenerator): frappe.db.set_value(dt, d.name, "item_wise_tax_detail", json.dumps(item_wise_tax_detail), update_modified=False) + def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name): + records = frappe.db.sql(""" SELECT parent, COUNT(*) as records + FROM `tabStock Reconciliation Item` + WHERE item_code = %s and docstatus = 1 + GROUP By item_code, warehouse, parent + HAVING records > 1 + """, new_name, as_dict=1) + + if not records: return + document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations") + + msg = _("The items {0} and {1} are present in the following {2} :
" + .format(frappe.bold(old_name), frappe.bold(new_name), document)) + + msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "

" + + msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}" + .format(frappe.bold(old_name))) + + frappe.throw(_(msg), title=_("Merge not allowed")) + def set_last_purchase_rate(self, new_name): last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) From 3994aa8b06d67c56cb634c54c8079901570904e6 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 22 Jul 2020 19:49:54 +0530 Subject: [PATCH 082/101] fix: Same Contact name for different parties at Portal Login (#22715) --- erpnext/portal/utils.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index 56e4fcde73..d6d4469420 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -88,21 +88,30 @@ def create_customer_or_supplier(): party.flags.ignore_mandatory = True party.insert(ignore_permissions=True) + alternate_doctype = "Customer" if doctype == "Supplier" else "Supplier" + + if party_exists(alternate_doctype, user): + # if user is both customer and supplier, alter fullname to avoid contact name duplication + fullname += "-" + doctype + + create_party_contact(doctype, fullname, user, party.name) + + return party + +def create_party_contact(doctype, fullname, user, party_name): contact = frappe.new_doc("Contact") contact.update({ "first_name": fullname, "email_id": user }) - contact.append('links', dict(link_doctype=doctype, link_name=party.name)) + contact.append('links', dict(link_doctype=doctype, link_name=party_name)) + contact.append('email_ids', dict(email_id=user)) contact.flags.ignore_mandatory = True contact.insert(ignore_permissions=True) - return party - - def party_exists(doctype, user): + # check if contact exists against party and if it is linked to the doctype contact_name = frappe.db.get_value("Contact", {"email_id": user}) - if contact_name: contact = frappe.get_doc('Contact', contact_name) doctypes = [d.link_doctype for d in contact.links] From fb899063542f3e29e19c5b24043ab8c6ba853bbf Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Wed, 22 Jul 2020 20:17:50 +0530 Subject: [PATCH 083/101] fix: pass date range instead of from date (#22114) * fix: pass date range instead of from date * fix: escape inputs Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- erpnext/startup/leaderboard.py | 60 +++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 90ecd46259..5545f13e8c 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -50,11 +50,12 @@ def get_leaderboards(): return leaderboards @frappe.whitelist() -def get_all_customers(from_date, company, field, limit = None): +def get_all_customers(date_range, company, field, limit = None): if field == "outstanding_amount": filters = [['docstatus', '=', '1'], ['company', '=', company]] - if from_date: - filters.append(['posting_date', '>=', from_date]) + if date_range: + date_range = frappe.parse_json(date_range) + filters.append(['posting_date', '>=', 'between', [date_range[0], date_range[1]]]) return frappe.db.get_all('Sales Invoice', fields = ['customer as name', 'sum(outstanding_amount) as value'], filters = filters, @@ -68,18 +69,20 @@ def get_all_customers(from_date, company, field, limit = None): elif field == "total_qty_sold": select_field = "sum(so_item.stock_qty)" + date_condition = get_date_condition(date_range, 'so.transaction_date') + return frappe.db.sql(""" select so.customer as name, {0} as value FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item ON so.name = so_item.parent - where so.docstatus = 1 and so.transaction_date >= %s and so.company = %s + where so.docstatus = 1 {1} and so.company = %s group by so.customer order by value DESC limit %s - """.format(select_field), (from_date, company, cint(limit)), as_dict=1) #nosec + """.format(select_field, date_condition), (company, cint(limit)), as_dict=1) @frappe.whitelist() -def get_all_items(from_date, company, field, limit = None): +def get_all_items(date_range, company, field, limit = None): if field in ("available_stock_qty", "available_stock_value"): select_field = "sum(actual_qty)" if field=="available_stock_qty" else "sum(stock_value)" return frappe.db.get_all('Bin', @@ -102,23 +105,25 @@ def get_all_items(from_date, company, field, limit = None): select_field = "sum(order_item.stock_qty)" select_doctype = "Purchase Order" + date_condition = get_date_condition(date_range, 'sales_order.transaction_date') + return frappe.db.sql(""" select order_item.item_code as name, {0} as value from `tab{1}` sales_order join `tab{1} Item` as order_item on sales_order.name = order_item.parent where sales_order.docstatus = 1 - and sales_order.company = %s and sales_order.transaction_date >= %s + and sales_order.company = %s {2} group by order_item.item_code order by value desc limit %s - """.format(select_field, select_doctype), (company, from_date, cint(limit)), as_dict=1) #nosec + """.format(select_field, select_doctype, date_condition), (company, cint(limit)), as_dict=1) #nosec @frappe.whitelist() -def get_all_suppliers(from_date, company, field, limit = None): +def get_all_suppliers(date_range, company, field, limit = None): if field == "outstanding_amount": filters = [['docstatus', '=', '1'], ['company', '=', company]] - if from_date: - filters.append(['posting_date', '>=', from_date]) + if date_range: + filters.append(['posting_date', 'between' [date_range[0], date_range[1]]]) return frappe.db.get_all('Purchase Invoice', fields = ['supplier as name', 'sum(outstanding_amount) as value'], filters = filters, @@ -132,18 +137,22 @@ def get_all_suppliers(from_date, company, field, limit = None): elif field == "total_qty_purchased": select_field = "sum(purchase_order_item.stock_qty)" + date_condition = get_date_condition(date_range, 'purchase_order.modified') + return frappe.db.sql(""" select purchase_order.supplier as name, {0} as value FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item` as purchase_order_item ON purchase_order.name = purchase_order_item.parent - where purchase_order.docstatus = 1 and purchase_order.modified >= %s + where + purchase_order.docstatus = 1 + {1} and purchase_order.company = %s group by purchase_order.supplier order by value DESC - limit %s""".format(select_field), (from_date, company, cint(limit)), as_dict=1) #nosec + limit %s""".format(select_field, date_condition), (company, cint(limit)), as_dict=1) #nosec @frappe.whitelist() -def get_all_sales_partner(from_date, company, field, limit = None): +def get_all_sales_partner(date_range, company, field, limit = None): if field == "total_sales_amount": select_field = "sum(`base_net_total`)" elif field == "total_commission": @@ -154,8 +163,9 @@ def get_all_sales_partner(from_date, company, field, limit = None): 'docstatus': 1, 'company': company } - if from_date: - filters['transaction_date'] = ['>=', from_date] + if date_range: + date_range = frappe.parse_json(date_range) + filters['transaction_date'] = ['between', [date_range[0], date_range[1]]] return frappe.get_list('Sales Order', fields=[ '`sales_partner` as name', @@ -163,15 +173,27 @@ def get_all_sales_partner(from_date, company, field, limit = None): ], filters=filters, group_by='sales_partner', order_by='value DESC', limit=limit) @frappe.whitelist() -def get_all_sales_person(from_date, company, field = None, limit = 0): +def get_all_sales_person(date_range, company, field = None, limit = 0): + date_condition = get_date_condition(date_range, 'sales_order.transaction_date') + return frappe.db.sql(""" select sales_team.sales_person as name, sum(sales_order.base_net_total) as value from `tabSales Order` as sales_order join `tabSales Team` as sales_team on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order' where sales_order.docstatus = 1 - and sales_order.transaction_date >= %s and sales_order.company = %s + {date_condition} group by sales_team.sales_person order by value DESC limit %s - """, (from_date, company, cint(limit)), as_dict=1) + """.format(date_condition=date_condition), (company, cint(limit)), as_dict=1) + +def get_date_condition(date_range, field): + date_condition = '' + if date_range: + date_range = frappe.parse_json(date_range) + from_date, to_date = date_range + date_condition = "and {0} between {1} and {2}".format( + field, frappe.db.escape(from_date), frappe.db.escape(to_date) + ) + return date_condition \ No newline at end of file From 4a5fc23d5c12ce5091a84e9e3c26c6ac025b46d9 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 23 Jul 2020 11:07:56 +0530 Subject: [PATCH 084/101] Export Project dashboard (#22782) --- .../project_summary/project_summary.json | 24 +++++++++ erpnext/projects/dashboard_fixtures.py | 50 ------------------- .../projects_dashboard/project/project.json | 21 ++++++++ 3 files changed, 45 insertions(+), 50 deletions(-) create mode 100644 erpnext/projects/dashboard_chart/project_summary/project_summary.json delete mode 100644 erpnext/projects/dashboard_fixtures.py create mode 100644 erpnext/projects/projects_dashboard/project/project.json diff --git a/erpnext/projects/dashboard_chart/project_summary/project_summary.json b/erpnext/projects/dashboard_chart/project_summary/project_summary.json new file mode 100644 index 0000000000..157ee1b954 --- /dev/null +++ b/erpnext/projects/dashboard_chart/project_summary/project_summary.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Project Summary", + "chart_type": "Report", + "creation": "2020-07-20 20:17:16.363681", + "custom_options": "{\"type\": \"bar\", \"colors\": [\"#fc4f51\", \"#78d6ff\", \"#7575ff\"], \"axisOptions\": { \"shortenYAxisNumbers\": 1}, \"barOptions\": { \"stacked\": 1 }}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", + "filters_json": "{\"status\":\"Open\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 17:16:39.627076", + "modified_by": "Administrator", + "module": "Projects", + "name": "Project Summary", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Project Summary", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/projects/dashboard_fixtures.py b/erpnext/projects/dashboard_fixtures.py deleted file mode 100644 index d89ffe9d83..0000000000 --- a/erpnext/projects/dashboard_fixtures.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import json -from frappe import _ - -def get_company_for_dashboards(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company_list = frappe.get_list("Company") - if company_list: - return company_list[0].name - return None - -def get_data(): - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(), - }) - -def get_dashboards(): - return [{ - "doctype": "Dashboard", - "name": "Project", - "dashboard_name": "Project", - "charts": [ - { "chart": "Project Summary", "width": "Full" } - ] - }] - -def get_charts(): - company = frappe.get_doc("Company", get_company_for_dashboards()) - - return [ - { - 'doctype': 'Dashboard Chart', - 'name': 'Project Summary', - 'chart_name': _('Project Summary'), - 'chart_type': 'Report', - 'report_name': 'Project Summary', - 'is_public': 1, - 'is_custom': 1, - 'filters_json': json.dumps({"company": company.name, "status": "Open"}), - 'type': 'Bar', - 'custom_options': '{"type": "bar", "colors": ["#fc4f51", "#78d6ff", "#7575ff"], "axisOptions": { "shortenYAxisNumbers": 1}, "barOptions": { "stacked": 1 }}', - } - ] \ No newline at end of file diff --git a/erpnext/projects/projects_dashboard/project/project.json b/erpnext/projects/projects_dashboard/project/project.json new file mode 100644 index 0000000000..f7824cee55 --- /dev/null +++ b/erpnext/projects/projects_dashboard/project/project.json @@ -0,0 +1,21 @@ +{ + "cards": [], + "charts": [ + { + "chart": "Project Summary", + "width": "Full" + } + ], + "creation": "2020-07-20 20:17:16.397373", + "dashboard_name": "Project", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-22 17:17:03.780625", + "modified_by": "Administrator", + "module": "Projects", + "name": "Project", + "owner": "Administrator" +} \ No newline at end of file From 43b4b1cb55d7378930d7e4e10b107ef3e86f8e78 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 23 Jul 2020 11:08:49 +0530 Subject: [PATCH 085/101] chore: Export Buying Dashboard (#22767) * chore: Export Buying Dashboard * fix: Use Timespan instead of Between for static filters --- .../buying_dashboard/buying/buying.json | 46 ++++ .../material_request_analysis.json | 27 +++ .../purchase_order_analysis.json | 24 ++ .../purchase_order_trends.json | 24 ++ .../top_suppliers/top_suppliers.json | 23 ++ erpnext/buying/dashboard_fixtures.py | 211 ------------------ .../active_suppliers/active_suppliers.json | 20 ++ .../annual_purchase/annual_purchase.json | 22 ++ .../purchase_orders_to_bill.json | 21 ++ .../purchase_orders_to_receive.json | 21 ++ 10 files changed, 228 insertions(+), 211 deletions(-) create mode 100644 erpnext/buying/buying_dashboard/buying/buying.json create mode 100644 erpnext/buying/dashboard_chart/material_request_analysis/material_request_analysis.json create mode 100644 erpnext/buying/dashboard_chart/purchase_order_analysis/purchase_order_analysis.json create mode 100644 erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json create mode 100644 erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json delete mode 100644 erpnext/buying/dashboard_fixtures.py create mode 100644 erpnext/buying/number_card/active_suppliers/active_suppliers.json create mode 100644 erpnext/buying/number_card/annual_purchase/annual_purchase.json create mode 100644 erpnext/buying/number_card/purchase_orders_to_bill/purchase_orders_to_bill.json create mode 100644 erpnext/buying/number_card/purchase_orders_to_receive/purchase_orders_to_receive.json diff --git a/erpnext/buying/buying_dashboard/buying/buying.json b/erpnext/buying/buying_dashboard/buying/buying.json new file mode 100644 index 0000000000..ab7ebac146 --- /dev/null +++ b/erpnext/buying/buying_dashboard/buying/buying.json @@ -0,0 +1,46 @@ +{ + "cards": [ + { + "card": "Annual Purchase" + }, + { + "card": "Purchase Orders to Receive" + }, + { + "card": "Purchase Orders to Bill" + }, + { + "card": "Active Suppliers" + } + ], + "charts": [ + { + "chart": "Purchase Order Trends", + "width": "Full" + }, + { + "chart": "Material Request Analysis", + "width": "Half" + }, + { + "chart": "Purchase Order Analysis", + "width": "Half" + }, + { + "chart": "Top Suppliers", + "width": "Full" + } + ], + "creation": "2020-07-20 21:01:02.541065", + "dashboard_name": "Buying", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 1, + "is_standard": 1, + "modified": "2020-07-22 12:48:38.112744", + "modified_by": "Administrator", + "module": "Buying", + "name": "Buying", + "owner": "Administrator" +} \ No newline at end of file diff --git a/erpnext/buying/dashboard_chart/material_request_analysis/material_request_analysis.json b/erpnext/buying/dashboard_chart/material_request_analysis/material_request_analysis.json new file mode 100644 index 0000000000..0b66f1f5e5 --- /dev/null +++ b/erpnext/buying/dashboard_chart/material_request_analysis/material_request_analysis.json @@ -0,0 +1,27 @@ +{ + "chart_name": "Material Request Analysis", + "chart_type": "Group By", + "creation": "2020-07-20 21:01:02.242563", + "custom_options": "{\"height\": 300}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Material Request", + "dynamic_filters_json": "[[\"Material Request\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Material Request\",\"status\",\"not in\",[\"Draft\",\"Cancelled\",\"Stopped\",null],false],[\"Material Request\",\"material_request_type\",\"=\",\"Purchase\",false],[\"Material Request\",\"docstatus\",\"=\",\"1\",false],[\"Material Request\",\"transaction_date\",\"Timespan\",\"last quarter\",false]]", + "group_by_based_on": "status", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 12:43:56.961250", + "modified": "2020-07-22 21:20:51.840194", + "modified_by": "Administrator", + "module": "Buying", + "name": "Material Request Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Donut", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/buying/dashboard_chart/purchase_order_analysis/purchase_order_analysis.json b/erpnext/buying/dashboard_chart/purchase_order_analysis/purchase_order_analysis.json new file mode 100644 index 0000000000..020755b92d --- /dev/null +++ b/erpnext/buying/dashboard_chart/purchase_order_analysis/purchase_order_analysis.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Purchase Order Analysis", + "chart_type": "Report", + "creation": "2020-07-20 21:01:02.203880", + "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -1)\",\"to_date\":\"frappe.datetime.nowdate()\"}", + "filters_json": "{\"group_by_po\":0}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 12:44:35.754973", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Order Analysis", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Purchase Order Analysis", + "timeseries": 0, + "type": "Donut", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json new file mode 100644 index 0000000000..6452ed2139 --- /dev/null +++ b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Purchase Order Trends", + "chart_type": "Report", + "creation": "2020-07-20 21:01:02.295012", + "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Item\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-21 16:13:25.092287", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Order Trends", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Purchase Order Trends", + "timeseries": 0, + "type": "Line", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json new file mode 100644 index 0000000000..6f7da8ea87 --- /dev/null +++ b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json @@ -0,0 +1,23 @@ +{ + "chart_name": "Top Suppliers", + "chart_type": "Report", + "creation": "2020-07-20 21:01:02.329519", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Supplier\"}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 12:43:40.829652", + "modified_by": "Administrator", + "module": "Buying", + "name": "Top Suppliers", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Purchase Receipt Trends", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py deleted file mode 100644 index c6e2ffa634..0000000000 --- a/erpnext/buying/dashboard_fixtures.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import json -from frappe import _ -from frappe.utils import nowdate -from erpnext.accounts.dashboard_fixtures import _get_fiscal_year - -def get_data(): - - fiscal_year = _get_fiscal_year(nowdate()) - - if not fiscal_year: - return frappe._dict() - - company = frappe.get_doc("Company", get_company_for_dashboards()) - fiscal_year_name = fiscal_year.get("name") - start_date = str(fiscal_year.get("year_start_date")) - end_date = str(fiscal_year.get("year_end_date")) - - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(company, fiscal_year_name, start_date, end_date), - "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date), - }) - -def get_company_for_dashboards(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company_list = frappe.get_list("Company") - if company_list: - return company_list[0].name - return None - -def get_dashboards(): - return [{ - "name": "Buying", - "dashboard_name": "Buying", - "charts": [ - { "chart": "Purchase Order Trends", "width": "Full"}, - { "chart": "Material Request Analysis", "width": "Half"}, - { "chart": "Purchase Order Analysis", "width": "Half"}, - { "chart": "Top Suppliers", "width": "Full"} - ], - "cards": [ - { "card": "Annual Purchase"}, - { "card": "Purchase Orders to Receive"}, - { "card": "Purchase Orders to Bill"}, - { "card": "Active Suppliers"} - ] - }] - -def get_charts(company, fiscal_year_name, start_date, end_date): - return [ - { - "name": "Purchase Order Analysis", - "chart_name": _("Purchase Order Analysis"), - "chart_type": "Report", - "custom_options": json.dumps({ - "type": "donut", - "height": 300, - "axisOptions": {"shortenYAxisNumbers": 1} - }), - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "from_date": start_date, - "to_date": end_date - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Purchase Order Analysis", - "type": "Donut" - }, - { - "name": "Material Request Analysis", - "chart_name": _("Material Request Analysis"), - "chart_type": "Group By", - "custom_options": json.dumps({"height": 300}), - "doctype": "Dashboard Chart", - "document_type": "Material Request", - "filters_json": json.dumps( - [["Material Request", "status", "not in", ["Draft", "Cancelled", "Stopped", None], False], - ["Material Request", "material_request_type", "=", "Purchase", False], - ["Material Request", "company", "=", company.name, False], - ["Material Request", "docstatus", "=", 1, False], - ["Material Request", "transaction_date", "Between", [start_date, end_date], False]] - ), - "group_by_based_on": "status", - "group_by_type": "Count", - "is_custom": 0, - "is_public": 1, - "number_of_groups": 0, - "owner": "Administrator", - "type": "Donut" - }, - { - "name": "Purchase Order Trends", - "chart_name": _("Purchase Order Trends"), - "chart_type": "Report", - "custom_options": json.dumps({ - "type": "line", - "axisOptions": {"shortenYAxisNumbers": 1}, - "tooltipOptions": {}, - "lineOptions": { - "regionFill": 1 - } - }), - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "period": "Monthly", - "fiscal_year": fiscal_year_name, - "period_based_on": "posting_date", - "based_on": "Item" - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Purchase Order Trends", - "type": "Line" - }, - { - "name": "Top Suppliers", - "chart_name": _("Top Suppliers"), - "chart_type": "Report", - "doctype": "Dashboard Chart", - "filters_json": json.dumps({ - "company": company.name, - "period": "Monthly", - "fiscal_year": fiscal_year_name, - "period_based_on": "posting_date", - "based_on": "Supplier" - }), - "is_custom": 1, - "is_public": 1, - "owner": "Administrator", - "report_name": "Purchase Receipt Trends", - "type": "Bar" - } - ] - -def get_number_cards(company, fiscal_year_name, start_date, end_date): - return [ - { - "name": "Annual Purchase", - "aggregate_function_based_on": "base_net_total", - "doctype": "Number Card", - "document_type": "Purchase Order", - "filters_json": json.dumps([ - ["Purchase Order", "transaction_date", "Between", [start_date, end_date], False], - ["Purchase Order", "status", "not in", ["Draft", "Cancelled", "Closed", None], False], - ["Purchase Order", "docstatus", "=", 1, False], - ["Purchase Order", "company", "=", company.name, False] - ]), - "function": "Sum", - "is_public": 1, - "label": _("Annual Purchase"), - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - }, - { - "name": "Purchase Orders to Receive", - "doctype": "Number Card", - "document_type": "Purchase Order", - "filters_json": json.dumps([ - ["Purchase Order", "status", "in", ["To Receive and Bill", "To Receive", None], False], - ["Purchase Order", "docstatus", "=", 1, False], - ["Purchase Order", "company", "=", company.name, False] - ]), - "function": "Count", - "is_public": 1, - "label": _("Purchase Orders to Receive"), - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Weekly" - }, - { - "name": "Purchase Orders to Bill", - "doctype": "Number Card", - "document_type": "Purchase Order", - "filters_json": json.dumps([ - ["Purchase Order", "status", "in", ["To Receive and Bill", "To Bill", None], False], - ["Purchase Order", "docstatus", "=", 1, False], - ["Purchase Order", "company", "=", company.name, False] - ]), - "function": "Count", - "is_public": 1, - "label": _("Purchase Orders to Bill"), - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Weekly" - }, - { - "name": "Active Suppliers", - "doctype": "Number Card", - "document_type": "Supplier", - "filters_json": json.dumps([["Supplier", "disabled", "=", "0"]]), - "function": "Count", - "is_public": 1, - "label": "Active Suppliers", - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Monthly" - } - ] \ No newline at end of file diff --git a/erpnext/buying/number_card/active_suppliers/active_suppliers.json b/erpnext/buying/number_card/active_suppliers/active_suppliers.json new file mode 100644 index 0000000000..91d5b13b06 --- /dev/null +++ b/erpnext/buying/number_card/active_suppliers/active_suppliers.json @@ -0,0 +1,20 @@ +{ + "creation": "2020-07-20 21:01:02.499689", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Supplier", + "filters_json": "[[\"Supplier\",\"disabled\",\"=\",\"0\"]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Active Suppliers", + "modified": "2020-07-22 12:48:23.295193", + "modified_by": "Administrator", + "module": "Buying", + "name": "Active Suppliers", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/buying/number_card/annual_purchase/annual_purchase.json b/erpnext/buying/number_card/annual_purchase/annual_purchase.json new file mode 100644 index 0000000000..79f1b65388 --- /dev/null +++ b/erpnext/buying/number_card/annual_purchase/annual_purchase.json @@ -0,0 +1,22 @@ +{ + "aggregate_function_based_on": "base_net_total", + "creation": "2020-07-20 21:01:02.367127", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Purchase Order", + "dynamic_filters_json": "[[\"Purchase Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Purchase Order\",\"transaction_date\",\"Timespan\",\"this year\",false],[\"Purchase Order\",\"status\",\"not in\",[\"Draft\",\"Cancelled\",\"Closed\",null],false],[\"Purchase Order\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Annual Purchase", + "modified": "2020-07-22 21:21:58.755188", + "modified_by": "Administrator", + "module": "Buying", + "name": "Annual Purchase", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/buying/number_card/purchase_orders_to_bill/purchase_orders_to_bill.json b/erpnext/buying/number_card/purchase_orders_to_bill/purchase_orders_to_bill.json new file mode 100644 index 0000000000..44a0456390 --- /dev/null +++ b/erpnext/buying/number_card/purchase_orders_to_bill/purchase_orders_to_bill.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 21:01:02.468514", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Purchase Order", + "dynamic_filters_json": "[[\"Purchase Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Purchase Order\",\"status\",\"in\",[\"To Receive and Bill\",\"To Bill\",null],false],[\"Purchase Order\",\"docstatus\",\"=\",1,false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Purchase Orders to Bill", + "modified": "2020-07-22 12:48:10.300711", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Orders to Bill", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/buying/number_card/purchase_orders_to_receive/purchase_orders_to_receive.json b/erpnext/buying/number_card/purchase_orders_to_receive/purchase_orders_to_receive.json new file mode 100644 index 0000000000..442dab4b0c --- /dev/null +++ b/erpnext/buying/number_card/purchase_orders_to_receive/purchase_orders_to_receive.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-20 21:01:02.438012", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Purchase Order", + "dynamic_filters_json": "[[\"Purchase Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Purchase Order\",\"status\",\"in\",[\"To Receive and Bill\",\"To Receive\",null],false],[\"Purchase Order\",\"docstatus\",\"=\",1,false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Purchase Orders to Receive", + "modified": "2020-07-22 12:47:47.460080", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Orders to Receive", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly", + "type": "Document Type" +} \ No newline at end of file From a18f2ec23ebe4ab2e264eab78efb9aa729f5d664 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 23 Jul 2020 11:09:17 +0530 Subject: [PATCH 086/101] fix: setting filter for project in kanban board (#22717) Co-authored-by: Nabin Hait --- erpnext/projects/doctype/project/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 32ea05b42a..6350f86abb 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -472,7 +472,7 @@ def create_kanban_board_if_not_exists(project): from frappe.desk.doctype.kanban_board.kanban_board import quick_kanban_board if not frappe.db.exists('Kanban Board', project): - quick_kanban_board('Task', project, 'status') + quick_kanban_board('Task', project, 'status', project) return True From 9c49f2d88615fc440fe7fc71bbd13077c8ccd61d Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 23 Jul 2020 11:11:23 +0530 Subject: [PATCH 087/101] feat: Enhancement in subscription (#22263) * feat: Add supplier in subscription doctype * fix: Code cleanup * fix: Add dynamic link in subscription invoices * fix: Multiple enhanccement in subscription * feat: Follow calendar months in subscription * fix: Test Cases and patch * fix: Patch * fix: Update patch and add fixes * fix: Update permission for subscription settings * fix: Patch and Test * fix: Add cost center dimension in Subscripiton --- .../doctype/subscription/subscription.js | 10 + .../doctype/subscription/subscription.json | 104 +++++-- .../doctype/subscription/subscription.py | 259 +++++++++++++----- .../doctype/subscription/subscription_list.js | 2 + .../doctype/subscription/test_subscription.py | 217 ++++++++++----- .../subscription_invoice.json | 103 +++---- .../subscription_plan/subscription_plan.json | 41 ++- .../subscription_plan/subscription_plan.py | 32 ++- .../subscription_plan_detail.json | 134 +++------ .../subscription_settings.json | 229 +++++----------- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_subscription.py | 41 +++ 12 files changed, 678 insertions(+), 495 deletions(-) create mode 100644 erpnext/patches/v13_0/update_subscription.py diff --git a/erpnext/accounts/doctype/subscription/subscription.js b/erpnext/accounts/doctype/subscription/subscription.js index dcbec12f8b..ba98eb9b2a 100644 --- a/erpnext/accounts/doctype/subscription/subscription.js +++ b/erpnext/accounts/doctype/subscription/subscription.js @@ -2,6 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on('Subscription', { + setup: function(frm) { + frm.set_query('party_type', function() { + return { + filters : { + name: ['in', ['Customer', 'Supplier']] + } + } + }); + }, + refresh: function(frm) { if(!frm.is_new()){ if(frm.doc.status !== 'Cancelled'){ diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index 32b97ba80b..afb94fe9c9 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -6,14 +6,18 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "customer", - "cb_1", + "party_type", "status", + "cb_1", + "party", "subscription_period", - "start", + "start_date", + "end_date", "cancelation_date", "trial_period_start", "trial_period_end", + "follow_calendar_months", + "generate_new_invoices_past_due_date", "column_break_11", "current_invoice_start", "current_invoice_end", @@ -23,7 +27,8 @@ "sb_4", "plans", "sb_1", - "tax_template", + "sales_tax_template", + "purchase_tax_template", "sb_2", "apply_additional_discount", "cb_2", @@ -32,18 +37,10 @@ "sb_3", "invoices", "accounting_dimensions_section", + "cost_center", "dimension_col_break" ], "fields": [ - { - "fieldname": "customer", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Customer", - "options": "Customer", - "reqd": 1, - "set_only_once": 1 - }, { "allow_on_submit": 1, "fieldname": "cb_1", @@ -53,7 +50,7 @@ "fieldname": "status", "fieldtype": "Select", "label": "Status", - "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid", + "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted", "read_only": 1 }, { @@ -61,12 +58,6 @@ "fieldtype": "Section Break", "label": "Subscription Period" }, - { - "fieldname": "start", - "fieldtype": "Date", - "label": "Subscription Start Date", - "set_only_once": 1 - }, { "fieldname": "cancelation_date", "fieldtype": "Date", @@ -137,16 +128,11 @@ "reqd": 1 }, { + "depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)", "fieldname": "sb_1", "fieldtype": "Section Break", "label": "Taxes" }, - { - "fieldname": "tax_template", - "fieldtype": "Link", - "label": "Sales Taxes and Charges Template", - "options": "Sales Taxes and Charges Template" - }, { "fieldname": "sb_2", "fieldtype": "Section Break", @@ -195,10 +181,74 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "party_type", + "fieldtype": "Link", + "label": "Party Type", + "options": "DocType", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "party", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Party", + "options": "party_type", + "reqd": 1, + "set_only_once": 1 + }, + { + "depends_on": "eval:doc.party_type === 'Customer'", + "fieldname": "sales_tax_template", + "fieldtype": "Link", + "label": "Sales Taxes and Charges Template", + "options": "Sales Taxes and Charges Template" + }, + { + "depends_on": "eval:doc.party_type === 'Supplier'", + "fieldname": "purchase_tax_template", + "fieldtype": "Link", + "label": "Purchase Taxes and Charges Template", + "options": "Purchase Taxes and Charges Template" + }, + { + "default": "0", + "description": "If this is checked subsequent new invoices will be created on calendar month and quarter start dates irrespective of current invoice start date", + "fieldname": "follow_calendar_months", + "fieldtype": "Check", + "label": "Follow Calendar Months", + "set_only_once": 1 + }, + { + "default": "0", + "description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date", + "fieldname": "generate_new_invoices_past_due_date", + "fieldtype": "Check", + "label": "Generate New Invoices Past Due Date" + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "label": "Subscription End Date", + "set_only_once": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Subscription Start Date", + "set_only_once": 1 + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" } ], "links": [], - "modified": "2020-01-27 14:37:32.845173", + "modified": "2020-06-25 10:52:52.265105", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 0933c7e8b8..07525317aa 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt +from frappe.utils.data import nowdate, getdate, cstr, cint, add_days, date_diff, get_last_day, add_to_date, flt from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions @@ -15,7 +15,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g class Subscription(Document): def before_insert(self): # update start just before the subscription doc is created - self.update_subscription_period(self.start) + self.update_subscription_period(self.start_date) def update_subscription_period(self, date=None): """ @@ -35,7 +35,9 @@ class Subscription(Document): If the `date` parameter is not given , it will be automatically set as today's date. """ - if self.trial_period_start and self.is_trialling(): + if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date): + self.current_invoice_start = add_days(self.trial_period_end, 1) + elif self.trial_period_start and self.is_trialling(): self.current_invoice_start = self.trial_period_start elif date: self.current_invoice_start = date @@ -53,15 +55,45 @@ class Subscription(Document): current billing period where `x` is the billing interval from the `Subscription Plan` in the `Subscription`. """ - if self.is_trialling(): + if self.is_trialling() and getdate(self.current_invoice_start) < getdate(self.trial_period_end): self.current_invoice_end = self.trial_period_end else: billing_cycle_info = self.get_billing_cycle_data() if billing_cycle_info: - self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info) + if self.is_new_subscription() and getdate(self.start_date) < getdate(self.current_invoice_start): + self.current_invoice_end = add_to_date(self.start_date, **billing_cycle_info) + + # For cases where trial period is for an entire billing interval + if getdate(self.current_invoice_end) < getdate(self.current_invoice_start): + self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info) + else: + self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info) else: self.current_invoice_end = get_last_day(self.current_invoice_start) + if self.follow_calendar_months: + billing_info = self.get_billing_cycle_and_interval() + billing_interval_count = billing_info[0]['billing_interval_count'] + calendar_months = get_calendar_months(billing_interval_count) + calendar_month = 0 + current_invoice_end_month = getdate(self.current_invoice_end).month + current_invoice_end_year = getdate(self.current_invoice_end).year + + for month in calendar_months: + if month <= current_invoice_end_month: + calendar_month = month + + if cint(calendar_month - billing_interval_count) <= 0 and \ + getdate(self.current_invoice_start).month != 1: + calendar_month = 12 + current_invoice_end_year -= 1 + + self.current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' \ + + cstr(calendar_month) + '-01') + + if self.end_date and getdate(self.current_invoice_end) > getdate(self.end_date): + self.current_invoice_end = self.end_date + @staticmethod def validate_plans_billing_cycle(billing_cycle_data): """ @@ -132,21 +164,22 @@ class Subscription(Document): """ if self.is_trialling(): self.status = 'Trialling' - elif self.status == 'Past Due Date' and self.is_past_grace_period(): + elif self.status == 'Active' and self.end_date and getdate() > getdate(self.end_date): + self.status = 'Completed' + elif self.is_past_grace_period(): subscription_settings = frappe.get_single('Subscription Settings') self.status = 'Cancelled' if cint(subscription_settings.cancel_after_grace) else 'Unpaid' - elif self.status == 'Past Due Date' and not self.has_outstanding_invoice(): - self.status = 'Active' - elif self.current_invoice_is_past_due(): + elif self.current_invoice_is_past_due() and not self.is_past_grace_period(): self.status = 'Past Due Date' + elif not self.has_outstanding_invoice(): + self.status = 'Active' elif self.is_new_subscription(): self.status = 'Active' - # todo: then generate new invoice self.save() def is_trialling(self): """ - Returns `True` if the `Subscription` is trial period. + Returns `True` if the `Subscription` is in trial period. """ return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription() @@ -160,7 +193,7 @@ class Subscription(Document): return True end_date = getdate(end_date) - return getdate(nowdate()) > getdate(end_date) + return getdate() > getdate(end_date) def is_past_grace_period(self): """ @@ -171,7 +204,7 @@ class Subscription(Document): subscription_settings = frappe.get_single('Subscription Settings') grace_period = cint(subscription_settings.grace_period) - return getdate(nowdate()) > add_days(current_invoice.due_date, grace_period) + return getdate() > add_days(current_invoice.due_date, grace_period) def current_invoice_is_past_due(self, current_invoice=None): """ @@ -180,22 +213,24 @@ class Subscription(Document): if not current_invoice: current_invoice = self.get_current_invoice() - if not current_invoice: + if not current_invoice or self.is_paid(current_invoice): return False else: - return getdate(nowdate()) > getdate(current_invoice.due_date) + return getdate() > getdate(current_invoice.due_date) def get_current_invoice(self): """ Returns the most recent generated invoice. """ + doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice' + if len(self.invoices): current = self.invoices[-1] - if frappe.db.exists('Sales Invoice', current.invoice): - doc = frappe.get_doc('Sales Invoice', current.invoice) + if frappe.db.exists(doctype, current.get('invoice')): + doc = frappe.get_doc(doctype, current.get('invoice')) return doc else: - frappe.throw(_('Invoice {0} no longer exists').format(current.invoice)) + frappe.throw(_('Invoice {0} no longer exists').format(current.get('invoice'))) def is_new_subscription(self): """ @@ -206,6 +241,8 @@ class Subscription(Document): def validate(self): self.validate_trial_period() self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval()) + self.validate_end_date() + self.validate_to_follow_calendar_months() def validate_trial_period(self): """ @@ -215,34 +252,72 @@ class Subscription(Document): if getdate(self.trial_period_end) < getdate(self.trial_period_start): frappe.throw(_('Trial Period End Date Cannot be before Trial Period Start Date')) - elif self.trial_period_start or self.trial_period_end: + if self.trial_period_start and not self.trial_period_end: frappe.throw(_('Both Trial Period Start Date and Trial Period End Date must be set')) + if self.trial_period_start and getdate(self.trial_period_start) > getdate(self.start_date): + frappe.throw(_('Trial Period Start date cannot be after Subscription Start Date')) + + def validate_end_date(self): + billing_cycle_info = self.get_billing_cycle_data() + end_date = add_to_date(self.start_date, **billing_cycle_info) + + if self.end_date and getdate(self.end_date) <= getdate(end_date): + frappe.throw(_('Subscription End Date must be after {0} as per the subscription plan').format(end_date)) + + def validate_to_follow_calendar_months(self): + if self.follow_calendar_months: + billing_info = self.get_billing_cycle_and_interval() + + if not self.end_date: + frappe.throw(_('Subscription End Date is mandatory to follow calendar months')) + + if billing_info[0]['billing_interval'] != 'Month': + frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months') + def after_insert(self): # todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype? self.set_subscription_status() def generate_invoice(self, prorate=0): """ - Creates a `Sales Invoice` for the `Subscription`, updates `self.invoices` and + Creates a `Invoice` for the `Subscription`, updates `self.invoices` and saves the `Subscription`. """ + + doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice' + invoice = self.create_invoice(prorate) - self.append('invoices', {'invoice': invoice.name}) + self.append('invoices', { + 'document_type': doctype, + 'invoice': invoice.name + }) + self.save() return invoice def create_invoice(self, prorate): """ - Creates a `Sales Invoice`, submits it and returns it + Creates a `Invoice`, submits it and returns it """ - invoice = frappe.new_doc('Sales Invoice') - invoice.set_posting_time = 1 - invoice.posting_date = self.current_invoice_start - invoice.customer = self.customer + doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice' - ## Add dimesnions in invoice for subscription: + invoice = frappe.new_doc(doctype) + invoice.set_posting_time = 1 + invoice.posting_date = self.current_invoice_start if self.generate_invoice_at_period_start \ + else self.current_invoice_end + + invoice.cost_center = self.cost_center + + if doctype == 'Sales Invoice': + invoice.customer = self.party + else: + invoice.supplier = self.party + if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'): + invoice.apply_tds = 1 + + ## Add dimensions in invoice for subscription: accounting_dimensions = get_accounting_dimensions() for dimension in accounting_dimensions: @@ -255,18 +330,25 @@ class Subscription(Document): # for that reason items_list = self.get_items_from_plans(self.plans, prorate) for item in items_list: - invoice.append('items', item) + invoice.append('items', item) # Taxes - if self.tax_template: - invoice.taxes_and_charges = self.tax_template + tax_template = '' + + if doctype == 'Sales Invoice' and self.sales_tax_template: + tax_template = self.sales_tax_template + if doctype == 'Purchase Invoice' and self.purchase_tax_template: + tax_template = self.purchase_tax_template + + if tax_template: + invoice.taxes_and_charges = tax_template invoice.set_taxes() # Due date invoice.append( 'payment_schedule', { - 'due_date': add_days(self.current_invoice_end, cint(self.days_until_due)), + 'due_date': add_days(invoice.posting_date, cint(self.days_until_due)), 'invoice_portion': 100 } ) @@ -300,13 +382,42 @@ class Subscription(Document): prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) items = [] - customer = self.customer + party = self.party for plan in plans: - item_code = frappe.db.get_value("Subscription Plan", plan.plan, "item") - if not prorate: - items.append({'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, customer)}) + plan_doc = frappe.get_doc('Subscription Plan', plan.plan) + + item_code = plan_doc.item + + if self.party == 'Customer': + deferred_field = 'enable_deferred_revenue' else: - items.append({'item_code': item_code, 'qty': plan.qty, 'rate': (get_plan_rate(plan.plan, plan.qty, customer) * prorate_factor)}) + deferred_field = 'enable_deferred_expense' + + deferred = frappe.db.get_value('Item', item_code, deferred_field) + + if not prorate: + item = {'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, party, + self.current_invoice_start, self.current_invoice_end), 'cost_center': plan_doc.cost_center} + else: + item = {'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, party, + self.current_invoice_start, self.current_invoice_end, prorate_factor), 'cost_center': plan_doc.cost_center} + + if deferred: + item.update({ + deferred_field: deferred, + 'service_start_date': self.current_invoice_start, + 'service_end_date': self.current_invoice_end + }) + + accounting_dimensions = get_accounting_dimensions() + + for dimension in accounting_dimensions: + if plan_doc.get(dimension): + item.update({ + dimension: plan_doc.get(dimension) + }) + + items.append(item) return items @@ -322,12 +433,13 @@ class Subscription(Document): elif self.status in ['Past Due Date', 'Unpaid']: self.process_for_past_due_date() + self.set_subscription_status() + self.save() def is_postpaid_to_invoice(self): - return getdate(nowdate()) > getdate(self.current_invoice_end) or \ - (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \ - not self.has_outstanding_invoice() + return getdate() > getdate(self.current_invoice_end) or \ + (getdate() >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) def is_prepaid_to_invoice(self): if not self.generate_invoice_at_period_start: @@ -337,14 +449,12 @@ class Subscription(Document): return True # Check invoice dates and make sure it doesn't have outstanding invoices - return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice() + return getdate() >= getdate(self.current_invoice_start) - def is_current_invoice_paid(self): - if self.is_new_subscription(): - return False + def is_current_invoice_generated(self): + invoice = self.get_current_invoice() - last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice) - if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid': + if invoice and getdate(self.current_invoice_start) <= getdate(invoice.posting_date) <= getdate(self.current_invoice_end): return True return False @@ -358,21 +468,23 @@ class Subscription(Document): 2. Change the `Subscription` status to 'Past Due Date' 3. Change the `Subscription` status to 'Cancelled' """ - if not self.is_current_invoice_paid() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): - self.generate_invoice() - if self.current_invoice_is_past_due(): - self.status = 'Past Due Date' + if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice(): + self.update_subscription_period(add_days(self.current_invoice_end, 1)) - if self.current_invoice_is_past_due() and getdate(nowdate()) > getdate(self.current_invoice_end): - self.status = 'Past Due Date' + if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') + self.generate_invoice(prorate) - if self.cancel_at_period_end and getdate(nowdate()) > getdate(self.current_invoice_end): + if self.cancel_at_period_end and getdate() > getdate(self.current_invoice_end): self.cancel_subscription_at_period_end() def cancel_subscription_at_period_end(self): """ Called when `Subscription.cancel_at_period_end` is truthy """ + if self.end_date and getdate() < getdate(self.end_date): + return + self.status = 'Cancelled' if not self.cancelation_date: self.cancelation_date = nowdate() @@ -390,14 +502,22 @@ class Subscription(Document): if not current_invoice: frappe.throw(_('Current invoice {0} is missing').format(current_invoice.invoice)) else: - if self.is_not_outstanding(current_invoice): + if not self.has_outstanding_invoice(): self.status = 'Active' - self.update_subscription_period(add_days(self.current_invoice_end, 1)) else: self.set_status_grace_period() + if getdate() > getdate(self.current_invoice_end): + self.update_subscription_period(add_days(self.current_invoice_end, 1)) + + # Generate invoices periodically even if current invoice are unpaid + if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() + or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') + self.generate_invoice(prorate) + @staticmethod - def is_not_outstanding(invoice): + def is_paid(invoice): """ Return `True` if the given invoice is paid """ @@ -407,11 +527,17 @@ class Subscription(Document): """ Returns `True` if the most recent invoice for the `Subscription` is not paid """ + doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice' current_invoice = self.get_current_invoice() - if not current_invoice: - return False + invoice_list = [d.invoice for d in self.invoices] + + outstanding_invoices = frappe.get_all(doctype, fields=['name'], + filters={'status': ('!=', 'Paid'), 'name': ('in', invoice_list)}) + + if outstanding_invoices: + return True else: - return not self.is_not_outstanding(current_invoice) + False def cancel_subscription(self): """ @@ -419,7 +545,7 @@ class Subscription(Document): but it will not affect already created invoices. """ if self.status != 'Cancelled': - to_generate_invoice = True if self.status == 'Active' else False + to_generate_invoice = True if self.status == 'Active' and not self.generate_invoice_at_period_start else False to_prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.status = 'Cancelled' self.cancelation_date = nowdate() @@ -435,7 +561,7 @@ class Subscription(Document): """ if self.status == 'Cancelled': self.status = 'Active' - self.db_set('start', nowdate()) + self.db_set('start_date', nowdate()) self.update_subscription_period(nowdate()) self.invoices = [] self.save() @@ -447,6 +573,14 @@ class Subscription(Document): if invoice: return invoice.precision('grand_total') +def get_calendar_months(billing_interval): + calendar_months = [] + start = 0 + while start < 12: + start += billing_interval + calendar_months.append(start) + + return calendar_months def get_prorata_factor(period_end, period_start): diff = flt(date_diff(nowdate(), period_start) + 1) @@ -469,10 +603,7 @@ def get_all_subscriptions(): """ Returns all `Subscription` documents """ - return frappe.db.sql( - 'select name from `tabSubscription` where status != "Cancelled"', - as_dict=1 - ) + return frappe.db.get_all('Subscription', {'status': ('!=','Cancelled')}) def process(data): diff --git a/erpnext/accounts/doctype/subscription/subscription_list.js b/erpnext/accounts/doctype/subscription/subscription_list.js index abcfc5e696..a4edb77dc9 100644 --- a/erpnext/accounts/doctype/subscription/subscription_list.js +++ b/erpnext/accounts/doctype/subscription/subscription_list.js @@ -4,6 +4,8 @@ frappe.listview_settings['Subscription'] = { return [__("Trialling"), "green"]; } else if(doc.status === 'Active') { return [__("Active"), "green"]; + } else if(doc.status === 'Completed') { + return [__("Completed"), "green"]; } else if(doc.status === 'Past Due Date') { return [__("Past Due Date"), "orange"]; } else if(doc.status === 'Unpaid') { diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 3d96f233b4..f41f08a6c4 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -7,7 +7,7 @@ import unittest import frappe from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor -from frappe.utils.data import nowdate, add_days, add_to_date, add_months, date_diff, flt +from frappe.utils.data import nowdate, add_days, add_to_date, add_months, date_diff, flt, get_date_str def create_plan(): @@ -15,7 +15,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name' plan.item = '_Test Non Stock Item' - plan.price_determination = "Fixed rate" + plan.price_determination = "Fixed Rate" plan.cost = 900 plan.billing_interval = 'Month' plan.billing_interval_count = 1 @@ -25,7 +25,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name 2' plan.item = '_Test Non Stock Item' - plan.price_determination = "Fixed rate" + plan.price_determination = "Fixed Rate" plan.cost = 1999 plan.billing_interval = 'Month' plan.billing_interval_count = 1 @@ -35,12 +35,29 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name 3' plan.item = '_Test Non Stock Item' - plan.price_determination = "Fixed rate" + plan.price_determination = "Fixed Rate" plan.cost = 1999 plan.billing_interval = 'Day' plan.billing_interval_count = 14 plan.insert() + # Defined a quarterly Subscription Plan + if not frappe.db.exists('Subscription Plan', '_Test Plan Name 4'): + plan = frappe.new_doc('Subscription Plan') + plan.plan_name = '_Test Plan Name 4' + plan.item = '_Test Non Stock Item' + plan.price_determination = "Monthly Rate" + plan.cost = 20000 + plan.billing_interval = 'Month' + plan.billing_interval_count = 3 + plan.insert() + + if not frappe.db.exists('Supplier', '_Test Supplier'): + supplier = frappe.new_doc('Supplier') + supplier.supplier_name = '_Test Supplier' + supplier.supplier_group = 'All Supplier Groups' + supplier.insert() + class TestSubscription(unittest.TestCase): def setUp(self): @@ -48,7 +65,8 @@ class TestSubscription(unittest.TestCase): def test_create_subscription_with_trial_with_correct_period(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.trial_period_start = nowdate() subscription.trial_period_end = add_days(nowdate(), 30) subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -56,8 +74,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual(subscription.trial_period_start, nowdate()) self.assertEqual(subscription.trial_period_end, add_days(nowdate(), 30)) - self.assertEqual(subscription.trial_period_start, subscription.current_invoice_start) - self.assertEqual(subscription.trial_period_end, subscription.current_invoice_end) + self.assertEqual(add_days(subscription.trial_period_end, 1), get_date_str(subscription.current_invoice_start)) + self.assertEqual(add_days(subscription.current_invoice_start, 30), get_date_str(subscription.current_invoice_end)) self.assertEqual(subscription.invoices, []) self.assertEqual(subscription.status, 'Trialling') @@ -65,7 +83,8 @@ class TestSubscription(unittest.TestCase): def test_create_subscription_without_trial_with_correct_period(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() @@ -81,7 +100,8 @@ class TestSubscription(unittest.TestCase): def test_create_subscription_trial_with_wrong_dates(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.trial_period_end = nowdate() subscription.trial_period_start = add_days(nowdate(), 30) subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -91,7 +111,8 @@ class TestSubscription(unittest.TestCase): def test_create_subscription_multi_with_different_billing_fails(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.trial_period_end = nowdate() subscription.trial_period_start = add_days(nowdate(), 30) subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) @@ -102,8 +123,9 @@ class TestSubscription(unittest.TestCase): def test_invoice_is_generated_at_end_of_billing_period(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' - subscription.start = '2018-01-01' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' + subscription.start_date = '2018-01-01' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.insert() @@ -114,18 +136,22 @@ class TestSubscription(unittest.TestCase): self.assertEqual(len(subscription.invoices), 1) self.assertEqual(subscription.current_invoice_start, '2018-01-01') - self.assertEqual(subscription.status, 'Past Due Date') + subscription.process() + self.assertEqual(subscription.status, 'Unpaid') subscription.delete() def test_status_goes_back_to_active_after_invoice_is_paid(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start = '2018-01-01' + subscription.start_date = '2018-01-01' subscription.insert() subscription.process() # generate first invoice self.assertEqual(len(subscription.invoices), 1) - self.assertEqual(subscription.status, 'Past Due Date') + + # Status is unpaid as Days until Due is zero and grace period is Zero + self.assertEqual(subscription.status, 'Unpaid') subscription.get_current_invoice() current_invoice = subscription.get_current_invoice() @@ -137,7 +163,7 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(subscription.status, 'Active') - self.assertEqual(subscription.current_invoice_start, add_months(subscription.start, 1)) + self.assertEqual(subscription.current_invoice_start, add_months(subscription.start_date, 1)) self.assertEqual(len(subscription.invoices), 1) subscription.delete() @@ -149,16 +175,17 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start = '2018-01-01' + subscription.start_date = '2018-01-01' subscription.insert() + + self.assertEqual(subscription.status, 'Active') + subscription.process() # generate first invoice - - self.assertEqual(subscription.status, 'Past Due Date') - - subscription.process() # This should change status to Cancelled since grace period is 0 + # And is backdated subscription so subscription will be cancelled after processing self.assertEqual(subscription.status, 'Cancelled') settings.cancel_after_grace = default_grace_period_action @@ -172,16 +199,14 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start = '2018-01-01' + subscription.start_date = '2018-01-01' subscription.insert() subscription.process() # generate first invoice - self.assertEqual(subscription.status, 'Past Due Date') - - subscription.process() - # This should change status to Cancelled since grace period is 0 + # Status is unpaid as Days until Due is zero and grace period is Zero self.assertEqual(subscription.status, 'Unpaid') settings.cancel_after_grace = default_grace_period_action @@ -190,10 +215,11 @@ class TestSubscription(unittest.TestCase): def test_subscription_invoice_days_until_due(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.days_until_due = 10 - subscription.start = add_months(nowdate(), -1) + subscription.start_date = add_months(nowdate(), -1) subscription.insert() subscription.process() # generate first invoice self.assertEqual(len(subscription.invoices), 1) @@ -208,9 +234,10 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start = '2018-01-01' + subscription.start_date = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -232,7 +259,8 @@ class TestSubscription(unittest.TestCase): def test_subscription_remains_active_during_invoice_period(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.process() # no changes expected @@ -258,7 +286,8 @@ class TestSubscription(unittest.TestCase): def test_subscription_cancelation(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -274,7 +303,8 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() @@ -309,7 +339,8 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -329,7 +360,8 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -353,16 +385,14 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start = '2018-01-01' + subscription.start_date = '2018-01-01' subscription.insert() subscription.process() # generate first invoice invoices = len(subscription.invoices) - self.assertEqual(subscription.status, 'Past Due Date') - self.assertEqual(len(subscription.invoices), invoices) - subscription.cancel_subscription() self.assertEqual(subscription.status, 'Cancelled') self.assertEqual(len(subscription.invoices), invoices) @@ -387,15 +417,14 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start = '2018-01-01' + subscription.start_date = '2018-01-01' subscription.insert() subscription.process() # generate first invoice - self.assertEqual(subscription.status, 'Past Due Date') - - subscription.process() + # Status is unpaid as Days until Due is zero and grace period is Zero self.assertEqual(subscription.status, 'Unpaid') subscription.cancel_subscription() @@ -424,16 +453,14 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start = '2018-01-01' + subscription.start_date = '2018-01-01' subscription.insert() + subscription.process() # generate first invoice - - self.assertEqual(subscription.status, 'Past Due Date') - - subscription.process() - # This should change status to Cancelled since grace period is 0 + # This should change status to Unpaid since grace period is 0 self.assertEqual(subscription.status, 'Unpaid') invoice = subscription.get_current_invoice() @@ -445,7 +472,7 @@ class TestSubscription(unittest.TestCase): # A new invoice is generated subscription.process() - self.assertEqual(subscription.status, 'Past Due Date') + self.assertEqual(subscription.status, 'Unpaid') settings.cancel_after_grace = default_grace_period_action settings.save() @@ -453,7 +480,8 @@ class TestSubscription(unittest.TestCase): def test_restart_active_subscription(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() @@ -463,7 +491,8 @@ class TestSubscription(unittest.TestCase): def test_subscription_invoice_discount_percentage(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.additional_discount_percentage = 10 subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() @@ -478,7 +507,8 @@ class TestSubscription(unittest.TestCase): def test_subscription_invoice_discount_amount(self): subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.additional_discount_amount = 11 subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() @@ -495,7 +525,8 @@ class TestSubscription(unittest.TestCase): # Create a non pre-billed subscription, processing should not create # invoices. subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.process() @@ -517,10 +548,12 @@ class TestSubscription(unittest.TestCase): settings.save() subscription = frappe.new_doc('Subscription') - subscription.customer = '_Test Customer' + subscription.party_type = 'Customer' + subscription.party = '_Test Customer' subscription.generate_invoice_at_period_start = True subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() + subscription.process() subscription.cancel_subscription() self.assertEqual(len(subscription.invoices), 1) @@ -538,3 +571,65 @@ class TestSubscription(unittest.TestCase): settings.save() subscription.delete() + + def test_subscription_with_follow_calendar_months(self): + subscription = frappe.new_doc('Subscription') + subscription.party_type = 'Supplier' + subscription.party = '_Test Supplier' + subscription.generate_invoice_at_period_start = 1 + subscription.follow_calendar_months = 1 + + # select subscription start date as '2018-01-15' + subscription.start_date = '2018-01-15' + subscription.end_date = '2018-07-15' + subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1}) + subscription.save() + + # even though subscription starts at '2018-01-15' and Billing interval is Month and count 3 + # First invoice will end at '2018-03-31' instead of '2018-04-14' + self.assertEqual(get_date_str(subscription.current_invoice_end), '2018-03-31') + + def test_subscription_generate_invoice_past_due(self): + subscription = frappe.new_doc('Subscription') + subscription.party_type = 'Supplier' + subscription.party = '_Test Supplier' + subscription.generate_invoice_at_period_start = 1 + subscription.generate_new_invoices_past_due_date = 1 + # select subscription start date as '2018-01-15' + subscription.start_date = '2018-01-01' + subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1}) + subscription.save() + + # Process subscription and create first invoice + # Subscription status will be unpaid since due date has already passed + subscription.process() + self.assertEqual(len(subscription.invoices), 1) + self.assertEqual(subscription.status, 'Unpaid') + + # Now the Subscription is unpaid + # Even then new invoice should be created as we have enabled `generate_new_invoices_past_due_date` in + # subscription + + subscription.process() + self.assertEqual(len(subscription.invoices), 2) + + def test_subscription_without_generate_invoice_past_due(self): + subscription = frappe.new_doc('Subscription') + subscription.party_type = 'Supplier' + subscription.party = '_Test Supplier' + subscription.generate_invoice_at_period_start = 1 + # select subscription start date as '2018-01-15' + subscription.start_date = '2018-01-01' + subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1}) + subscription.save() + + # Process subscription and create first invoice + # Subscription status will be unpaid since due date has already passed + subscription.process() + self.assertEqual(len(subscription.invoices), 1) + self.assertEqual(subscription.status, 'Unpaid') + + subscription.process() + self.assertEqual(len(subscription.invoices), 1) + + diff --git a/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json b/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json index c4bae1d3c3..f54e887f26 100644 --- a/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json +++ b/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json @@ -1,73 +1,40 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-02-26 04:21:41.265055", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-02-26 04:21:41.265055", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "invoice" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "invoice", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Invoice", - "length": 0, - "no_copy": 0, - "options": "Sales Invoice", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "document_type", + "fieldtype": "Link", + "label": "Document Type ", + "options": "DocType", + "read_only": 1 + }, + { + "fieldname": "invoice", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Invoice", + "options": "document_type", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-02-26 10:48:07.033422", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Subscription Invoice", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-06-01 22:23:54.462718", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Subscription Invoice", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index 9f79066235..46ce0939e4 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_rename": 1, "autoname": "field:plan_name", "creation": "2018-02-24 11:31:23.066506", @@ -24,6 +25,7 @@ "column_break_16", "payment_gateway", "accounting_dimensions_section", + "cost_center", "dimension_col_break" ], "fields": [ @@ -60,8 +62,8 @@ { "fieldname": "price_determination", "fieldtype": "Select", - "label": "Price Determination", - "options": "\nFixed rate\nBased on price list", + "label": "Subscription Price Based On", + "options": "\nFixed Rate\nBased On Price List\nMonthly Rate", "reqd": 1 }, { @@ -69,7 +71,7 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:doc.price_determination==\"Fixed rate\"", + "depends_on": "eval:['Fixed Rate', 'Monthly Rate'].includes(doc.price_determination)", "fieldname": "cost", "fieldtype": "Currency", "in_list_view": 1, @@ -136,9 +138,16 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" } ], - "modified": "2019-07-25 18:35:04.362556", + "links": [], + "modified": "2020-06-25 10:53:44.205774", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", @@ -155,6 +164,30 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index 625979bee1..1ca442a453 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import get_first_day, get_last_day, date_diff, flt, getdate from frappe.model.document import Document from erpnext.utilities.product import get_price @@ -17,12 +18,12 @@ class SubscriptionPlan(Document): frappe.throw(_('Billing Interval Count cannot be less than 1')) @frappe.whitelist() -def get_plan_rate(plan, quantity=1, customer=None): +def get_plan_rate(plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1): plan = frappe.get_doc("Subscription Plan", plan) - if plan.price_determination == "Fixed rate": - return plan.cost + if plan.price_determination == "Fixed Rate": + return plan.cost * prorate_factor - elif plan.price_determination == "Based on price list": + elif plan.price_determination == "Based On Price List": if customer: customer_group = frappe.db.get_value("Customer", customer, "customer_group") else: @@ -32,4 +33,25 @@ def get_plan_rate(plan, quantity=1, customer=None): if not price: return 0 else: - return price.price_list_rate + return price.price_list_rate * prorate_factor + + elif plan.price_determination == 'Monthly Rate': + start_date = getdate(start_date) + end_date = getdate(end_date) + + no_of_months = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + 1 + cost = plan.cost * no_of_months + + # Adjust cost if start or end date is not month start or end + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') + + if prorate: + prorate_factor = flt(date_diff(start_date, get_first_day(start_date)) / date_diff( + get_last_day(start_date), get_first_day(start_date)), 1) + + prorate_factor += flt(date_diff(get_last_day(end_date), end_date) / date_diff( + get_last_day(end_date), get_first_day(end_date)), 1) + + cost -= (plan.cost * prorate_factor) + + return cost \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json index ca54a167f5..3e1630342c 100644 --- a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json +++ b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json @@ -1,106 +1,40 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-02-25 07:35:07.736146", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-02-25 07:35:07.736146", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "plan", + "qty" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "qty", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Quantity", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "plan", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Plan", - "length": 0, - "no_copy": 0, - "options": "Subscription Plan", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "plan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Plan", + "options": "Subscription Plan", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-06-20 15:35:13.514699", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Subscription Plan Detail", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-06-14 17:44:05.275100", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Subscription Plan Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription_settings/subscription_settings.json b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json index 8c7c6f34e5..821db7e95c 100644 --- a/erpnext/accounts/doctype/subscription_settings/subscription_settings.json +++ b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json @@ -1,179 +1,76 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-02-26 06:13:37.910139", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-02-26 06:13:37.910139", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "grace_period", + "cancel_after_grace", + "prorate" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "description": "Number of days after invoice date has elapsed before canceling subscription or marking subscription as unpaid", - "fieldname": "grace_period", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Grace Period", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "description": "Number of days after invoice date has elapsed before canceling subscription or marking subscription as unpaid", + "fieldname": "grace_period", + "fieldtype": "Int", + "label": "Grace Period" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "cancel_after_grace", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cancel Invoice After Grace Period", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "cancel_after_grace", + "fieldtype": "Check", + "label": "Cancel Subscription After Grace Period" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "prorate", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Prorate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "1", + "fieldname": "prorate", + "fieldtype": "Check", + "label": "Prorate" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-02-26 13:58:09.455832", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Subscription Settings", - "name_case": "", - "owner": "Administrator", + ], + "issingle": 1, + "links": [], + "modified": "2020-06-23 09:13:44.292792", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Subscription Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index fb88fec175..0c2b873f15 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -697,6 +697,7 @@ execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True) erpnext.patches.v12_0.update_uom_conversion_factor erpnext.patches.v13_0.delete_old_purchase_reports erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions +erpnext.patches.v13_0.update_subscription erpnext.patches.v12_0.unhide_cost_center_field erpnext.patches.v13_0.update_sla_enhancements erpnext.patches.v12_0.update_address_template_for_india diff --git a/erpnext/patches/v13_0/update_subscription.py b/erpnext/patches/v13_0/update_subscription.py new file mode 100644 index 0000000000..871ebf17c4 --- /dev/null +++ b/erpnext/patches/v13_0/update_subscription.py @@ -0,0 +1,41 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from six import iteritems + +def execute(): + + frappe.reload_doc('accounts', 'doctype', 'subscription') + frappe.reload_doc('accounts', 'doctype', 'subscription_invoice') + frappe.reload_doc('accounts', 'doctype', 'subscription_plan') + + if frappe.db.has_column('Subscription', 'customer'): + frappe.db.sql(""" + UPDATE `tabSubscription` + SET + start_date = start, + party_type = 'Customer', + party = customer, + sales_tax_template = tax_template + WHERE IFNULL(party,'') = '' + """) + + frappe.db.sql(""" + UPDATE `tabSubscription Invoice` + SET document_type = 'Sales Invoice' + WHERE IFNULL(document_type, '') = '' + """) + + price_determination_map = { + 'Fixed rate': 'Fixed Rate', + 'Based on price list': 'Based On Price List' + } + + for key, value in iteritems(price_determination_map): + frappe.db.sql(""" + UPDATE `tabSubscription Plan` + SET price_determination = %s + WHERE price_determination = %s + """, (value, key)) \ No newline at end of file From ce5b490f6609c63fdd909eddd8944e02d93affaa Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:04:58 +0530 Subject: [PATCH 088/101] feat : Recruitment analytics (#21732) * feat: recruitment_anlytics * fix: filters * feat: added missing column designation Co-authored-by: Marica --- .../report/recruitment_analytics/__init__.py | 0 .../recruitment_analytics.js | 23 +++ .../recruitment_analytics.json | 27 +++ .../recruitment_analytics.py | 188 ++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 erpnext/hr/report/recruitment_analytics/__init__.py create mode 100644 erpnext/hr/report/recruitment_analytics/recruitment_analytics.js create mode 100644 erpnext/hr/report/recruitment_analytics/recruitment_analytics.json create mode 100644 erpnext/hr/report/recruitment_analytics/recruitment_analytics.py diff --git a/erpnext/hr/report/recruitment_analytics/__init__.py b/erpnext/hr/report/recruitment_analytics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js new file mode 100644 index 0000000000..9620f52000 --- /dev/null +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js @@ -0,0 +1,23 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Recruitment Analytics"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"on_date", + "label": __("On Date"), + "fieldtype": "Date", + "default": frappe.datetime.now_date(), + "reqd": 1, + }, + ] +}; \ No newline at end of file diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.json b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.json new file mode 100644 index 0000000000..30a8e17eb8 --- /dev/null +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "creation": "2020-05-14 16:28:45.743869", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-05-14 16:28:45.743869", + "modified_by": "Administrator", + "module": "HR", + "name": "Recruitment Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Staffing Plan", + "report_name": "Recruitment Analytics", + "report_type": "Script Report", + "roles": [ + { + "role": "HR Manager" + }, + { + "role": "HR User" + } + ] +} \ No newline at end of file diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py new file mode 100644 index 0000000000..867209436c --- /dev/null +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py @@ -0,0 +1,188 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ + +def execute(filters=None): + + if not filters: filters = {} + filters = frappe._dict(filters) + + columns = get_columns() + + data = get_data(filters) + + return columns, data + + +def get_columns(): + return [ + { + "label": _("Staffing Plan"), + "fieldtype": "Link", + "fieldname": "staffing_plan", + "options": "Staffing Plan", + "width": 150 + }, + { + "label": _("Job Opening"), + "fieldtype": "Link", + "fieldname": "job_opening", + "options": "Job Opening", + "width": 100 + }, + { + "label": _("Job Applicant"), + "fieldtype": "Link", + "fieldname": "job_applicant", + "options": "Job Applicant", + "width": 150 + }, + { + "label": _("Applicant name"), + "fieldtype": "data", + "fieldname": "applicant_name", + "width": 120 + }, + { + "label": _("Application Status"), + "fieldtype": "Data", + "fieldname": "application_status", + "width": 100 + }, + { + "label": _("Job Offer"), + "fieldtype": "Link", + "fieldname": "job_offer", + "options": "job Offer", + "width": 150 + }, + { + "label": _("Designation"), + "fieldtype": "Data", + "fieldname": "designation", + "width": 100 + }, + { + "label": _("Offer Date"), + "fieldtype": "date", + "fieldname": "offer_date", + "width": 100 + }, + { + "label": _("Job Offer status"), + "fieldtype": "Data", + "fieldname": "job_offer_status", + "width": 150 + } + ] + +def get_data(filters): + data = [] + staffing_plan_details = get_staffing_plan(filters) + staffing_plan_list = list(set([details["name"] for details in staffing_plan_details])) + sp_jo_map , jo_list = get_job_opening(staffing_plan_list) + jo_ja_map , ja_list = get_job_applicant(jo_list) + ja_joff_map = get_job_offer(ja_list) + + for sp in sp_jo_map.keys(): + parent_row = get_parent_row(sp_jo_map, sp, jo_ja_map, ja_joff_map) + data += parent_row + + return data + + +def get_parent_row(sp_jo_map, sp, jo_ja_map, ja_joff_map): + data = [] + for jo in sp_jo_map[sp]: + row = { + "staffing_plan" : sp, + "job_opening" : jo["name"], + } + data.append(row) + child_row = get_child_row( jo["name"], jo_ja_map, ja_joff_map) + data += child_row + return data + +def get_child_row(jo, jo_ja_map, ja_joff_map): + data = [] + for ja in jo_ja_map[jo]: + row = { + "indent":1, + "job_applicant": ja.name, + "applicant_name": ja.applicant_name, + "application_status": ja.status, + } + if ja.name in ja_joff_map.keys(): + jo_detail =ja_joff_map[ja.name][0] + row["job_offer"] = jo_detail.name + row["job_offer_status"] = jo_detail.status + row["offer_date"]= jo_detail.offer_date.strftime("%d-%m-%Y") + row["designation"] = jo_detail.designation + + data.append(row) + return data + +def get_staffing_plan(filters): + + staffing_plan = frappe.db.sql(""" + select + sp.name, sp.department, spd.designation, spd.vacancies, spd.current_count, spd.parent, sp.to_date + from + `tabStaffing Plan Detail` spd , `tabStaffing Plan` sp + where + spd.parent = sp.name + And + sp.to_date > '{0}' + """.format(filters.on_date), as_dict = 1) + + return staffing_plan + +def get_job_opening(sp_list): + + job_openings = frappe.get_all("Job Opening", filters = [["staffing_plan", "IN", sp_list]], fields =["name", "staffing_plan"]) + + sp_jo_map = {} + jo_list = [] + + for openings in job_openings: + if openings.staffing_plan not in sp_jo_map.keys(): + sp_jo_map[openings.staffing_plan] = [openings] + else: + sp_jo_map[openings.staffing_plan].append(openings) + + jo_list.append(openings.name) + + return sp_jo_map, jo_list + +def get_job_applicant(jo_list): + + jo_ja_map = {} + ja_list =[] + + applicants = frappe.get_all("Job Applicant", filters = [["job_title", "IN", jo_list]], fields =["name", "job_title","applicant_name", 'status']) + + for applicant in applicants: + if applicant.job_title not in jo_ja_map.keys(): + jo_ja_map[applicant.job_title] = [applicant] + else: + jo_ja_map[applicant.job_title].append(applicant) + + ja_list.append(applicant.name) + + return jo_ja_map , ja_list + +def get_job_offer(ja_list): + ja_joff_map = {} + + offers = frappe.get_all("Job offer", filters = [["job_applicant", "IN", ja_list]], fields =["name", "job_applicant", "status", 'offer_date', 'designation']) + + for offer in offers: + if offer.job_applicant not in ja_joff_map.keys(): + ja_joff_map[offer.job_applicant] = [offer] + else: + ja_joff_map[offer.job_applicant].append(offer) + + return ja_joff_map \ No newline at end of file From 9aa06020658dc53f9184f6014226a46ce85cc0e7 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:10:48 +0530 Subject: [PATCH 089/101] Fix: HR and Payroll Dashboard (#22771) * Fix: HR and Payroll Dashboard * fix: requested changes --- .../attendance_count/attendance_count.json | 27 ++ .../department_wise_employee_count.json | 29 +++ .../department_wise_openings.json | 29 +++ .../designation_wise_employee_count.json | 29 +++ .../designation_wise_openings.json | 30 +++ .../gender_diversity_ratio.json | 29 +++ .../job_application_status.json | 29 +++ erpnext/hr/dashboard_fixtures.py | 190 -------------- .../human_resource/human_resource.json | 58 +++++ .../employees_left_(last_year).json | 21 ++ .../new_joinees_(last_year).json | 21 ++ .../total_applicants_(last_month).json | 21 ++ .../total_employees/total_employees.json | 21 ++ .../monthly_attendance_sheet.js | 21 +- .../monthly_attendance_sheet.py | 5 +- .../department_wise_salary(last_month).json | 30 +++ .../designation_wise_salary(last_month).json | 30 +++ .../outgoing_salary/outgoing_salary.json | 29 +++ erpnext/payroll/dashboard_fixtures.py | 100 -------- .../doctype/salary_slip/salary_slip.json | 238 +++++------------- .../total_declaration_submitted.json | 21 ++ .../total_incentive_given(last_month).json | 22 ++ .../total_outgoing_salary(last_month).json | 22 ++ .../total_salary_structure.json | 21 ++ .../payroll_dashboard/payroll/payroll.json | 42 ++++ 25 files changed, 639 insertions(+), 476 deletions(-) create mode 100644 erpnext/hr/dashboard_chart/attendance_count/attendance_count.json create mode 100644 erpnext/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json create mode 100644 erpnext/hr/dashboard_chart/department_wise_openings/department_wise_openings.json create mode 100644 erpnext/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json create mode 100644 erpnext/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json create mode 100644 erpnext/hr/dashboard_chart/gender_diversity_ratio/gender_diversity_ratio.json create mode 100644 erpnext/hr/dashboard_chart/job_application_status/job_application_status.json delete mode 100644 erpnext/hr/dashboard_fixtures.py create mode 100644 erpnext/hr/hr_dashboard/human_resource/human_resource.json create mode 100644 erpnext/hr/number_card/employees_left_(last_year)/employees_left_(last_year).json create mode 100644 erpnext/hr/number_card/new_joinees_(last_year)/new_joinees_(last_year).json create mode 100644 erpnext/hr/number_card/total_applicants_(last_month)/total_applicants_(last_month).json create mode 100644 erpnext/hr/number_card/total_employees/total_employees.json create mode 100644 erpnext/payroll/dashboard_chart/department_wise_salary(last_month)/department_wise_salary(last_month).json create mode 100644 erpnext/payroll/dashboard_chart/designation_wise_salary(last_month)/designation_wise_salary(last_month).json create mode 100644 erpnext/payroll/dashboard_chart/outgoing_salary/outgoing_salary.json delete mode 100644 erpnext/payroll/dashboard_fixtures.py create mode 100644 erpnext/payroll/number_card/total_declaration_submitted/total_declaration_submitted.json create mode 100644 erpnext/payroll/number_card/total_incentive_given(last_month)/total_incentive_given(last_month).json create mode 100644 erpnext/payroll/number_card/total_outgoing_salary(last_month)/total_outgoing_salary(last_month).json create mode 100644 erpnext/payroll/number_card/total_salary_structure/total_salary_structure.json create mode 100644 erpnext/payroll/payroll_dashboard/payroll/payroll.json diff --git a/erpnext/hr/dashboard_chart/attendance_count/attendance_count.json b/erpnext/hr/dashboard_chart/attendance_count/attendance_count.json new file mode 100644 index 0000000000..4666aec4c6 --- /dev/null +++ b/erpnext/hr/dashboard_chart/attendance_count/attendance_count.json @@ -0,0 +1,27 @@ +{ + "chart_name": "Attendance Count", + "chart_type": "Report", + "creation": "2020-07-22 11:56:32.730068", + "custom_options": "{\n\t\t\"type\": \"line\",\n\t\t\"axisOptions\": {\n\t\t\t\"shortenYAxisNumbers\": 1\n\t\t},\n\t\t\"tooltipOptions\": {}\n\t}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"month\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1\",\"year\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getFullYear();\",\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", + "filters_json": "{}", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2020-07-22 14:32:40.334424", + "modified_by": "Administrator", + "module": "HR", + "name": "Attendance Count", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Monthly Attendance Sheet", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 1, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json b/erpnext/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json new file mode 100644 index 0000000000..c21bfb9f36 --- /dev/null +++ b/erpnext/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json @@ -0,0 +1,29 @@ +{ + "chart_name": "Department Wise Employee Count", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:32.760730", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Employee", + "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", + "group_by_based_on": "department", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 14:27:40.574194", + "modified": "2020-07-22 14:33:38.036794", + "modified_by": "Administrator", + "module": "HR", + "name": "Department Wise Employee Count", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Donut", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/hr/dashboard_chart/department_wise_openings/department_wise_openings.json b/erpnext/hr/dashboard_chart/department_wise_openings/department_wise_openings.json new file mode 100644 index 0000000000..b1953d40ff --- /dev/null +++ b/erpnext/hr/dashboard_chart/department_wise_openings/department_wise_openings.json @@ -0,0 +1,29 @@ +{ + "aggregate_function_based_on": "planned_vacancies", + "chart_name": "Department Wise Openings", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:32.849775", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Job Opening", + "filters_json": "[]", + "group_by_based_on": "department", + "group_by_type": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 14:33:44.834801", + "modified": "2020-07-22 14:34:45.273591", + "modified_by": "Administrator", + "module": "HR", + "name": "Department Wise Openings", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json b/erpnext/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json new file mode 100644 index 0000000000..b10235cb8e --- /dev/null +++ b/erpnext/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json @@ -0,0 +1,29 @@ +{ + "chart_name": "Designation Wise Employee Count", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:32.790337", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Employee", + "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", + "group_by_based_on": "designation", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 14:27:40.602783", + "modified": "2020-07-22 14:31:49.665555", + "modified_by": "Administrator", + "module": "HR", + "name": "Designation Wise Employee Count", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Donut", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json b/erpnext/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json new file mode 100644 index 0000000000..49ea98a4fc --- /dev/null +++ b/erpnext/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json @@ -0,0 +1,30 @@ +{ + "aggregate_function_based_on": "planned_vacancies", + "chart_name": "Designation Wise Openings", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:32.820217", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Job Opening", + "dynamic_filters_json": "", + "filters_json": "[]", + "group_by_based_on": "designation", + "group_by_type": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 14:33:44.806626", + "modified": "2020-07-22 14:34:32.711881", + "modified_by": "Administrator", + "module": "HR", + "name": "Designation Wise Openings", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/hr/dashboard_chart/gender_diversity_ratio/gender_diversity_ratio.json b/erpnext/hr/dashboard_chart/gender_diversity_ratio/gender_diversity_ratio.json new file mode 100644 index 0000000000..48578c9728 --- /dev/null +++ b/erpnext/hr/dashboard_chart/gender_diversity_ratio/gender_diversity_ratio.json @@ -0,0 +1,29 @@ +{ + "chart_name": "Gender Diversity Ratio", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:32.667291", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Employee", + "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", + "group_by_based_on": "gender", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 14:27:40.143783", + "modified": "2020-07-22 14:32:50.962459", + "modified_by": "Administrator", + "module": "HR", + "name": "Gender Diversity Ratio", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Pie", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json b/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json new file mode 100644 index 0000000000..bfcfa96752 --- /dev/null +++ b/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json @@ -0,0 +1,29 @@ +{ + "chart_name": "Job Application Status", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:32.699696", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Job Applicant", + "dynamic_filters_json": "", + "filters_json": "[[\"Job Applicant\",\"creation\",\"Previous\",\"1 month\"]]", + "group_by_based_on": "status", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 14:27:40.118498", + "modified": "2020-07-22 14:33:00.404144", + "modified_by": "Administrator", + "module": "HR", + "name": "Job Application Status", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Pie", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/hr/dashboard_fixtures.py b/erpnext/hr/dashboard_fixtures.py deleted file mode 100644 index 1e9b4f3c93..0000000000 --- a/erpnext/hr/dashboard_fixtures.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import erpnext -import json -from frappe import _ - -def get_data(): - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(), - "number_cards": get_number_cards(), - }) - -def get_dashboards(): - dashboards = [] - dashboards.append(get_human_resource_dashboard()) - return dashboards - -def get_human_resource_dashboard(): - return { - "name": "Human Resource", - "dashboard_name": "Human Resource", - "is_default": 1, - "charts": [ - { "chart": "Attendance Count", "width": "Full"}, - { "chart": "Gender Diversity Ratio", "width": "Half"}, - { "chart": "Job Application Status", "width": "Half"}, - { "chart": 'Designation Wise Employee Count', "width": "Half"}, - { "chart": 'Department Wise Employee Count', "width": "Half"}, - { "chart": 'Designation Wise Openings', "width": "Half"}, - { "chart": 'Department Wise Openings', "width": "Half"} - ], - "cards": [ - {"card": "Total Employees"}, - {"card": "New Joinees (Last year)"}, - {'card': "Employees Left (Last year)"}, - {'card': "Total Applicants (Last month)"}, - ] - } - -def get_recruitment_dashboard(): - pass - - -def get_charts(): - company = erpnext.get_default_company() - date = frappe.utils.get_datetime() - - month_map = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov","Dec"] - - - if not company: - company = frappe.db.get_value("Company", {"is_group": 0}, "name") - - dashboard_charts = [ - get_dashboards_chart_doc('Gender Diversity Ratio', "Group By", "Pie", - document_type = "Employee", group_by_type="Count", group_by_based_on="gender", - filters_json = json.dumps([["Employee", "status", "=", "Active"]])) - ] - - dashboard_charts.append( - get_dashboards_chart_doc('Job Application Status', "Group By", "Pie", - document_type = "Job Applicant", group_by_type="Count", group_by_based_on="status", - filters_json = json.dumps([["Job Applicant", "creation", "Previous", "1 month"]])) - ) - - custom_options = '''{ - "type": "line", - "axisOptions": { - "shortenYAxisNumbers": 1 - }, - "tooltipOptions": {} - }''' - - filters_json = json.dumps({ - "month": month_map[date.month - 1], - "year": str(date.year), - "company":company - }) - - dashboard_charts.append( - get_dashboards_chart_doc('Attendance Count', "Report", "Line", - report_name = "Monthly Attendance Sheet", is_custom =1, group_by_type="Count", - filters_json = filters_json, custom_options=custom_options) - ) - - dashboard_charts.append( - get_dashboards_chart_doc('Department Wise Employee Count', "Group By", "Donut", - document_type = "Employee", group_by_type="Count", group_by_based_on="department", - filters_json = json.dumps([["Employee", "status", "=", "Active"]])) - ) - - dashboard_charts.append( - get_dashboards_chart_doc('Designation Wise Employee Count', "Group By", "Donut", - document_type = "Employee", group_by_type="Count", group_by_based_on="designation", - filters_json = json.dumps([["Employee", "status", "=", "Active"]])) - ) - - dashboard_charts.append( - get_dashboards_chart_doc('Designation Wise Openings', "Group By", "Bar", - document_type = "Job Opening", group_by_type="Sum", group_by_based_on="designation", - time_interval = "Monthly", aggregate_function_based_on = "planned_vacancies") - ) - dashboard_charts.append( - get_dashboards_chart_doc('Department Wise Openings', "Group By", "Bar", - document_type = "Job Opening", group_by_type="Sum", group_by_based_on="department", - time_interval = "Monthly", aggregate_function_based_on = "planned_vacancies") - ) - return dashboard_charts - - -def get_number_cards(): - number_cards = [] - - number_cards = [ - get_number_cards_doc("Employee", "Total Employees", filters_json = json.dumps([ - ["Employee","status","=","Active"] - ]) - ) - ] - - number_cards.append( - get_number_cards_doc("Employee", "New Joinees (Last year)", filters_json = json.dumps([ - ["Employee","date_of_joining","Timespan","last year"], - ["Employee","status","=","Active"] - ]) - ) - ) - - number_cards.append( - get_number_cards_doc("Employee", "Employees Left (Last year)", filters_json = json.dumps([ - ["Employee", "relieving_date", "Timespan", "last year"], - ["Employee", "status", "=", "Left"] - ]) - ) - ) - - number_cards.append( - get_number_cards_doc("Job Applicant", "Total Applicants (Last month)", filters_json = json.dumps([ - ["Job Applicant", "creation", "Timespan", "last month"] - ]) - ) - ) - - return number_cards - - -def get_number_cards_doc(document_type, label, **args): - args = frappe._dict(args) - - return { - "doctype": "Number Card", - "document_type": document_type, - "function": args.func or "Count", - "is_public": args.is_public or 1, - "label": _(label), - "name": args.name or label, - "show_percentage_stats": args.show_percentage_stats or 1, - "stats_time_interval": args.stats_time_interval or 'Monthly', - "filters_json": args.filters_json or '[]', - "aggregate_function_based_on": args.aggregate_function_based_on or None - } - -def get_dashboards_chart_doc(name, chart_type, graph_type, **args): - args = frappe._dict(args) - - return { - "name": name, - "chart_name": _(args.chart_name or name), - "chart_type": chart_type, - "document_type": args.document_type or None, - "report_name": args.report_name or None, - "is_custom": args.is_custom or 0, - "group_by_type": args.group_by_type or None, - "group_by_based_on": args.group_by_based_on or None, - "based_on": args.based_on or None, - "value_based_on": args.value_based_on or None, - "number_of_groups": args.number_of_groups or 0, - "is_public": args.is_public or 1, - "timespan": args.timespan or "Last Year", - "time_interval": args.time_interval or "Yearly", - "timeseries": args.timeseries or 0, - "filters_json": args.filters_json or '[]', - "type": graph_type, - "custom_options": args.custom_options or '', - "doctype": "Dashboard Chart", - "aggregate_function_based_on": args.aggregate_function_based_on or None - } \ No newline at end of file diff --git a/erpnext/hr/hr_dashboard/human_resource/human_resource.json b/erpnext/hr/hr_dashboard/human_resource/human_resource.json new file mode 100644 index 0000000000..f74d9a3c57 --- /dev/null +++ b/erpnext/hr/hr_dashboard/human_resource/human_resource.json @@ -0,0 +1,58 @@ +{ + "cards": [ + { + "card": "Total Employees" + }, + { + "card": "New Joinees (Last year)" + }, + { + "card": "Employees Left (Last year)" + }, + { + "card": "Total Applicants (Last month)" + } + ], + "charts": [ + { + "chart": "Attendance Count", + "width": "Full" + }, + { + "chart": "Gender Diversity Ratio", + "width": "Half" + }, + { + "chart": "Job Application Status", + "width": "Half" + }, + { + "chart": "Designation Wise Employee Count", + "width": "Half" + }, + { + "chart": "Department Wise Employee Count", + "width": "Half" + }, + { + "chart": "Designation Wise Openings", + "width": "Half" + }, + { + "chart": "Department Wise Openings", + "width": "Half" + } + ], + "creation": "2020-07-22 11:56:33.015888", + "dashboard_name": "Human Resource", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-22 14:42:12.789249", + "modified_by": "Administrator", + "module": "HR", + "name": "Human Resource", + "owner": "Administrator" +} \ No newline at end of file diff --git a/erpnext/hr/number_card/employees_left_(last_year)/employees_left_(last_year).json b/erpnext/hr/number_card/employees_left_(last_year)/employees_left_(last_year).json new file mode 100644 index 0000000000..6a91912eff --- /dev/null +++ b/erpnext/hr/number_card/employees_left_(last_year)/employees_left_(last_year).json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-22 11:56:32.947790", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Employee", + "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Employee\",\"relieving_date\",\"Timespan\",\"last year\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Employees Left (Last year)", + "modified": "2020-07-23 12:03:26.747447", + "modified_by": "Administrator", + "module": "HR", + "name": "Employees Left (Last year)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/hr/number_card/new_joinees_(last_year)/new_joinees_(last_year).json b/erpnext/hr/number_card/new_joinees_(last_year)/new_joinees_(last_year).json new file mode 100644 index 0000000000..8f5ad9ce31 --- /dev/null +++ b/erpnext/hr/number_card/new_joinees_(last_year)/new_joinees_(last_year).json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-22 11:56:32.914057", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Employee", + "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Employee\",\"date_of_joining\",\"Timespan\",\"last year\",false],[\"Employee\",\"status\",\"=\",\"Active\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "New Joinees (Last year)", + "modified": "2020-07-22 14:32:09.352301", + "modified_by": "Administrator", + "module": "HR", + "name": "New Joinees (Last year)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/hr/number_card/total_applicants_(last_month)/total_applicants_(last_month).json b/erpnext/hr/number_card/total_applicants_(last_month)/total_applicants_(last_month).json new file mode 100644 index 0000000000..1af42cabf6 --- /dev/null +++ b/erpnext/hr/number_card/total_applicants_(last_month)/total_applicants_(last_month).json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-22 11:56:32.977716", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Job Applicant", + "dynamic_filters_json": "", + "filters_json": "[[\"Job Applicant\",\"creation\",\"Timespan\",\"last month\"]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Applicants (Last month)", + "modified": "2020-07-22 14:32:27.656855", + "modified_by": "Administrator", + "module": "HR", + "name": "Total Applicants (Last month)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/hr/number_card/total_employees/total_employees.json b/erpnext/hr/number_card/total_employees/total_employees.json new file mode 100644 index 0000000000..932e255c9c --- /dev/null +++ b/erpnext/hr/number_card/total_employees/total_employees.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-22 11:56:32.874849", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Employee", + "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Employees", + "modified": "2020-07-22 14:31:59.118650", + "modified_by": "Administrator", + "module": "HR", + "name": "Total Employees", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js index bd4ed3c4ca..4b9b928640 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js @@ -5,12 +5,25 @@ frappe.query_reports["Monthly Attendance Sheet"] = { "filters": [ { - "fieldname":"month", + "fieldname": "month", "label": __("Month"), "fieldtype": "Select", - "options": "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug\nSep\nOct\nNov\nDec", - "default": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", - "Dec"][frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth()], + "reqd": 1 , + "options": [ + { "value": 1, "label": __("Jan") }, + { "value": 2, "label": __("Feb") }, + { "value": 3, "label": __("Mar") }, + { "value": 4, "label": __("Apr") }, + { "value": 5, "label": __("May") }, + { "value": 6, "label": __("June") }, + { "value": 7, "label": __("July") }, + { "value": 8, "label": __("Aug") }, + { "value": 9, "label": __("Sep") }, + { "value": 10, "label": __("Oct") }, + { "value": 11, "label": __("Nov") }, + { "value": 12, "label": __("Dec") }, + ], + "default": frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1 }, { "fieldname":"year", diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index 47daab1901..46082129e2 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -248,10 +248,7 @@ def get_conditions(filters): if not (filters.get("month") and filters.get("year")): msgprint(_("Please select month and year"), raise_exception=1) - filters["month"] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", - "Dec"].index(filters.month) + 1 - - filters["total_days_in_month"] = monthrange(cint(filters.year), filters.month)[1] + filters["total_days_in_month"] = monthrange(cint(filters.year), cint(filters.month))[1] conditions = " and month(attendance_date) = %(month)s and year(attendance_date) = %(year)s" diff --git a/erpnext/payroll/dashboard_chart/department_wise_salary(last_month)/department_wise_salary(last_month).json b/erpnext/payroll/dashboard_chart/department_wise_salary(last_month)/department_wise_salary(last_month).json new file mode 100644 index 0000000000..61ae86ff02 --- /dev/null +++ b/erpnext/payroll/dashboard_chart/department_wise_salary(last_month)/department_wise_salary(last_month).json @@ -0,0 +1,30 @@ +{ + "aggregate_function_based_on": "rounded_total", + "chart_name": "Department Wise Salary(Last Month)", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:34.511940", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Salary Slip", + "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false],[\"Salary Slip\",\"start_date\",\"Timespan\",\"last month\",false]]", + "group_by_based_on": "department", + "group_by_type": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 12:46:05.272076", + "modified": "2020-07-22 12:48:12.080992", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Department Wise Salary(Last Month)", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/payroll/dashboard_chart/designation_wise_salary(last_month)/designation_wise_salary(last_month).json b/erpnext/payroll/dashboard_chart/designation_wise_salary(last_month)/designation_wise_salary(last_month).json new file mode 100644 index 0000000000..b3c4e59395 --- /dev/null +++ b/erpnext/payroll/dashboard_chart/designation_wise_salary(last_month)/designation_wise_salary(last_month).json @@ -0,0 +1,30 @@ +{ + "aggregate_function_based_on": "rounded_total", + "chart_name": "Designation Wise Salary(Last Month)", + "chart_type": "Group By", + "creation": "2020-07-22 11:56:34.550339", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Salary Slip", + "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false],[\"Salary Slip\",\"start_date\",\"Timespan\",\"last month\",false]]", + "group_by_based_on": "designation", + "group_by_type": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 12:22:18.412822", + "modified": "2020-07-22 12:39:07.923382", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Designation Wise Salary(Last Month)", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/payroll/dashboard_chart/outgoing_salary/outgoing_salary.json b/erpnext/payroll/dashboard_chart/outgoing_salary/outgoing_salary.json new file mode 100644 index 0000000000..c77c8a5a36 --- /dev/null +++ b/erpnext/payroll/dashboard_chart/outgoing_salary/outgoing_salary.json @@ -0,0 +1,29 @@ +{ + "based_on": "end_date", + "chart_name": "Outgoing Salary", + "chart_type": "Sum", + "creation": "2020-07-22 11:56:34.478848", + "custom_options": "", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Salary Slip", + "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false]]", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 12:11:27.481231", + "modified": "2020-07-22 12:20:05.777715", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Outgoing Salary", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "rounded_total", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/payroll/dashboard_fixtures.py b/erpnext/payroll/dashboard_fixtures.py deleted file mode 100644 index ae7a9ff51a..0000000000 --- a/erpnext/payroll/dashboard_fixtures.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import erpnext -from erpnext.hr.dashboard_fixtures import get_dashboards_chart_doc, get_number_cards_doc -import json -from frappe import _ - -def get_data(): - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(), - "number_cards": get_number_cards(), - }) - -def get_dashboards(): - dashboards = [] - dashboards.append(get_payroll_dashboard()) - return dashboards - -def get_payroll_dashboard(): - return { - "name": "Payroll", - "dashboard_name": "Payroll", - "is_default": 1, - "charts": [ - { "chart": "Outgoing Salary", "width": "Full"}, - { "chart": "Designation Wise Salary(Last Month)", "width": "Half"}, - { "chart": "Department Wise Salary(Last Month)", "width": "Half"}, - ], - "cards": [ - {"card": "Total Declaration Submitted"}, - {"card": "Total Salary Structure"}, - {"card": "Total Incentive Given(Last month)"}, - {"card": "Total Outgoing Salary(Last month)"}, - ] - } - -def get_charts(): - dashboard_charts= [ - get_dashboards_chart_doc('Outgoing Salary', "Sum", "Line", - document_type = "Salary Slip", based_on="end_date", - value_based_on = "rounded_total", time_interval = "Monthly", timeseries = 1, - filters_json = json.dumps([["Salary Slip", "docstatus", "=", 1]])) - ] - - dashboard_charts.append( - get_dashboards_chart_doc('Department Wise Salary(Last Month)', "Group By", "Bar", - document_type = "Salary Slip", group_by_type="Sum", group_by_based_on="department", - time_interval = "Monthly", aggregate_function_based_on = "rounded_total", - filters_json = json.dumps([ - ["Salary Slip", "docstatus", "=", 1], - ["Salary Slip", "start_date", "Previous","1 month"] - ]) - ) - ) - - dashboard_charts.append( - get_dashboards_chart_doc('Designation Wise Salary(Last Month)', "Group By", "Bar", - document_type = "Salary Slip", group_by_type="Sum", group_by_based_on="designation", - time_interval = "Monthly", aggregate_function_based_on = "rounded_total", - filters_json = json.dumps([ - ["Salary Slip", "docstatus", "=", 1], - ["Salary Slip", "start_date", "Previous","1 month"] - ]) - ) - ) - - return dashboard_charts - -def get_number_cards(): - number_cards = [get_number_cards_doc("Employee Tax Exemption Declaration", "Total Declaration Submitted", filters_json = json.dumps([ - ["Employee Tax Exemption Declaration", "docstatus", "=","1"], - ["Employee Tax Exemption Declaration","creation","Previous","1 year"] - ]) - )] - - number_cards.append(get_number_cards_doc("Employee Incentive", "Total Incentive Given(Last month)", - time_interval = "Monthly", func = "Sum", aggregate_function_based_on = "incentive_amount", - filters_json = json.dumps([ - ["Employee Incentive", "docstatus", "=", 1], - ["Employee Incentive","payroll_date","Previous","1 year"] - ])) - ) - - number_cards.append(get_number_cards_doc("Salary Slip", "Total Outgoing Salary(Last month)", - time_interval = "Monthly", time_span= "Monthly", func = "Sum", aggregate_function_based_on = "rounded_total", - filters_json = json.dumps([ - ["Salary Slip", "docstatus", "=", 1], - ["Salary Slip", "start_date","Previous","1 month"] - ])) - ) - number_cards.append(get_number_cards_doc("Salary Structure", "Total Salary Structure", - filters_json = json.dumps([ - ["Salary Structure", "docstatus", "=", 1] - ])) - ) - - return number_cards \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 88931c2a4b..27a974ac83 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -74,9 +74,7 @@ "fieldtype": "Date", "in_list_view": 1, "label": "Posting Date", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "employee", @@ -89,9 +87,7 @@ "oldfieldtype": "Link", "options": "Employee", "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fetch_from": "employee.employee_name", @@ -102,9 +98,7 @@ "label": "Employee Name", "oldfieldname": "employee_name", "oldfieldtype": "Data", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fetch_from": "employee.department", @@ -115,20 +109,18 @@ "oldfieldname": "department", "oldfieldtype": "Link", "options": "Department", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:doc.designation", "fetch_from": "employee.designation", "fieldname": "designation", - "fieldtype": "Read Only", + "fieldtype": "Link", "label": "Designation", "oldfieldname": "designation", "oldfieldtype": "Link", - "show_days": 1, - "show_seconds": 1 + "options": "Designation", + "read_only": 1 }, { "fetch_from": "employee.branch", @@ -139,16 +131,12 @@ "oldfieldname": "branch", "oldfieldtype": "Link", "options": "Branch", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -156,27 +144,21 @@ "fieldtype": "Select", "label": "Status", "options": "Draft\nSubmitted\nCancelled", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "journal_entry", "fieldtype": "Link", "label": "Journal Entry", "options": "Journal Entry", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "payroll_entry", "fieldtype": "Link", "label": "Payroll Entry", "options": "Payroll Entry", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "company", @@ -186,9 +168,7 @@ "label": "Company", "options": "Company", "remember_last_selected_value": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "allow_on_submit": 1, @@ -197,62 +177,46 @@ "ignore_user_permissions": 1, "label": "Letter Head", "options": "Letter Head", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_10", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "default": "0", "fieldname": "salary_slip_based_on_timesheet", "fieldtype": "Check", "label": "Salary Slip Based on Timesheet", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "start_date", "fieldtype": "Date", - "label": "Start Date", - "show_days": 1, - "show_seconds": 1 + "label": "Start Date" }, { "fieldname": "end_date", "fieldtype": "Date", - "label": "End Date", - "show_days": 1, - "show_seconds": 1 + "label": "End Date" }, { "fieldname": "column_break_15", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "salary_structure", "fieldtype": "Link", "label": "Salary Structure", "options": "Salary Structure", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)", "fieldname": "payroll_frequency", "fieldtype": "Select", "label": "Payroll Frequency", - "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily", - "show_days": 1, - "show_seconds": 1 + "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily" }, { "fieldname": "total_working_days", @@ -261,18 +225,14 @@ "oldfieldname": "total_days_in_month", "oldfieldtype": "Int", "read_only": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "leave_without_pay", "fieldtype": "Float", "label": "Leave Without Pay", "oldfieldname": "leave_without_pay", - "oldfieldtype": "Currency", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Currency" }, { "fieldname": "payment_days", @@ -281,52 +241,38 @@ "oldfieldname": "payment_days", "oldfieldtype": "Float", "read_only": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "hourly_wages", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "timesheets", "fieldtype": "Table", "label": "Salary Slip Timesheet", - "options": "Salary Slip Timesheet", - "show_days": 1, - "show_seconds": 1 + "options": "Salary Slip Timesheet" }, { "fieldname": "column_break_20", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "total_working_hours", "fieldtype": "Float", "label": "Total Working Hours", - "print_hide_if_no_value": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide_if_no_value": 1 }, { "fieldname": "hour_rate", "fieldtype": "Currency", "label": "Hour Rate", "options": "Company:company:default_currency", - "print_hide_if_no_value": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide_if_no_value": 1 }, { "fieldname": "section_break_26", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "bank_name", @@ -334,9 +280,7 @@ "label": "Bank Name", "oldfieldname": "bank_name", "oldfieldtype": "Data", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "bank_account_no", @@ -344,46 +288,34 @@ "label": "Bank Account No.", "oldfieldname": "bank_account_no", "oldfieldtype": "Data", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "section_break_32", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "default": "0", "fieldname": "deduct_tax_for_unclaimed_employee_benefits", "fieldtype": "Check", - "label": "Deduct Tax For Unclaimed Employee Benefits", - "show_days": 1, - "show_seconds": 1 + "label": "Deduct Tax For Unclaimed Employee Benefits" }, { "default": "0", "fieldname": "deduct_tax_for_unsubmitted_tax_exemption_proof", "fieldtype": "Check", - "label": "Deduct Tax For Unsubmitted Tax Exemption Proof", - "show_days": 1, - "show_seconds": 1 + "label": "Deduct Tax For Unsubmitted Tax Exemption Proof" }, { "fieldname": "earning_deduction", "fieldtype": "Section Break", "label": "Earning & Deduction", - "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Section Break" }, { "fieldname": "earning", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -392,16 +324,12 @@ "label": "Earnings", "oldfieldname": "earning_details", "oldfieldtype": "Table", - "options": "Salary Detail", - "show_days": 1, - "show_seconds": 1 + "options": "Salary Detail" }, { "fieldname": "deduction", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -410,16 +338,12 @@ "label": "Deductions", "oldfieldname": "deduction_details", "oldfieldtype": "Table", - "options": "Salary Detail", - "show_days": 1, - "show_seconds": 1 + "options": "Salary Detail" }, { "fieldname": "totals", "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Section Break" }, { "fieldname": "gross_pay", @@ -428,15 +352,11 @@ "oldfieldname": "gross_pay", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_25", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "total_deduction", @@ -445,32 +365,24 @@ "oldfieldname": "total_deduction", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "total_loan_repayment", "fieldname": "loan_repayment", "fieldtype": "Section Break", - "label": "Loan repayment", - "show_days": 1, - "show_seconds": 1 + "label": "Loan repayment" }, { "fieldname": "loans", "fieldtype": "Table", "label": "Employee Loan", "options": "Salary Slip Loan", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_43", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "default": "0", @@ -478,9 +390,7 @@ "fieldtype": "Currency", "label": "Total Principal Amount", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", @@ -488,15 +398,11 @@ "fieldtype": "Currency", "label": "Total Interest Amount", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_45", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0", @@ -504,16 +410,12 @@ "fieldtype": "Currency", "label": "Total Loan Repayment", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "net_pay_info", "fieldtype": "Section Break", - "label": "net pay info", - "show_days": 1, - "show_seconds": 1 + "label": "net pay info" }, { "description": "Gross Pay - Total Deduction - Loan Repayment", @@ -523,15 +425,11 @@ "oldfieldname": "net_pay", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_53", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "bold": 1, @@ -539,15 +437,11 @@ "fieldtype": "Currency", "label": "Rounded Total", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "section_break_55", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "description": "Net Pay (in words) will be visible once you save the Salary Slip.", @@ -556,9 +450,7 @@ "label": "Total in words", "oldfieldname": "net_pay_in_words", "oldfieldtype": "Data", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "amended_from", @@ -570,9 +462,7 @@ "oldfieldtype": "Data", "options": "Salary Slip", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fetch_from": "employee.payroll_cost_center", @@ -581,40 +471,32 @@ "fieldtype": "Link", "label": "Payroll Cost Center", "options": "Cost Center", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "mode_of_payment", "fieldtype": "Select", "label": "Mode Of Payment", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "absent_days", "fieldtype": "Float", "label": "Absent Days", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "unmarked_days", "fieldtype": "Float", "hidden": 1, - "label": "Unmarked days", - "show_days": 1, - "show_seconds": 1 + "label": "Unmarked days" } ], "icon": "fa fa-file-text", "idx": 9, "is_submittable": 1, "links": [], - "modified": "2020-06-25 14:42:43.921828", + "modified": "2020-07-22 12:41:03.659422", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/erpnext/payroll/number_card/total_declaration_submitted/total_declaration_submitted.json b/erpnext/payroll/number_card/total_declaration_submitted/total_declaration_submitted.json new file mode 100644 index 0000000000..fa5739b2f3 --- /dev/null +++ b/erpnext/payroll/number_card/total_declaration_submitted/total_declaration_submitted.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-22 11:56:34.575627", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Employee Tax Exemption Declaration", + "dynamic_filters_json": "[[\"Employee Tax Exemption Declaration\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Employee Tax Exemption Declaration\",\"creation\",\"Timespan\",\"last year\",false],[\"Employee Tax Exemption Declaration\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Declaration Submitted", + "modified": "2020-07-22 13:22:46.001099", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Total Declaration Submitted", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/payroll/number_card/total_incentive_given(last_month)/total_incentive_given(last_month).json b/erpnext/payroll/number_card/total_incentive_given(last_month)/total_incentive_given(last_month).json new file mode 100644 index 0000000000..2106706173 --- /dev/null +++ b/erpnext/payroll/number_card/total_incentive_given(last_month)/total_incentive_given(last_month).json @@ -0,0 +1,22 @@ +{ + "aggregate_function_based_on": "incentive_amount", + "creation": "2020-07-22 11:56:34.599047", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Employee Incentive", + "dynamic_filters_json": "", + "filters_json": "[[\"Employee Incentive\",\"docstatus\",\"=\",\"1\",false],[\"Employee Incentive\",\"payroll_date\",\"Timespan\",\"last year\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Incentive Given(Last month)", + "modified": "2020-07-23 12:05:26.963616", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Total Incentive Given(Last month)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/payroll/number_card/total_outgoing_salary(last_month)/total_outgoing_salary(last_month).json b/erpnext/payroll/number_card/total_outgoing_salary(last_month)/total_outgoing_salary(last_month).json new file mode 100644 index 0000000000..44ee72203f --- /dev/null +++ b/erpnext/payroll/number_card/total_outgoing_salary(last_month)/total_outgoing_salary(last_month).json @@ -0,0 +1,22 @@ +{ + "aggregate_function_based_on": "rounded_total", + "creation": "2020-07-22 11:56:34.626019", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Salary Slip", + "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false],[\"Salary Slip\",\"start_date\",\"Timespan\",\"last month\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Outgoing Salary(Last month)", + "modified": "2020-07-22 13:54:14.678954", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Total Outgoing Salary(Last month)", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/payroll/number_card/total_salary_structure/total_salary_structure.json b/erpnext/payroll/number_card/total_salary_structure/total_salary_structure.json new file mode 100644 index 0000000000..030935f96d --- /dev/null +++ b/erpnext/payroll/number_card/total_salary_structure/total_salary_structure.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-22 11:56:34.688843", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Salary Structure", + "dynamic_filters_json": "[[\"Salary Structure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Salary Structure\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Salary Structure", + "modified": "2020-07-22 13:24:03.938846", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Total Salary Structure", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/payroll/payroll_dashboard/payroll/payroll.json b/erpnext/payroll/payroll_dashboard/payroll/payroll.json new file mode 100644 index 0000000000..fb49d88de7 --- /dev/null +++ b/erpnext/payroll/payroll_dashboard/payroll/payroll.json @@ -0,0 +1,42 @@ +{ + "cards": [ + { + "card": "Total Declaration Submitted" + }, + { + "card": "Total Salary Structure" + }, + { + "card": "Total Incentive Given(Last month)" + }, + { + "card": "Total Outgoing Salary(Last month)" + } + ], + "charts": [ + { + "chart": "Outgoing Salary", + "width": "Full" + }, + { + "chart": "Designation Wise Salary(Last Month)", + "width": "Half" + }, + { + "chart": "Department Wise Salary(Last Month)", + "width": "Half" + } + ], + "creation": "2020-07-22 11:56:34.727185", + "dashboard_name": "Payroll", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 1, + "is_standard": 1, + "modified": "2020-07-22 13:20:18.608969", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Payroll", + "owner": "Administrator" +} \ No newline at end of file From 7ac4ad8410a1ab6a4c29e4299d042906c186ad89 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:12:55 +0530 Subject: [PATCH 090/101] fix: Error due to comma in Pricing rule name (#22741) * fix: Error due to commma in Pricing rule name * fix: Remove print statement * fix: Tests Co-authored-by: Marica --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 4 ++-- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- erpnext/controllers/taxes_and_totals.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index d4d83af1ed..d90ae28e5a 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -276,7 +276,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa item_details.has_pricing_rule = 1 - item_details.pricing_rules = ','.join([d.pricing_rule for d in rules]) + item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules]) if not doc: return item_details @@ -366,7 +366,7 @@ def set_discount_amount(rate, item_details): def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items - for d in pricing_rules.split(','): + for d in json.loads(pricing_rules): if not d or not frappe.db.exists("Pricing Rule", d): continue pricing_rule = frappe.get_cached_doc('Pricing Rule', d) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index ad983830f3..3fd316f75e 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -448,7 +448,7 @@ def apply_pricing_rule_on_transaction(doc): doc.set_missing_values() def get_applied_pricing_rules(item_row): - return (item_row.get("pricing_rules").split(',') + return (json.loads(item_row.get("pricing_rules")) if item_row.get("pricing_rules") else []) def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index a9eb9963bf..6449c71edd 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -597,7 +597,7 @@ class calculate_taxes_and_totals(object): base_rate_with_margin = 0.0 if item.price_list_rate: if item.pricing_rules and not self.doc.ignore_pricing_rule: - for d in item.pricing_rules.split(','): + for d in json.loads(item.pricing_rules): pricing_rule = frappe.get_cached_doc('Pricing Rule', d) if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ From 453325fbc9534accb79106c3eedd5c752992b824 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:22:42 +0530 Subject: [PATCH 091/101] fix: job applicant fixes (#22448) * fix(validation): not two job offer should be allowed for single job_applicant * fix: don't allow to create employee_onboarding without job offer * fix: server side validation * fix: requested changes --- .../employee_onboarding.js | 12 +- .../employee_onboarding.json | 781 ++++-------------- .../employee_onboarding.py | 6 + .../hr/doctype/job_applicant/job_applicant.js | 4 + erpnext/hr/doctype/job_offer/job_offer.py | 3 + 5 files changed, 206 insertions(+), 600 deletions(-) diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js index c1285675e5..d6047e1846 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js @@ -8,10 +8,20 @@ frappe.ui.form.on('Employee Onboarding', { frm.add_fetch("employee_onboarding_template", "designation", "designation"); frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade"); + + frm.set_query("job_applicant", function () { + return { + filters:{ + "status": "Accepted", + } + }; + }); + frm.set_query('job_offer', function () { return { filters: { - 'job_applicant': frm.doc.job_applicant + 'job_applicant': frm.doc.job_applicant, + 'docstatus': 1 } }; }); diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json index 3b95cabf8f..783c7574ef 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json @@ -1,620 +1,203 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "HR-EMP-ONB-.YYYY.-.#####", - "beta": 0, - "creation": "2018-05-09 04:57:20.016220", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "HR-EMP-ONB-.YYYY.-.#####", + "creation": "2018-05-09 04:57:20.016220", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "job_applicant", + "job_offer", + "employee_name", + "employee", + "date_of_joining", + "boarding_status", + "notify_users_by_email", + "column_break_7", + "employee_onboarding_template", + "company", + "department", + "designation", + "employee_grade", + "project", + "table_for_activity", + "activities", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "job_applicant", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Job Applicant", - "length": 0, - "no_copy": 0, - "options": "Job Applicant", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "job_offer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Job Offer", - "length": 0, - "no_copy": 0, - "options": "Job Offer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "job_applicant.applicant_name", - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "date_of_joining", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Date of Joining", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "boarding_status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nPending\nIn Process\nCompleted", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "job_applicant", + "fieldtype": "Link", + "label": "Job Applicant", + "options": "Job Applicant", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "notify_users_by_email", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notify users by email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "job_offer", + "fieldtype": "Link", + "label": "Job Offer", + "options": "Job Offer", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "job_applicant.applicant_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Employee Name", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee_onboarding_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee Onboarding Template", - "length": 0, - "no_copy": 0, - "options": "Employee Onboarding Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "date_of_joining", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date of Joining", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "boarding_status", + "fieldtype": "Select", + "label": "Status", + "options": "\nPending\nIn Process\nCompleted", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "designation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Designation", - "length": 0, - "no_copy": 0, - "options": "Designation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "notify_users_by_email", + "fieldtype": "Check", + "label": "Notify users by email", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee_grade", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee Grade", - "length": 0, - "no_copy": 0, - "options": "Employee Grade", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "employee_onboarding_template", + "fieldtype": "Link", + "label": "Employee Onboarding Template", + "options": "Employee Onboarding Template", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "table_for_activity", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "activities", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Activities", - "length": 0, - "no_copy": 0, - "options": "Employee Boarding Activity", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Department", + "options": "Department", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Employee Onboarding", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "designation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Designation", + "options": "Designation", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "employee_grade", + "fieldtype": "Link", + "label": "Employee Grade", + "options": "Employee Grade", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "table_for_activity", + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "activities", + "fieldtype": "Table", + "label": "Activities", + "options": "Employee Boarding Activity", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Onboarding", + "print_hide": 1, + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-08-01 16:15:55.968224", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Onboarding", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2020-06-25 15:22:24.923835", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Onboarding", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "employee_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py index 19ff3bd497..6cc2bf5cd8 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py @@ -13,6 +13,12 @@ class IncompleteTaskError(frappe.ValidationError): pass class EmployeeOnboarding(EmployeeBoardingController): def validate(self): super(EmployeeOnboarding, self).validate() + self.validate_duplicate_employee_onboarding() + + def validate_duplicate_employee_onboarding(self): + emp_onboarding = frappe.db.exists("Employee Onboarding",{"job_applicant": self.job_applicant}) + if emp_onboarding and emp_onboarding != self.name: + frappe.throw(_("Employee Onboarding: {0} is already for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant))) def validate_employee_creation(self): if self.docstatus != 1: diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js index 05071e1974..c62515597c 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.js +++ b/erpnext/hr/doctype/job_applicant/job_applicant.js @@ -10,10 +10,14 @@ frappe.ui.form.on("Job Applicant", { refresh: function(frm) { if (!frm.doc.__islocal) { if (frm.doc.__onload && frm.doc.__onload.job_offer) { + $('[data-doctype="Employee Onboarding"]').find("button").show(); + $('[data-doctype="Job Offer"]').find("button").hide(); frm.add_custom_button(__("Job Offer"), function() { frappe.set_route("Form", "Job Offer", frm.doc.__onload.job_offer); }, __("View")); } else { + $('[data-doctype="Employee Onboarding"]').find("button").hide(); + $('[data-doctype="Job Offer"]').find("button").show(); frm.add_custom_button(__("Job Offer"), function() { frappe.route_options = { "job_applicant": frm.doc.name, diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index f9ee44a4de..e7e1a37480 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -15,6 +15,9 @@ class JobOffer(Document): def validate(self): self.validate_vacancies() + job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant}) + if job_offer and job_offer != self.name: + frappe.throw(_("Job Offer: {0} is already for Job Applicant: {1}").format(frappe.bold(job_offer), frappe.bold(self.job_applicant))) def validate_vacancies(self): staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date) From 23481cc484492678e36d6d0476a75b48b9e46f8f Mon Sep 17 00:00:00 2001 From: Anoop Date: Thu, 23 Jul 2020 15:00:54 +0530 Subject: [PATCH 092/101] feat: Laboratory Module Enhancements (#22416) * feat: option to add organism count in descriptive tests print format to accommodate descriptive test changes fix: refactoring, clean up wip * lab test - result based on secondary uom, result format options lab test print - consider result format options, result legend and worksheet instructions * feat: option to allow results blank in lab test if configured via lab test template * fix: rename child doctypes, rename related links * fix: field display depends on, cleanup - rename fields and variables * fix: get from encounter popup layout, code cleanup and minor fixes * fix: sms send dialog bug * fix: lab test get from encounter html class name typo corrected * fix: lab test organisms depends on condition * fix: reorder fields - depends on fields brought together * fix: PR review feedback, removed : separator, result formatting applied to result UOM * fix: child table js removed * fix: internal field require_result_value hidden, secondary uom result field type set to data * fix: Use ORM methods instead of db.sql, code cleanup, style * fix: create item price for lab test template if billable, code cleanup * fix: patch rename doctypes before fields, deleted renamed child doctypes * fix: secondary uom result calc - warn user if calculation fails * fix: patch to insert entries into renamed child tables * fix: codacy issues * fix: patch duplicate error as items are already migrated in v12 migrate organisms child table field name updated * fix: patch a bit lenghty, added comments * fix: patch * fix: require_result_value flag marked hidden * fix: code cleanup-up * fix: patch - lab_test rename field, lab_test_group_template select option corrected Co-authored-by: Rucha Mahabal --- .../doctype/antibiotic/antibiotic.json | 244 ++++--- .../__init__.py | 0 .../descriptive_test_result.json | 74 ++ .../descriptive_test_result.py} | 2 +- .../__init__.py | 0 .../descriptive_test_template.json | 41 ++ .../descriptive_test_template.py | 9 + .../healthcare_settings.json | 4 +- .../healthcare/doctype/lab_test/lab_test.js | 319 +++++---- .../healthcare/doctype/lab_test/lab_test.json | 223 +++++-- .../healthcare/doctype/lab_test/lab_test.py | 322 +++++---- .../doctype/lab_test/lab_test_list.js | 56 +- .../__init__.py | 0 .../lab_test_group_template.json | 118 ++++ .../lab_test_group_template.py} | 2 +- .../lab_test_groups/lab_test_groups.json | 310 --------- .../lab_test_template/lab_test_template.js | 77 +-- .../lab_test_template/lab_test_template.json | 112 +++- .../lab_test_template/lab_test_template.py | 120 ++-- .../lab_test_template_list.js | 4 +- .../normal_test_items/normal_test_items.js | 4 - .../normal_test_items/normal_test_items.json | 301 --------- .../__init__.py | 0 .../normal_test_result.json | 186 ++++++ .../normal_test_result.py} | 2 +- .../normal_test_template.json | 268 +++----- .../__init__.py | 0 .../healthcare/doctype/organism/organism.js | 5 + .../healthcare/doctype/organism/organism.json | 152 +++++ .../healthcare/doctype/organism/organism.py | 9 + .../doctype/organism/test_organism.js | 23 + .../doctype/organism/test_organism.py | 8 + .../doctype/organism_test_item/__init__.py | 0 .../organism_test_item.json | 144 ++++ .../organism_test_item/organism_test_item.py | 9 + .../doctype/organism_test_result/__init__.py | 0 .../organism_test_result.json | 144 ++++ .../organism_test_result.py | 9 + .../sensitivity_test_items.json | 103 --- .../sensitivity_test_result/__init__.py | 0 .../sensitivity_test_result.json | 103 +++ .../sensitivity_test_result.py} | 2 +- .../special_test_items.json | 175 ----- .../special_test_template.json | 72 -- .../special_test_template.py | 9 - .../lab_test_print/lab_test_print.json | 7 +- .../web_form/lab_test/lab_test.json | 630 ++++++++++++------ erpnext/patches.txt | 1 + .../healthcare_lab_module_rename_doctypes.py | 51 ++ 49 files changed, 2439 insertions(+), 2015 deletions(-) rename erpnext/healthcare/doctype/{lab_test_groups => descriptive_test_result}/__init__.py (100%) create mode 100644 erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json rename erpnext/healthcare/doctype/{sensitivity_test_items/sensitivity_test_items.py => descriptive_test_result/descriptive_test_result.py} (84%) rename erpnext/healthcare/doctype/{normal_test_items => descriptive_test_template}/__init__.py (100%) create mode 100644 erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json create mode 100644 erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py rename erpnext/healthcare/doctype/{sensitivity_test_items => lab_test_group_template}/__init__.py (100%) create mode 100644 erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json rename erpnext/healthcare/doctype/{lab_test_groups/lab_test_groups.py => lab_test_group_template/lab_test_group_template.py} (84%) delete mode 100644 erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.json delete mode 100644 erpnext/healthcare/doctype/normal_test_items/normal_test_items.js delete mode 100644 erpnext/healthcare/doctype/normal_test_items/normal_test_items.json rename erpnext/healthcare/doctype/{special_test_items => normal_test_result}/__init__.py (100%) create mode 100644 erpnext/healthcare/doctype/normal_test_result/normal_test_result.json rename erpnext/healthcare/doctype/{normal_test_items/normal_test_items.py => normal_test_result/normal_test_result.py} (85%) rename erpnext/healthcare/doctype/{special_test_template => organism}/__init__.py (100%) create mode 100644 erpnext/healthcare/doctype/organism/organism.js create mode 100644 erpnext/healthcare/doctype/organism/organism.json create mode 100644 erpnext/healthcare/doctype/organism/organism.py create mode 100644 erpnext/healthcare/doctype/organism/test_organism.js create mode 100644 erpnext/healthcare/doctype/organism/test_organism.py create mode 100644 erpnext/healthcare/doctype/organism_test_item/__init__.py create mode 100644 erpnext/healthcare/doctype/organism_test_item/organism_test_item.json create mode 100644 erpnext/healthcare/doctype/organism_test_item/organism_test_item.py create mode 100644 erpnext/healthcare/doctype/organism_test_result/__init__.py create mode 100644 erpnext/healthcare/doctype/organism_test_result/organism_test_result.json create mode 100644 erpnext/healthcare/doctype/organism_test_result/organism_test_result.py delete mode 100644 erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.json create mode 100644 erpnext/healthcare/doctype/sensitivity_test_result/__init__.py create mode 100644 erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json rename erpnext/healthcare/doctype/{special_test_items/special_test_items.py => sensitivity_test_result/sensitivity_test_result.py} (84%) delete mode 100644 erpnext/healthcare/doctype/special_test_items/special_test_items.json delete mode 100644 erpnext/healthcare/doctype/special_test_template/special_test_template.json delete mode 100644 erpnext/healthcare/doctype/special_test_template/special_test_template.py create mode 100644 erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py diff --git a/erpnext/healthcare/doctype/antibiotic/antibiotic.json b/erpnext/healthcare/doctype/antibiotic/antibiotic.json index d481036ee6..41a3e318f3 100644 --- a/erpnext/healthcare/doctype/antibiotic/antibiotic.json +++ b/erpnext/healthcare/doctype/antibiotic/antibiotic.json @@ -1,115 +1,151 @@ { - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:antibiotic_name", - "beta": 1, - "creation": "2016-02-23 11:11:30.749731", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_copy": 1, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:antibiotic_name", + "beta": 1, + "creation": "2016-02-23 11:11:30.749731", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "antibiotic_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Antibiotic Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "antibiotic_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Antibiotic Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "abbr", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Abbr", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-08-31 13:44:43.199657", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Antibiotic", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-10-01 17:58:23.136498", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Antibiotic", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 0 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "antibiotic_name", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "antibiotic_name", - "track_changes": 0, - "track_seen": 0 + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "antibiotic_name", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "antibiotic_name", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_groups/__init__.py b/erpnext/healthcare/doctype/descriptive_test_result/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/lab_test_groups/__init__.py rename to erpnext/healthcare/doctype/descriptive_test_result/__init__.py diff --git a/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json new file mode 100644 index 0000000000..fcd3828aa5 --- /dev/null +++ b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json @@ -0,0 +1,74 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 15:12:36.202380", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lab_test_particulars", + "result_value", + "allow_blank", + "template", + "require_result_value" + ], + "fields": [ + { + "fieldname": "lab_test_particulars", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Particulars", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value == 1", + "fieldname": "result_value", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Value" + }, + { + "fieldname": "template", + "fieldtype": "Link", + "hidden": 1, + "label": "Template", + "options": "Lab Test Template", + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "require_result_value", + "fieldtype": "Check", + "hidden": 1, + "label": "Require Result Value", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "default": "1", + "fieldname": "allow_blank", + "fieldtype": "Check", + "label": "Allow Blank", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-23 12:33:47.693065", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Descriptive Test Result", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py similarity index 84% rename from erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py rename to erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py index 35c8efde79..7ccf6b57aa 100644 --- a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py +++ b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals from frappe.model.document import Document -class SensitivityTestItems(Document): +class DescriptiveTestResult(Document): pass diff --git a/erpnext/healthcare/doctype/normal_test_items/__init__.py b/erpnext/healthcare/doctype/descriptive_test_template/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/normal_test_items/__init__.py rename to erpnext/healthcare/doctype/descriptive_test_template/__init__.py diff --git a/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json new file mode 100644 index 0000000000..9ee8f4fc68 --- /dev/null +++ b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 16:12:12.394200", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "particulars", + "allow_blank" + ], + "fields": [ + { + "fieldname": "particulars", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Result Component" + }, + { + "default": "0", + "fieldname": "allow_blank", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Blank" + } + ], + "istable": 1, + "links": [], + "modified": "2020-06-24 14:03:51.728863", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Descriptive Test Template", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py new file mode 100644 index 0000000000..281f32db7f --- /dev/null +++ b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class DescriptiveTestTemplate(Document): + pass diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json index 2f0115c36a..0104386714 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json @@ -39,8 +39,8 @@ "create_lab_test_on_si_submit", "create_sample_collection_for_lab_test", "column_break_34", - "employee_name_and_designation_in_print", "lab_test_approval_required", + "employee_name_and_designation_in_print", "custom_signature_in_print", "laboratory_sms_alerts", "sms_printed", @@ -306,7 +306,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-03-26 11:25:21.842092", + "modified": "2020-07-08 15:17:21.543218", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Settings", diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index bf1ecc87e4..8036c7dc13 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -1,49 +1,53 @@ // Copyright (c) 2016, ESS and contributors // For license information, please see license.txt -cur_frm.cscript.custom_refresh = function(doc) { - cur_frm.toggle_display("sb_sensitivity", doc.sensitivity_toggle=="1"); - cur_frm.toggle_display("sb_special", doc.special_toggle=="1"); - cur_frm.toggle_display("sb_normal", doc.normal_toggle=="1"); +cur_frm.cscript.custom_refresh = function (doc) { + cur_frm.toggle_display('sb_sensitivity', doc.sensitivity_toggle); + cur_frm.toggle_display('organisms_section', doc.descriptive_toggle); + cur_frm.toggle_display('sb_descriptive', doc.descriptive_toggle); + cur_frm.toggle_display('sb_normal', doc.normal_toggle); }; frappe.ui.form.on('Lab Test', { - setup: function(frm) { + setup: function (frm) { frm.get_field('normal_test_items').grid.editable_fields = [ - {fieldname: 'lab_test_name', columns: 3}, - {fieldname: 'lab_test_event', columns: 2}, - {fieldname: 'result_value', columns: 2}, - {fieldname: 'lab_test_uom', columns: 1}, - {fieldname: 'normal_range', columns: 2} + { fieldname: 'lab_test_name', columns: 3 }, + { fieldname: 'lab_test_event', columns: 2 }, + { fieldname: 'result_value', columns: 2 }, + { fieldname: 'lab_test_uom', columns: 1 }, + { fieldname: 'normal_range', columns: 2 } ]; - frm.get_field('special_test_items').grid.editable_fields = [ - {fieldname: 'lab_test_particulars', columns: 3}, - {fieldname: 'result_value', columns: 7} + frm.get_field('descriptive_test_items').grid.editable_fields = [ + { fieldname: 'lab_test_particulars', columns: 3 }, + { fieldname: 'result_value', columns: 7 } ]; }, - refresh : function(frm){ + refresh: function (frm) { refresh_field('normal_test_items'); - refresh_field('special_test_items'); - if(frm.doc.__islocal){ + refresh_field('descriptive_test_items'); + if (frm.doc.__islocal) { frm.add_custom_button(__('Get from Patient Encounter'), function () { get_lab_test_prescribed(frm); }); } - if(frm.doc.docstatus==1 && frm.doc.status!='Approved' && frm.doc.status!='Rejected' && frappe.defaults.get_default("lab_test_approval_required") && frappe.user.has_role("LabTest Approver")){ - frm.add_custom_button(__('Approve'), function() { - status_update(1,frm); - }); - frm.add_custom_button(__('Reject'), function() { - status_update(0,frm); - }); + if (frappe.defaults.get_default('lab_test_approval_required') && frappe.user.has_role('LabTest Approver')) { + if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') { + frm.add_custom_button(__('Approve'), function () { + status_update(1, frm); + }); + frm.add_custom_button(__('Reject'), function () { + status_update(0, frm); + }); + } } - if(frm.doc.docstatus==1 && frm.doc.sms_sent==0){ - frm.add_custom_button(__('Send SMS'), function() { + + if (frm.doc.docstatus === 1 && frm.doc.sms_sent === 0 && frm.doc.status !== 'Rejected' ) { + frm.add_custom_button(__('Send SMS'), function () { frappe.call({ - method: "erpnext.healthcare.doctype.healthcare_settings.healthcare_settings.get_sms_text", - args:{doc: frm.doc.name}, - callback: function(r) { - if(!r.exc) { + method: 'erpnext.healthcare.doctype.healthcare_settings.healthcare_settings.get_sms_text', + args: { doc: frm.doc.name }, + callback: function (r) { + if (!r.exc) { var emailed = r.message.emailed; var printed = r.message.printed; make_dialog(frm, emailed, printed); @@ -53,246 +57,223 @@ frappe.ui.form.on('Lab Test', { }); } - }, - onload: function (frm) { - frm.add_fetch("practitioner", "department", "department"); - if(frm.doc.employee){ - frappe.call({ - method: "frappe.client.get", - args:{ - doctype: "Employee", - name: frm.doc.employee - }, - callback: function(arg){ - frappe.model.set_value(frm.doctype,frm.docname,"employee_name", arg.message.employee_name); - frappe.model.set_value(frm.doctype,frm.docname,"employee_designation", arg.message.designation); - } - }); - } } }); -frappe.ui.form.on("Lab Test", "patient", function(frm) { - if(frm.doc.patient){ +frappe.ui.form.on('Lab Test', 'patient', function (frm) { + if (frm.doc.patient) { frappe.call({ - "method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail", - args: { - patient: frm.doc.patient - }, + 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', + args: { patient: frm.doc.patient }, callback: function (data) { var age = null; - if(data.message.dob){ + if (data.message.dob) { age = calculate_age(data.message.dob); } - frappe.model.set_value(frm.doctype,frm.docname, "patient_age", age); - frappe.model.set_value(frm.doctype,frm.docname, "patient_sex", data.message.sex); - frappe.model.set_value(frm.doctype,frm.docname, "email", data.message.email); - frappe.model.set_value(frm.doctype,frm.docname, "mobile", data.message.mobile); - frappe.model.set_value(frm.doctype,frm.docname, "report_preference", data.message.report_preference); + let values = { + 'patient_age': age, + 'patient_sex': data.message.sex, + 'email': data.message.email, + 'mobile': data.message.mobile, + 'report_preference': data.message.report_preference + }; + frm.set_value(values); } }); } }); -frappe.ui.form.on('Normal Test Items', { - normal_test_items_remove: function() { - frappe.msgprint(__("Not permitted, configure Lab Test Template as required")); +frappe.ui.form.on('Normal Test Result', { + normal_test_items_remove: function () { + frappe.msgprint(__('Not permitted, configure Lab Test Template as required')); cur_frm.reload_doc(); } }); -frappe.ui.form.on('Special Test Items', { - special_test_items_remove: function() { - frappe.msgprint(__("Not permitted, configure Lab Test Template as required")); +frappe.ui.form.on('Descriptive Test Result', { + descriptive_test_items_remove: function () { + frappe.msgprint(__('Not permitted, configure Lab Test Template as required')); cur_frm.reload_doc(); } }); -var status_update = function(approve,frm){ +var status_update = function (approve, frm) { var doc = frm.doc; var status = null; - if(approve == 1){ - status = "Approved"; + if (approve == 1) { + status = 'Approved'; } else { - status = "Rejected"; + status = 'Rejected'; } frappe.call({ - method: "erpnext.healthcare.doctype.lab_test.lab_test.update_status", - args: {status: status, name: doc.name}, - callback: function(){ + method: 'erpnext.healthcare.doctype.lab_test.lab_test.update_status', + args: { status: status, name: doc.name }, + callback: function () { cur_frm.reload_doc(); } }); }; -var get_lab_test_prescribed = function(frm){ - if(frm.doc.patient){ +var get_lab_test_prescribed = function (frm) { + if (frm.doc.patient) { frappe.call({ - method: "erpnext.healthcare.doctype.lab_test.lab_test.get_lab_test_prescribed", - args: {patient: frm.doc.patient}, - callback: function(r){ + method: 'erpnext.healthcare.doctype.lab_test.lab_test.get_lab_test_prescribed', + args: { patient: frm.doc.patient }, + callback: function (r) { show_lab_tests(frm, r.message); } }); } - else{ - frappe.msgprint(__("Please select a Patient to get Lab Tests")); + else { + frappe.msgprint(__('Please select Patient to get Lab Tests')); } }; -var show_lab_tests = function(frm, result){ +var show_lab_tests = function (frm, lab_test_list) { var d = new frappe.ui.Dialog({ - title: __("Lab Tests"), - fields: [ - { - fieldtype: "HTML", fieldname: "lab_test" - } - ] + title: __('Lab Tests'), + fields: [{ + fieldtype: 'HTML', fieldname: 'lab_test' + }] }); var html_field = d.fields_dict.lab_test.$wrapper; html_field.empty(); - $.each(result, function(x, y){ - var row = $(repl('
\ -
%(lab_test)s
\ -
%(encounter)s
\ -
%(practitioner)s
\ -
%(date)s
\ -
', {name:y[0], lab_test: y[1], encounter:y[2], invoiced:y[3], practitioner:y[4], date:y[5]})).appendTo(html_field); - row.find("a").click(function() { - frm.doc.template = $(this).attr("data-lab-test"); - frm.doc.prescription = $(this).attr("data-name"); - frm.doc.practitioner = $(this).attr("data-practitioner"); - frm.set_df_property("template", "read_only", 1); - frm.set_df_property("patient", "read_only", 1); - frm.set_df_property("practitioner", "read_only", 1); + $.each(lab_test_list, function (x, y) { + var row = $(repl( + '
\ +
%(lab_test)s
\ +
%(practitioner_name)s
%(encounter)s
\ +
%(date)s
\ +
\ + \ +
\ +

', + { name: y[0], lab_test: y[1], encounter: y[2], invoiced: y[3], practitioner: y[4], practitioner_name: y[5], date: y[6] }) + ).appendTo(html_field); + + row.find("a").click(function () { + frm.doc.template = $(this).attr('data-lab-test'); + frm.doc.prescription = $(this).attr('data-name'); + frm.doc.practitioner = $(this).attr('data-practitioner'); + frm.set_df_property('template', 'read_only', 1); + frm.set_df_property('patient', 'read_only', 1); + frm.set_df_property('practitioner', 'read_only', 1); frm.doc.invoiced = 0; - if($(this).attr("data-invoiced") == 1){ + if ($(this).attr('data-invoiced') === 1) { frm.doc.invoiced = 1; } - refresh_field("invoiced"); - refresh_field("template"); + refresh_field('invoiced'); + refresh_field('template'); d.hide(); return false; }); }); - if(!result.length){ - var msg = __("No Lab Tests found for the Patient {0}", [frm.doc.patient_name.bold()]); + if (!lab_test_list.length) { + var msg = __('No Lab Tests found for the Patient {0}', [frm.doc.patient_name.bold()]); html_field.empty(); - $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); + $(repl('
%(msg)s
', { msg: msg })).appendTo(html_field); } d.show(); }; -cur_frm.cscript.custom_before_submit = function(doc) { - if(doc.normal_test_items){ - for(let result in doc.normal_test_items){ - if(!doc.normal_test_items[result].result_value && doc.normal_test_items[result].require_result_value == 1){ - frappe.msgprint(__("Please input all required Result Value(s)")); - throw("Error"); +cur_frm.cscript.custom_before_submit = function (doc) { + if (doc.normal_test_items) { + for (let result in doc.normal_test_items) { + if (!doc.normal_test_items[result].result_value && !doc.normal_test_items[result].allow_blank && doc.normal_test_items[result].require_result_value) { + frappe.throw(__('Please input all required result values')); } } } - if(doc.special_test_items){ - for(let result in doc.special_test_items){ - if(!doc.special_test_items[result].result_value && doc.special_test_items[result].require_result_value == 1){ - frappe.msgprint(__("Please input all required Result Value(s)")); - throw("Error"); + if (doc.descriptive_test_items) { + for (let result in doc.descriptive_test_items) { + if (!doc.descriptive_test_items[result].result_value && !doc.descriptive_test_items[result].allow_blank && doc.descriptive_test_items[result].require_result_value) { + frappe.throw(__('Please input all required result values')); } } } }; -var make_dialog = function(frm, emailed, printed) { +var make_dialog = function (frm, emailed, printed) { var number = frm.doc.mobile; var dialog = new frappe.ui.Dialog({ title: 'Send SMS', width: 400, fields: [ - {fieldname:'sms_type', fieldtype:'Select', label:'Type', options: - ['Emailed','Printed']}, - {fieldname:'number', fieldtype:'Data', label:'Mobile Number', reqd:1}, - {fieldname:'messages_label', fieldtype:'HTML'}, - {fieldname:'messages', fieldtype:'HTML', reqd:1} + { fieldname: 'sms_type', fieldtype: 'Select', label: 'Type', options: ['Emailed', 'Printed'] }, + { fieldname: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 }, + { fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 } ], - primary_action_label: __("Send"), - primary_action : function(){ + primary_action_label: __('Send'), + primary_action: function () { var values = dialog.fields_dict; - if(!values){ + if (!values) { return; } - send_sms(values,frm); + send_sms(values, frm); dialog.hide(); } }); - if(frm.doc.report_preference == "Email"){ + if (frm.doc.report_preference == 'Print') { dialog.set_values({ - 'sms_type': "Emailed", - 'number': number + 'sms_type': 'Printed', + 'number': number, + 'message': printed }); - dialog.fields_dict.messages_label.html("Message".bold()); - dialog.fields_dict.messages.html(emailed); - }else{ + } else { dialog.set_values({ - 'sms_type': "Printed", - 'number': number + 'sms_type': 'Emailed', + 'number': number, + 'message': emailed }); - dialog.fields_dict.messages_label.html("Message".bold()); - dialog.fields_dict.messages.html(printed); } var fd = dialog.fields_dict; - $(fd.sms_type.input).change(function(){ - if(dialog.get_value('sms_type') == 'Emailed'){ + $(fd.sms_type.input).change(function () { + if (dialog.get_value('sms_type') == 'Emailed') { dialog.set_values({ - 'number': number + 'number': number, + 'message': emailed }); - fd.messages_label.html("Message".bold()); - fd.messages.html(emailed); - }else{ + } else { dialog.set_values({ - 'number': number + 'number': number, + 'message': printed }); - fd.messages_label.html("Message".bold()); - fd.messages.html(printed); } }); dialog.show(); }; -var send_sms = function(v,frm){ - var doc = frm.doc; - var number = v.number.last_value; - var messages = v.messages.wrapper.innerText; +var send_sms = function (vals, frm) { + var number = vals.number.value; + var message = vals.message.last_value; + + if (!number || !message) { + frappe.throw(__('Did not send SMS, missing patient mobile number or message content.')); + } frappe.call({ - method: "frappe.core.doctype.sms_settings.sms_settings.send_sms", + method: 'frappe.core.doctype.sms_settings.sms_settings.send_sms', args: { receiver_list: [number], - msg: messages + msg: message }, - callback: function(r) { - if(r.exc) {frappe.msgprint(r.exc); return; } - else{ - frappe.call({ - method: "erpnext.healthcare.doctype.lab_test.lab_test.update_lab_test_print_sms_email_status", - args: {print_sms_email: "sms_sent", name: doc.name}, - callback: function(){ - cur_frm.reload_doc(); - } - }); + callback: function (r) { + if (r.exc) { + frappe.msgprint(r.exc); + } else { + frm.reload_doc(); } } }); }; -var calculate_age = function(birth) { - var ageMS = Date.parse(Date()) - Date.parse(birth); - var age = new Date(); +var calculate_age = function (dob) { + var ageMS = Date.parse(Date()) - Date.parse(dob); + var age = new Date(); age.setTime(ageMS); - var years = age.getFullYear() - 1970; - return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; + var years = age.getFullYear() - 1970; + return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; }; diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index 88eeb46a24..2eb8014b7e 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -10,49 +10,63 @@ "engine": "InnoDB", "field_order": [ "naming_series", + "template", + "lab_test_name", + "lab_test_group", + "medical_code", + "department", + "column_break_26", + "company", + "status", + "submitted_date", + "result_date", + "approved_date", + "expected_result_date", + "expected_result_time", + "printed_on", + "invoiced", + "sb_first", "patient", "patient_name", "patient_age", "patient_sex", + "inpatient_record", "report_preference", "email", "mobile", - "practitioner", "c_b", - "inpatient_record", - "company", - "department", - "status", - "submitted_date", - "approved_date", - "sample", - "result_date", + "practitioner", + "practitioner_name", + "requesting_department", "employee", "employee_name", "employee_designation", "user", - "invoiced", - "sb_first", - "template", - "lab_test_name", - "column_break_26", - "medical_code", - "lab_test_group", + "sample", "sb_normal", + "lab_test_html", "normal_test_items", - "sb_special", - "special_test_items", + "sb_descriptive", + "descriptive_test_items", + "organisms_section", + "organism_test_items", "sb_sensitivity", "sensitivity_test_items", "sb_comments", "lab_test_comment", "sb_customresult", "custom_result", + "worksheet_section", + "worksheet_instructions", + "result_legend_section", + "legend_print_position", + "result_legend", + "section_break_50", "email_sent", "sms_sent", "printed", "normal_toggle", - "special_toggle", + "descriptive_toggle", "sensitivity_toggle", "amended_from", "prescription" @@ -89,7 +103,6 @@ "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Patient", "options": "Patient", @@ -120,6 +133,7 @@ "label": "Gender", "options": "Gender", "print_hide": 1, + "read_only": 1, "report_hide": 1, "reqd": 1, "set_only_once": 1 @@ -128,11 +142,14 @@ "fieldname": "practitioner", "fieldtype": "Link", "ignore_user_permissions": 1, - "label": "Healthcare Practitioner", + "in_list_view": 1, + "label": "Requesting Practitioner", + "no_copy": 1, "options": "Healthcare Practitioner", "search_index": 1 }, { + "fetch_from": "patient.email", "fieldname": "email", "fieldtype": "Data", "hidden": 1, @@ -142,6 +159,7 @@ "report_hide": 1 }, { + "fetch_from": "patient.mobile", "fieldname": "mobile", "fieldtype": "Data", "hidden": 1, @@ -166,21 +184,23 @@ "print_hide": 1 }, { + "fetch_from": "template.department", "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, "in_standard_filter": 1, "label": "Department", "options": "Medical Department", + "read_only": 1, "search_index": 1 }, { "fieldname": "status", "fieldtype": "Select", - "hidden": 1, "label": "Status", "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", "print_hide": 1, + "read_only": 1, "report_hide": 1, "search_index": 1 }, @@ -211,16 +231,39 @@ "read_only": 1, "report_hide": 1 }, + { + "default": "Today", + "fieldname": "expected_result_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Expected Result Date", + "read_only": 1 + }, + { + "fieldname": "expected_result_time", + "fieldtype": "Time", + "hidden": 1, + "label": "Expected Result Time", + "read_only": 1 + }, { "fieldname": "result_date", "fieldtype": "Date", + "hidden": 1, "label": "Result Date", "search_index": 1 }, + { + "allow_on_submit": 1, + "fieldname": "printed_on", + "fieldtype": "Datetime", + "label": "Printed on", + "read_only": 1 + }, { "fieldname": "employee", "fieldtype": "Link", - "label": "Lab Technician", + "label": "Employee (Lab Technician)", "no_copy": 1, "options": "Employee", "print_hide": 1, @@ -230,7 +273,7 @@ "fetch_from": "employee.employee_name", "fieldname": "employee_name", "fieldtype": "Data", - "label": "Technician Name", + "label": "Lab Technician Name", "no_copy": 1, "print_hide": 1, "read_only": 1, @@ -240,7 +283,7 @@ "fetch_from": "employee.designation", "fieldname": "employee_designation", "fieldtype": "Data", - "label": "Designation", + "label": "Lab Technician Designation", "no_copy": 1, "print_hide": 1, "read_only": 1, @@ -257,6 +300,7 @@ "report_hide": 1 }, { + "fetch_from": "patient.report_preference", "fieldname": "report_preference", "fieldtype": "Data", "label": "Report Preference", @@ -272,7 +316,6 @@ "fieldname": "lab_test_name", "fieldtype": "Data", "in_list_view": 1, - "in_standard_filter": 1, "label": "Test Name", "no_copy": 1, "print_hide": 1, @@ -280,14 +323,11 @@ "report_hide": 1, "search_index": 1 }, - { - "fieldname": "column_break_26", - "fieldtype": "Column Break" - }, { "fieldname": "template", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_standard_filter": 1, "label": "Test Template", "options": "Lab Test Template", "print_hide": 1, @@ -304,6 +344,14 @@ "print_hide": 1, "report_hide": 1 }, + { + "fetch_from": "template.medical_code", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code", + "read_only": 1 + }, { "fieldname": "sb_normal", "fieldtype": "Section Break" @@ -311,19 +359,18 @@ { "fieldname": "normal_test_items", "fieldtype": "Table", - "options": "Normal Test Items" + "options": "Normal Test Result", + "print_hide": 1 }, { - "fieldname": "sb_special", + "fieldname": "lab_test_html", + "fieldtype": "HTML" + }, + { + "depends_on": "descriptive_toggle", + "fieldname": "organisms_section", "fieldtype": "Section Break" }, - { - "fieldname": "special_test_items", - "fieldtype": "Table", - "options": "Special Test Items", - "print_hide": 1, - "report_hide": 1 - }, { "fieldname": "sb_sensitivity", "fieldtype": "Section Break" @@ -331,7 +378,7 @@ { "fieldname": "sensitivity_test_items", "fieldtype": "Table", - "options": "Sensitivity Test Items", + "options": "Sensitivity Test Result", "print_hide": 1, "report_hide": 1 }, @@ -343,7 +390,8 @@ "fieldname": "lab_test_comment", "fieldtype": "Text", "ignore_xss_filter": 1, - "label": "Comments" + "label": "Comments", + "print_hide": 1 }, { "collapsible": 1, @@ -355,7 +403,8 @@ "fieldname": "custom_result", "fieldtype": "Text Editor", "ignore_xss_filter": 1, - "label": "Custom Result" + "label": "Custom Result", + "print_hide": 1 }, { "default": "0", @@ -389,14 +438,6 @@ "print_hide": 1, "report_hide": 1 }, - { - "default": "0", - "fieldname": "special_toggle", - "fieldtype": "Check", - "hidden": 1, - "print_hide": 1, - "report_hide": 1 - }, { "default": "0", "fieldname": "sensitivity_toggle", @@ -427,17 +468,89 @@ "report_hide": 1 }, { - "fetch_from": "template.medical_code", - "fieldname": "medical_code", + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, + { + "fetch_from": "practitioner.department", + "fieldname": "requesting_department", "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code", + "in_list_view": 1, + "label": "Requesting Department", + "options": "Medical Department", "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "label": "Requesting Practitioner", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "result_legend_section", + "fieldtype": "Section Break", + "label": "Result Legend Print" + }, + { + "fieldname": "legend_print_position", + "fieldtype": "Select", + "label": "Print Position", + "options": "\nBottom\nTop\nBoth", + "print_hide": 1 + }, + { + "fieldname": "result_legend", + "fieldtype": "Text Editor", + "label": "Result Legend", + "print_hide": 1 + }, + { + "fieldname": "section_break_50", + "fieldtype": "Section Break" + }, + { + "fieldname": "worksheet_instructions", + "fieldtype": "Text Editor", + "label": "Worksheet Instructions", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "worksheet_section", + "fieldtype": "Section Break", + "label": "Worksheet Print" + }, + { + "fieldname": "descriptive_test_items", + "fieldtype": "Table", + "options": "Descriptive Test Result", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "sb_descriptive", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "descriptive_toggle", + "fieldtype": "Check", + "hidden": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "organism_test_items", + "fieldtype": "Table", + "options": "Organism Test Result", + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-06-29 14:24:26.509721", + "modified": "2020-07-16 13:35:24.811062", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index b2c5e6bf43..865f4a14e3 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -10,26 +10,30 @@ from frappe.utils import getdate, cstr class LabTest(Document): def on_submit(self): - frappe.db.set_value(self.doctype,self.name,"submitted_date", getdate()) + self.db_set('submitted_date', getdate()) + self.db_set('status', 'Completed') insert_lab_test_to_medical_record(self) - frappe.db.set_value("Lab Test", self.name, "status", "Completed") def on_cancel(self): delete_lab_test_from_medical_record(self) - frappe.db.set_value("Lab Test", self.name, "status", "Cancelled") + self.db_set('status', 'Cancelled') self.reload() + def validate(self): + if not self.is_new(): + self.set_secondary_uom_result() + def on_update(self): - if(self.sensitivity_test_items): + if self.sensitivity_test_items: sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity) for i, item in enumerate(sensitivity): - item.idx = i+1 + item.idx = i + 1 self.sensitivity_test_items = sensitivity def after_insert(self): - if(self.prescription): - frappe.db.set_value("Lab Prescription", self.prescription, "lab_test_created", 1) - if frappe.db.get_value("Lab Prescription", self.prescription, 'invoiced') == 1: + if self.prescription: + frappe.db.set_value('Lab Prescription', self.prescription, 'lab_test_created', 1) + if frappe.db.get_value('Lab Prescription', self.prescription, 'invoiced'): self.invoiced = True if not self.lab_test_name and self.template: self.load_test_from_template() @@ -40,109 +44,110 @@ class LabTest(Document): create_test_from_template(lab_test) self.reload() + def set_secondary_uom_result(self): + for item in self.normal_test_items: + if item.result_value and item.secondary_uom and item.conversion_factor: + try: + item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor) + except: + item.secondary_uom_result = '' + frappe.msgprint(_('Result for Secondary UOM not calculated for row #{0}'.format(item.idx)), title = _('Warning')) + + def create_test_from_template(lab_test): - template = frappe.get_doc("Lab Test Template", lab_test.template) - patient = frappe.get_doc("Patient", lab_test.patient) + template = frappe.get_doc('Lab Test Template', lab_test.template) + patient = frappe.get_doc('Patient', lab_test.patient) lab_test.lab_test_name = template.lab_test_name lab_test.result_date = getdate() lab_test.department = template.department lab_test.lab_test_group = template.lab_test_group + lab_test.legend_print_position = template.legend_print_position + lab_test.result_legend = template.result_legend + lab_test.worksheet_instructions = template.worksheet_instructions lab_test = create_sample_collection(lab_test, template, patient, None) lab_test = load_result_format(lab_test, template, None, None) @frappe.whitelist() def update_status(status, name): - frappe.db.sql("""update `tabLab Test` set status=%s, approved_date=%s where name = %s""", (status, getdate(), name)) - -@frappe.whitelist() -def update_lab_test_print_sms_email_status(print_sms_email, name): - frappe.db.set_value("Lab Test",name,print_sms_email,1) + if name and status: + frappe.db.set_value('Lab Test', name, { + 'status': status, + 'approved_date': getdate() + }) @frappe.whitelist() def create_multiple(doctype, docname): + if not doctype or not docname: + frappe.throw(_('Sales Invoice or Patient Encounter is required to create Lab Tests'), title=_('Insufficient Data')) + lab_test_created = False - if doctype == "Sales Invoice": + if doctype == 'Sales Invoice': lab_test_created = create_lab_test_from_invoice(docname) - elif doctype == "Patient Encounter": + elif doctype == 'Patient Encounter': lab_test_created = create_lab_test_from_encounter(docname) if lab_test_created: - frappe.msgprint(_("Lab Test(s) {0} created".format(lab_test_created))) + frappe.msgprint(_('Lab Test(s) {0} created'.format(lab_test_created))) else: - frappe.msgprint(_("No Lab Tests created")) + frappe.msgprint(_('No Lab Tests created')) -def create_lab_test_from_encounter(encounter_id): +def create_lab_test_from_encounter(encounter): lab_test_created = False - encounter = frappe.get_doc("Patient Encounter", encounter_id) + encounter = frappe.get_doc('Patient Encounter', encounter) - lab_test_ids = frappe.db.sql("""select lp.name, lp.lab_test_code, lp.invoiced - from `tabPatient Encounter` et, `tabLab Prescription` lp - where et.patient=%s and lp.parent=%s and - lp.parent=et.name and lp.lab_test_created=0 and et.docstatus=1""", (encounter.patient, encounter_id)) - - if lab_test_ids: - patient = frappe.get_doc("Patient", encounter.patient) - for lab_test_id in lab_test_ids: - template = get_lab_test_template(lab_test_id[1]) - if template: - lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template, encounter.company) - lab_test.save(ignore_permissions = True) - frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1) - if not lab_test_created: - lab_test_created = lab_test.name - else: - lab_test_created += ", "+lab_test.name + if encounter and encounter.lab_test_prescription: + patient = frappe.get_doc('Patient', encounter.patient) + for item in encounter.lab_test_prescription: + if not item.lab_test_created: + template = get_lab_test_template(item.lab_test_code) + if template: + lab_test = create_lab_test_doc(item.invoiced, encounter.practitioner, patient, template, encounter.company) + lab_test.save(ignore_permissions = True) + frappe.db.set_value('Lab Prescription', item.name, 'lab_test_created', 1) + if not lab_test_created: + lab_test_created = lab_test.name + else: + lab_test_created += ', ' + lab_test.name return lab_test_created -def create_lab_test_from_invoice(invoice_name): +def create_lab_test_from_invoice(sales_invoice): lab_tests_created = False - invoice = frappe.get_doc("Sales Invoice", invoice_name) - if invoice.patient: - patient = frappe.get_doc("Patient", invoice.patient) + invoice = frappe.get_doc('Sales Invoice', sales_invoice) + if invoice and invoice.patient: + patient = frappe.get_doc('Patient', invoice.patient) for item in invoice.items: lab_test_created = 0 - if item.reference_dt == "Lab Prescription": - lab_test_created = frappe.db.get_value("Lab Prescription", item.reference_dn, "lab_test_created") - elif item.reference_dt == "Lab Test": + if item.reference_dt == 'Lab Prescription': + lab_test_created = frappe.db.get_value('Lab Prescription', item.reference_dn, 'lab_test_created') + elif item.reference_dt == 'Lab Test': lab_test_created = 1 if lab_test_created != 1: template = get_lab_test_template(item.item_code) if template: lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, invoice.company) - if item.reference_dt == "Lab Prescription": + if item.reference_dt == 'Lab Prescription': lab_test.prescription = item.reference_dn lab_test.save(ignore_permissions = True) - if item.reference_dt != "Lab Prescription": - frappe.db.set_value("Sales Invoice Item", item.name, "reference_dt", "Lab Test") - frappe.db.set_value("Sales Invoice Item", item.name, "reference_dn", lab_test.name) + if item.reference_dt != 'Lab Prescription': + frappe.db.set_value('Sales Invoice Item', item.name, 'reference_dt', 'Lab Test') + frappe.db.set_value('Sales Invoice Item', item.name, 'reference_dn', lab_test.name) if not lab_tests_created: lab_tests_created = lab_test.name else: - lab_tests_created += ", " + lab_test.name + lab_tests_created += ', ' + lab_test.name return lab_tests_created def get_lab_test_template(item): - template_id = check_template_exists(item) + template_id = frappe.db.exists('Lab Test Template', {'item': item}) if template_id: - return frappe.get_doc("Lab Test Template", template_id) - return False - -def check_template_exists(item): - template_exists = frappe.db.exists( - "Lab Test Template", - { - 'item': item - } - ) - if template_exists: - return template_exists + return frappe.get_doc('Lab Test Template', template_id) return False def create_lab_test_doc(invoiced, practitioner, patient, template, company): - lab_test = frappe.new_doc("Lab Test") + lab_test = frappe.new_doc('Lab Test') lab_test.invoiced = invoiced lab_test.practitioner = practitioner lab_test.patient = patient.name @@ -159,63 +164,71 @@ def create_lab_test_doc(invoiced, practitioner, patient, template, company): return lab_test def create_normals(template, lab_test): - lab_test.normal_toggle = "1" - normal = lab_test.append("normal_test_items") + lab_test.normal_toggle = 1 + normal = lab_test.append('normal_test_items') normal.lab_test_name = template.lab_test_name normal.lab_test_uom = template.lab_test_uom + normal.secondary_uom = template.secondary_uom + normal.conversion_factor = template.conversion_factor normal.normal_range = template.lab_test_normal_range normal.require_result_value = 1 + normal.allow_blank = 0 normal.template = template.name def create_compounds(template, lab_test, is_group): - lab_test.normal_toggle = "1" + lab_test.normal_toggle = 1 for normal_test_template in template.normal_test_templates: - normal = lab_test.append("normal_test_items") + normal = lab_test.append('normal_test_items') if is_group: normal.lab_test_event = normal_test_template.lab_test_event else: normal.lab_test_name = normal_test_template.lab_test_event normal.lab_test_uom = normal_test_template.lab_test_uom + normal.secondary_uom = normal_test_template.secondary_uom + normal.conversion_factor = normal_test_template.conversion_factor normal.normal_range = normal_test_template.normal_range normal.require_result_value = 1 + normal.allow_blank = normal_test_template.allow_blank normal.template = template.name -def create_specials(template, lab_test): - lab_test.special_toggle = "1" - if(template.sensitivity): - lab_test.sensitivity_toggle = "1" - for special_test_template in template.special_test_template: - special = lab_test.append("special_test_items") - special.lab_test_particulars = special_test_template.particulars - special.require_result_value = 1 - special.template = template.name +def create_descriptives(template, lab_test): + lab_test.descriptive_toggle = 1 + if template.sensitivity: + lab_test.sensitivity_toggle = 1 + for descriptive_test_template in template.descriptive_test_templates: + descriptive = lab_test.append('descriptive_test_items') + descriptive.lab_test_particulars = descriptive_test_template.particulars + descriptive.require_result_value = 1 + descriptive.allow_blank = descriptive_test_template.allow_blank + descriptive.template = template.name def create_sample_doc(template, patient, invoice, company = None): if template.sample: sample_exists = frappe.db.exists({ - "doctype": "Sample Collection", - "patient": patient.name, - "docstatus": 0, - "sample": template.sample + 'doctype': 'Sample Collection', + 'patient': patient.name, + 'docstatus': 0, + 'sample': template.sample }) if sample_exists: - # update Sample Collection by adding quantity - sample_collection = frappe.get_doc("Sample Collection", sample_exists[0][0]) + # Update Sample Collection by adding quantity + sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0]) quantity = int(sample_collection.sample_qty) + int(template.sample_qty) if template.sample_details: - sample_details = sample_collection.sample_details + "\n==============\n" + _("Test: ") - sample_details += (template.get("lab_test_name") or template.get("template")) + "\n" - sample_details += _("Collection Details: ") + "\n\t" + template.sample_details + sample_details = sample_collection.sample_details + '\n-\n' + _('Test: ') + sample_details += (template.get('lab_test_name') or template.get('template')) + '\n' + sample_details += _('Collection Details: ') + '\n\t' + template.sample_details + frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_details', sample_details) - frappe.db.set_value("Sample Collection", sample_collection.name, "sample_details", sample_details) - frappe.db.set_value("Sample Collection", sample_collection.name, "sample_qty", quantity) + frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_qty', quantity) else: - #create Sample Collection for template, copy vals from Invoice - sample_collection = frappe.new_doc("Sample Collection") - if(invoice): + # Create Sample Collection for template, copy vals from Invoice + sample_collection = frappe.new_doc('Sample Collection') + if invoice: sample_collection.invoiced = True + sample_collection.patient = patient.name sample_collection.patient_age = patient.get_age() sample_collection.patient_sex = patient.sex @@ -224,125 +237,146 @@ def create_sample_doc(template, patient, invoice, company = None): sample_collection.sample_qty = template.sample_qty sample_collection.company = company - if(template.sample_details): - sample_collection.sample_details = "Test :" + (template.get("lab_test_name") or template.get("template")) +"\n"+"Collection Detials:\n\t"+template.sample_details + if template.sample_details: + sample_collection.sample_details = 'Test :' + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details sample_collection.save(ignore_permissions=True) return sample_collection def create_sample_collection(lab_test, template, patient, invoice): - if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"): + if frappe.get_cached_value('Healthcare Settings', None, 'create_sample_collection_for_lab_test'): sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) - if(sample_collection): + if sample_collection: lab_test.sample = sample_collection.name + return lab_test def load_result_format(lab_test, template, prescription, invoice): - if(template.lab_test_template_type == 'Single'): + if template.lab_test_template_type == 'Single': create_normals(template, lab_test) - elif(template.lab_test_template_type == 'Compound'): + elif template.lab_test_template_type == 'Compound': create_compounds(template, lab_test, False) - elif(template.lab_test_template_type == 'Descriptive'): - create_specials(template, lab_test) - elif(template.lab_test_template_type == 'Grouped'): - #iterate for each template in the group and create one result for all. + elif template.lab_test_template_type == 'Descriptive': + create_descriptives(template, lab_test) + elif template.lab_test_template_type == 'Grouped': + # Iterate for each template in the group and create one result for all. for lab_test_group in template.lab_test_groups: - #template_in_group = None - if(lab_test_group.lab_test_template): - template_in_group = frappe.get_doc("Lab Test Template", + # Template_in_group = None + if lab_test_group.lab_test_template: + template_in_group = frappe.get_doc('Lab Test Template', lab_test_group.lab_test_template) - if(template_in_group): - if(template_in_group.lab_test_template_type == 'Single'): + if template_in_group: + if template_in_group.lab_test_template_type == 'Single': create_normals(template_in_group, lab_test) - elif(template_in_group.lab_test_template_type == 'Compound'): - normal_heading = lab_test.append("normal_test_items") + elif template_in_group.lab_test_template_type == 'Compound': + normal_heading = lab_test.append('normal_test_items') normal_heading.lab_test_name = template_in_group.lab_test_name normal_heading.require_result_value = 0 + normal_heading.allow_blank = 1 normal_heading.template = template_in_group.name create_compounds(template_in_group, lab_test, True) - elif(template_in_group.lab_test_template_type == 'Descriptive'): - special_heading = lab_test.append("special_test_items") - special_heading.lab_test_name = template_in_group.lab_test_name - special_heading.require_result_value = 0 - special_heading.template = template_in_group.name - create_specials(template_in_group, lab_test) - else: - normal = lab_test.append("normal_test_items") + elif template_in_group.lab_test_template_type == 'Descriptive': + descriptive_heading = lab_test.append('descriptive_test_items') + descriptive_heading.lab_test_name = template_in_group.lab_test_name + descriptive_heading.require_result_value = 0 + descriptive_heading.allow_blank = 1 + descriptive_heading.template = template_in_group.name + create_descriptives(template_in_group, lab_test) + else: # Lab Test Group - Add New Line + normal = lab_test.append('normal_test_items') normal.lab_test_name = lab_test_group.group_event normal.lab_test_uom = lab_test_group.group_test_uom + normal.secondary_uom = lab_test_group.secondary_uom + normal.conversion_factor = lab_test_group.conversion_factor normal.normal_range = lab_test_group.group_test_normal_range + normal.allow_blank = lab_test_group.allow_blank normal.require_result_value = 1 normal.template = template.name - if(template.lab_test_template_type != 'No Result'): - if(prescription): + if template.lab_test_template_type != 'No Result': + if prescription: lab_test.prescription = prescription - if(invoice): - frappe.db.set_value("Lab Prescription", prescription, "invoiced", True) - lab_test.save(ignore_permissions=True) # insert the result + if invoice: + frappe.db.set_value('Lab Prescription', prescription, 'invoiced', True) + lab_test.save(ignore_permissions=True) # Insert the result return lab_test @frappe.whitelist() def get_employee_by_user_id(user_id): - emp_id = frappe.db.get_value("Employee",{"user_id":user_id}) - employee = frappe.get_doc("Employee",emp_id) + emp_id = frappe.db.get_value('Employee', { 'user_id': user_id }) + employee = frappe.get_doc('Employee', emp_id) return employee 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 + "
" + subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '
' if doc.normal_test_items: item = doc.normal_test_items[0] - comment = "" + comment = '' if item.lab_test_comment: comment = str(item.lab_test_comment) - table_row = frappe.bold(_("Lab Test Conducted: ")) + item.lab_test_name + 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 + 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 + table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value if item.normal_range: - table_row += " " + _("Normal Range:") + item.normal_range - table_row += " " + comment + table_row += ' ' + _('Normal Range:') + item.normal_range + table_row += ' ' + comment - elif doc.special_test_items: - item = doc.special_test_items[0] + 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 + 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 + table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity if table_row: - subject += "
" + table_row + subject += '
' + table_row if doc.lab_test_comment: - subject += "
" + cstr(doc.lab_test_comment) + subject += '
' + cstr(doc.lab_test_comment) - medical_record = frappe.new_doc("Patient Medical Record") + medical_record = frappe.new_doc('Patient Medical Record') medical_record.patient = doc.patient medical_record.subject = subject - medical_record.status = "Open" + medical_record.status = 'Open' medical_record.communication_date = doc.result_date - medical_record.reference_doctype = "Lab Test" + medical_record.reference_doctype = 'Lab Test' medical_record.reference_name = doc.name medical_record.reference_owner = doc.owner - medical_record.save(ignore_permissions=True) + 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)) + 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.delete_doc('Patient Medical Record', medical_record_id[0][0]) @frappe.whitelist() def get_lab_test_prescribed(patient): - return frappe.db.sql("""select cp.name, cp.lab_test_code, cp.parent, cp.invoiced, ct.practitioner, ct.encounter_date from `tabPatient Encounter` ct, - `tabLab Prescription` cp where ct.patient=%s and cp.parent=ct.name and cp.lab_test_created=0""", (patient)) + return frappe.db.sql( + ''' + select + lp.name, + lp.lab_test_code, + lp.parent, + lp.invoiced, + pe.practitioner, + pe.practitioner_name, + pe.encounter_date + from + `tabPatient Encounter` pe, `tabLab Prescription` lp + where + pe.patient=%s + and lp.parent=pe.name + and lp.lab_test_created=0 + ''', (patient)) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test_list.js b/erpnext/healthcare/doctype/lab_test/lab_test_list.js index 1f6a12f935..6783bb3a59 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test_list.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test_list.js @@ -2,57 +2,63 @@ (c) ESS 2015-16 */ frappe.listview_settings['Lab Test'] = { - add_fields: ["name", "status", "invoiced"], - filters:[["docstatus","=","0"]], - get_indicator: function(doc) { - if(doc.status=="Approved"){ - return [__("Approved"), "green", "status,=,Approved"]; + add_fields: ['name', 'status', 'invoiced'], + filters: [['docstatus', '=', '0']], + get_indicator: function (doc) { + if (doc.status == 'Approved') { + return [__('Approved'), 'green', 'status, = ,Approved']; } - if(doc.status=="Rejected"){ - return [__("Rejected"), "yellow", "status,=,Rejected"]; + if (doc.status == 'Rejected') { + return [__('Rejected'), 'orange', 'status, =, Rejected']; } }, - onload: function(listview) { - listview.page.add_menu_item(__("Create Multiple"), function() { + onload: function (listview) { + listview.page.add_menu_item(__('Create Multiple'), function () { create_multiple_dialog(listview); }); } }; -var create_multiple_dialog = function(listview){ +var create_multiple_dialog = function (listview) { var dialog = new frappe.ui.Dialog({ title: 'Create Multiple Lab Test', width: 100, fields: [ - {fieldtype: "Link", label: "Patient", fieldname: "patient", options: "Patient", reqd: 1}, - {fieldtype: "Select", label: "Invoice / Patient Encounter", fieldname: "doctype", - options: "\nSales Invoice\nPatient Encounter", reqd: 1}, - {fieldtype: "Dynamic Link", fieldname: "docname", options: "doctype", reqd: 1, - get_query: function(){ + { fieldtype: 'Link', label: 'Patient', fieldname: 'patient', options: 'Patient', reqd: 1 }, + { + fieldtype: 'Select', label: 'Invoice / Patient Encounter', fieldname: 'doctype', + options: '\nSales Invoice\nPatient Encounter', reqd: 1 + }, + { + fieldtype: 'Dynamic Link', fieldname: 'docname', options: 'doctype', reqd: 1, + get_query: function () { return { filters: { - "patient": dialog.get_value("patient"), - "docstatus": 1 + 'patient': dialog.get_value('patient'), + 'docstatus': 1 } }; } } ], - primary_action_label: __("Create Lab Test"), - primary_action : function(){ + primary_action_label: __('Create Lab Test'), + primary_action: function () { frappe.call({ method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple', - args:{ - 'doctype': dialog.get_value("doctype"), - 'docname': dialog.get_value("docname") + args: { + 'doctype': dialog.get_value('doctype'), + 'docname': dialog.get_value('docname') }, - callback: function(data) { - if(!data.exc){ + callback: function (data) { + if (!data.exc) { + if (!data.message) { + frappe.msgprint(__('No Lab Tests created')); + } listview.refresh(); } }, freeze: true, - freeze_message: "Creating Lab Test..." + freeze_message: 'Creating Lab Tests...' }); dialog.hide(); } diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/__init__.py b/erpnext/healthcare/doctype/lab_test_group_template/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/sensitivity_test_items/__init__.py rename to erpnext/healthcare/doctype/lab_test_group_template/__init__.py diff --git a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json new file mode 100644 index 0000000000..beea7a357e --- /dev/null +++ b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-03-29 17:37:29.913583", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "template_or_new_line", + "lab_test_template", + "lab_test_rate", + "lab_test_description", + "group_event", + "group_test_uom", + "secondary_uom", + "conversion_factor", + "allow_blank", + "column_break_8", + "group_test_normal_range" + ], + "fields": [ + { + "default": "Add Test", + "fieldname": "template_or_new_line", + "fieldtype": "Select", + "options": "Add Test\nAdd New Line", + "print_hide": 1, + "report_hide": 1 + }, + { + "depends_on": "eval:doc.template_or_new_line == 'Add Test'", + "fieldname": "lab_test_template", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Test Name", + "options": "Lab Test Template" + }, + { + "fetch_from": "lab_test_template.lab_test_rate", + "fieldname": "lab_test_rate", + "fieldtype": "Currency", + "label": "Rate", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fetch_from": "lab_test_template.lab_test_description", + "fieldname": "lab_test_description", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Description", + "read_only": 1 + }, + { + "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", + "fieldname": "group_event", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Event" + }, + { + "depends_on": "eval:doc.template_or_new_line =='Add New Line'", + "fieldname": "group_test_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", + "fieldname": "group_test_normal_range", + "fieldtype": "Long Text", + "ignore_xss_filter": 1, + "label": "Normal Range" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.template_or_new_line =='Add New Line'", + "fieldname": "secondary_uom", + "fieldtype": "Link", + "label": "Secondary UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor" + }, + { + "default": "0", + "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", + "fieldname": "allow_blank", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Blank" + } + ], + "istable": 1, + "links": [], + "modified": "2020-06-24 10:59:01.921924", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test Group Template", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.py b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py similarity index 84% rename from erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.py rename to erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py index c67531c179..1e2cef4e18 100644 --- a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.py +++ b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals from frappe.model.document import Document -class LabTestGroups(Document): +class LabTestGroupTemplate(Document): pass diff --git a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.json b/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.json deleted file mode 100644 index e51d8b7557..0000000000 --- a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.json +++ /dev/null @@ -1,310 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2016-03-29 17:37:29.913583", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Add Test", - "depends_on": "", - "fieldname": "template_or_new_line", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "options": "Add Test\nAdd new line", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.template_or_new_line == 'Add Test'", - "fieldname": "lab_test_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Test Name", - "length": 0, - "no_copy": 0, - "options": "Lab Test Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "lab_test_template.lab_test_rate", - "fieldname": "lab_test_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "lab_test_template.lab_test_description", - "fieldname": "lab_test_description", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.template_or_new_line == 'Add new line'", - "fieldname": "group_event", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Event", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.template_or_new_line =='Add new line'", - "fieldname": "group_test_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "UOM", - "length": 0, - "no_copy": 0, - "options": "Lab Test UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.template_or_new_line == 'Add new line'", - "fieldname": "group_test_normal_range", - "fieldtype": "Long Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Normal Range", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-09-04 09:49:24.817787", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Groups", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js index c3eedbbdf1..2e41f518f0 100644 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js +++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js @@ -1,25 +1,25 @@ // Copyright (c) 2016, ESS // License: ESS license.txt -frappe.ui.form.on("Lab Test Template",{ +frappe.ui.form.on('Lab Test Template', { lab_test_name: function(frm) { if (!frm.doc.lab_test_code) - frm.set_value("lab_test_code", frm.doc.lab_test_name); + frm.set_value('lab_test_code', frm.doc.lab_test_name); if (!frm.doc.lab_test_description) - frm.set_value("lab_test_description", frm.doc.lab_test_name); + frm.set_value('lab_test_description', frm.doc.lab_test_name); }, - refresh: function(frm) { - // Restrict Special, Grouped type templates in Child TestGroups - frm.set_query("lab_test_template", "lab_test_groups", function() { + refresh : function(frm) { + // Restrict Special, Grouped type templates in Child Test Groups + frm.set_query('lab_test_template', 'lab_test_groups', function() { return { filters: { - lab_test_template_type: ['in',['Single','Compound']] + lab_test_template_type: ['in', ['Single','Compound']] } }; }); }, medical_code: function(frm) { - frm.set_query("medical_code", function() { + frm.set_query('medical_code', function() { return { filters: { medical_code_standard: frm.doc.medical_code_standard @@ -30,10 +30,10 @@ frappe.ui.form.on("Lab Test Template",{ }); cur_frm.cscript.custom_refresh = function(doc) { - cur_frm.set_df_property("lab_test_code", "read_only", doc.__islocal ? 0 : 1); + cur_frm.set_df_property('lab_test_code', 'read_only', doc.__islocal ? 0 : 1); if (!doc.__islocal) { - cur_frm.add_custom_button(__("Change Template Code"), function() { + cur_frm.add_custom_button(__('Change Template Code'), function() { change_template_code(doc); }); } @@ -41,12 +41,12 @@ cur_frm.cscript.custom_refresh = function(doc) { let change_template_code = function(doc) { let d = new frappe.ui.Dialog({ - title:__("Change Template Code"), + title:__('Change Template Code'), fields:[ { - "fieldtype": "Data", - "label": "Lab Test Template Code", - "fieldname": "lab_test_code", + 'fieldtype': 'Data', + 'label': 'Lab Test Template Code', + 'fieldname': 'lab_test_code', reqd: 1 } ], @@ -54,49 +54,44 @@ let change_template_code = function(doc) { let values = d.get_values(); if (values) { frappe.call({ - "method": "erpnext.healthcare.doctype.lab_test_template.lab_test_template.change_test_code_from_template", - "args": {lab_test_code: values.lab_test_code, doc: doc}, + 'method': 'erpnext.healthcare.doctype.lab_test_template.lab_test_template.change_test_code_from_template', + 'args': {lab_test_code: values.lab_test_code, doc: doc}, callback: function (data) { - frappe.set_route("Form", "Lab Test Template", data.message); + frappe.set_route('Form', 'Lab Test Template', data.message); } }); } d.hide(); }, - primary_action_label: __("Change Template Code") + primary_action_label: __('Change Template Code') }); d.show(); d.set_values({ - "lab_test_code": doc.lab_test_code + 'lab_test_code': doc.lab_test_code }); }; -frappe.ui.form.on("Lab Test Template", "lab_test_name", function(frm){ - +frappe.ui.form.on('Lab Test Template', 'lab_test_name', function(frm) { frm.doc.change_in_item = 1; - -}); -frappe.ui.form.on("Lab Test Template", "lab_test_rate", function(frm){ - - frm.doc.change_in_item = 1; - -}); -frappe.ui.form.on("Lab Test Template", "lab_test_group", function(frm){ - - frm.doc.change_in_item = 1; - -}); -frappe.ui.form.on("Lab Test Template", "lab_test_description", function(frm){ - - frm.doc.change_in_item = 1; - }); -frappe.ui.form.on("Lab Test Groups", "template_or_new_line", function (frm, cdt, cdn) { +frappe.ui.form.on('Lab Test Template', 'lab_test_rate', function(frm) { + frm.doc.change_in_item = 1; +}); + +frappe.ui.form.on('Lab Test Template', 'lab_test_group', function(frm) { + frm.doc.change_in_item = 1; +}); + +frappe.ui.form.on('Lab Test Template', 'lab_test_description', function(frm) { + frm.doc.change_in_item = 1; +}); + +frappe.ui.form.on('Lab Test Groups', 'template_or_new_line', function (frm, cdt, cdn) { let child = locals[cdt][cdn]; - if (child.template_or_new_line == "Add new line") { - frappe.model.set_value(cdt, cdn, 'lab_test_template', ""); - frappe.model.set_value(cdt, cdn, 'lab_test_description', ""); + if (child.template_or_new_line == 'Add New Line') { + frappe.model.set_value(cdt, cdn, 'lab_test_template', ''); + frappe.model.set_value(cdt, cdn, 'lab_test_description', ''); } }); diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json index ebd2ec0246..db64297269 100644 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json +++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json @@ -15,31 +15,38 @@ "lab_test_group", "department", "column_break_3", - "lab_test_template_type", "disabled", + "lab_test_template_type", "is_billable", "lab_test_rate", - "medical_coding_section", - "medical_code_standard", - "medical_code", + "section_break_description", + "lab_test_description", "section_break_normal", "lab_test_uom", - "lab_test_normal_range", + "secondary_uom", + "conversion_factor", "column_break_10", + "lab_test_normal_range", "section_break_compound", "normal_test_templates", "section_break_special", "sensitivity", - "special_test_template", + "descriptive_test_templates", "section_break_group", "lab_test_groups", - "section_break_description", - "lab_test_description", + "medical_coding_section", + "medical_code_standard", + "medical_code", "sb_sample_collection", "sample", "sample_uom", "sample_qty", "sample_details", + "worksheet_section", + "worksheet_instructions", + "result_legend_section", + "legend_print_position", + "result_legend", "change_in_item" ], "fields": [ @@ -95,7 +102,7 @@ "fieldtype": "Column Break" }, { - "description": "Single for results which require only a single input, result UOM and normal value \n
\nCompound for results which require multiple input fields with corresponding event names, result UOMs and normal values\n
\nDescriptive for tests which have multiple result components and corresponding result entry fields. \n
\nGrouped for test templates which are a group of other test templates.\n
\nNo Result for tests with no results. Also, no Lab Test is created. e.g.. Sub Tests for Grouped results.", + "description": "Single: Results which require only a single input.\n
\nCompound: Results which require multiple event inputs.\n
\nDescriptive: Tests which have multiple result components with manual result entry.\n
\nGrouped: Test templates which are a group of other test templates.\n
\nNo Result: Tests with no results, can be ordered and billed but no Lab Test will be created. e.g.. Sub Tests for Grouped results", "fieldname": "lab_test_template_type", "fieldtype": "Select", "in_standard_filter": 1, @@ -120,6 +127,24 @@ "label": "Rate", "mandatory_depends_on": "eval:doc.is_billable == 1" }, + { + "fieldname": "medical_coding_section", + "fieldtype": "Section Break", + "label": "Medical Coding" + }, + { + "depends_on": "medical_code_standard", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code" + }, + { + "fieldname": "medical_code_standard", + "fieldtype": "Link", + "label": "Medical Code Standard", + "options": "Medical Code Standard" + }, { "depends_on": "eval:doc.lab_test_template_type == 'Single'", "fieldname": "section_break_normal", @@ -159,7 +184,7 @@ "depends_on": "eval:doc.lab_test_template_type == 'Descriptive'", "fieldname": "section_break_special", "fieldtype": "Section Break", - "label": "Special" + "label": "Descriptive" }, { "default": "0", @@ -167,11 +192,6 @@ "fieldtype": "Check", "label": "Sensitivity" }, - { - "fieldname": "special_test_template", - "fieldtype": "Table", - "options": "Special Test Template" - }, { "depends_on": "eval:doc.lab_test_template_type == 'Grouped'", "fieldname": "section_break_group", @@ -181,20 +201,23 @@ { "fieldname": "lab_test_groups", "fieldtype": "Table", - "options": "Lab Test Groups" + "options": "Lab Test Group Template" }, { + "collapsible": 1, "fieldname": "section_break_description", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Description " }, { "fieldname": "lab_test_description", - "fieldtype": "Text", + "fieldtype": "Text Editor", "ignore_xss_filter": 1, "label": "Description", "no_copy": 1 }, { + "collapsible": 1, "fieldname": "sb_sample_collection", "fieldtype": "Section Break", "label": "Sample Collection" @@ -237,32 +260,61 @@ }, { "fieldname": "sample_details", - "fieldtype": "Text", + "fieldtype": "Small Text", "ignore_xss_filter": 1, "label": "Collection Details" }, { "collapsible": 1, - "fieldname": "medical_coding_section", + "description": "Information to help easily interpret the test report, will be printed as part of the Lab Test result.", + "fieldname": "result_legend_section", "fieldtype": "Section Break", - "label": "Medical Coding" + "label": "Result Legend Print" }, { - "depends_on": "medical_code_standard", - "fieldname": "medical_code", - "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code" + "fieldname": "result_legend", + "fieldtype": "Text Editor", + "label": "Result Legend" }, { - "fieldname": "medical_code_standard", + "fieldname": "legend_print_position", + "fieldtype": "Select", + "label": "Print Position", + "options": "Bottom\nTop\nBoth" + }, + { + "fieldname": "secondary_uom", "fieldtype": "Link", - "label": "Medical Code Standard", - "options": "Medical Code Standard" + "label": "Secondary UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "mandatory_depends_on": "secondary_uom" + }, + { + "description": "Instructions to be printed on the worksheet", + "fieldname": "worksheet_instructions", + "fieldtype": "Text Editor", + "label": "Worksheet Instructions" + }, + { + "collapsible": 1, + "fieldname": "worksheet_section", + "fieldtype": "Section Break", + "label": "Worksheet Print" + }, + { + "fieldname": "descriptive_test_templates", + "fieldtype": "Table", + "options": "Descriptive Test Template" } ], "links": [], - "modified": "2020-06-29 14:07:20.772219", + "modified": "2020-07-13 12:57:09.925436", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test Template", diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py index 3521561f34..6f0d08cf85 100644 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py +++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py @@ -14,37 +14,37 @@ class LabTestTemplate(Document): create_item_from_template(self) def validate(self): + if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0): + frappe.throw(_("Standard Selling Rate should be greater than zero.")) + self.validate_conversion_factor() self.enable_disable_item() def on_update(self): - # if change_in_item update Item and Price List + # If change_in_item update Item and Price List if self.change_in_item and self.is_billable and self.item: self.update_item() item_price = self.item_price_exists() if not item_price: - if self.lab_test_rate != 0.0: - price_list_name = frappe.db.get_value("Price List", {"selling": 1}) - if self.lab_test_rate: - make_item_price(self.lab_test_code, price_list_name, self.lab_test_rate) - else: - make_item_price(self.lab_test_code, price_list_name, 0.0) + if self.lab_test_rate and self.lab_test_rate > 0.0: + price_list_name = frappe.db.get_value('Price List', {'selling': 1}) + make_item_price(self.lab_test_code, price_list_name, self.lab_test_rate) else: - frappe.db.set_value("Item Price", item_price, "price_list_rate", self.lab_test_rate) + frappe.db.set_value('Item Price', item_price, 'price_list_rate', self.lab_test_rate) - frappe.db.set_value(self.doctype, self.name, "change_in_item", 0) + self.db_set('change_in_item', 0) elif not self.is_billable and self.item: - frappe.db.set_value("Item", self.item, "disabled", 1) + frappe.db.set_value('Item', self.item, 'disabled', 1) self.reload() def on_trash(self): - # remove template reference from item and disable item + # Remove template reference from item and disable item if self.item: try: - frappe.delete_doc("Item", self.item) + frappe.delete_doc('Item', self.item) except Exception: - frappe.throw(_("Not permitted. Please disable the Lab Test Template")) + frappe.throw(_('Not permitted. Please disable the Lab Test Template')) def enable_disable_item(self): if self.is_billable: @@ -54,78 +54,86 @@ class LabTestTemplate(Document): frappe.db.set_value('Item', self.item, 'disabled', 0) def update_item(self): - item = frappe.get_doc("Item", self.item) + item = frappe.get_doc('Item', self.item) if item: item.update({ - "item_name": self.lab_test_name, - "item_group": self.lab_test_group, - "disabled": 0, - "standard_rate": self.lab_test_rate, - "description": self.lab_test_description + 'item_name': self.lab_test_name, + 'item_group': self.lab_test_group, + 'disabled': 0, + 'standard_rate': self.lab_test_rate, + 'description': self.lab_test_description }) item.save() def item_price_exists(self): - item_price = frappe.db.exists({"doctype": "Item Price", "item_code": self.lab_test_code}) + item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code}) if item_price: return item_price[0][0] else: return False + def validate_conversion_factor(self): + if self.lab_test_template_type == "Single" and self.secondary_uom and not self.conversion_factor: + frappe.throw(_("Conversion Factor is mandatory")) + if self.lab_test_template_type == "Compound": + for item in self.normal_test_templates: + if item.secondary_uom and not item.conversion_factor: + frappe.throw(_("Conversion Factor is mandatory")) + if self.lab_test_template_type == "Grouped": + for group in self.lab_test_groups: + if group.template_or_new_line == "Add New Line" and group.secondary_uom and not group.conversion_factor: + frappe.throw(_("Conversion Factor is mandatory")) + def create_item_from_template(doc): - disabled = doc.disabled - if doc.is_billable and not doc.disabled: - disabled = 0 - uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom') - # insert item + # Insert item item = frappe.get_doc({ - "doctype": "Item", - "item_code": doc.lab_test_code, - "item_name":doc.lab_test_name, - "item_group": doc.lab_test_group, - "description":doc.lab_test_description, - "is_sales_item": 1, - "is_service_item": 1, - "is_purchase_item": 0, - "is_stock_item": 0, - "show_in_website": 0, - "is_pro_applicable": 0, - "disabled": disabled, - "stock_uom": uom - }).insert(ignore_permissions=True, ignore_mandatory=True) + 'doctype': 'Item', + 'item_code': doc.lab_test_code, + 'item_name':doc.lab_test_name, + 'item_group': doc.lab_test_group, + 'description':doc.lab_test_description, + 'is_sales_item': 1, + 'is_service_item': 1, + 'is_purchase_item': 0, + 'is_stock_item': 0, + 'include_item_in_manufacturing': 0, + 'show_in_website': 0, + 'is_pro_applicable': 0, + 'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled, + 'stock_uom': uom + }).insert(ignore_permissions = True, ignore_mandatory = True) - # insert item price - # get item price list to insert item price - if doc.lab_test_rate != 0.0: - price_list_name = frappe.db.get_value("Price List", {"selling": 1}) + # Insert item price + if doc.is_billable and doc.lab_test_rate != 0.0: + price_list_name = frappe.db.get_value('Price List', {'selling': 1}) if doc.lab_test_rate: make_item_price(item.name, price_list_name, doc.lab_test_rate) else: make_item_price(item.name, price_list_name, 0.0) # Set item in the template - frappe.db.set_value("Lab Test Template", doc.name, "item", item.name) + frappe.db.set_value('Lab Test Template', doc.name, 'item', item.name) doc.reload() def make_item_price(item, price_list_name, item_price): frappe.get_doc({ - "doctype": "Item Price", - "price_list": price_list_name, - "item_code": item, - "price_list_rate": item_price - }).insert(ignore_permissions=True, ignore_mandatory=True) + 'doctype': 'Item Price', + 'price_list': price_list_name, + 'item_code': item, + 'price_list_rate': item_price + }).insert(ignore_permissions = True, ignore_mandatory = True) @frappe.whitelist() def change_test_code_from_template(lab_test_code, doc): doc = frappe._dict(json.loads(doc)) - if frappe.db.exists({ "doctype": "Item", "item_code": lab_test_code}): - frappe.throw(_("Lab Test Item {0} already exist").format(lab_test_code)) + if frappe.db.exists({'doctype': 'Item', 'item_code': lab_test_code}): + frappe.throw(_('Lab Test Item {0} already exist').format(lab_test_code)) else: - rename_doc("Item", doc.name, lab_test_code, ignore_permissions=True) - frappe.db.set_value("Lab Test Template", doc.name, "lab_test_code", lab_test_code) - frappe.db.set_value("Lab Test Template", doc.name, "lab_test_name", lab_test_code) - rename_doc("Lab Test Template", doc.name, lab_test_code, ignore_permissions=True) - return lab_test_code \ No newline at end of file + rename_doc('Item', doc.name, lab_test_code, ignore_permissions = True) + frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_code', lab_test_code) + frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_name', lab_test_code) + rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions = True) + return lab_test_code diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js index a86075fc94..a3417ebdfc 100644 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js +++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js @@ -2,6 +2,6 @@ (c) ESS 2015-16 */ frappe.listview_settings['Lab Test Template'] = { - add_fields: ["lab_test_name", "lab_test_code", "lab_test_rate"], - filters: [["disabled", "=", 0]] + add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'], + filters: [['disabled', '=', 0]] }; diff --git a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.js b/erpnext/healthcare/doctype/normal_test_items/normal_test_items.js deleted file mode 100644 index 0371ddd5c9..0000000000 --- a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.js +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) 2016, ESS -// License: ESS license.txt - - diff --git a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.json b/erpnext/healthcare/doctype/normal_test_items/normal_test_items.json deleted file mode 100644 index a7a952b8cd..0000000000 --- a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2016-02-22 15:06:08.295224", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Test Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_event", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Event", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.require_result_value == 1 ", - "fieldname": "result_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Result Value", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_uom", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "UOM", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "normal_range", - "fieldtype": "Long Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Normal Range", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_comment", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Comment", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "require_result_value", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Require Result Value", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "template", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Template", - "length": 0, - "no_copy": 0, - "options": "Lab Test Template", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-09-04 11:42:43.095726", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Normal Test Items", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/special_test_items/__init__.py b/erpnext/healthcare/doctype/normal_test_result/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/special_test_items/__init__.py rename to erpnext/healthcare/doctype/normal_test_result/__init__.py diff --git a/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json new file mode 100644 index 0000000000..c8f43d3a54 --- /dev/null +++ b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json @@ -0,0 +1,186 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 15:06:08.295224", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lab_test_name", + "lab_test_event", + "result_value", + "lab_test_uom", + "secondary_uom_result", + "secondary_uom", + "conversion_factor", + "column_break_10", + "allow_blank", + "normal_range", + "lab_test_comment", + "bold", + "italic", + "underline", + "template", + "require_result_value" + ], + "fields": [ + { + "fieldname": "lab_test_name", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Test Name", + "read_only": 1 + }, + { + "fieldname": "lab_test_event", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Event", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "result_value", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Result Value" + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "lab_test_uom", + "fieldtype": "Link", + "label": "UOM", + "options": "Lab Test UOM", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "normal_range", + "fieldtype": "Long Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Normal Range", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "lab_test_comment", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Comment", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "template", + "fieldtype": "Link", + "hidden": 1, + "label": "Template", + "options": "Lab Test Template", + "print_hide": 1, + "report_hide": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "secondary_uom", + "fieldtype": "Link", + "label": "Secondary UOM", + "options": "Lab Test UOM", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "mandatory_depends_on": "secondary_uom", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value && doc.result_value", + "fieldname": "secondary_uom_result", + "fieldtype": "Data", + "label": "Secondary UOM Result", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "depends_on": "eval:doc.require_result_value", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "depends_on": "eval:doc.require_result_value", + "fieldname": "italic", + "fieldtype": "Check", + "label": "Italic", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "depends_on": "eval:doc.require_result_value", + "fieldname": "underline", + "fieldtype": "Check", + "label": "Underline", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "require_result_value", + "fieldtype": "Check", + "hidden": 1, + "label": "Require Result Value", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.require_result_value", + "fieldname": "allow_blank", + "fieldtype": "Check", + "label": "Allow Blank", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-08 16:03:17.522893", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Normal Test Result", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.py b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.py similarity index 85% rename from erpnext/healthcare/doctype/normal_test_items/normal_test_items.py rename to erpnext/healthcare/doctype/normal_test_result/normal_test_result.py index a0069d7252..63abf0297e 100644 --- a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.py +++ b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals from frappe.model.document import Document -class NormalTestItems(Document): +class NormalTestResult(Document): pass diff --git a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json b/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json index a36c28d070..8dd6476ea8 100644 --- a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json +++ b/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json @@ -1,202 +1,84 @@ { - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2016-02-22 16:09:54.310628", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 16:09:54.310628", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "heading_text", + "lab_test_event", + "allow_blank", + "lab_test_uom", + "secondary_uom", + "conversion_factor", + "column_break_5", + "normal_range" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "heading_text", - "fieldtype": "Heading", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Test", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "heading_text", + "fieldtype": "Heading", + "ignore_xss_filter": 1, + "label": "Test" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_event", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Event", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "lab_test_event", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Event" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "UOM", - "length": 0, - "no_copy": 0, - "options": "Lab Test UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "lab_test_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "UOM", + "options": "Lab Test UOM" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "normal_range", - "fieldtype": "Long Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Normal Range", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "normal_range", + "fieldtype": "Long Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Normal Range" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "secondary_uom", + "fieldtype": "Link", + "label": "Secondary UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "mandatory_depends_on": "secondary_uom" + }, + { + "default": "0", + "fieldname": "allow_blank", + "fieldtype": "Check", + "label": "Allow Blank" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-09-04 11:42:30.766950", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Normal Test Template", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-06-23 13:28:40.156224", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Normal Test Template", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/special_test_template/__init__.py b/erpnext/healthcare/doctype/organism/__init__.py similarity index 100% rename from erpnext/healthcare/doctype/special_test_template/__init__.py rename to erpnext/healthcare/doctype/organism/__init__.py diff --git a/erpnext/healthcare/doctype/organism/organism.js b/erpnext/healthcare/doctype/organism/organism.js new file mode 100644 index 0000000000..fbcb0942e9 --- /dev/null +++ b/erpnext/healthcare/doctype/organism/organism.js @@ -0,0 +1,5 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Organism', { +}); diff --git a/erpnext/healthcare/doctype/organism/organism.json b/erpnext/healthcare/doctype/organism/organism.json new file mode 100644 index 0000000000..88a7686777 --- /dev/null +++ b/erpnext/healthcare/doctype/organism/organism.json @@ -0,0 +1,152 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:organism", + "beta": 1, + "creation": "2019-09-06 16:29:07.797960", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "organism", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Organism", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "abbr", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Abbr", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-10-04 19:45:33.353753", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Organism", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "organism, abbr", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "organism", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/organism/organism.py b/erpnext/healthcare/doctype/organism/organism.py new file mode 100644 index 0000000000..1ead762c2f --- /dev/null +++ b/erpnext/healthcare/doctype/organism/organism.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class Organism(Document): + pass diff --git a/erpnext/healthcare/doctype/organism/test_organism.js b/erpnext/healthcare/doctype/organism/test_organism.js new file mode 100644 index 0000000000..d57e5536c6 --- /dev/null +++ b/erpnext/healthcare/doctype/organism/test_organism.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Organism", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Organism + () => frappe.tests.make('Organism', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/healthcare/doctype/organism/test_organism.py b/erpnext/healthcare/doctype/organism/test_organism.py new file mode 100644 index 0000000000..ecb96650e1 --- /dev/null +++ b/erpnext/healthcare/doctype/organism/test_organism.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals +import unittest + +class TestOrganism(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/organism_test_item/__init__.py b/erpnext/healthcare/doctype/organism_test_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json new file mode 100644 index 0000000000..56d0a4d905 --- /dev/null +++ b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json @@ -0,0 +1,144 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2019-09-06 16:37:59.698996", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "organism", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Organism", + "length": 0, + "no_copy": 0, + "options": "Organism", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_population", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony Population", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony UOM", + "length": 0, + "no_copy": 0, + "options": "Lab Test UOM", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2019-10-04 19:48:04.104234", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Organism Test Item", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py new file mode 100644 index 0000000000..019a55b396 --- /dev/null +++ b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class OrganismTestItem(Document): + pass diff --git a/erpnext/healthcare/doctype/organism_test_result/__init__.py b/erpnext/healthcare/doctype/organism_test_result/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json new file mode 100644 index 0000000000..8b238de4cd --- /dev/null +++ b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json @@ -0,0 +1,144 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2019-09-06 16:37:59.698996", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "organism", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Organism", + "length": 0, + "no_copy": 0, + "options": "Organism", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_population", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony Population", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony UOM", + "length": 0, + "no_copy": 0, + "options": "Lab Test UOM", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2019-10-04 19:48:04.104234", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Organism Test Result", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py new file mode 100644 index 0000000000..02393c2700 --- /dev/null +++ b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class OrganismTestResult(Document): + pass diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.json b/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.json deleted file mode 100644 index 86f5e26f0a..0000000000 --- a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2016-02-22 15:18:01.769903", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "antibiotic", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Antibiotic", - "length": 0, - "no_copy": 0, - "options": "Antibiotic", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "antibiotic_sensitivity", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Sensitivity", - "length": 0, - "no_copy": 0, - "options": "Sensitivity", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-10-05 11:08:06.327972", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Sensitivity Test Items", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/sensitivity_test_result/__init__.py b/erpnext/healthcare/doctype/sensitivity_test_result/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json new file mode 100644 index 0000000000..768c17710f --- /dev/null +++ b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json @@ -0,0 +1,103 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2016-02-22 15:18:01.769903", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "antibiotic", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Antibiotic", + "length": 0, + "no_copy": 0, + "options": "Antibiotic", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "antibiotic_sensitivity", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Sensitivity", + "length": 0, + "no_copy": 0, + "options": "Sensitivity", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-10-05 11:08:06.327972", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Sensitivity Test Result", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/special_test_items/special_test_items.py b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py similarity index 84% rename from erpnext/healthcare/doctype/special_test_items/special_test_items.py rename to erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py index 17080b7e3b..64f1e6ca25 100644 --- a/erpnext/healthcare/doctype/special_test_items/special_test_items.py +++ b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals from frappe.model.document import Document -class SpecialTestItems(Document): +class SensitivityTestResult(Document): pass diff --git a/erpnext/healthcare/doctype/special_test_items/special_test_items.json b/erpnext/healthcare/doctype/special_test_items/special_test_items.json deleted file mode 100644 index a15806e8a5..0000000000 --- a/erpnext/healthcare/doctype/special_test_items/special_test_items.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2016-02-22 15:12:36.202380", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_particulars", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Particulars", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.require_result_value == 1", - "fieldname": "result_value", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Value", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "require_result_value", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Require Result Value", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "template", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Template", - "length": 0, - "no_copy": 0, - "options": "Lab Test Template", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-09-04 12:01:18.801216", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Special Test Items", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/special_test_template/special_test_template.json b/erpnext/healthcare/doctype/special_test_template/special_test_template.json deleted file mode 100644 index 372af0a959..0000000000 --- a/erpnext/healthcare/doctype/special_test_template/special_test_template.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2016-02-22 16:12:12.394200", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "particulars", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Result Component", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-10-04 16:20:09.565316", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Special Test Template", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/special_test_template/special_test_template.py b/erpnext/healthcare/doctype/special_test_template/special_test_template.py deleted file mode 100644 index e4e0d5b7bd..0000000000 --- a/erpnext/healthcare/doctype/special_test_template/special_test_template.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -from frappe.model.document import Document - -class SpecialTestTemplate(Document): - pass diff --git a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json index e8e95d8439..f7d16769c6 100644 --- a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json +++ b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json @@ -7,16 +7,17 @@ "docstatus": 0, "doctype": "Print Format", "font": "Default", - "html": "
\n {% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n
\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n Lab Tests have to be Submitted for Print .. !\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"lab_test_approval_required\") == '1' and doc.approval_status != \"Approved\") %}\n Lab Tests have to be Approved for Print .. !\n {%- else -%}\n
\n
\n\n
\n
\n \n
\n {% if doc.patient %}\n
\n : {{doc.patient}}\n
\n {% else %}\n
\n : Patient Name\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n : {{doc.patient_age}}\n
\n
\n\n
\n
\n \n
\n
\n : {{doc.patient_sex}}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner %}\n
\n : {{doc.practitioner}}\n
\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n : {{doc.sample_date}}\n
\n
\n {%- endif -%}\n\n {% if doc.result_date %}\n
\n
\n \n
\n
\n : {{doc.result_date}}\n
\n
\n {%- endif -%}\n\n
\n\n
\n\n
\n

Department of {{doc.department}}

\n
\n\n \n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%} \n {%- if row.lab_test_uom -%}{{ row.lab_test_uom }}{%- endif -%}\n \n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n \n \n {%- if doc.special_test_items -%}\n \n \n \n \n \n {%- for row in doc.special_test_items -%}\n \n \n \n \n\n {%- endfor -%}\n {%- endif -%}\n\n {%- if doc.sensitivity_test_items -%}\n \n \n \n \n {%- for row in doc.sensitivity_test_items -%}\n \n \n \n \n\n {%- endfor -%}\n {%- endif -%}\n\n \n
Name of TestResult
{{ doc.lab_test_name }}
  {{ row.lab_test_particulars }} \n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n
AntibioticSensitivity
{{ row.antibiotic }} {{ row.antibiotic_sensitivity }}
\n {%- endif -%}\n\n
\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n
{{doc.employee_name}}
\n
{{doc.employee_designation}}
\n {%- else -%}\n
{{frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}
\n {%- endif -%}\n
\n
\n", + "html": "
\n {% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n
\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n

WORKSHEET

\n\t
\n\t
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n {% else %}\n\t\t\t{% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t {% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n
\n
\n\n\t
\n

Department of {{ doc.department }}

\n
\n\n\t\n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n \n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n\t\n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }}
\n
\n {% if doc.worksheet_instructions %}\n
\n Instructions\n {{ doc.worksheet_instructions }}\n {%- endif -%}\n
\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"require_test_result_approval\") == '1' and doc.status != \"Approved\") %}\n Lab Tests have to be Approved for Print .. !\n {%- else -%}\n
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n\t\t{% else %}\n\t\t {% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t\t{% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n\n {% if doc.result_date %}\n
\n
\n \n
\n
\n {{ doc.result_date }}\n
\n
\n {%- endif -%}\n\n
\n\n
\n\n
\n

Department of {{ doc.department }}

\n
\n\n\t
\n\t\t{% if doc.result_legend and (doc.legend_print_position == \"Top\" or doc.legend_print_position == \"Both\")%}\n\t\tResult Legend:\n\t\t{{ doc.result_legend }}\n\t\t{%- endif -%}\n\t
\n\n \n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n\t\t\t\t\t{%- if row.result_value -%}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.result_value }}\n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t{%- endif -%}\n \n\t\t\t\t\t{%- if row.secondary_uom and row.conversion_factor and row.secondary_uom_result -%}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.secondary_uom_result }}\n  {{ row.secondary_uom }}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t \n\t\t\t\t\t{%- endif -%}\n
\n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n \n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n\n\t\t\t{%- if doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n\t\t\t{%- if doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }} \n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n
OrganismColony Population
{{ row.organism }} \n\t\t\t\t\t{{ row.colony_population }}\n\t\t\t\t\t{% if row.colony_uom %}\n\t\t\t\t\t\t{{ row.colony_uom }}\n\t\t\t\t\t{% endif %}\n\t\t\t\t
AntibioticSensitivity
{{ row.antibiotic }} {{ row.antibiotic_sensitivity }}
\n
\n {% if doc.custom_result %}\n
\n
{{ doc.custom_result }}
\n {%- endif -%}\n
\n\n
\n {% if doc.lab_test_comment %}\n
\n Comments\n {{ doc.lab_test_comment }}\n {%- endif -%}\n
\n\n
\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n {%- if doc.employee_name -%}\n
{{ doc.employee_name }}
\n {%- endif -%}\n {%- if doc.employee_designation -%}\n
{{ doc.employee_designation }}
\n {%- endif -%}\n {%- else -%}\n {%- if frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") -%}\n
{{ frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}
\n {%- endif -%}\n {%- endif -%}\n
\n\n
\n {% if doc.result_legend and (doc.legend_print_position == \"Bottom\" or doc.legend_print_position == \"Both\" or doc.legend_print_position == \"\")%}\n
\n Result Legend\n {{ doc.result_legend }}\n {%- endif -%}\n
\n {%- endif -%}\n
", "idx": 0, "line_breaks": 0, - "modified": "2018-09-04 12:03:47.066918", + "modified": "2020-07-08 15:34:28.866798", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test Print", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", + "raw_printing": 0, "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.json b/erpnext/healthcare/web_form/lab_test/lab_test.json index 88a9756e12..35099174e8 100644 --- a/erpnext/healthcare/web_form/lab_test/lab_test.json +++ b/erpnext/healthcare/web_form/lab_test/lab_test.json @@ -1,255 +1,459 @@ { - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 1, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2017-06-06 16:12:33.052258", - "currency": "INR", - "doc_type": "Lab Test", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "Lab Test", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2018-09-04 08:50:41.314546", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "lab-test", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "print_format": "Lab Test Print", - "published": 1, - "route": "lab-test", - "show_in_grid": 0, - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/lab-test", - "title": "Lab Test", + "accept_payment": 0, + "allow_comments": 1, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 1, + "amount": 0.0, + "amount_based_on_field": 0, + "creation": "2017-06-06 16:12:33.052258", + "currency": "INR", + "doc_type": "Lab Test", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "introduction_text": "Lab Test", + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2020-06-22 12:59:49.126398", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "lab-test", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "print_format": "Lab Test Print", + "published": 1, + "route": "lab-test", + "route_to_success_link": 0, + "show_attachments": 0, + "show_in_grid": 0, + "show_sidebar": 1, + "sidebar_items": [], + "success_url": "/lab-test", + "title": "Lab Test", "web_form_fields": [ { - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "label": "Series", - "max_length": 0, - "max_value": 0, - "options": "LP-", - "read_only": 0, - "reqd": 1, + "allow_read_on_all_link_options": 0, + "fieldname": "lab_test_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Test Name", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "hidden": 0, - "label": "Invoiced", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "department", + "fieldtype": "Link", + "hidden": 0, + "label": "Department", + "max_length": 0, + "max_value": 0, + "options": "Medical Department", + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "label": "Patient", - "max_length": 0, - "max_value": 0, - "options": "Patient", - "read_only": 0, - "reqd": 1, + "allow_read_on_all_link_options": 0, + "fieldname": "column_break_26", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Patient Name", - "max_length": 0, - "max_value": 0, - "options": "patient.patient_name", - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "label": "Company", + "max_length": 0, + "max_value": 0, + "options": "Company", + "read_only": 0, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "practitioner", - "fieldtype": "Link", - "hidden": 0, - "label": "Healthcare Practitioner", - "max_length": 0, - "max_value": 0, - "options": "Healthcare Practitioner", - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "label": "Status", + "max_length": 0, + "max_value": 0, + "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "label": "Status", - "max_length": 0, - "max_value": 0, - "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "submitted_date", + "fieldtype": "Datetime", + "hidden": 0, + "label": "Submitted Date", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "label": "Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "sb_first", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "sample", - "fieldtype": "Link", - "hidden": 0, - "label": "Sample ID", - "max_length": 0, - "max_value": 0, - "options": "Sample Collection", - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "patient", + "fieldtype": "Link", + "hidden": 0, + "label": "Patient", + "max_length": 0, + "max_value": 0, + "options": "Patient", + "read_only": 0, + "reqd": 1, "show_in_filter": 0 - }, + }, { - "default": "", - "fieldname": "result_date", - "fieldtype": "Date", - "hidden": 0, - "label": "Result Date", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "patient_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Patient Name", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "report_preference", - "fieldtype": "Data", - "hidden": 0, - "label": "Report Preference", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "patient_age", + "fieldtype": "Data", + "hidden": 0, + "label": "Age", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "lab_test_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Test Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "patient_sex", + "fieldtype": "Link", + "hidden": 0, + "label": "Gender", + "max_length": 0, + "max_value": 0, + "options": "Gender", + "read_only": 0, + "reqd": 1, "show_in_filter": 0 - }, + }, { - "fieldname": "normal_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Normal Test Items", - "read_only": 1, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "inpatient_record", + "fieldtype": "Link", + "hidden": 0, + "label": "Inpatient Record", + "max_length": 0, + "max_value": 0, + "options": "Inpatient Record", + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "special_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Special Test Items", - "read_only": 1, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "report_preference", + "fieldtype": "Data", + "hidden": 0, + "label": "Report Preference", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "sensitivity_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Sensitivity Test Items", - "read_only": 1, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "email", + "fieldtype": "Data", + "hidden": 1, + "label": "Email", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "lab_test_comment", - "fieldtype": "Text", - "hidden": 0, - "label": "Comments", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "mobile", + "fieldtype": "Data", + "hidden": 1, + "label": "Mobile", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "fieldname": "custom_result", - "fieldtype": "Text Editor", - "hidden": 0, - "label": "Custom Result", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "c_b", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "default": "0", - "fieldname": "sensitivity_toggle", - "fieldtype": "Check", - "hidden": 1, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "practitioner", + "fieldtype": "Link", + "hidden": 0, + "label": "Requesting Practitioner", + "max_length": 0, + "max_value": 0, + "options": "Healthcare Practitioner", + "read_only": 0, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "default": "0", - "fieldname": "special_toggle", - "fieldtype": "Check", - "hidden": 1, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "practitioner_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Requesting Practitioner", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, "show_in_filter": 0 - }, + }, { - "default": "0", - "fieldname": "normal_toggle", - "fieldtype": "Check", - "hidden": 1, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, + "allow_read_on_all_link_options": 0, + "fieldname": "requesting_department", + "fieldtype": "Link", + "hidden": 0, + "label": "Requesting Department", + "max_length": 0, + "max_value": 0, + "options": "Medical Department", + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "label": "Employee (Lab Technician)", + "max_length": 0, + "max_value": 0, + "options": "Employee", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "employee_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Lab Technician Name", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "employee_designation", + "fieldtype": "Data", + "hidden": 0, + "label": "Lab Technician Designation", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_normal", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "lab_test_html", + "fieldtype": "HTML", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "normal_test_items", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Normal Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_descriptive", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "descriptive_test_items", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Descriptive Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "depends_on": "special_toggle", + "fieldname": "organisms_section", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "organisms", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Organism Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_sensitivity", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sensitivity_test_items", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Sensitivity Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_comments", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "lab_test_comment", + "fieldtype": "Text", + "hidden": 0, + "label": "Comments", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_customresult", + "fieldtype": "Section Break", + "hidden": 0, + "label": "Custom Result", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "custom_result", + "fieldtype": "Text Editor", + "hidden": 0, + "label": "Custom Result", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, "show_in_filter": 0 } ] diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0c2b873f15..ea2611f698 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -712,3 +712,4 @@ erpnext.patches.v12_0.add_taxjar_integration_field erpnext.patches.v13_0.delete_report_requested_items_to_order erpnext.patches.v12_0.update_item_tax_template_company erpnext.patches.v13_0.move_branch_code_to_bank_account +erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py new file mode 100644 index 0000000000..5920bf1f70 --- /dev/null +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -0,0 +1,51 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if frappe.db.exists('DocType', 'Lab Test') and frappe.db.exists('DocType', 'Lab Test Template'): + # rename child doctypes + doctypes = { + 'Lab Test Groups': 'Lab Test Group Template', + 'Normal Test Items': 'Normal Test Result', + 'Sensitivity Test Items': 'Sensitivity Test Result', + 'Special Test Items': 'Descriptive Test Result', + 'Special Test Template': 'Descriptive Test Template' + } + + frappe.reload_doc('healthcare', 'doctype', 'lab_test') + frappe.reload_doc('healthcare', 'doctype', 'lab_test_template') + + for old_dt, new_dt in doctypes.items(): + if not frappe.db.table_exists(new_dt) and frappe.db.table_exists(old_dt): + frappe.rename_doc('DocType', old_dt, new_dt, force=True) + frappe.reload_doc('healthcare', 'doctype', frappe.scrub(new_dt)) + frappe.delete_doc_if_exists('DocType', old_dt) + + parent_fields = { + 'Lab Test Group Template': 'lab_test_groups', + 'Descriptive Test Template': 'descriptive_test_templates', + 'Normal Test Result': 'normal_test_items', + 'Sensitivity Test Result': 'sensitivity_test_items', + 'Descriptive Test Result': 'descriptive_test_items' + } + + for doctype, parentfield in parent_fields.items(): + frappe.db.sql(""" + UPDATE `tab{0}` + SET parentfield = %(parentfield)s + """.format(doctype), {'parentfield': parentfield}) + + # rename field + frappe.reload_doc('healthcare', 'doctype', 'lab_test') + if frappe.db.has_column('Lab Test', 'special_toggle'): + rename_field('Lab Test', 'special_toggle', 'descriptive_toggle') + + if frappe.db.exists('DocType', 'Lab Test Group Template'): + # fix select field option + frappe.reload_doc('healthcare', 'doctype', 'lab_test_group_template') + frappe.db.sql(""" + UPDATE `tabLab Test Group Template` + SET template_or_new_line = 'Add New Line' + WHERE template_or_new_line = 'Add new line' + """) From 2826fd3c205567f88950359ca733bda327020ea9 Mon Sep 17 00:00:00 2001 From: KanchanChauhan Date: Thu, 23 Jul 2020 15:45:03 +0530 Subject: [PATCH 093/101] feat: Dunning (#22559) * feat: Dunning * fix: Replaces spaces with tab Co-authored-by: Nabin Hait --- erpnext/accounts/doctype/dunning/__init__.py | 0 erpnext/accounts/doctype/dunning/dunning.js | 149 +++++++ erpnext/accounts/doctype/dunning/dunning.json | 370 ++++++++++++++++++ erpnext/accounts/doctype/dunning/dunning.py | 119 ++++++ .../accounts/doctype/dunning/dunning_list.js | 9 + .../accounts/doctype/dunning/test_dunning.py | 100 +++++ .../doctype/dunning_letter_text/__init__.py | 0 .../dunning_letter_text.json | 70 ++++ .../dunning_letter_text.py | 10 + .../accounts/doctype/dunning_type/__init__.py | 0 .../doctype/dunning_type/dunning_type.js | 8 + .../doctype/dunning_type/dunning_type.json | 129 ++++++ .../doctype/dunning_type/dunning_type.py | 10 + .../doctype/dunning_type/test_dunning_type.py | 10 + .../doctype/payment_entry/payment_entry.js | 8 +- .../doctype/payment_entry/payment_entry.py | 57 ++- .../doctype/sales_invoice/sales_invoice.js | 12 + .../doctype/sales_invoice/sales_invoice.py | 34 ++ .../print_format/dunning_letter/__init__.py | 0 .../dunning_letter/dunning_letter.json | 25 ++ erpnext/hooks.py | 4 +- 21 files changed, 1103 insertions(+), 21 deletions(-) create mode 100644 erpnext/accounts/doctype/dunning/__init__.py create mode 100644 erpnext/accounts/doctype/dunning/dunning.js create mode 100644 erpnext/accounts/doctype/dunning/dunning.json create mode 100644 erpnext/accounts/doctype/dunning/dunning.py create mode 100644 erpnext/accounts/doctype/dunning/dunning_list.js create mode 100644 erpnext/accounts/doctype/dunning/test_dunning.py create mode 100644 erpnext/accounts/doctype/dunning_letter_text/__init__.py create mode 100644 erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json create mode 100644 erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py create mode 100644 erpnext/accounts/doctype/dunning_type/__init__.py create mode 100644 erpnext/accounts/doctype/dunning_type/dunning_type.js create mode 100644 erpnext/accounts/doctype/dunning_type/dunning_type.json create mode 100644 erpnext/accounts/doctype/dunning_type/dunning_type.py create mode 100644 erpnext/accounts/doctype/dunning_type/test_dunning_type.py create mode 100644 erpnext/accounts/print_format/dunning_letter/__init__.py create mode 100644 erpnext/accounts/print_format/dunning_letter/dunning_letter.json diff --git a/erpnext/accounts/doctype/dunning/__init__.py b/erpnext/accounts/doctype/dunning/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js new file mode 100644 index 0000000000..c563368894 --- /dev/null +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -0,0 +1,149 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Dunning", { + setup: function (frm) { + frm.set_query("sales_invoice", () => { + return { + filters: { + docstatus: 1, + company: frm.doc.company, + outstanding_amount: [">", 0], + status: "Overdue" + }, + }; + }); + frm.set_query("income_account", () => { + return { + filters: { + company: frm.doc.company, + root_type: "Income", + is_group: 0 + } + }; + }); + }, + refresh: function (frm) { + frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1); + frm.set_df_property( + "sales_invoice", + "read_only", + frm.doc.__islocal ? 0 : 1 + ); + if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") { + frm.add_custom_button(__("Resolve"), () => { + frm.set_value("status", "Resolved"); + }); + } + if (frm.doc.docstatus === 1 && frm.doc.status !== "Resolved") { + frm.add_custom_button( + __("Payment"), + function () { + frm.events.make_payment_entry(frm); + },__("Create") + ); + frm.page.set_inner_btn_group_as_primary(__("Create")); + } + }, + overdue_days: function (frm) { + frappe.db.get_value( + "Dunning Type", + { + start_day: ["<", frm.doc.overdue_days], + end_day: [">=", frm.doc.overdue_days], + }, + "dunning_type", + (r) => { + if (r) { + frm.set_value("dunning_type", r.dunning_type); + } else { + frm.set_value("dunning_type", ""); + frm.set_value("rate_of_interest", ""); + frm.set_value("dunning_fee", ""); + } + } + ); + }, + dunning_type: function (frm) { + frm.trigger("get_dunning_letter_text"); + }, + language: function (frm) { + frm.trigger("get_dunning_letter_text"); + }, + get_dunning_letter_text: function (frm) { + if (frm.doc.dunning_type) { + frappe.call({ + method: + "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text", + args: { + dunning_type: frm.doc.dunning_type, + language: frm.doc.language, + doc: frm.doc, + }, + callback: function (r) { + if (r.message) { + frm.set_value("body_text", r.message.body_text); + frm.set_value("closing_text", r.message.closing_text); + frm.set_value("language", r.message.language); + } else { + frm.set_value("body_text", ""); + frm.set_value("closing_text", ""); + } + }, + }); + } + }, + due_date: function (frm) { + frm.trigger("calculate_overdue_days"); + }, + posting_date: function (frm) { + frm.trigger("calculate_overdue_days"); + }, + rate_of_interest: function (frm) { + frm.trigger("calculate_interest_and_amount"); + }, + outstanding_amount: function (frm) { + frm.trigger("calculate_interest_and_amount"); + }, + interest_amount: function (frm) { + frm.trigger("calculate_interest_and_amount"); + }, + dunning_fee: function (frm) { + frm.trigger("calculate_interest_and_amount"); + }, + sales_invoice: function (frm) { + frm.trigger("calculate_overdue_days"); + }, + calculate_overdue_days: function (frm) { + if (frm.doc.posting_date && frm.doc.due_date) { + const overdue_days = moment(frm.doc.posting_date).diff( + frm.doc.due_date, + "days" + ); + frm.set_value("overdue_days", overdue_days); + } + }, + calculate_interest_and_amount: function (frm) { + const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100; + const interest_amount = interest_per_year / 365 * frm.doc.overdue_days || 0; + const dunning_amount = interest_amount + frm.doc.dunning_fee; + const grand_total = frm.doc.outstanding_amount + dunning_amount; + frm.set_value("interest_amount", interest_amount); + frm.set_value("dunning_amount", dunning_amount); + frm.set_value("grand_total", grand_total); + }, + make_payment_entry: function (frm) { + return frappe.call({ + method: + "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry", + args: { + dt: frm.doc.doctype, + dn: frm.doc.name, + }, + callback: function (r) { + var doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + }, + }); + }, +}); diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json new file mode 100644 index 0000000000..b3eddf5f22 --- /dev/null +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -0,0 +1,370 @@ +{ + "actions": [], + "allow_events_in_timeline": 1, + "autoname": "naming_series:", + "creation": "2019-07-05 16:34:31.013238", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "title", + "naming_series", + "sales_invoice", + "customer", + "customer_name", + "outstanding_amount", + "currency", + "conversion_rate", + "column_break_3", + "company", + "posting_date", + "posting_time", + "due_date", + "overdue_days", + "address_and_contact_section", + "address_display", + "contact_display", + "contact_mobile", + "contact_email", + "column_break_18", + "company_address_display", + "section_break_6", + "dunning_type", + "interest_amount", + "column_break_8", + "rate_of_interest", + "dunning_fee", + "section_break_12", + "dunning_amount", + "grand_total", + "income_account", + "column_break_17", + "status", + "printing_setting_section", + "language", + "body_text", + "column_break_22", + "letter_head", + "closing_text", + "amended_from" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "default": "DUNN-.MM.-.YY.-", + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "DUNN-.MM.-.YY.-" + }, + { + "fieldname": "sales_invoice", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Sales Invoice", + "options": "Sales Invoice", + "reqd": 1 + }, + { + "fetch_from": "sales_invoice.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Customer Name", + "read_only": 1 + }, + { + "fetch_from": "sales_invoice.outstanding_amount", + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "label": "Outstanding Amount", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Date" + }, + { + "fieldname": "overdue_days", + "fieldtype": "Int", + "label": "Overdue Days", + "read_only": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "dunning_type", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Dunning Type", + "options": "Dunning Type", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "interest_amount", + "fieldtype": "Currency", + "label": "Interest Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fetch_from": "dunning_type.dunning_fee", + "fetch_if_empty": 1, + "fieldname": "dunning_fee", + "fieldtype": "Currency", + "label": "Dunning Fee", + "precision": "2" + }, + { + "fieldname": "section_break_12", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "printing_setting_section", + "fieldtype": "Section Break", + "label": "Printing Setting" + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" + }, + { + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fetch_from": "sales_invoice.currency", + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Dunning", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "{customer_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title" + }, + { + "fieldname": "body_text", + "fieldtype": "Text Editor", + "label": "Body Text" + }, + { + "fieldname": "closing_text", + "fieldtype": "Text Editor", + "label": "Closing Text" + }, + { + "fetch_from": "sales_invoice.due_date", + "fieldname": "due_date", + "fieldtype": "Date", + "label": "Due Date", + "read_only": 1 + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time" + }, + { + "default": "0", + "fetch_from": "dunning_type.interest_rate", + "fetch_if_empty": 1, + "fieldname": "rate_of_interest", + "fieldtype": "Float", + "label": "Rate of Interest (%) Yearly" + }, + { + "fieldname": "address_and_contact_section", + "fieldtype": "Section Break", + "label": "Address and Contact" + }, + { + "fetch_from": "sales_invoice.address_display", + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Address", + "read_only": 1 + }, + { + "fetch_from": "sales_invoice.contact_display", + "fieldname": "contact_display", + "fieldtype": "Small Text", + "label": "Contact", + "read_only": 1 + }, + { + "fetch_from": "sales_invoice.contact_mobile", + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "label": "Mobile No", + "read_only": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fetch_from": "sales_invoice.company_address_display", + "fieldname": "company_address_display", + "fieldtype": "Small Text", + "label": "Company Address", + "read_only": 1 + }, + { + "fetch_from": "sales_invoice.contact_email", + "fieldname": "contact_email", + "fieldtype": "Data", + "label": "Contact Email", + "options": "Email", + "read_only": 1 + }, + { + "fetch_from": "sales_invoice.customer", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "grand_total", + "fieldtype": "Currency", + "label": "Grand Total", + "precision": "2", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "Unresolved", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "options": "Draft\nResolved\nUnresolved\nCancelled" + }, + { + "fieldname": "dunning_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Dunning Amount", + "read_only": 1 + }, + { + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "options": "Account" + }, + { + "fetch_from": "sales_invoice.conversion_rate", + "fieldname": "conversion_rate", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Rate", + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-07-21 18:20:23.512151", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Dunning", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "ASC", + "title_field": "customer_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py new file mode 100644 index 0000000000..0be6a480c9 --- /dev/null +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -0,0 +1,119 @@ +# -*- 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 six import string_types +from frappe.utils import getdate, get_datetime, rounded, flt +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year +from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions +from erpnext.controllers.accounts_controller import AccountsController + + +class Dunning(AccountsController): + def validate(self): + self.validate_overdue_days() + self.validate_amount() + if not self.income_account: + self.income_account = frappe.db.get_value('Company', self.company, 'default_income_account') + + def validate_overdue_days(self): + self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0 + + def validate_amount(self): + amounts = calculate_interest_and_amount( + self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days) + if self.interest_amount != amounts.get('interest_amount'): + self.interest_amount = amounts.get('interest_amount') + if self.dunning_amount != amounts.get('dunning_amount'): + self.dunning_amount = amounts.get('dunning_amount') + if self.grand_total != amounts.get('grand_total'): + self.grand_total = amounts.get('grand_total') + + def on_submit(self): + self.make_gl_entries() + + def on_cancel(self): + if self.dunning_amount: + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + + def make_gl_entries(self): + if not self.dunning_amount: + return + gl_entries = [] + invoice_fields = ["project", "cost_center", "debit_to", "party_account_currency", "conversion_rate", "cost_center"] + inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1) + accounting_dimensions = get_accounting_dimensions() + invoice_fields.extend(accounting_dimensions) + dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate) + default_cost_center = frappe.get_cached_value('Company', self.company, 'cost_center') + gl_entries.append( + self.get_gl_dict({ + "account": inv.debit_to, + "party_type": "Customer", + "party": self.customer, + "due_date": self.due_date, + "against": self.income_account, + "debit": dunning_in_company_currency, + "debit_in_account_currency": self.dunning_amount, + "against_voucher": self.name, + "against_voucher_type": "Dunning", + "cost_center": inv.cost_center or default_cost_center, + "project": inv.project + }, inv.party_account_currency, item=inv) + ) + gl_entries.append( + self.get_gl_dict({ + "account": self.income_account, + "against": self.customer, + "credit": dunning_in_company_currency, + "cost_center": inv.cost_center or default_cost_center, + "credit_in_account_currency": self.dunning_amount, + "project": inv.project + }, item=inv) + ) + make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False) + + +def resolve_dunning(doc, state): + for reference in doc.references: + if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0: + dunnings = frappe.get_list('Dunning', filters={ + 'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')}) + + for dunning in dunnings: + frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved') + +def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days): + interest_amount = 0 + if rate_of_interest: + interest_per_year = rounded(flt(outstanding_amount) * flt(rate_of_interest))/100 + interest_amount = ( + interest_per_year / days_in_year(get_datetime(posting_date).year)) * int(overdue_days) + grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee) + dunning_amount = flt(interest_amount) + flt(dunning_fee) + return { + 'interest_amount': interest_amount, + 'grand_total': grand_total, + 'dunning_amount': dunning_amount} + +@frappe.whitelist() +def get_dunning_letter_text(dunning_type, doc, language=None): + if isinstance(doc, string_types): + doc = json.loads(doc) + if language: + filters = {'parent': dunning_type, 'language': language} + else: + filters = {'parent': dunning_type, 'is_default_language': 1} + letter_text = frappe.db.get_value('Dunning Letter Text', filters, + ['body_text', 'closing_text', 'language'], as_dict=1) + if letter_text: + return { + 'body_text': frappe.render_template(letter_text.body_text, doc), + 'closing_text': frappe.render_template(letter_text.closing_text, doc), + 'language': letter_text.language + } diff --git a/erpnext/accounts/doctype/dunning/dunning_list.js b/erpnext/accounts/doctype/dunning/dunning_list.js new file mode 100644 index 0000000000..8dc0a8c857 --- /dev/null +++ b/erpnext/accounts/doctype/dunning/dunning_list.js @@ -0,0 +1,9 @@ +frappe.listview_settings["Dunning"] = { + get_indicator: function (doc) { + if (doc.status === "Resolved") { + return [__("Resolved"), "green", "status,=,Resolved"]; + } else { + return [__("Unresolved"), "red", "status,=,Unresolved"]; + } + }, +}; diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py new file mode 100644 index 0000000000..cb18309e3c --- /dev/null +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from frappe.utils import add_days, today, nowdate +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice_against_cost_center +from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount +from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + +class TestDunning(unittest.TestCase): + @classmethod + def setUpClass(self): + create_dunning_type() + unlink_payment_on_cancel_of_invoice() + + @classmethod + def tearDownClass(self): + unlink_payment_on_cancel_of_invoice(0) + + def test_dunning(self): + dunning = create_dunning() + amounts = calculate_interest_and_amount( + dunning.posting_date, dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days) + self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44) + self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44) + self.assertEqual(round(amounts.get('grand_total'), 2), 120.44) + + def test_gl_entries(self): + dunning = create_dunning() + dunning.submit() + gl_entries = frappe.db.sql("""select account, debit, credit + from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s + order by account asc""", dunning.name, as_dict=1) + self.assertTrue(gl_entries) + expected_values = dict((d[0], d) for d in [ + ['Debtors - _TC', 20.44, 0.0], + ['Sales - _TC', 0.0, 20.44] + ]) + for gle in gl_entries: + self.assertEquals(expected_values[gle.account][0], gle.account) + self.assertEquals(expected_values[gle.account][1], gle.debit) + self.assertEquals(expected_values[gle.account][2], gle.credit) + + def test_payment_entry(self): + dunning = create_dunning() + dunning.submit() + pe = get_payment_entry("Dunning", dunning.name) + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = dunning.currency + pe.paid_to_account_currency = dunning.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.insert() + pe.submit() + si_doc = frappe.get_doc('Sales Invoice', dunning.sales_invoice) + self.assertEqual(si_doc.outstanding_amount, 0) + + +def create_dunning(): + posting_date = add_days(today(), -20) + due_date = add_days(today(), -15) + sales_invoice = create_sales_invoice_against_cost_center( + posting_date=posting_date, due_date=due_date, status='Overdue') + dunning_type = frappe.get_doc("Dunning Type", 'First Notice') + dunning = frappe.new_doc("Dunning") + dunning.sales_invoice = sales_invoice.name + dunning.customer_name = sales_invoice.customer_name + dunning.outstanding_amount = sales_invoice.outstanding_amount + dunning.debit_to = sales_invoice.debit_to + dunning.currency = sales_invoice.currency + dunning.company = sales_invoice.company + dunning.posting_date = nowdate() + dunning.due_date = sales_invoice.due_date + dunning.dunning_type = 'First Notice' + dunning.rate_of_interest = dunning_type.rate_of_interest + dunning.dunning_fee = dunning_type.dunning_fee + dunning.save() + return dunning + +def create_dunning_type(): + dunning_type = frappe.new_doc("Dunning Type") + dunning_type.dunning_type = 'First Notice' + dunning_type.start_day = 10 + dunning_type.end_day = 20 + dunning_type.dunning_fee = 20 + dunning_type.rate_of_interest = 8 + dunning_type.append( + "dunning_letter_text", { + 'language': 'en', + 'body_text': 'We have still not received payment for our invoice ', + 'closing_text': 'We kindly request that you pay the outstanding amount immediately, including interest and late fees.' + } + ) + dunning_type.save() diff --git a/erpnext/accounts/doctype/dunning_letter_text/__init__.py b/erpnext/accounts/doctype/dunning_letter_text/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json new file mode 100644 index 0000000000..5ede3a1071 --- /dev/null +++ b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "creation": "2019-12-06 04:25:40.215625", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "language", + "is_default_language", + "section_break_4", + "body_text", + "closing_text", + "section_break_7", + "body_and_closing_text_help" + ], + "fields": [ + { + "fieldname": "language", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Language", + "options": "Language" + }, + { + "default": "0", + "fieldname": "is_default_language", + "fieldtype": "Check", + "label": "Is Default Language" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "description": "Letter or Email Body Text", + "fieldname": "body_text", + "fieldtype": "Text Editor", + "in_list_view": 1, + "label": "Body Text" + }, + { + "description": "Letter or Email Closing Text", + "fieldname": "closing_text", + "fieldtype": "Text Editor", + "in_list_view": 1, + "label": "Closing Text" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "body_and_closing_text_help", + "fieldtype": "HTML", + "label": "Body and Closing Text Help", + "options": "

Body Text and Closing Text Example

\n\n
We have noticed that you have not yet paid invoice {{sales_invoice}} for {{frappe.db.get_value(\"Currency\", currency, \"symbol\")}} {{outstanding_amount}}. This is a friendly reminder that the invoice was due on {{due_date}}. Please pay the amount due immediately to avoid any further dunning cost.
\n\n

How to get fieldnames

\n\n

The fieldnames you can use in your template are the fields in the document. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)

\n\n

Templating

\n\n

Templates are compiled using the Jinja Templating Language. To learn more about Jinja, read this documentation.

" + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-14 18:02:35.988958", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Dunning Letter Text", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py new file mode 100644 index 0000000000..426497b607 --- /dev/null +++ b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py @@ -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 DunningLetterText(Document): + pass diff --git a/erpnext/accounts/doctype/dunning_type/__init__.py b/erpnext/accounts/doctype/dunning_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.js b/erpnext/accounts/doctype/dunning_type/dunning_type.js new file mode 100644 index 0000000000..54156b488d --- /dev/null +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Dunning Type', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.json b/erpnext/accounts/doctype/dunning_type/dunning_type.json new file mode 100644 index 0000000000..da43664472 --- /dev/null +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.json @@ -0,0 +1,129 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:dunning_type", + "creation": "2019-12-04 04:59:08.003664", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "dunning_type", + "overdue_interval_section", + "start_day", + "column_break_4", + "end_day", + "section_break_6", + "dunning_fee", + "column_break_8", + "rate_of_interest", + "text_block_section", + "dunning_letter_text" + ], + "fields": [ + { + "fieldname": "dunning_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Dunning Type", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "dunning_fee", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Dunning Fee" + }, + { + "description": "This section allows the user to set the Body and Closing text of the Dunning Letter for the Dunning Type based on language, which can be used in Print.", + "fieldname": "text_block_section", + "fieldtype": "Section Break", + "label": "Dunning Letter" + }, + { + "fieldname": "dunning_letter_text", + "fieldtype": "Table", + "options": "Dunning Letter Text" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "overdue_interval_section", + "fieldtype": "Section Break", + "label": "Overdue Interval" + }, + { + "fieldname": "start_day", + "fieldtype": "Int", + "label": "Start Day" + }, + { + "fieldname": "end_day", + "fieldtype": "Int", + "label": "End Day" + }, + { + "fieldname": "rate_of_interest", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Rate of Interest (%) Yearly" + } + ], + "links": [], + "modified": "2020-07-15 17:14:17.835074", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Dunning Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.py b/erpnext/accounts/doctype/dunning_type/dunning_type.py new file mode 100644 index 0000000000..8708748428 --- /dev/null +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.py @@ -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 DunningType(Document): + pass diff --git a/erpnext/accounts/doctype/dunning_type/test_dunning_type.py b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py new file mode 100644 index 0000000000..b2fb26f34a --- /dev/null +++ b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDunningType(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 42c9fdeba4..4bbf63bdd9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -90,7 +90,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_query("reference_doctype", "references", function() { if (frm.doc.party_type=="Customer") { - var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry"]; + var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"]; } else if (frm.doc.party_type=="Supplier") { var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"]; } else if (frm.doc.party_type=="Employee") { @@ -125,7 +125,7 @@ frappe.ui.form.on('Payment Entry', { const child = locals[cdt][cdn]; const filters = {"docstatus": 1, "company": doc.company}; const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice', - 'Purchase Order', 'Expense Claim', 'Fees']; + 'Purchase Order', 'Expense Claim', 'Fees', 'Dunning']; if (in_list(party_type_doctypes, child.reference_doctype)) { filters[doc.party_type.toLowerCase()] = doc.party; @@ -863,10 +863,10 @@ frappe.ui.form.on('Payment Entry', { } if(frm.doc.party_type=="Customer" && - !in_list(["Sales Order", "Sales Invoice", "Journal Entry"], row.reference_doctype) + !in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype) ) { frappe.model.set_value(row.doctype, row.name, "reference_doctype", null); - frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice or Journal Entry", [row.idx])); + frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning", [row.idx])); return false; } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1cecab74ef..f9db14b90f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -199,8 +199,8 @@ class PaymentEntry(AccountsController): def validate_account_type(self, account, account_types): account_type = frappe.db.get_value("Account", account, "account_type") - if account_type not in account_types: - frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types))) + # if account_type not in account_types: + # frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types))) def set_exchange_rate(self): if self.paid_from and not self.source_exchange_rate: @@ -223,7 +223,7 @@ class PaymentEntry(AccountsController): if self.party_type == "Student": valid_reference_doctypes = ("Fees") elif self.party_type == "Customer": - valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry") + valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning") elif self.party_type == "Supplier": valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry") elif self.party_type == "Employee": @@ -897,6 +897,10 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre total_amount = ref_doc.get("grand_total") exchange_rate = 1 outstanding_amount = ref_doc.get("outstanding_amount") + if reference_doctype == "Dunning": + total_amount = ref_doc.get("dunning_amount") + exchange_rate = 1 + outstanding_amount = ref_doc.get("dunning_amount") elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1: total_amount = ref_doc.get("total_amount") if ref_doc.multi_currency: @@ -951,7 +955,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0: frappe.throw(_("Can only make payment against unbilled {0}").format(dt)) - if dt in ("Sales Invoice", "Sales Order"): + if dt in ("Sales Invoice", "Sales Order", "Dunning"): party_type = "Customer" elif dt in ("Purchase Invoice", "Purchase Order"): party_type = "Supplier" @@ -980,7 +984,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account) # payment type - if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \ + if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \ or (dt=="Purchase Invoice" and doc.outstanding_amount < 0): payment_type = "Receive" else: @@ -1006,6 +1010,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= elif dt == "Fees": grand_total = doc.grand_total outstanding_amount = doc.outstanding_amount + elif dt == "Dunning": + grand_total = doc.grand_total + outstanding_amount = doc.grand_total else: if party_account_currency == doc.company_currency: grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total) @@ -1075,15 +1082,35 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount): pe.append('references', reference) else: - pe.append("references", { - 'reference_doctype': dt, - 'reference_name': dn, - "bill_no": doc.get("bill_no"), - "due_date": doc.get("due_date"), - 'total_amount': grand_total, - 'outstanding_amount': outstanding_amount, - 'allocated_amount': outstanding_amount - }) + if dt == "Dunning": + pe.append("references", { + 'reference_doctype': 'Sales Invoice', + 'reference_name': doc.get('sales_invoice'), + "bill_no": doc.get("bill_no"), + "due_date": doc.get("due_date"), + 'total_amount': doc.get('outstanding_amount'), + 'outstanding_amount': doc.get('outstanding_amount'), + 'allocated_amount': doc.get('outstanding_amount') + }) + pe.append("references", { + 'reference_doctype': dt, + 'reference_name': dn, + "bill_no": doc.get("bill_no"), + "due_date": doc.get("due_date"), + 'total_amount': doc.get('dunning_amount'), + 'outstanding_amount': doc.get('dunning_amount'), + 'allocated_amount': doc.get('dunning_amount') + }) + else: + pe.append("references", { + 'reference_doctype': dt, + 'reference_name': dn, + "bill_no": doc.get("bill_no"), + "due_date": doc.get("due_date"), + 'total_amount': grand_total, + 'outstanding_amount': outstanding_amount, + 'allocated_amount': outstanding_amount + }) pe.setup_party_account_field() pe.set_missing_values() @@ -1172,4 +1199,4 @@ def make_payment_order(source_name, target_doc=None): }, target_doc, set_missing_values) - return doclist + return doclist \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index df0c3d2299..061ce1cbb9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -96,6 +96,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte cur_frm.add_custom_button(__('Invoice Discounting'), function() { cur_frm.events.create_invoice_discounting(cur_frm); }, __('Create')); + + if (doc.due_date < frappe.datetime.get_today()) { + cur_frm.add_custom_button(__('Dunning'), function() { + cur_frm.events.create_dunning(cur_frm); + }, __('Create')); + } } if (doc.docstatus === 1) { @@ -824,6 +830,12 @@ frappe.ui.form.on('Sales Invoice', { method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting", frm: frm }); + }, + create_dunning: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning", + frm: frm + }); } }) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bab5208370..89843484f9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1602,3 +1602,37 @@ def create_invoice_discounting(source_name, target_doc=None): }) return invoice_discounting + +@frappe.whitelist() +def create_dunning(source_name, target_doc=None): + from frappe.model.mapper import get_mapped_doc + from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount + def set_missing_values(source, target): + target.sales_invoice = source_name + target.outstanding_amount = source.outstanding_amount + overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days + target.overdue_days = overdue_days + if frappe.db.exists('Dunning Type', {'start_day': [ + '<', overdue_days], 'end_day': ['>=', overdue_days]}): + dunning_type = frappe.get_doc('Dunning Type', {'start_day': [ + '<', overdue_days], 'end_day': ['>=', overdue_days]}) + target.dunning_type = dunning_type.name + target.rate_of_interest = dunning_type.rate_of_interest + target.dunning_fee = dunning_type.dunning_fee + letter_text = get_dunning_letter_text(dunning_type = dunning_type.name, doc = target.as_dict()) + if letter_text: + target.body_text = letter_text.get('body_text') + target.closing_text = letter_text.get('closing_text') + target.language = letter_text.get('language') + amounts = calculate_interest_and_amount(target.posting_date, target.outstanding_amount, + target.rate_of_interest, target.dunning_fee, target.overdue_days) + target.interest_amount = amounts.get('interest_amount') + target.dunning_amount = amounts.get('dunning_amount') + target.grand_total = amounts.get('grand_total') + + doclist = get_mapped_doc("Sales Invoice", source_name, { + "Sales Invoice": { + "doctype": "Dunning", + } + }, target_doc, set_missing_values) + return doclist \ No newline at end of file diff --git a/erpnext/accounts/print_format/dunning_letter/__init__.py b/erpnext/accounts/print_format/dunning_letter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/print_format/dunning_letter/dunning_letter.json b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json new file mode 100644 index 0000000000..a7eac70b65 --- /dev/null +++ b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json @@ -0,0 +1,25 @@ +{ + "align_labels_right": 0, + "creation": "2019-12-11 04:37:14.012805", + "css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n", + "custom_format": 0, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Dunning", + "docstatus": 0, + "doctype": "Print Format", + "font": "Arial", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{{doc.customer_name}}
\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n
{{_(doc.dunning_type)}}
\\n
{{ doc.name }}
\\n
\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldname\": \"sales_invoice\", \"print_hide\": 0, \"label\": \"Sales Invoice\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"label\": \"Due Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n \\n \\n \\n\\t \\n \\n \\n \\n \\n \\n {%if doc.rate_of_interest > 0%}\\n \\n \\n \\n \\n {% endif %}\\n {%if doc.dunning_fee > 0%}\\n \\n \\n \\n \\n {% endif %}\\n \\n
{{_(\\\"Description\\\")}}{{_(\\\"Amount\\\")}}
\\n {{_(\\\"Outstanding Amount\\\")}}\\n \\n {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n
\\n {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n \\n {{doc.get_formatted(\\\"interest_amount\\\")}}\\n
\\n {{_(\\\"Dunning Fee\\\")}}\\n \\n {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n
\\n\\t\\t
\\n\\t\\t\\t{{_(\\\"Grand Total\\\")}}
\\n\\t\\t
\\n\\t\\t\\t{{doc.get_formatted(\\\"grand_total\\\")}}\\n\\t\\t
\\n
\\n\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]", + "idx": 0, + "line_breaks": 0, + "modified": "2020-07-14 18:25:44.348207", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Dunning Letter", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e8dda207ec..95a836fe65 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -249,7 +249,7 @@ doc_events = { "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm" }, "Payment Entry": { - "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"], + "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"], "on_trash": "erpnext.regional.check_deletion_permission" }, 'Address': { @@ -552,4 +552,4 @@ global_search_doctypes = { {'doctype': 'Hotel Room Package', 'index': 3}, {'doctype': 'Hotel Room Type', 'index': 4} ] -} +} \ No newline at end of file From 5142fc9365617af3be73c2aeb4002691096e7e42 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 23 Jul 2020 15:57:27 +0530 Subject: [PATCH 094/101] feat(Education): added form dashboards and refactored custom buttons for better linking (#22727) * feat: add form dashboards to all forms in Education Module * feat: Add Course to Programs button in Course DocType * refactor: custom buttons in Education module forms * feat: buttons to add topic, article, quiz to their respective parent doctypes * feat: add charts to form dashboards * fix: code cleanup --- .../academic_term/academic_term_dashboard.py | 25 + .../doctype/academic_year/academic_year.js | 10 +- .../academic_year/academic_year_dashboard.py | 25 + erpnext/education/doctype/article/article.js | 48 + erpnext/education/doctype/article/article.py | 12 +- .../assessment_group_dashboard.py | 15 + .../assessment_plan/assessment_plan.js | 18 +- .../assessment_plan_dashboard.py | 8 +- .../assessment_result/assessment_result.js | 61 +- .../assessment_result_dashboard.py | 14 + erpnext/education/doctype/course/course.js | 89 +- erpnext/education/doctype/course/course.py | 34 +- .../doctype/course/course_dashboard.py | 8 +- .../course_enrollment_dashboard.py | 15 + .../course_schedule/course_schedule.js | 4 +- .../course_schedule/course_schedule.json | 966 +++++++++--------- .../course_schedule_dashboard.py | 15 + .../doctype/fee_schedule/fee_schedule.js | 75 +- .../fee_schedule/fee_schedule_dashboard.py | 13 + .../doctype/fee_structure/fee_structure.js | 28 +- .../fee_structure/fee_structure_dashboard.py | 15 + .../grading_scale/grading_scale_dashboard.py | 20 + .../doctype/instructor/instructor.js | 28 +- .../doctype/instructor/instructor.py | 12 +- .../instructor/instructor_dashboard.py | 24 + .../doctype/program/program_dashboard.py | 8 +- .../program_enrollment_dashboard.py | 19 + erpnext/education/doctype/quiz/quiz.js | 56 +- erpnext/education/doctype/quiz/quiz.py | 10 + erpnext/education/doctype/room/room.js | 10 +- .../education/doctype/room/room_dashboard.py | 19 + .../student_attendance_dashboard.py | 12 + .../student_category_dashboard.py | 13 + .../doctype/student_group/student_group.js | 109 +- .../student_group/student_group_dashboard.py | 19 + erpnext/education/doctype/topic/topic.js | 47 + erpnext/education/doctype/topic/topic.py | 44 +- 37 files changed, 1249 insertions(+), 699 deletions(-) create mode 100644 erpnext/education/doctype/academic_term/academic_term_dashboard.py create mode 100644 erpnext/education/doctype/academic_year/academic_year_dashboard.py create mode 100644 erpnext/education/doctype/assessment_group/assessment_group_dashboard.py create mode 100644 erpnext/education/doctype/assessment_result/assessment_result_dashboard.py create mode 100644 erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py create mode 100644 erpnext/education/doctype/course_schedule/course_schedule_dashboard.py create mode 100644 erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py create mode 100644 erpnext/education/doctype/fee_structure/fee_structure_dashboard.py create mode 100644 erpnext/education/doctype/grading_scale/grading_scale_dashboard.py create mode 100644 erpnext/education/doctype/instructor/instructor_dashboard.py create mode 100644 erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py create mode 100644 erpnext/education/doctype/room/room_dashboard.py create mode 100644 erpnext/education/doctype/student_attendance/student_attendance_dashboard.py create mode 100644 erpnext/education/doctype/student_category/student_category_dashboard.py create mode 100644 erpnext/education/doctype/student_group/student_group_dashboard.py diff --git a/erpnext/education/doctype/academic_term/academic_term_dashboard.py b/erpnext/education/doctype/academic_term/academic_term_dashboard.py new file mode 100644 index 0000000000..871e0f3284 --- /dev/null +++ b/erpnext/education/doctype/academic_term/academic_term_dashboard.py @@ -0,0 +1,25 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'academic_term', + 'transactions': [ + { + 'label': _('Student'), + 'items': ['Student Applicant', 'Student Group', 'Student Log'] + }, + { + 'label': _('Fee'), + 'items': ['Fees', 'Fee Schedule', 'Fee Structure'] + }, + { + 'label': _('Program'), + 'items': ['Program Enrollment'] + }, + { + 'label': _('Assessment'), + 'items': ['Assessment Plan', 'Assessment Result'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/academic_year/academic_year.js b/erpnext/education/doctype/academic_year/academic_year.js index 21caa63369..0e8619849c 100644 --- a/erpnext/education/doctype/academic_year/academic_year.js +++ b/erpnext/education/doctype/academic_year/academic_year.js @@ -1,10 +1,2 @@ -frappe.ui.form.on("Academic Year", "refresh", function(frm) { - if(!frm.doc.__islocal) { - frm.add_custom_button(__("Student Group"), function() { - frappe.route_options = { - academic_year: frm.doc.name - } - frappe.set_route("List", "Student Group"); - }); - } +frappe.ui.form.on("Academic Year", { }); \ No newline at end of file diff --git a/erpnext/education/doctype/academic_year/academic_year_dashboard.py b/erpnext/education/doctype/academic_year/academic_year_dashboard.py new file mode 100644 index 0000000000..f27f7d14cf --- /dev/null +++ b/erpnext/education/doctype/academic_year/academic_year_dashboard.py @@ -0,0 +1,25 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'academic_year', + 'transactions': [ + { + 'label': _('Student'), + 'items': ['Student Admission', 'Student Applicant', 'Student Group', 'Student Log'] + }, + { + 'label': _('Fee'), + 'items': ['Fees', 'Fee Schedule', 'Fee Structure'] + }, + { + 'label': _('Academic Term and Program'), + 'items': ['Academic Term', 'Program Enrollment'] + }, + { + 'label': _('Assessment'), + 'items': ['Assessment Plan', 'Assessment Result'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/article/article.js b/erpnext/education/doctype/article/article.js index 4c9c6f01f0..edfec26273 100644 --- a/erpnext/education/doctype/article/article.js +++ b/erpnext/education/doctype/article/article.js @@ -3,6 +3,54 @@ frappe.ui.form.on('Article', { refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__('Add to Topics'), function() { + frm.trigger('add_article_to_topics'); + }, __('Action')); + } + }, + add_article_to_topics: function(frm) { + get_topics_without_article(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'topics', + label: __('Topics'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + } + ], + function(data) { + frappe.call({ + method: 'erpnext.education.doctype.topic.topic.add_content_to_topics', + args: { + 'content_type': 'Article', + 'content': frm.doc.name, + 'topics': data.topics, + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Article to Topics') + }); + }, __('Add Article to Topics'), __('Add')); + } else { + frappe.msgprint(__('This article is already added to the existing topics')); + } + }); } }); + +let get_topics_without_article = function(article) { + return frappe.call({ + type: 'GET', + method: 'erpnext.education.doctype.article.article.get_topics_without_article', + args: {'article': article} + }); +}; \ No newline at end of file diff --git a/erpnext/education/doctype/article/article.py b/erpnext/education/doctype/article/article.py index 7dc850be37..8ba367da76 100644 --- a/erpnext/education/doctype/article/article.py +++ b/erpnext/education/doctype/article/article.py @@ -7,9 +7,15 @@ import frappe from frappe.model.document import Document class Article(Document): - - def get_article(self): pass - +@frappe.whitelist() +def get_topics_without_article(article): + data = [] + for entry in frappe.db.get_all('Topic'): + topic = frappe.get_doc('Topic', entry.name) + topic_contents = [tc.content for tc in topic.topic_content] + if not topic_contents or article not in topic_contents: + data.append(topic.name) + return data \ No newline at end of file diff --git a/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py new file mode 100644 index 0000000000..2649d4b90c --- /dev/null +++ b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py @@ -0,0 +1,15 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'assessment_group', + 'transactions': [ + { + 'label': _('Assessment'), + 'items': ['Assessment Plan', 'Assessment Result'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan.js b/erpnext/education/doctype/assessment_plan/assessment_plan.js index 0cb642bb6b..c4c56143c3 100644 --- a/erpnext/education/doctype/assessment_plan/assessment_plan.js +++ b/erpnext/education/doctype/assessment_plan/assessment_plan.js @@ -2,9 +2,9 @@ // For license information, please see license.txt -frappe.ui.form.on("Assessment Plan", { +frappe.ui.form.on('Assessment Plan', { onload: function(frm) { - frm.set_query("assessment_group", function(doc, cdt, cdn) { + frm.set_query('assessment_group', function(doc, cdt, cdn) { return{ filters: { 'is_group': 0 @@ -22,20 +22,20 @@ frappe.ui.form.on("Assessment Plan", { refresh: function(frm) { if (frm.doc.docstatus == 1) { - frm.add_custom_button(__("Assessment Result"), function() { + frm.add_custom_button(__('Assessment Result Tool'), function() { frappe.route_options = { assessment_plan: frm.doc.name, student_group: frm.doc.student_group } - frappe.set_route("Form", "Assessment Result Tool"); - }); + frappe.set_route('Form', 'Assessment Result Tool'); + }, __('Tools')); } }, course: function(frm) { if (frm.doc.course && frm.doc.maximum_assessment_score) { frappe.call({ - method: "erpnext.education.api.get_assessment_criteria", + method: 'erpnext.education.api.get_assessment_criteria', args: { course: frm.doc.course }, @@ -43,12 +43,12 @@ frappe.ui.form.on("Assessment Plan", { if (r.message) { frm.doc.assessment_criteria = []; $.each(r.message, function(i, d) { - var row = frappe.model.add_child(frm.doc, "Assessment Plan Criteria", "assessment_criteria"); + var row = frappe.model.add_child(frm.doc, 'Assessment Plan Criteria', 'assessment_criteria'); row.assessment_criteria = d.assessment_criteria; row.maximum_score = d.weightage / 100 * frm.doc.maximum_assessment_score; }); } - refresh_field("assessment_criteria"); + refresh_field('assessment_criteria'); } }); @@ -56,6 +56,6 @@ frappe.ui.form.on("Assessment Plan", { }, maximum_assessment_score: function(frm) { - frm.trigger("course"); + frm.trigger('course'); } }); \ No newline at end of file diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py index c36dfb11b5..5e6c29dcdf 100644 --- a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py +++ b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py @@ -6,12 +6,16 @@ from frappe import _ def get_data(): return { 'fieldname': 'assessment_plan', - 'non_standard_fieldnames': { - }, 'transactions': [ { 'label': _('Assessment'), 'items': ['Assessment Result'] } + ], + 'reports': [ + { + 'label': _('Report'), + 'items': ['Assessment Plan Status'] + } ] } \ No newline at end of file diff --git a/erpnext/education/doctype/assessment_result/assessment_result.js b/erpnext/education/doctype/assessment_result/assessment_result.js index 84865ca8ec..12fdd91c25 100644 --- a/erpnext/education/doctype/assessment_result/assessment_result.js +++ b/erpnext/education/doctype/assessment_result/assessment_result.js @@ -1,7 +1,13 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on("Assessment Result", { +frappe.ui.form.on('Assessment Result', { + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.trigger('setup_chart'); + } + }, + onload: function(frm) { frm.set_query('assessment_plan', function(){ return { @@ -15,7 +21,7 @@ frappe.ui.form.on("Assessment Result", { assessment_plan: function(frm) { if (frm.doc.assessment_plan) { frappe.call({ - method: "erpnext.education.api.get_assessment_details", + method: 'erpnext.education.api.get_assessment_details', args: { assessment_plan: frm.doc.assessment_plan }, @@ -23,40 +29,75 @@ frappe.ui.form.on("Assessment Result", { if (r.message) { frm.doc.details = []; $.each(r.message, function(i, d) { - var row = frappe.model.add_child(frm.doc, "Assessment Result Detail", "details"); + var row = frappe.model.add_child(frm.doc, 'Assessment Result Detail', 'details'); row.assessment_criteria = d.assessment_criteria; row.maximum_score = d.maximum_score; }); } - refresh_field("details"); + refresh_field('details'); } }); } + }, + + setup_chart: function(frm) { + let labels = []; + let maximum_scores = []; + let scores = []; + $.each(frm.doc.details, function(_i, e) { + labels.push(e.assessment_criteria); + maximum_scores.push(e.maximum_score); + scores.push(e.score); + }); + + if (labels.length && maximum_scores.length && scores.length) { + frm.dashboard.chart_area.empty().removeClass('hidden'); + new frappe.Chart('.form-graph', { + title: 'Assessment Results', + data: { + labels: labels, + datasets: [ + { + name: 'Maximum Score', + chartType: 'bar', + values: maximum_scores, + }, + { + name: 'Score Obtained', + chartType: 'bar', + values: scores, + } + ] + }, + colors: ['#4CA746', '#98D85B'], + type: 'bar' + }); + } } }); -frappe.ui.form.on("Assessment Result Detail", { +frappe.ui.form.on('Assessment Result Detail', { score: function(frm, cdt, cdn) { var d = locals[cdt][cdn]; if(!d.maximum_score || !frm.doc.grading_scale) { - d.score = ""; - frappe.throw(__("Please fill in all the details to generate Assessment Result.")); + d.score = ''; + frappe.throw(__('Please fill in all the details to generate Assessment Result.')); } if (d.score > d.maximum_score) { - frappe.throw(__("Score cannot be greater than Maximum Score")); + frappe.throw(__('Score cannot be greater than Maximum Score')); } else { frappe.call({ - method: "erpnext.education.api.get_grade", + method: 'erpnext.education.api.get_grade', args: { grading_scale: frm.doc.grading_scale, percentage: ((d.score/d.maximum_score) * 100) }, callback: function(r) { if (r.message) { - frappe.model.set_value(cdt, cdn, "grade", r.message); + frappe.model.set_value(cdt, cdn, 'grade', r.message); } } }); diff --git a/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py new file mode 100644 index 0000000000..438379d08e --- /dev/null +++ b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py @@ -0,0 +1,14 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'reports': [ + { + 'label': _('Reports'), + 'items': ['Final Assessment Grades', 'Course wise Assessment Report'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/course/course.js b/erpnext/education/doctype/course/course.js index 69329896e0..81e4a8c08d 100644 --- a/erpnext/education/doctype/course/course.js +++ b/erpnext/education/doctype/course/course.js @@ -1,41 +1,60 @@ -frappe.ui.form.on("Course", "refresh", function(frm) { - if(!cur_frm.doc.__islocal) { - frm.add_custom_button(__("Program"), function() { - frappe.route_options = { - "Program Course.course": frm.doc.name - } - frappe.set_route("List", "Program"); - }); +frappe.ui.form.on('Course', { + refresh: function(frm) { + if (!cur_frm.doc.__islocal) { + frm.add_custom_button(__('Add to Programs'), function() { + frm.trigger('add_course_to_programs') + }, __('Action')); + } - frm.add_custom_button(__("Student Group"), function() { - frappe.route_options = { - course: frm.doc.name + frm.set_query('default_grading_scale', function(){ + return { + filters: { + docstatus: 1 + } } - frappe.set_route("List", "Student Group"); }); + }, - frm.add_custom_button(__("Course Schedule"), function() { - frappe.route_options = { - course: frm.doc.name + add_course_to_programs: function(frm) { + get_programs_without_course(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'programs', + label: __('Programs'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + }, + { + fieldtype: 'Check', + label: __('Is Mandatory'), + fieldname: 'mandatory', + } + ], + function(data) { + frappe.call({ + method: 'erpnext.education.doctype.course.course.add_course_to_programs', + args: { + 'course': frm.doc.name, + 'programs': data.programs, + 'mandatory': data.mandatory + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Course to Programs') + }) + }, __('Add Course to Programs'), __('Add')); + } else { + frappe.msgprint(__('This course is already added to the existing programs')); } - frappe.set_route("List", "Course Schedule"); - }); - - frm.add_custom_button(__("Assessment Plan"), function() { - frappe.route_options = { - course: frm.doc.name - } - frappe.set_route("List", "Assessment Plan"); }); } - - frm.set_query('default_grading_scale', function(){ - return { - filters: { - docstatus: 1 - } - } - }); }); frappe.ui.form.on('Course Topic', { @@ -50,3 +69,11 @@ frappe.ui.form.on('Course Topic', { }; } }); + +let get_programs_without_course = function(course) { + return frappe.call({ + type: 'GET', + method: 'erpnext.education.doctype.course.course.get_programs_without_course', + args: {'course': course} + }); +} \ No newline at end of file diff --git a/erpnext/education/doctype/course/course.py b/erpnext/education/doctype/course/course.py index 0747a22f8d..06efa54e77 100644 --- a/erpnext/education/doctype/course/course.py +++ b/erpnext/education/doctype/course/course.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +import json from frappe.model.document import Document from frappe import _ @@ -17,12 +18,39 @@ class Course(Document): for criteria in self.assessment_criteria: total_weightage += criteria.weightage or 0 if total_weightage != 100: - frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%")) + frappe.throw(_('Total Weightage of all Assessment Criteria must be 100%')) def get_topics(self): topic_data= [] for topic in self.topics: - topic_doc = frappe.get_doc("Topic", topic.topic) + topic_doc = frappe.get_doc('Topic', topic.topic) if topic_doc.topic_content: topic_data.append(topic_doc) - return topic_data \ No newline at end of file + return topic_data + + +@frappe.whitelist() +def add_course_to_programs(course, programs, mandatory=False): + programs = json.loads(programs) + for entry in programs: + program = frappe.get_doc('Program', entry) + program.append('courses', { + 'course': course, + 'course_name': course, + 'mandatory': mandatory + }) + program.flags.ignore_mandatory = True + program.save() + frappe.db.commit() + frappe.msgprint(_('Course {0} has been added to all the selected programs successfully.').format(frappe.bold(course)), + title=_('Programs updated'), indicator='green') + +@frappe.whitelist() +def get_programs_without_course(course): + data = [] + for entry in frappe.db.get_all('Program'): + program = frappe.get_doc('Program', entry.name) + courses = [c.course for c in program.courses] + if not courses or course not in courses: + data.append(program.name) + return data \ No newline at end of file diff --git a/erpnext/education/doctype/course/course_dashboard.py b/erpnext/education/doctype/course/course_dashboard.py index 752af29a9d..8a570bdc57 100644 --- a/erpnext/education/doctype/course/course_dashboard.py +++ b/erpnext/education/doctype/course/course_dashboard.py @@ -6,12 +6,10 @@ from frappe import _ def get_data(): return { 'fieldname': 'course', - 'non_standard_fieldnames': { - }, 'transactions': [ { - 'label': _('Course'), - 'items': ['Course Enrollment', 'Course Schedule'] + 'label': _('Program and Course'), + 'items': ['Program', 'Course Enrollment', 'Course Schedule'] }, { 'label': _('Student'), @@ -19,7 +17,7 @@ def get_data(): }, { 'label': _('Assessment'), - 'items': ['Assessment Plan'] + 'items': ['Assessment Plan', 'Assessment Result'] }, ] } \ No newline at end of file diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py new file mode 100644 index 0000000000..b9dd457b61 --- /dev/null +++ b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py @@ -0,0 +1,15 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'enrollment', + 'transactions': [ + { + 'label': _('Activity'), + 'items': ['Course Activity', 'Quiz Activity'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/course_schedule/course_schedule.js b/erpnext/education/doctype/course_schedule/course_schedule.js index 692c2a8389..4275f6ef2a 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.js +++ b/erpnext/education/doctype/course_schedule/course_schedule.js @@ -4,13 +4,13 @@ cur_frm.add_fetch("student_group", "course", "course") frappe.ui.form.on("Course Schedule", { refresh: function(frm) { if (!frm.doc.__islocal) { - frm.add_custom_button(__("Attendance"), function() { + frm.add_custom_button(__("Mark Attendance"), function() { frappe.route_options = { based_on: "Course Schedule", course_schedule: frm.doc.name } frappe.set_route("Form", "Student Attendance Tool"); - }); + }).addClass("btn-primary"); } } }); \ No newline at end of file diff --git a/erpnext/education/doctype/course_schedule/course_schedule.json b/erpnext/education/doctype/course_schedule/course_schedule.json index 7346cab438..8c6746bda8 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.json +++ b/erpnext/education/doctype/course_schedule/course_schedule.json @@ -1,520 +1,520 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2015-09-09 16:34:04.960369", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2015-09-09 16:34:04.960369", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "student_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Student Group", - "length": 0, - "no_copy": 0, - "options": "Student Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Student Group", + "length": 0, + "no_copy": 0, + "options": "Student Group", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "instructor", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Instructor", - "length": 0, - "no_copy": 0, - "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "instructor", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Instructor", + "length": 0, + "no_copy": 0, + "options": "Instructor", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "instructor.Instructor_name", - "fieldname": "instructor_name", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Instructor Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "instructor.Instructor_name", + "fieldname": "instructor_name", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Instructor Name", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Naming Series", - "length": 0, - "no_copy": 0, - "options": "EDU-CSH-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Naming Series", + "length": 0, + "no_copy": 0, + "options": "EDU-CSH-.YYYY.-", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "course", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course", - "length": 0, - "no_copy": 0, - "options": "Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "course", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Course", + "length": 0, + "no_copy": 0, + "options": "Course", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "color", - "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Color", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "color", + "fieldtype": "Color", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Color", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "schedule_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Schedule Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "schedule_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Schedule Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "room", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Room", - "length": 0, - "no_copy": 0, - "options": "Room", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "room", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Room", + "length": 0, + "no_copy": 0, + "options": "Room", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_9", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "From Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "From Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "To Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "To Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Title", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-08-21 14:44:51.827225", - "modified_by": "Administrator", - "module": "Education", - "name": "Course Schedule", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "menu_index": 0, + "modified": "2018-08-21 14:44:51.827225", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Schedule", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "show_name_in_global_search": 0, - "sort_field": "schedule_date", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 0, - "track_seen": 0, + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "schedule_date", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 0, + "track_seen": 0, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py new file mode 100644 index 0000000000..0866cd6535 --- /dev/null +++ b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py @@ -0,0 +1,15 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'course_schedule', + 'transactions': [ + { + 'label': _('Attendance'), + 'items': ['Student Attendance'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js index 13383312a8..75dd4469e8 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.js +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js @@ -3,13 +3,13 @@ frappe.ui.form.on('Fee Schedule', { setup: function(frm) { - frm.add_fetch("fee_structure", "receivable_account", "receivable_account"); - frm.add_fetch("fee_structure", "income_account", "income_account"); - frm.add_fetch("fee_structure", "cost_center", "cost_center"); + frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account'); + frm.add_fetch('fee_structure', 'income_account', 'income_account'); + frm.add_fetch('fee_structure', 'cost_center', 'cost_center'); }, onload: function(frm) { - frm.set_query("receivable_account", function(doc) { + frm.set_query('receivable_account', function(doc) { return { filters: { 'account_type': 'Receivable', @@ -18,7 +18,8 @@ frappe.ui.form.on('Fee Schedule', { } }; }); - frm.set_query("income_account", function(doc) { + + frm.set_query('income_account', function(doc) { return { filters: { 'account_type': 'Income Account', @@ -27,57 +28,59 @@ frappe.ui.form.on('Fee Schedule', { } }; }); - frm.set_query("student_group", "student_groups", function() { + + frm.set_query('student_group', 'student_groups', function() { return { - "program": frm.doc.program, - "academic_term": frm.doc.academic_term, - "academic_year": frm.doc.academic_year, - "disabled": 0 + 'program': frm.doc.program, + 'academic_term': frm.doc.academic_term, + 'academic_year': frm.doc.academic_year, + 'disabled': 0 }; }); - frappe.realtime.on("fee_schedule_progress", function(data) { + + frappe.realtime.on('fee_schedule_progress', function(data) { if (data.reload && data.reload === 1) { frm.reload_doc(); } if (data.progress) { - let progress_bar = $(cur_frm.dashboard.progress_area).find(".progress-bar"); + let progress_bar = $(cur_frm.dashboard.progress_area).find('.progress-bar'); if (progress_bar) { - $(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped"); - $(progress_bar).css("width", data.progress+"%"); + $(progress_bar).removeClass('progress-bar-danger').addClass('progress-bar-success progress-bar-striped'); + $(progress_bar).css('width', data.progress+'%'); } } }); }, refresh: function(frm) { - if(!frm.doc.__islocal && frm.doc.__onload && frm.doc.__onload.dashboard_info && - frm.doc.fee_creation_status=="Successful") { + if (!frm.doc.__islocal && frm.doc.__onload && frm.doc.__onload.dashboard_info && + frm.doc.fee_creation_status === 'Successful') { var info = frm.doc.__onload.dashboard_info; frm.dashboard.add_indicator(__('Total Collected: {0}', [format_currency(info.total_paid, info.currency)]), 'blue'); frm.dashboard.add_indicator(__('Total Outstanding: {0}', [format_currency(info.total_unpaid, info.currency)]), info.total_unpaid ? 'orange' : 'green'); } - if (frm.doc.fee_creation_status=="In Process") { - frm.dashboard.add_progress("Fee Creation Status", "0"); + if (frm.doc.fee_creation_status === 'In Process') { + frm.dashboard.add_progress('Fee Creation Status', '0'); } - if (frm.doc.docstatus==1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status == "Failed") { + if (frm.doc.docstatus === 1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status === 'Failed') { frm.add_custom_button(__('Create Fees'), function() { frappe.call({ - method: "create_fees", + method: 'create_fees', doc: frm.doc, callback: function() { frm.refresh(); } }); - }, "fa fa-play", "btn-success"); + }).addClass('btn-primary');; } - if (frm.doc.fee_creation_status == "Successful") { - frm.add_custom_button(__("View Fees Records"), function() { + if (frm.doc.fee_creation_status === 'Successful') { + frm.add_custom_button(__('View Fees Records'), function() { frappe.route_options = { fee_schedule: frm.doc.name }; - frappe.set_route("List", "Fees"); + frappe.set_route('List', 'Fees'); }); } @@ -86,35 +89,35 @@ frappe.ui.form.on('Fee Schedule', { fee_structure: function(frm) { if (frm.doc.fee_structure) { frappe.call({ - method: "erpnext.education.doctype.fee_schedule.fee_schedule.get_fee_structure", + method: 'erpnext.education.doctype.fee_schedule.fee_schedule.get_fee_structure', args: { - "target_doc": frm.doc.name, - "source_name": frm.doc.fee_structure + 'target_doc': frm.doc.name, + 'source_name': frm.doc.fee_structure }, callback: function(r) { var doc = frappe.model.sync(r.message); - frappe.set_route("Form", doc[0].doctype, doc[0].name); + frappe.set_route('Form', doc[0].doctype, doc[0].name); } }); } } }); -frappe.ui.form.on("Fee Schedule Student Group", { +frappe.ui.form.on('Fee Schedule Student Group', { student_group: function(frm, cdt, cdn) { var row = locals[cdt][cdn]; if (row.student_group && frm.doc.academic_year) { frappe.call({ - method: "erpnext.education.doctype.fee_schedule.fee_schedule.get_total_students", + method: 'erpnext.education.doctype.fee_schedule.fee_schedule.get_total_students', args: { - "student_group": row.student_group, - "academic_year": frm.doc.academic_year, - "academic_term": frm.doc.academic_term, - "student_category": frm.doc.student_category + 'student_group': row.student_group, + 'academic_year': frm.doc.academic_year, + 'academic_term': frm.doc.academic_term, + 'student_category': frm.doc.student_category }, callback: function(r) { - if(!r.exc) { - frappe.model.set_value(cdt, cdn, "total_students", r.message); + if (!r.exc) { + frappe.model.set_value(cdt, cdn, 'total_students', r.message); } } }); diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py new file mode 100644 index 0000000000..acfe400219 --- /dev/null +++ b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py @@ -0,0 +1,13 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals + +def get_data(): + return { + 'fieldname': 'fee_schedule', + 'transactions': [ + { + 'items': ['Fees'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js index f09d2efcb9..b331c6d3c0 100644 --- a/erpnext/education/doctype/fee_structure/fee_structure.js +++ b/erpnext/education/doctype/fee_structure/fee_structure.js @@ -3,21 +3,21 @@ frappe.ui.form.on('Fee Structure', { setup: function(frm) { - frm.add_fetch("company", "default_receivable_account", "receivable_account"); - frm.add_fetch("company", "default_income_account", "income_account"); - frm.add_fetch("company", "cost_center", "cost_center"); + frm.add_fetch('company', 'default_receivable_account', 'receivable_account'); + frm.add_fetch('company', 'default_income_account', 'income_account'); + frm.add_fetch('company', 'cost_center', 'cost_center'); }, onload: function(frm) { - frm.set_query("academic_term", function() { + frm.set_query('academic_term', function() { return { - "filters": { - "academic_year": frm.doc.academic_year + 'filters': { + 'academic_year': frm.doc.academic_year } }; }); - frm.set_query("receivable_account", function(doc) { + frm.set_query('receivable_account', function(doc) { return { filters: { 'account_type': 'Receivable', @@ -26,7 +26,7 @@ frappe.ui.form.on('Fee Structure', { } }; }); - frm.set_query("income_account", function(doc) { + frm.set_query('income_account', function(doc) { return { filters: { 'account_type': 'Income Account', @@ -38,27 +38,27 @@ frappe.ui.form.on('Fee Structure', { }, refresh: function(frm) { - if(frm.doc.docstatus === 1) { + if (frm.doc.docstatus === 1) { frm.add_custom_button(__('Create Fee Schedule'), function() { frm.events.make_fee_schedule(frm); - }); + }).addClass('btn-primary'); } }, make_fee_schedule: function(frm) { frappe.model.open_mapped_doc({ - method: "erpnext.education.doctype.fee_structure.fee_structure.make_fee_schedule", + method: 'erpnext.education.doctype.fee_structure.fee_structure.make_fee_schedule', frm: frm }); } }); -frappe.ui.form.on("Fee Component", { +frappe.ui.form.on('Fee Component', { amount: function(frm) { var total_amount = 0; - for(var i=0;i { + if (!frm.doc.employee) return; + frappe.db.get_value("Employee", {name: frm.doc.employee}, "company", (d) => { frm.set_query("department", function() { return { "filters": { @@ -22,30 +22,16 @@ frappe.ui.form.on("Instructor", { }); }, refresh: function(frm) { - if(!frm.doc.__islocal) { - frm.add_custom_button(__("Student Group"), function() { - frappe.route_options = { - instructor: frm.doc.name - } - frappe.set_route("List", "Student Group"); - }); - frm.add_custom_button(__("Course Schedule"), function() { - frappe.route_options = { - instructor: frm.doc.name - } - frappe.set_route("List", "Course Schedule"); - }); + if (!frm.doc.__islocal) { frm.add_custom_button(__("As Examiner"), function() { - frappe.route_options = { + frappe.new_doc("Assessment Plan", { examiner: frm.doc.name - } - frappe.set_route("List", "Assessment Plan"); + }); }, __("Assessment Plan")); frm.add_custom_button(__("As Supervisor"), function() { - frappe.route_options = { + frappe.new_doc("Assessment Plan", { supervisor: frm.doc.name - } - frappe.set_route("List", "Assessment Plan"); + }); }, __("Assessment Plan")); } frm.set_query("employee", function(doc) { diff --git a/erpnext/education/doctype/instructor/instructor.py b/erpnext/education/doctype/instructor/instructor.py index 28df2fcdc1..b1bfcbb2f1 100644 --- a/erpnext/education/doctype/instructor/instructor.py +++ b/erpnext/education/doctype/instructor/instructor.py @@ -30,4 +30,14 @@ class Instructor(Document): if self.employee and frappe.db.get_value("Instructor", {'employee': self.employee, 'name': ['!=', self.name]}, 'name'): frappe.throw(_("Employee ID is linked with another instructor")) - +def get_timeline_data(doctype, name): + """Return timeline for course schedule""" + return dict(frappe.db.sql( + """ + SELECT unix_timestamp(`schedule_date`), count(*) + FROM `tabCourse Schedule` + WHERE + instructor=%s and + `schedule_date` > date_sub(curdate(), interval 1 year) + GROUP BY schedule_date + """, name)) diff --git a/erpnext/education/doctype/instructor/instructor_dashboard.py b/erpnext/education/doctype/instructor/instructor_dashboard.py new file mode 100644 index 0000000000..a404fc56c5 --- /dev/null +++ b/erpnext/education/doctype/instructor/instructor_dashboard.py @@ -0,0 +1,24 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'heatmap': True, + 'heatmap_message': _('This is based on the course schedules of this Instructor'), + 'fieldname': 'instructor', + 'non_standard_fieldnames': { + 'Assessment Plan': 'supervisor' + }, + 'transactions': [ + { + 'label': _('Course and Assessment'), + 'items': ['Course Schedule', 'Assessment Plan'] + }, + { + 'label': _('Students'), + 'items': ['Student Group'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/program/program_dashboard.py b/erpnext/education/doctype/program/program_dashboard.py index cb8f74207e..c5d249451f 100644 --- a/erpnext/education/doctype/program/program_dashboard.py +++ b/erpnext/education/doctype/program/program_dashboard.py @@ -10,11 +10,15 @@ def get_data(): }, { 'label': _('Student Activity'), - 'items': ['Student Group' ] + 'items': ['Student Group', 'Student Log'] }, { 'label': _('Fee'), - 'items': ['Fees','Fee Structure'] + 'items': ['Fees','Fee Structure', 'Fee Schedule'] + }, + { + 'label': _('Assessment'), + 'items': ['Assessment Plan', 'Assessment Result'] } ] } \ No newline at end of file diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py new file mode 100644 index 0000000000..18d307cdf0 --- /dev/null +++ b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'program_enrollment', + 'transactions': [ + { + 'label': _('Course and Fee'), + 'items': ['Course Enrollment', 'Fees'] + } + ], + 'reports': [ + { + 'label': _('Report'), + 'items': ['Student and Guardian Contact Details'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/quiz/quiz.js b/erpnext/education/doctype/quiz/quiz.js index 7b870886ec..01bcf73236 100644 --- a/erpnext/education/doctype/quiz/quiz.js +++ b/erpnext/education/doctype/quiz/quiz.js @@ -3,11 +3,17 @@ frappe.ui.form.on('Quiz', { refresh: function(frm) { - + if (!frm.doc.__islocal) { + frm.add_custom_button(__('Add to Topics'), function() { + frm.trigger('add_quiz_to_topics'); + }, __('Action')); + } }, + validate: function(frm){ frm.events.check_duplicate_question(frm.doc.question); }, + check_duplicate_question: function(questions_data){ var questions = []; questions_data.forEach(function(q){ @@ -15,7 +21,51 @@ frappe.ui.form.on('Quiz', { }); var questions_set = new Set(questions); if (questions.length != questions_set.size) { - frappe.throw(__("The question cannot be duplicate")); + frappe.throw(__('The question cannot be duplicate')); } + }, + + add_quiz_to_topics: function(frm) { + get_topics_without_quiz(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'topics', + label: __('Topics'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + } + ], + function(data) { + frappe.call({ + method: 'erpnext.education.doctype.topic.topic.add_content_to_topics', + args: { + 'content_type': 'Quiz', + 'content': frm.doc.name, + 'topics': data.topics, + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Quiz to Topics') + }); + }, __('Add Quiz to Topics'), __('Add')); + } else { + frappe.msgprint(__('This quiz is already added to the existing topics')); + } + }); } -}); \ No newline at end of file +}); + +let get_topics_without_quiz = function(quiz) { + return frappe.call({ + type: 'GET', + method: 'erpnext.education.doctype.quiz.quiz.get_topics_without_quiz', + args: {'quiz': quiz} + }); +}; \ No newline at end of file diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py index ae1cb6ce42..a774b88579 100644 --- a/erpnext/education/doctype/quiz/quiz.py +++ b/erpnext/education/doctype/quiz/quiz.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +import json from frappe import _ from frappe.model.document import Document @@ -59,3 +60,12 @@ def compare_list_elementwise(*args): except TypeError: frappe.throw(_("Compare List function takes on list arguments")) +@frappe.whitelist() +def get_topics_without_quiz(quiz): + data = [] + for entry in frappe.db.get_all('Topic'): + topic = frappe.get_doc('Topic', entry.name) + topic_contents = [tc.content for tc in topic.topic_content] + if not topic_contents or quiz not in topic_contents: + data.append(topic.name) + return data \ No newline at end of file diff --git a/erpnext/education/doctype/room/room.js b/erpnext/education/doctype/room/room.js index 032db9835b..20cee6b2a6 100644 --- a/erpnext/education/doctype/room/room.js +++ b/erpnext/education/doctype/room/room.js @@ -1,10 +1,2 @@ -frappe.ui.form.on("Room", "refresh", function(frm) { - if(!cur_frm.doc.__islocal) { - frm.add_custom_button(__("Course Schedule"), function() { - frappe.route_options = { - room: frm.doc.name - } - frappe.set_route("List", "Course Schedule"); - }); - } +frappe.ui.form.on("Room", { }); \ No newline at end of file diff --git a/erpnext/education/doctype/room/room_dashboard.py b/erpnext/education/doctype/room/room_dashboard.py new file mode 100644 index 0000000000..99aac3393e --- /dev/null +++ b/erpnext/education/doctype/room/room_dashboard.py @@ -0,0 +1,19 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'room', + 'transactions': [ + { + 'label': _('Course'), + 'items': ['Course Schedule'] + }, + { + 'label': _('Assessment'), + 'items': ['Assessment Plan'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py b/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py new file mode 100644 index 0000000000..9c41b8f3dc --- /dev/null +++ b/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'reports': [ + { + 'label': _('Reports'), + 'items': ['Student Monthly Attendance Sheet', 'Student Batch-Wise Attendance'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/student_category/student_category_dashboard.py b/erpnext/education/doctype/student_category/student_category_dashboard.py new file mode 100644 index 0000000000..f31c34bd94 --- /dev/null +++ b/erpnext/education/doctype/student_category/student_category_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'student_category', + 'transactions': [ + { + 'label': _('Fee'), + 'items': ['Fee Structure', 'Fee Schedule', 'Fees'] + } + ] + } diff --git a/erpnext/education/doctype/student_group/student_group.js b/erpnext/education/doctype/student_group/student_group.js index 13724409ba..51e3b74a5c 100644 --- a/erpnext/education/doctype/student_group/student_group.js +++ b/erpnext/education/doctype/student_group/student_group.js @@ -1,18 +1,18 @@ -cur_frm.add_fetch("student", "title", "student_name"); +cur_frm.add_fetch('student', 'title', 'student_name'); -frappe.ui.form.on("Student Group", { +frappe.ui.form.on('Student Group', { onload: function(frm) { - frm.set_query("academic_term", function() { + frm.set_query('academic_term', function() { return { - "filters": { - "academic_year": (frm.doc.academic_year) + filters: { + 'academic_year': (frm.doc.academic_year) } }; }); if (!frm.__islocal) { - frm.set_query("student", "students", function() { + frm.set_query('student', 'students', function() { return{ - query: "erpnext.education.doctype.student_group.student_group.fetch_students", + query: 'erpnext.education.doctype.student_group.student_group.fetch_students', filters: { 'academic_year': frm.doc.academic_year, 'group_based_on': frm.doc.group_based_on, @@ -30,87 +30,86 @@ frappe.ui.form.on("Student Group", { refresh: function(frm) { if (!frm.doc.__islocal) { - frm.add_custom_button(__("Attendance"), function() { - frappe.route_options = { - based_on: "Student Group", - student_group: frm.doc.name - } - frappe.set_route("List", "Student Attendance Tool"); - }); - frm.add_custom_button(__("Course Schedule"), function() { - frappe.route_options = { - student_group: frm.doc.name - } - frappe.set_route("List", "Course Schedule"); - }); - frm.add_custom_button(__("Assessment Plan"), function() { - frappe.route_options = { - student_group: frm.doc.name - } - frappe.set_route("List", "Assessment Plan"); - }); - frm.add_custom_button(__("Update Email Group"), function() { + + frm.add_custom_button(__('Add Guardians to Email Group'), function() { frappe.call({ - method: "erpnext.education.api.update_email_group", + method: 'erpnext.education.api.update_email_group', args: { - "doctype": "Student Group", - "name": frm.doc.name + 'doctype': 'Student Group', + 'name': frm.doc.name } }); - }); - frm.add_custom_button(__("Newsletter"), function() { + }, __('Actions')); + + frm.add_custom_button(__('Student Attendance Tool'), function() { frappe.route_options = { - "Newsletter Email Group.email_group": frm.doc.name + based_on: 'Student Group', + student_group: frm.doc.name } - frappe.set_route("List", "Newsletter"); - }); + frappe.set_route('Form', 'Student Attendance Tool', 'Student Attendance Tool'); + }, __('Tools')); + + frm.add_custom_button(__('Course Scheduling Tool'), function() { + frappe.route_options = { + student_group: frm.doc.name + } + frappe.set_route('Form', 'Course Scheduling Tool', 'Course Scheduling Tool'); + }, __('Tools')); + + frm.add_custom_button(__('Newsletter'), function() { + frappe.route_options = { + 'Newsletter Email Group.email_group': frm.doc.name + } + frappe.set_route('List', 'Newsletter'); + }, __('View')); + } }, - + group_based_on: function(frm) { - if (frm.doc.group_based_on == "Batch") { + if (frm.doc.group_based_on == 'Batch') { frm.doc.course = null; frm.set_df_property('program', 'reqd', 1); frm.set_df_property('course', 'reqd', 0); } - else if (frm.doc.group_based_on == "Course") { + else if (frm.doc.group_based_on == 'Course') { frm.set_df_property('program', 'reqd', 0); frm.set_df_property('course', 'reqd', 1); } - else if (frm.doc.group_based_on == "Activity") { + else if (frm.doc.group_based_on == 'Activity') { frm.set_df_property('program', 'reqd', 0); frm.set_df_property('course', 'reqd', 0); } }, get_students: function(frm) { - if (frm.doc.group_based_on == "Batch" || frm.doc.group_based_on == "Course") { + if (frm.doc.group_based_on == 'Batch' || frm.doc.group_based_on == 'Course') { var student_list = []; var max_roll_no = 0; - $.each(frm.doc.students, function(i,d) { + $.each(frm.doc.students, function(_i,d) { student_list.push(d.student); if (d.group_roll_number>max_roll_no) { max_roll_no = d.group_roll_number; } }); - if(frm.doc.academic_year) { + if (frm.doc.academic_year) { frappe.call({ - method: "erpnext.education.doctype.student_group.student_group.get_students", + method: 'erpnext.education.doctype.student_group.student_group.get_students', args: { - "academic_year": frm.doc.academic_year, - "academic_term": frm.doc.academic_term, - "group_based_on": frm.doc.group_based_on, - "program": frm.doc.program, - "batch" : frm.doc.batch, - "student_category" : frm.doc.student_category, - "course": frm.doc.course + 'academic_year': frm.doc.academic_year, + 'academic_term': frm.doc.academic_term, + 'group_based_on': frm.doc.group_based_on, + 'program': frm.doc.program, + 'batch' : frm.doc.batch, + 'student_category' : frm.doc.student_category, + 'course': frm.doc.course }, callback: function(r) { - if(r.message) { + if (r.message) { $.each(r.message, function(i, d) { if(!in_list(student_list, d.student)) { - var s = frm.add_child("students"); + var s = frm.add_child('students'); s.student = d.student; s.student_name = d.student_name; if (d.active === 0) { @@ -119,16 +118,16 @@ frappe.ui.form.on("Student Group", { s.group_roll_number = ++max_roll_no; } }); - refresh_field("students"); + refresh_field('students'); frm.save(); } else { - frappe.msgprint(__("Student Group is already updated.")) + frappe.msgprint(__('Student Group is already updated.')) } } }) } } else { - frappe.msgprint(__("Select students manually for the Activity based Group")); + frappe.msgprint(__('Select students manually for the Activity based Group')); } } }); diff --git a/erpnext/education/doctype/student_group/student_group_dashboard.py b/erpnext/education/doctype/student_group/student_group_dashboard.py new file mode 100644 index 0000000000..ad7a6de7b3 --- /dev/null +++ b/erpnext/education/doctype/student_group/student_group_dashboard.py @@ -0,0 +1,19 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'student_group', + 'transactions': [ + { + 'label': _('Assessment'), + 'items': ['Assessment Plan', 'Assessment Result'] + }, + { + 'label': _('Course'), + 'items': ['Course Schedule'] + } + ] + } \ No newline at end of file diff --git a/erpnext/education/doctype/topic/topic.js b/erpnext/education/doctype/topic/topic.js index 695c17476c..2002b0c8e3 100644 --- a/erpnext/education/doctype/topic/topic.js +++ b/erpnext/education/doctype/topic/topic.js @@ -3,6 +3,53 @@ frappe.ui.form.on('Topic', { refresh: function(frm) { + if (!cur_frm.doc.__islocal) { + frm.add_custom_button(__('Add to Courses'), function() { + frm.trigger('add_topic_to_courses'); + }, __('Action')); + } + }, + add_topic_to_courses: function(frm) { + get_courses_without_topic(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'courses', + label: __('Courses'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + } + ], + function(data) { + frappe.call({ + method: 'erpnext.education.doctype.topic.topic.add_topic_to_courses', + args: { + 'topic': frm.doc.name, + 'courses': data.courses + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Topic to Courses') + }); + }, __('Add Topic to Courses'), __('Add')); + } else { + frappe.msgprint(__('This topic is already added to the existing courses')); + } + }); } }); + +let get_courses_without_topic = function(topic) { + return frappe.call({ + type: 'GET', + method: 'erpnext.education.doctype.topic.topic.get_courses_without_topic', + args: {'topic': topic} + }); +}; \ No newline at end of file diff --git a/erpnext/education/doctype/topic/topic.py b/erpnext/education/doctype/topic/topic.py index 7e5da329eb..a5253e9329 100644 --- a/erpnext/education/doctype/topic/topic.py +++ b/erpnext/education/doctype/topic/topic.py @@ -4,6 +4,8 @@ from __future__ import unicode_literals import frappe +import json +from frappe import _ from frappe.model.document import Document class Topic(Document): @@ -14,4 +16,44 @@ class Topic(Document): except Exception as e: frappe.log_error(frappe.get_traceback()) return None - return content_data \ No newline at end of file + return content_data + +@frappe.whitelist() +def get_courses_without_topic(topic): + data = [] + for entry in frappe.db.get_all('Course'): + course = frappe.get_doc('Course', entry.name) + topics = [t.topic for t in course.topics] + if not topics or topic not in topics: + data.append(course.name) + return data + +@frappe.whitelist() +def add_topic_to_courses(topic, courses, mandatory=False): + courses = json.loads(courses) + for entry in courses: + course = frappe.get_doc('Course', entry) + course.append('topics', { + 'topic': topic, + 'topic_name': topic + }) + course.flags.ignore_mandatory = True + course.save() + frappe.db.commit() + frappe.msgprint(_('Topic {0} has been added to all the selected courses successfully.').format(frappe.bold(topic)), + title=_('Courses updated'), indicator='green') + +@frappe.whitelist() +def add_content_to_topics(content_type, content, topics): + topics = json.loads(topics) + for entry in topics: + topic = frappe.get_doc('Topic', entry) + topic.append('topic_content', { + 'content_type': content_type, + 'content': content, + }) + topic.flags.ignore_mandatory = True + topic.save() + frappe.db.commit() + frappe.msgprint(_('{0} {1} has been added to all the selected topics successfully.').format(content_type, frappe.bold(content)), + title=_('Topics updated'), indicator='green') \ No newline at end of file From 7137b0b67035a799986a7b37782a3f33936d4a8c Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 23 Jul 2020 15:58:47 +0530 Subject: [PATCH 095/101] refactor: shopping cart (#22617) * refactoring shopping cart settings * adding column_break in shopping cart settings --- erpnext/public/js/shopping_cart.js | 9 + .../shopping_cart_settings.js | 31 +- .../shopping_cart_settings.json | 940 ++++-------------- .../shopping_cart_settings.py | 1 + .../generators/item/item_configure.html | 2 +- 5 files changed, 210 insertions(+), 773 deletions(-) diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js index 44a8cd0067..6a923ae423 100644 --- a/erpnext/public/js/shopping_cart.js +++ b/erpnext/public/js/shopping_cart.js @@ -55,6 +55,7 @@ frappe.ready(function() { shopping_cart.show_shoppingcart_dropdown(); shopping_cart.set_cart_count(); shopping_cart.bind_dropdown_cart_buttons(); + shopping_cart.show_cart_navbar(); }); $.extend(shopping_cart, { @@ -177,4 +178,12 @@ $.extend(shopping_cart, { }, + show_cart_navbar: function () { + frappe.call({ + method: "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.is_cart_enabled", + callback: function(r) { + $(".shopping-cart").toggleClass('hidden', r.message ? false : true); + } + }); + } }); diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js index ffc5daba62..14500ba6b3 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js @@ -1,31 +1,16 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -$.extend(cur_frm.cscript, { - onload: function() { - if(cur_frm.doc.__onload && cur_frm.doc.__onload.quotation_series) { - cur_frm.fields_dict.quotation_series.df.options = cur_frm.doc.__onload.quotation_series; - cur_frm.refresh_field("quotation_series"); +frappe.ui.form.on("Shopping Cart Settings", { + onload: function(frm) { + if(frm.doc.__onload && frm.doc.__onload.quotation_series) { + frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series; + frm.refresh_field("quotation_series"); } }, - refresh: function(){ - toggle_mandatory(cur_frm) - }, - enable_checkout: function(){ - toggle_mandatory(cur_frm) - }, - enabled: function() { - if (cur_frm.doc.enabled === 1) { - cur_frm.doc.show_configure_button = 1; - cur_frm.refresh_field('show_configure_button'); + enabled: function(frm) { + if (frm.doc.enabled === 1) { + frm.set_value('enable_variants', 1); } } }); - - -function toggle_mandatory (cur_frm){ - cur_frm.toggle_reqd("payment_gateway_account", false); - if(cur_frm.doc.enabled && cur_frm.doc.enable_checkout) { - cur_frm.toggle_reqd("payment_gateway_account", true); - } -} diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json index e828f54878..c574afa68c 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json @@ -1,750 +1,192 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-06-19 15:57:32", - "custom": 0, - "description": "Default settings for Shopping Cart", - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Enable Shopping Cart", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "display_settings", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Display Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "show_attachments", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Public Attachments", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "show_price", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Price", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "show_stock_availability", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Stock Availability", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "show_configure_button", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Configure Button", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "show_contact_us_button", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Contact Us Button", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "show_stock_availability", - "fieldname": "show_quantity_in_website", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Stock Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "show_apply_coupon_code_in_website", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Apply Coupon Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_items_not_in_stock", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow items not in stock to be added to cart", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enabled", - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Prices will not be shown if Price List is not set", - "fieldname": "price_list", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "default_customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "quotation_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quotation Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "eval:doc.enable_checkout", - "columns": 0, - "depends_on": "enabled", - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Checkout Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", - "fieldname": "enable_checkout", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enable Checkout", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Orders", - "description": "After payment completion redirect user to selected page.", - "fieldname": "payment_success_url", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Success Url", - "length": 0, - "no_copy": 0, - "options": "\nOrders\nInvoices\nMy Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_gateway_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Gateway Account", - "length": 0, - "no_copy": 0, - "options": "Payment Gateway Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-shopping-cart", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-10-14 13:54:24.575322", - "modified_by": "Administrator", - "module": "Shopping Cart", - "name": "Shopping Cart Settings", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Website Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 - } \ No newline at end of file + "actions": [], + "creation": "2013-06-19 15:57:32", + "description": "Default settings for Shopping Cart", + "doctype": "DocType", + "document_type": "System", + "engine": "InnoDB", + "field_order": [ + "enabled", + "display_settings", + "show_attachments", + "show_price", + "show_stock_availability", + "enable_variants", + "column_break_7", + "show_contact_us_button", + "show_quantity_in_website", + "show_apply_coupon_code_in_website", + "allow_items_not_in_stock", + "section_break_2", + "company", + "price_list", + "column_break_4", + "default_customer_group", + "quotation_series", + "section_break_8", + "enable_checkout", + "payment_success_url", + "column_break_11", + "payment_gateway_account" + ], + "fields": [ + { + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Enable Shopping Cart" + }, + { + "fieldname": "display_settings", + "fieldtype": "Section Break", + "label": "Display Settings" + }, + { + "default": "0", + "fieldname": "show_attachments", + "fieldtype": "Check", + "label": "Show Public Attachments" + }, + { + "default": "0", + "fieldname": "show_price", + "fieldtype": "Check", + "label": "Show Price" + }, + { + "default": "0", + "fieldname": "show_stock_availability", + "fieldtype": "Check", + "label": "Show Stock Availability" + }, + { + "default": "0", + "fieldname": "show_contact_us_button", + "fieldtype": "Check", + "label": "Show Contact Us Button" + }, + { + "default": "0", + "depends_on": "show_stock_availability", + "fieldname": "show_quantity_in_website", + "fieldtype": "Check", + "label": "Show Stock Quantity" + }, + { + "default": "0", + "fieldname": "show_apply_coupon_code_in_website", + "fieldtype": "Check", + "label": "Show Apply Coupon Code" + }, + { + "default": "0", + "fieldname": "allow_items_not_in_stock", + "fieldtype": "Check", + "label": "Allow items not in stock to be added to cart" + }, + { + "depends_on": "enabled", + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "description": "Prices will not be shown if Price List is not set", + "fieldname": "price_list", + "fieldtype": "Link", + "label": "Price List", + "options": "Price List" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_customer_group", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Customer Group", + "options": "Customer Group", + "reqd": 1 + }, + { + "fieldname": "quotation_series", + "fieldtype": "Select", + "label": "Quotation Series", + "reqd": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.enable_checkout", + "depends_on": "enabled", + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Checkout Settings" + }, + { + "default": "0", + "fieldname": "enable_checkout", + "fieldtype": "Check", + "label": "Enable Checkout" + }, + { + "default": "Orders", + "description": "After payment completion redirect user to selected page.", + "fieldname": "payment_success_url", + "fieldtype": "Select", + "label": "Payment Success Url", + "options": "\nOrders\nInvoices\nMy Account" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "payment_gateway_account", + "fieldtype": "Link", + "label": "Payment Gateway Account", + "options": "Payment Gateway Account" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "enable_variants", + "fieldtype": "Check", + "label": "Enable Variants" + } + ], + "icon": "fa fa-shopping-cart", + "idx": 1, + "issingle": 1, + "links": [], + "modified": "2020-07-17 17:53:22.667228", + "modified_by": "Administrator", + "module": "Shopping Cart", + "name": "Shopping Cart Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Website Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "ASC" +} \ No newline at end of file diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py index 3098190383..c069b90e98 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -80,6 +80,7 @@ def get_shopping_cart_settings(): return frappe.local.shopping_cart_settings +@frappe.whitelist(allow_guest=True) def is_cart_enabled(): return get_shopping_cart_settings().enabled diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html index 04f89eca9d..b8b0d98bdc 100644 --- a/erpnext/templates/generators/item/item_configure.html +++ b/erpnext/templates/generators/item/item_configure.html @@ -2,7 +2,7 @@ {% set cart_settings = shopping_cart.cart_settings %}
- {% if cart_settings.show_configure_button | int %} + {% if cart_settings.enable_variants | int %}
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js new file mode 100644 index 0000000000..8dcd2e4a72 --- /dev/null +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -0,0 +1,149 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('POS Closing Entry', { + onload: function(frm) { + frm.set_query("pos_profile", function(doc) { + return { + filters: { 'user': doc.user } + }; + }); + + frm.set_query("user", function(doc) { + return { + query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers", + filters: { 'parent': doc.pos_profile } + }; + }); + + frm.set_query("pos_opening_entry", function(doc) { + return { filters: { 'status': 'Open', 'docstatus': 1 } }; + }); + + if (frm.doc.docstatus === 0) frm.set_value("period_end_date", frappe.datetime.now_datetime()); + if (frm.doc.docstatus === 1) set_html_data(frm); + }, + + pos_opening_entry(frm) { + if (frm.doc.pos_opening_entry && frm.doc.period_start_date && frm.doc.period_end_date && frm.doc.user) { + reset_values(frm); + frm.trigger("set_opening_amounts"); + frm.trigger("get_pos_invoices"); + } + }, + + set_opening_amounts(frm) { + frappe.db.get_doc("POS Opening Entry", frm.doc.pos_opening_entry) + .then(({ balance_details }) => { + balance_details.forEach(detail => { + frm.add_child("payment_reconciliation", { + mode_of_payment: detail.mode_of_payment, + opening_amount: detail.opening_amount, + expected_amount: detail.opening_amount + }); + }) + }); + }, + + get_pos_invoices(frm) { + frappe.call({ + method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices', + args: { + start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date), + end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date), + user: frm.doc.user + }, + callback: (r) => { + let pos_docs = r.message; + set_form_data(pos_docs, frm) + refresh_fields(frm) + set_html_data(frm) + } + }) + } +}); + +frappe.ui.form.on('POS Closing Entry Detail', { + closing_amount: (frm, cdt, cdn) => { + const row = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount)) + } +}) + +function set_form_data(data, frm) { + data.forEach(d => { + add_to_pos_transaction(d, frm); + frm.doc.grand_total += flt(d.grand_total); + frm.doc.net_total += flt(d.net_total); + frm.doc.total_quantity += flt(d.total_qty); + add_to_payments(d, frm); + add_to_taxes(d, frm); + }); +} + +function add_to_pos_transaction(d, frm) { + frm.add_child("pos_transactions", { + pos_invoice: d.name, + posting_date: d.posting_date, + grand_total: d.grand_total, + customer: d.customer + }) +} + +function add_to_payments(d, frm) { + d.payments.forEach(p => { + const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment); + if (payment) { + payment.expected_amount += flt(p.amount); + } else { + frm.add_child("payment_reconciliation", { + mode_of_payment: p.mode_of_payment, + opening_amount: 0, + expected_amount: p.amount + }) + } + }) +} + +function add_to_taxes(d, frm) { + d.taxes.forEach(t => { + const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate); + if (tax) { + tax.amount += flt(t.tax_amount); + } else { + frm.add_child("taxes", { + account_head: t.account_head, + rate: t.rate, + amount: t.tax_amount + }) + } + }) +} + +function reset_values(frm) { + frm.set_value("pos_transactions", []); + frm.set_value("payment_reconciliation", []); + frm.set_value("taxes", []); + frm.set_value("grand_total", 0); + frm.set_value("net_total", 0); + frm.set_value("total_quantity", 0); +} + +function refresh_fields(frm) { + frm.refresh_field("pos_transactions"); + frm.refresh_field("payment_reconciliation"); + frm.refresh_field("taxes"); + frm.refresh_field("grand_total"); + frm.refresh_field("net_total"); + frm.refresh_field("total_quantity"); +} + +function set_html_data(frm) { + frappe.call({ + method: "get_payment_reconciliation_details", + doc: frm.doc, + callback: (r) => { + frm.get_field("payment_reconciliation_details").$wrapper.html(r.message); + } + }) +} diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json new file mode 100644 index 0000000000..32bca3b840 --- /dev/null +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -0,0 +1,242 @@ +{ + "actions": [], + "autoname": "POS-CLO-.YYYY.-.#####", + "creation": "2018-05-28 19:06:40.830043", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "period_start_date", + "period_end_date", + "column_break_3", + "posting_date", + "pos_opening_entry", + "section_break_5", + "company", + "column_break_7", + "pos_profile", + "user", + "section_break_12", + "pos_transactions", + "section_break_9", + "payment_reconciliation_details", + "section_break_11", + "payment_reconciliation", + "section_break_13", + "grand_total", + "net_total", + "total_quantity", + "column_break_16", + "taxes", + "section_break_14", + "amended_from" + ], + "fields": [ + { + "fetch_from": "pos_opening_entry.period_start_date", + "fieldname": "period_start_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Period Start Date", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "period_end_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Period End Date", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fetch_from": "pos_opening_entry.pos_profile", + "fieldname": "pos_profile", + "fieldtype": "Link", + "in_list_view": 1, + "label": "POS Profile", + "options": "POS Profile", + "reqd": 1 + }, + { + "fetch_from": "pos_opening_entry.user", + "fieldname": "user", + "fieldtype": "Link", + "label": "Cashier", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "read_only": 1 + }, + { + "depends_on": "eval:doc.docstatus==1", + "fieldname": "payment_reconciliation_details", + "fieldtype": "HTML" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "label": "Modes of Payment" + }, + { + "fieldname": "payment_reconciliation", + "fieldtype": "Table", + "label": "Payment Reconciliation", + "options": "POS Closing Entry Detail" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.docstatus==0", + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "default": "0", + "fieldname": "grand_total", + "fieldtype": "Currency", + "label": "Grand Total", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total", + "read_only": 1 + }, + { + "fieldname": "total_quantity", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes", + "options": "POS Closing Entry Taxes", + "read_only": 1 + }, + { + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "label": "Linked Invoices" + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "POS Closing Entry", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "pos_transactions", + "fieldtype": "Table", + "label": "POS Transactions", + "options": "POS Invoice Reference", + "reqd": 1 + }, + { + "fieldname": "pos_opening_entry", + "fieldtype": "Link", + "label": "POS Opening Entry", + "options": "POS Opening Entry", + "reqd": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-05-29 15:03:22.226113", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Closing Entry", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py new file mode 100644 index 0000000000..8eb0a222a4 --- /dev/null +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, 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.model.document import Document +from frappe.utils import getdate, get_datetime, flt +from collections import defaultdict +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 + +class POSClosingEntry(Document): + def validate(self): + user = frappe.get_all('POS Closing Entry', + filters = { 'user': self.user, 'docstatus': 1 }, + or_filters = { + 'period_start_date': ('between', [self.period_start_date, self.period_end_date]), + 'period_end_date': ('between', [self.period_start_date, self.period_end_date]) + }) + + if user: + frappe.throw(_("POS Closing Entry {} against {} between selected period" + .format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period")) + + 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")) + + 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() + + 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}) + +@frappe.whitelist() +def get_cashiers(doctype, txt, searchfield, start, page_len, filters): + cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) + return [c['user'] for c in cashiers_list] + +@frappe.whitelist() +def get_pos_invoices(start, end, user): + data = frappe.db.sql(""" + select + name, timestamp(posting_date, posting_time) as "timestamp" + from + `tabPOS Invoice` + where + owner = %s and docstatus = 1 and + (consolidated_invoice is NULL or consolidated_invoice = '') + """, (user), as_dict=1) + + data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data)) + # need to get taxes and payments so can't avoid get_doc + data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data] + + return data + +def make_closing_entry_from_opening(opening_entry): + closing_entry = frappe.new_doc("POS Closing Entry") + closing_entry.pos_opening_entry = opening_entry.name + closing_entry.period_start_date = opening_entry.period_start_date + closing_entry.period_end_date = frappe.utils.get_datetime() + closing_entry.pos_profile = opening_entry.pos_profile + closing_entry.user = opening_entry.user + closing_entry.company = opening_entry.company + closing_entry.grand_total = 0 + closing_entry.net_total = 0 + closing_entry.total_quantity = 0 + + invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date, closing_entry.user) + + pos_transactions = [] + taxes = [] + payments = [] + for detail in opening_entry.balance_details: + payments.append(frappe._dict({ + 'mode_of_payment': detail.mode_of_payment, + 'opening_amount': detail.opening_amount, + 'expected_amount': detail.opening_amount + })) + + for d in invoices: + pos_transactions.append(frappe._dict({ + 'pos_invoice': d.name, + 'posting_date': d.posting_date, + 'grand_total': d.grand_total, + 'customer': d.customer + })) + closing_entry.grand_total += flt(d.grand_total) + closing_entry.net_total += flt(d.net_total) + closing_entry.total_quantity += flt(d.total_qty) + + for t in d.taxes: + existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate] + if existing_tax: + existing_tax[0].amount += flt(t.tax_amount); + else: + taxes.append(frappe._dict({ + 'account_head': t.account_head, + 'rate': t.rate, + 'amount': t.tax_amount + })) + + for p in d.payments: + existing_pay = [pay for pay in payments if pay.mode_of_payment == p.mode_of_payment] + if existing_pay: + existing_pay[0].expected_amount += flt(p.amount); + else: + payments.append(frappe._dict({ + 'mode_of_payment': p.mode_of_payment, + 'opening_amount': 0, + 'expected_amount': p.amount + })) + + closing_entry.set("pos_transactions", pos_transactions) + closing_entry.set("payment_reconciliation", payments) + closing_entry.set("taxes", taxes) + + return closing_entry diff --git a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js similarity index 69% rename from erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js rename to erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js index 76338151ec..48109b159c 100644 --- a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js @@ -2,15 +2,15 @@ // rename this file from _test_[name] to test_[name] to activate // and remove above this line -QUnit.test("test: POS Closing Voucher", function (assert) { +QUnit.test("test: POS Closing Entry", function (assert) { let done = assert.async(); // number of asserts assert.expect(1); frappe.run_serially([ - // insert a new POS Closing Voucher - () => frappe.tests.make('POS Closing Voucher', [ + // insert a new POS Closing Entry + () => frappe.tests.make('POS Closing Entry', [ // values to be set {key: 'value'} ]), diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py new file mode 100644 index 0000000000..aa6a388df5 --- /dev/null +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals +import frappe +import unittest +from frappe.utils import nowdate +from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice +from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening +from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile + +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) + 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() + + self.assertEqual(pcv_doc.total_quantity, 2) + self.assertEqual(pcv_doc.net_total, 6700) + + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + +def init_user_and_profile(): + user = 'test@example.com' + test_user = frappe.get_doc('User', user) + + roles = ("Accounts Manager", "Accounts User", "Sales Manager") + test_user.add_roles(*roles) + frappe.set_user(user) + + pos_profile = make_pos_profile() + pos_profile.append('applicable_for_users', { + 'default': 1, + 'user': user + }) + + pos_profile.save() + + return test_user, pos_profile \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/accounts/doctype/pos_closing_entry_detail/__init__.py similarity index 100% rename from erpnext/selling/doctype/pos_closing_voucher/__init__.py rename to erpnext/accounts/doctype/pos_closing_entry_detail/__init__.py diff --git a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json new file mode 100644 index 0000000000..798637a840 --- /dev/null +++ b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "creation": "2018-05-28 19:10:47.580174", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "mode_of_payment", + "opening_amount", + "closing_amount", + "expected_amount", + "difference" + ], + "fields": [ + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Mode of Payment", + "options": "Mode of Payment", + "reqd": 1 + }, + { + "fieldname": "expected_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Expected Amount", + "options": "company:company_currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "difference", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Difference", + "options": "company:company_currency", + "read_only": 1 + }, + { + "fieldname": "opening_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Opening Amount", + "options": "company:company_currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "closing_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Closing Amount", + "options": "company:company_currency", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-29 15:03:34.533607", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Closing Entry Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.py similarity index 85% rename from erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py rename to erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.py index 87ce842991..46b6c773bc 100644 --- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py +++ b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals from frappe.model.document import Document -class POSClosingVoucherTaxes(Document): +class POSClosingEntryDetail(Document): pass diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/__init__.py b/erpnext/accounts/doctype/pos_closing_entry_taxes/__init__.py similarity index 100% rename from erpnext/selling/doctype/pos_closing_voucher_details/__init__.py rename to erpnext/accounts/doctype/pos_closing_entry_taxes/__init__.py diff --git a/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json new file mode 100644 index 0000000000..42e7d0ef96 --- /dev/null +++ b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json @@ -0,0 +1,48 @@ +{ + "actions": [], + "creation": "2018-05-30 09:11:22.535470", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account_head", + "rate", + "amount" + ], + "fields": [ + { + "fieldname": "rate", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "Rate", + "read_only": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "read_only": 1 + }, + { + "fieldname": "account_head", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account Head", + "options": "Account", + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-29 15:03:39.872884", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Closing Entry Taxes", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py similarity index 84% rename from erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py rename to erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py index 6bc323f7ad..f72d9a61e1 100644 --- a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py +++ b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals from frappe.model.document import Document -class POSClosingVoucherDetails(Document): +class POSClosingEntryTaxes(Document): pass diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py b/erpnext/accounts/doctype/pos_invoice/__init__.py similarity index 100% rename from erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py rename to erpnext/accounts/doctype/pos_invoice/__init__.py diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js new file mode 100644 index 0000000000..3be43044aa --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -0,0 +1,205 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +{% include 'erpnext/selling/sales_common.js' %}; + +erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({ + setup(doc) { + this.setup_posting_date_time_check(); + this._super(doc); + }, + + onload() { + this._super(); + if(this.frm.doc.__islocal && this.frm.doc.is_pos) { + //Load pos profile data on the invoice if the default value of Is POS is 1 + + me.frm.script_manager.trigger("is_pos"); + me.frm.refresh_fields(); + } + }, + + refresh(doc) { + this._super(); + if (doc.docstatus == 1 && !doc.is_return) { + if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { + cur_frm.add_custom_button(__('Return'), + this.make_sales_return, __('Create')); + cur_frm.page.set_inner_btn_group_as_primary(__('Create')); + } + } + + if (this.frm.doc.is_return) { + this.frm.return_print_format = "Sales Invoice Return"; + cur_frm.set_value('consolidated_invoice', ''); + } + }, + + is_pos: function(frm){ + this.set_pos_data(); + }, + + set_pos_data: function() { + if(this.frm.doc.is_pos) { + this.frm.set_value("allocate_advances_automatically", 0); + if(!this.frm.doc.company) { + this.frm.set_value("is_pos", 0); + frappe.msgprint(__("Please specify Company to proceed")); + } else { + var me = this; + return this.frm.call({ + doc: me.frm.doc, + method: "set_missing_values", + callback: function(r) { + if(!r.exc) { + if(r.message) { + me.frm.pos_print_format = r.message.print_format || ""; + me.frm.meta.default_print_format = r.message.print_format || ""; + me.frm.allow_edit_rate = r.message.allow_edit_rate; + me.frm.allow_edit_discount = r.message.allow_edit_discount; + me.frm.doc.campaign = r.message.campaign; + me.frm.allow_print_before_pay = r.message.allow_print_before_pay; + } + me.frm.script_manager.trigger("update_stock"); + me.calculate_taxes_and_totals(); + if(me.frm.doc.taxes_and_charges) { + me.frm.script_manager.trigger("taxes_and_charges"); + } + frappe.model.set_default_values(me.frm.doc); + me.set_dynamic_labels(); + + } + } + }); + } + } + else this.frm.trigger("refresh"); + }, + + customer() { + if (!this.frm.doc.customer) return + + if (this.frm.doc.is_pos){ + var pos_profile = this.frm.doc.pos_profile; + } + var me = this; + if(this.frm.updating_party_details) return; + erpnext.utils.get_party_details(this.frm, + "erpnext.accounts.party.get_party_details", { + posting_date: this.frm.doc.posting_date, + party: this.frm.doc.customer, + party_type: "Customer", + account: this.frm.doc.debit_to, + price_list: this.frm.doc.selling_price_list, + pos_profile: pos_profile + }, function() { + me.apply_pricing_rule(); + }); + }, + + amount: function(){ + this.write_off_outstanding_amount_automatically() + }, + + change_amount: function(){ + if(this.frm.doc.paid_amount > this.frm.doc.grand_total){ + this.calculate_write_off_amount(); + }else { + this.frm.set_value("change_amount", 0.0); + this.frm.set_value("base_change_amount", 0.0); + } + + this.frm.refresh_fields(); + }, + + loyalty_amount: function(){ + this.calculate_outstanding_amount(); + this.frm.refresh_field("outstanding_amount"); + this.frm.refresh_field("paid_amount"); + this.frm.refresh_field("base_paid_amount"); + }, + + write_off_outstanding_amount_automatically: function() { + if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) { + frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]); + // this will make outstanding amount 0 + this.frm.set_value("write_off_amount", + flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount")) + ); + this.frm.toggle_enable("write_off_amount", false); + + } else { + this.frm.toggle_enable("write_off_amount", true); + } + + this.calculate_outstanding_amount(false); + this.frm.refresh_fields(); + }, + + make_sales_return: function() { + frappe.model.open_mapped_doc({ + method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return", + frm: cur_frm + }) + }, +}) + +$.extend(cur_frm.cscript, new erpnext.selling.POSInvoiceController({ frm: cur_frm })) + +frappe.ui.form.on('POS Invoice', { + redeem_loyalty_points: function(frm) { + frm.events.get_loyalty_details(frm); + }, + + loyalty_points: function(frm) { + if (frm.redemption_conversion_factor) { + frm.events.set_loyalty_points(frm); + } else { + frappe.call({ + method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor", + args: { + "loyalty_program": frm.doc.loyalty_program + }, + callback: function(r) { + if (r) { + frm.redemption_conversion_factor = r.message; + frm.events.set_loyalty_points(frm); + } + } + }); + } + }, + + get_loyalty_details: function(frm) { + if (frm.doc.customer && frm.doc.redeem_loyalty_points) { + frappe.call({ + method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details", + args: { + "customer": frm.doc.customer, + "loyalty_program": frm.doc.loyalty_program, + "expiry_date": frm.doc.posting_date, + "company": frm.doc.company + }, + callback: function(r) { + if (r) { + frm.set_value("loyalty_redemption_account", r.message.expense_account); + frm.set_value("loyalty_redemption_cost_center", r.message.cost_center); + frm.redemption_conversion_factor = r.message.conversion_factor; + } + } + }); + } + }, + + set_loyalty_points: function(frm) { + if (frm.redemption_conversion_factor) { + let loyalty_amount = flt(frm.redemption_conversion_factor*flt(frm.doc.loyalty_points), precision("loyalty_amount")); + var remaining_amount = flt(frm.doc.grand_total) - flt(frm.doc.total_advance) - flt(frm.doc.write_off_amount); + if (frm.doc.grand_total && (remaining_amount < loyalty_amount)) { + let redeemable_points = parseInt(remaining_amount/frm.redemption_conversion_factor); + frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_points])); + } + frm.set_value("loyalty_amount", loyalty_amount); + } + } +}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json new file mode 100644 index 0000000000..2a2e3df8ae --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -0,0 +1,1637 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2020-01-24 15:29:29.933693", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "customer_section", + "title", + "naming_series", + "customer", + "customer_name", + "tax_id", + "is_pos", + "pos_profile", + "offline_pos_name", + "is_return", + "consolidated_invoice", + "column_break1", + "company", + "posting_date", + "posting_time", + "set_posting_time", + "due_date", + "amended_from", + "returns", + "return_against", + "column_break_21", + "update_billed_amount_in_sales_order", + "accounting_dimensions_section", + "project", + "dimension_col_break", + "cost_center", + "customer_po_details", + "po_no", + "column_break_23", + "po_date", + "address_and_contact", + "customer_address", + "address_display", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "territory", + "col_break4", + "shipping_address_name", + "shipping_address", + "company_address", + "company_address_display", + "currency_and_price_list", + "currency", + "conversion_rate", + "column_break2", + "selling_price_list", + "price_list_currency", + "plc_conversion_rate", + "ignore_pricing_rule", + "sec_warehouse", + "set_warehouse", + "items_section", + "update_stock", + "scan_barcode", + "items", + "pricing_rule_details", + "pricing_rules", + "packing_list", + "packed_items", + "product_bundle_help", + "time_sheet_list", + "timesheets", + "total_billing_amount", + "section_break_30", + "total_qty", + "base_total", + "base_net_total", + "column_break_32", + "total", + "net_total", + "total_net_weight", + "taxes_section", + "taxes_and_charges", + "column_break_38", + "shipping_rule", + "tax_category", + "section_break_40", + "taxes", + "sec_tax_breakup", + "other_charges_calculation", + "section_break_43", + "base_total_taxes_and_charges", + "column_break_47", + "total_taxes_and_charges", + "loyalty_points_redemption", + "loyalty_points", + "loyalty_amount", + "redeem_loyalty_points", + "column_break_77", + "loyalty_program", + "loyalty_redemption_account", + "loyalty_redemption_cost_center", + "section_break_49", + "apply_discount_on", + "base_discount_amount", + "column_break_51", + "additional_discount_percentage", + "discount_amount", + "totals", + "base_grand_total", + "base_rounding_adjustment", + "base_rounded_total", + "base_in_words", + "column_break5", + "grand_total", + "rounding_adjustment", + "rounded_total", + "in_words", + "total_advance", + "outstanding_amount", + "advances_section", + "allocate_advances_automatically", + "get_advances", + "advances", + "payment_schedule_section", + "payment_terms_template", + "payment_schedule", + "payments_section", + "cash_bank_account", + "payments", + "section_break_84", + "base_paid_amount", + "column_break_86", + "paid_amount", + "section_break_88", + "base_change_amount", + "column_break_90", + "change_amount", + "account_for_change_amount", + "column_break4", + "write_off_amount", + "base_write_off_amount", + "write_off_outstanding_amount_automatically", + "column_break_74", + "write_off_account", + "write_off_cost_center", + "terms_section_break", + "tc_name", + "terms", + "edit_printing_settings", + "letter_head", + "group_same_items", + "language", + "column_break_84", + "select_print_heading", + "more_information", + "inter_company_invoice_reference", + "customer_group", + "campaign", + "is_discounted", + "col_break23", + "status", + "source", + "more_info", + "debit_to", + "party_account_currency", + "is_opening", + "c_form_applicable", + "c_form_no", + "column_break8", + "remarks", + "sales_team_section_break", + "sales_partner", + "column_break10", + "commission_rate", + "total_commission", + "section_break2", + "sales_team", + "subscription_section", + "from_date", + "to_date", + "column_break_140", + "auto_repeat", + "update_auto_repeat_reference", + "against_income_account", + "pos_total_qty" + ], + "fields": [ + { + "fieldname": "customer_section", + "fieldtype": "Section Break", + "options": "fa fa-user" + }, + { + "allow_on_submit": 1, + "default": "{customer_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, + { + "bold": 1, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "oldfieldname": "naming_series", + "oldfieldtype": "Select", + "options": "ACC-PSINV-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "customer", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "search_index": 1 + }, + { + "bold": 1, + "depends_on": "customer", + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Customer Name", + "oldfieldname": "customer_name", + "oldfieldtype": "Data", + "read_only": 1 + }, + { + "fieldname": "tax_id", + "fieldtype": "Data", + "label": "Tax Id", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "1", + "fieldname": "is_pos", + "fieldtype": "Check", + "label": "Include Payment (POS)", + "oldfieldname": "is_pos", + "oldfieldtype": "Check", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "depends_on": "is_pos", + "fieldname": "pos_profile", + "fieldtype": "Link", + "label": "POS Profile", + "options": "POS Profile", + "print_hide": 1 + }, + { + "fieldname": "offline_pos_name", + "fieldtype": "Data", + "hidden": 1, + "label": "Offline POS Name", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "is_return", + "fieldtype": "Check", + "label": "Is Return (Credit Note)", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "bold": 1, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Date", + "no_copy": 1, + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "no_copy": 1, + "oldfieldname": "posting_time", + "oldfieldtype": "Time", + "print_hide": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.docstatus==0", + "fieldname": "set_posting_time", + "fieldtype": "Check", + "label": "Edit Posting Date and Time", + "print_hide": 1 + }, + { + "fieldname": "due_date", + "fieldtype": "Date", + "label": "Payment Due Date", + "no_copy": 1, + "oldfieldname": "due_date", + "oldfieldtype": "Date" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Link", + "options": "POS Invoice", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "return_against", + "fieldname": "returns", + "fieldtype": "Section Break", + "label": "Returns" + }, + { + "depends_on": "return_against", + "fieldname": "return_against", + "fieldtype": "Link", + "label": "Return Against POS Invoice", + "no_copy": 1, + "options": "POS Invoice", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval: doc.is_return && doc.return_against", + "fieldname": "update_billed_amount_in_sales_order", + "fieldtype": "Check", + "label": "Update Billed Amount in Sales Order" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Project", + "oldfieldname": "project_name", + "oldfieldtype": "Link", + "options": "Project", + "print_hide": 1 + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "collapsible": 1, + "collapsible_depends_on": "po_no", + "fieldname": "customer_po_details", + "fieldtype": "Section Break", + "label": "Customer PO Details" + }, + { + "allow_on_submit": 1, + "fieldname": "po_no", + "fieldtype": "Data", + "label": "Customer's Purchase Order", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "column_break_23", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "po_date", + "fieldtype": "Date", + "label": "Customer's Purchase Order Date" + }, + { + "collapsible": 1, + "fieldname": "address_and_contact", + "fieldtype": "Section Break", + "label": "Address and Contact" + }, + { + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Address", + "read_only": 1 + }, + { + "fieldname": "contact_person", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, + { + "fieldname": "contact_display", + "fieldtype": "Small Text", + "label": "Contact", + "read_only": 1 + }, + { + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Mobile No", + "read_only": 1 + }, + { + "fieldname": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", + "print_hide": 1 + }, + { + "fieldname": "col_break4", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address_name", + "fieldtype": "Link", + "label": "Shipping Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "shipping_address", + "fieldtype": "Small Text", + "label": "Shipping Address", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "company_address", + "fieldtype": "Link", + "label": "Company Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "company_address_display", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Company Address", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "customer", + "fieldname": "currency_and_price_list", + "fieldtype": "Section Break", + "label": "Currency and Price List" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "oldfieldname": "currency", + "oldfieldtype": "Select", + "options": "Currency", + "print_hide": 1, + "reqd": 1 + }, + { + "description": "Rate at which Customer Currency is converted to customer's base currency", + "fieldname": "conversion_rate", + "fieldtype": "Float", + "label": "Exchange Rate", + "oldfieldname": "conversion_rate", + "oldfieldtype": "Currency", + "precision": "9", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "column_break2", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "selling_price_list", + "fieldtype": "Link", + "label": "Price List", + "oldfieldname": "price_list_name", + "oldfieldtype": "Select", + "options": "Price List", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "price_list_currency", + "fieldtype": "Link", + "label": "Price List Currency", + "options": "Currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "description": "Rate at which Price list currency is converted to customer's base currency", + "fieldname": "plc_conversion_rate", + "fieldtype": "Float", + "label": "Price List Exchange Rate", + "precision": "9", + "print_hide": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, + { + "fieldname": "sec_warehouse", + "fieldtype": "Section Break" + }, + { + "depends_on": "update_stock", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Set Source Warehouse", + "options": "Warehouse", + "print_hide": 1 + }, + { + "fieldname": "items_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, + { + "default": "0", + "fieldname": "update_stock", + "fieldtype": "Check", + "label": "Update Stock", + "oldfieldname": "update_stock", + "oldfieldtype": "Check", + "print_hide": 1 + }, + { + "fieldname": "scan_barcode", + "fieldtype": "Data", + "label": "Scan Barcode" + }, + { + "allow_bulk_edit": 1, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "entries", + "oldfieldtype": "Table", + "options": "POS Invoice Item", + "reqd": 1 + }, + { + "fieldname": "pricing_rule_details", + "fieldtype": "Section Break", + "label": "Pricing Rules" + }, + { + "fieldname": "pricing_rules", + "fieldtype": "Table", + "label": "Pricing Rule Detail", + "options": "Pricing Rule Detail", + "read_only": 1 + }, + { + "fieldname": "packing_list", + "fieldtype": "Section Break", + "label": "Packing List", + "options": "fa fa-suitcase", + "print_hide": 1 + }, + { + "fieldname": "packed_items", + "fieldtype": "Table", + "label": "Packed Items", + "options": "Packed Item", + "print_hide": 1 + }, + { + "fieldname": "product_bundle_help", + "fieldtype": "HTML", + "label": "Product Bundle Help", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.total_billing_amount > 0", + "fieldname": "time_sheet_list", + "fieldtype": "Section Break", + "label": "Time Sheet List" + }, + { + "fieldname": "timesheets", + "fieldtype": "Table", + "label": "Time Sheets", + "options": "Sales Invoice Timesheet", + "print_hide": 1 + }, + { + "default": "0", + "fieldname": "total_billing_amount", + "fieldtype": "Currency", + "label": "Total Billing Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_30", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "base_total", + "fieldtype": "Currency", + "label": "Total (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_net_total", + "fieldtype": "Currency", + "label": "Net Total (Company Currency)", + "oldfieldname": "net_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_32", + "fieldtype": "Column Break" + }, + { + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "total_net_weight", + "fieldtype": "Float", + "label": "Total Net Weight", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "taxes_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-money" + }, + { + "fieldname": "taxes_and_charges", + "fieldtype": "Link", + "label": "Sales Taxes and Charges Template", + "oldfieldname": "charge", + "oldfieldtype": "Link", + "options": "Sales Taxes and Charges Template", + "print_hide": 1 + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Shipping Rule", + "oldfieldtype": "Button", + "options": "Shipping Rule", + "print_hide": 1 + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category", + "print_hide": 1 + }, + { + "fieldname": "section_break_40", + "fieldtype": "Section Break" + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Sales Taxes and Charges", + "oldfieldname": "other_charges", + "oldfieldtype": "Table", + "options": "Sales Taxes and Charges" + }, + { + "collapsible": 1, + "fieldname": "sec_tax_breakup", + "fieldtype": "Section Break", + "label": "Tax Breakup" + }, + { + "fieldname": "other_charges_calculation", + "fieldtype": "Long Text", + "label": "Taxes and Charges Calculation", + "no_copy": 1, + "oldfieldtype": "HTML", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_43", + "fieldtype": "Section Break" + }, + { + "fieldname": "base_total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges (Company Currency)", + "oldfieldname": "other_charges_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_47", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "loyalty_points_redemption", + "fieldtype": "Section Break", + "label": "Loyalty Points Redemption" + }, + { + "depends_on": "redeem_loyalty_points", + "fieldname": "loyalty_points", + "fieldtype": "Int", + "label": "Loyalty Points", + "no_copy": 1, + "print_hide": 1 + }, + { + "depends_on": "redeem_loyalty_points", + "fieldname": "loyalty_amount", + "fieldtype": "Currency", + "label": "Loyalty Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "redeem_loyalty_points", + "fieldtype": "Check", + "label": "Redeem Loyalty Points", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "column_break_77", + "fieldtype": "Column Break" + }, + { + "fetch_from": "customer.loyalty_program", + "fieldname": "loyalty_program", + "fieldtype": "Link", + "label": "Loyalty Program", + "no_copy": 1, + "options": "Loyalty Program", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "redeem_loyalty_points", + "fieldname": "loyalty_redemption_account", + "fieldtype": "Link", + "label": "Redemption Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "redeem_loyalty_points", + "fieldname": "loyalty_redemption_cost_center", + "fieldtype": "Link", + "label": "Redemption Cost Center", + "no_copy": 1, + "options": "Cost Center" + }, + { + "collapsible": 1, + "collapsible_depends_on": "discount_amount", + "fieldname": "section_break_49", + "fieldtype": "Section Break", + "label": "Additional Discount" + }, + { + "default": "Grand Total", + "fieldname": "apply_discount_on", + "fieldtype": "Select", + "label": "Apply Additional Discount On", + "options": "\nGrand Total\nNet Total", + "print_hide": 1 + }, + { + "fieldname": "base_discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_51", + "fieldtype": "Column Break" + }, + { + "fieldname": "additional_discount_percentage", + "fieldtype": "Float", + "label": "Additional Discount Percentage", + "print_hide": 1 + }, + { + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount", + "options": "currency", + "print_hide": 1 + }, + { + "fieldname": "totals", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-money", + "print_hide": 1 + }, + { + "fieldname": "base_grand_total", + "fieldtype": "Currency", + "label": "Grand Total (Company Currency)", + "oldfieldname": "grand_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "base_rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total (Company Currency)", + "oldfieldname": "rounded_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "description": "In Words will be visible once you save the Sales Invoice.", + "fieldname": "base_in_words", + "fieldtype": "Data", + "label": "In Words (Company Currency)", + "oldfieldname": "in_words", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break5", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "bold": 1, + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Grand Total", + "oldfieldname": "grand_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment", + "no_copy": 1, + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "bold": 1, + "fieldname": "rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total", + "oldfieldname": "rounded_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "in_words", + "fieldtype": "Data", + "label": "In Words", + "oldfieldname": "in_words_export", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "total_advance", + "fieldtype": "Currency", + "label": "Total Advance", + "oldfieldname": "total_advance", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "label": "Outstanding Amount", + "no_copy": 1, + "oldfieldname": "outstanding_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "advances", + "fieldname": "advances_section", + "fieldtype": "Section Break", + "label": "Advance Payments", + "oldfieldtype": "Section Break", + "options": "fa fa-money", + "print_hide": 1 + }, + { + "default": "0", + "fieldname": "allocate_advances_automatically", + "fieldtype": "Check", + "label": "Allocate Advances Automatically (FIFO)" + }, + { + "depends_on": "eval:!doc.allocate_advances_automatically", + "fieldname": "get_advances", + "fieldtype": "Button", + "label": "Get Advances Received", + "options": "set_advances" + }, + { + "fieldname": "advances", + "fieldtype": "Table", + "label": "Advances", + "oldfieldname": "advance_adjustment_details", + "oldfieldtype": "Table", + "options": "Sales Invoice Advance", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)", + "fieldname": "payment_schedule_section", + "fieldtype": "Section Break", + "label": "Payment Terms" + }, + { + "depends_on": "eval:(!doc.is_pos && !doc.is_return)", + "fieldname": "payment_terms_template", + "fieldtype": "Link", + "label": "Payment Terms Template", + "no_copy": 1, + "options": "Payment Terms Template", + "print_hide": 1 + }, + { + "depends_on": "eval:(!doc.is_pos && !doc.is_return)", + "fieldname": "payment_schedule", + "fieldtype": "Table", + "label": "Payment Schedule", + "no_copy": 1, + "options": "Payment Schedule", + "print_hide": 1 + }, + { + "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)", + "fieldname": "payments_section", + "fieldtype": "Section Break", + "label": "Payments", + "options": "fa fa-money" + }, + { + "depends_on": "is_pos", + "fieldname": "cash_bank_account", + "fieldtype": "Link", + "hidden": 1, + "label": "Cash/Bank Account", + "oldfieldname": "cash_bank_account", + "oldfieldtype": "Link", + "options": "Account", + "print_hide": 1 + }, + { + "depends_on": "eval:doc.is_pos===1", + "fieldname": "payments", + "fieldtype": "Table", + "label": "Sales Invoice Payment", + "options": "Sales Invoice Payment", + "print_hide": 1 + }, + { + "fieldname": "section_break_84", + "fieldtype": "Section Break" + }, + { + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_86", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.is_pos || doc.redeem_loyalty_points", + "fieldname": "paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount", + "no_copy": 1, + "oldfieldname": "paid_amount", + "oldfieldtype": "Currency", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_88", + "fieldtype": "Section Break" + }, + { + "depends_on": "is_pos", + "fieldname": "base_change_amount", + "fieldtype": "Currency", + "label": "Base Change Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_90", + "fieldtype": "Column Break" + }, + { + "depends_on": "is_pos", + "fieldname": "change_amount", + "fieldtype": "Currency", + "label": "Change Amount", + "no_copy": 1, + "options": "currency", + "print_hide": 1 + }, + { + "depends_on": "is_pos", + "fieldname": "account_for_change_amount", + "fieldtype": "Link", + "label": "Account for Change Amount", + "options": "Account", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "write_off_amount", + "depends_on": "grand_total", + "fieldname": "column_break4", + "fieldtype": "Section Break", + "label": "Write Off", + "width": "50%" + }, + { + "fieldname": "write_off_amount", + "fieldtype": "Currency", + "label": "Write Off Amount", + "no_copy": 1, + "options": "currency", + "print_hide": 1 + }, + { + "fieldname": "base_write_off_amount", + "fieldtype": "Currency", + "label": "Write Off Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "0", + "depends_on": "is_pos", + "fieldname": "write_off_outstanding_amount_automatically", + "fieldtype": "Check", + "label": "Write Off Outstanding Amount", + "print_hide": 1 + }, + { + "fieldname": "column_break_74", + "fieldtype": "Column Break" + }, + { + "fieldname": "write_off_account", + "fieldtype": "Link", + "label": "Write Off Account", + "options": "Account", + "print_hide": 1 + }, + { + "fieldname": "write_off_cost_center", + "fieldtype": "Link", + "label": "Write Off Cost Center", + "options": "Cost Center", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "terms", + "fieldname": "terms_section_break", + "fieldtype": "Section Break", + "label": "Terms and Conditions", + "oldfieldtype": "Section Break" + }, + { + "fieldname": "tc_name", + "fieldtype": "Link", + "label": "Terms", + "oldfieldname": "tc_name", + "oldfieldtype": "Link", + "options": "Terms and Conditions", + "print_hide": 1 + }, + { + "fieldname": "terms", + "fieldtype": "Text Editor", + "label": "Terms and Conditions Details", + "oldfieldname": "terms", + "oldfieldtype": "Text Editor" + }, + { + "collapsible": 1, + "fieldname": "edit_printing_settings", + "fieldtype": "Section Break", + "label": "Printing Settings" + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "oldfieldname": "letter_head", + "oldfieldtype": "Select", + "options": "Letter Head", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "group_same_items", + "fieldtype": "Check", + "label": "Group same items", + "print_hide": 1 + }, + { + "fieldname": "language", + "fieldtype": "Data", + "label": "Print Language", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_84", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "oldfieldname": "select_print_heading", + "oldfieldtype": "Link", + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "collapsible": 1, + "depends_on": "customer", + "fieldname": "more_information", + "fieldtype": "Section Break", + "label": "More Information" + }, + { + "fieldname": "inter_company_invoice_reference", + "fieldtype": "Link", + "label": "Inter Company Invoice Reference", + "options": "Purchase Invoice", + "read_only": 1 + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "hidden": 1, + "label": "Customer Group", + "options": "Customer Group", + "print_hide": 1 + }, + { + "fieldname": "campaign", + "fieldtype": "Link", + "label": "Campaign", + "oldfieldname": "campaign", + "oldfieldtype": "Link", + "options": "Campaign", + "print_hide": 1 + }, + { + "default": "0", + "fieldname": "is_discounted", + "fieldtype": "Check", + "label": "Is Discounted", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "col_break23", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "\nDraft\nReturn\nCredit Note Issued\nConsolidated\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "source", + "fieldtype": "Link", + "label": "Source", + "oldfieldname": "source", + "oldfieldtype": "Select", + "options": "Lead Source", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "Accounting Details", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text", + "print_hide": 1 + }, + { + "fieldname": "debit_to", + "fieldtype": "Link", + "label": "Debit To", + "oldfieldname": "debit_to", + "oldfieldtype": "Link", + "options": "Account", + "print_hide": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "party_account_currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Party Account Currency", + "no_copy": 1, + "options": "Currency", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "No", + "fieldname": "is_opening", + "fieldtype": "Select", + "label": "Is Opening Entry", + "oldfieldname": "is_opening", + "oldfieldtype": "Select", + "options": "No\nYes", + "print_hide": 1 + }, + { + "fieldname": "c_form_applicable", + "fieldtype": "Select", + "label": "C-Form Applicable", + "no_copy": 1, + "options": "No\nYes", + "print_hide": 1 + }, + { + "fieldname": "c_form_no", + "fieldtype": "Link", + "label": "C-Form No", + "no_copy": 1, + "options": "C-Form", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break8", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1 + }, + { + "fieldname": "remarks", + "fieldtype": "Small Text", + "label": "Remarks", + "no_copy": 1, + "oldfieldname": "remarks", + "oldfieldtype": "Text", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "sales_partner", + "fieldname": "sales_team_section_break", + "fieldtype": "Section Break", + "label": "Commission", + "oldfieldtype": "Section Break", + "options": "fa fa-group", + "print_hide": 1 + }, + { + "fieldname": "sales_partner", + "fieldtype": "Link", + "label": "Sales Partner", + "oldfieldname": "sales_partner", + "oldfieldtype": "Link", + "options": "Sales Partner", + "print_hide": 1 + }, + { + "fieldname": "column_break10", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "fieldname": "commission_rate", + "fieldtype": "Float", + "label": "Commission Rate (%)", + "oldfieldname": "commission_rate", + "oldfieldtype": "Currency", + "print_hide": 1 + }, + { + "fieldname": "total_commission", + "fieldtype": "Currency", + "label": "Total Commission", + "oldfieldname": "total_commission", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "sales_team", + "fieldname": "section_break2", + "fieldtype": "Section Break", + "label": "Sales Team", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "sales_team", + "fieldtype": "Table", + "label": "Sales Team1", + "oldfieldname": "sales_team", + "oldfieldtype": "Table", + "options": "Sales Team", + "print_hide": 1 + }, + { + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "label": "Subscription Section" + }, + { + "allow_on_submit": 1, + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date", + "no_copy": 1, + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "column_break_140", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "auto_repeat", + "fieldtype": "Link", + "label": "Auto Repeat", + "no_copy": 1, + "options": "Auto Repeat", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval: doc.auto_repeat", + "fieldname": "update_auto_repeat_reference", + "fieldtype": "Button", + "label": "Update Auto Repeat Reference" + }, + { + "fieldname": "against_income_account", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Against Income Account", + "no_copy": 1, + "oldfieldname": "against_income_account", + "oldfieldtype": "Small Text", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "pos_total_qty", + "fieldtype": "Float", + "hidden": 1, + "label": "Total Qty", + "print_hide": 1, + "print_hide_if_no_value": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "consolidated_invoice", + "fieldtype": "Link", + "label": "Consolidated Sales Invoice", + "options": "Sales Invoice", + "read_only": 1 + } + ], + "icon": "fa fa-file-text", + "is_submittable": 1, + "links": [], + "modified": "2020-05-29 15:08:39.337385", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Invoice", + "name_case": "Title Case", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "Accounts Manager", + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "All" + } + ], + "quick_entry": 1, + "search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "customer", + "title_field": "title", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py new file mode 100644 index 0000000000..8680b710ac --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -0,0 +1,374 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +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 erpnext.accounts.doctype.loyalty_program.loyalty_program import \ + get_loyalty_program_details_with_points, validate_loyalty_points + +from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option +from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos + +from six import iteritems + +class POSInvoice(SalesInvoice): + def __init__(self, *args, **kwargs): + super(POSInvoice, self).__init__(*args, **kwargs) + + def validate(self): + if not cint(self.is_pos): + frappe.throw(_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment"))) + + # run on validate method of selling controller + super(SalesInvoice, self).validate() + self.validate_auto_set_posting_time() + self.validate_pos_paid_amount() + self.validate_pos_return() + self.validate_uom_is_integer("stock_uom", "stock_qty") + self.validate_uom_is_integer("uom", "qty") + self.validate_debit_to_acc() + self.validate_write_off_account() + self.validate_change_amount() + self.validate_change_account() + self.validate_item_cost_centers() + self.validate_serialised_or_batched_item() + self.validate_stock_availablility() + self.validate_return_items() + self.set_status() + self.set_account_for_mode_of_payment() + self.validate_pos() + self.verify_payment_amount() + self.validate_loyalty_transaction() + + def on_submit(self): + # create the loyalty point ledger entry if the customer is enrolled in any loyalty program + if self.loyalty_program: + self.make_loyalty_point_entry() + elif self.is_return and self.return_against and self.loyalty_program: + against_psi_doc = frappe.get_doc("POS Invoice", self.return_against) + against_psi_doc.delete_loyalty_point_entry() + against_psi_doc.make_loyalty_point_entry() + if self.redeem_loyalty_points and self.loyalty_points: + self.apply_loyalty_points() + self.set_status(update=True) + + def on_cancel(self): + # run on cancel method of selling controller + super(SalesInvoice, self).on_cancel() + if self.loyalty_program: + self.delete_loyalty_point_entry() + elif self.is_return and self.return_against and self.loyalty_program: + against_psi_doc = frappe.get_doc("POS Invoice", self.return_against) + against_psi_doc.delete_loyalty_point_entry() + against_psi_doc.make_loyalty_point_entry() + + def validate_stock_availablility(self): + allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') + + for d in self.get('items'): + if d.serial_no: + filters = { + "item_code": d.item_code, + "warehouse": d.warehouse, + "delivery_document_no": "", + "sales_invoice": "" + } + if d.batch_no: + filters["batch_no"] = d.batch_no + reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters) + serial_nos = d.serial_no.split("\n") + serial_nos = ' '.join(serial_nos).split() # remove whitespaces + invalid_serial_nos = [] + for s in serial_nos: + if s in reserved_serial_nos: + invalid_serial_nos.append(s) + + if len(invalid_serial_nos): + multiple_nos = 's' if len(invalid_serial_nos) > 1 else '' + frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \ + Please select valid serial no.".format(d.idx, multiple_nos, + frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available")) + else: + if allow_negative_stock: + return + + available_stock = get_stock_availability(d.item_code, d.warehouse) + if not (flt(available_stock) > 0): + frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.' + .format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available")) + elif flt(available_stock) < flt(d.qty): + frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \ + Available quantity {}.'.format(d.idx, frappe.bold(d.item_code), + frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available")) + + def validate_serialised_or_batched_item(self): + for d in self.get("items"): + serialized = d.get("has_serial_no") + batched = d.get("has_batch_no") + no_serial_selected = not d.get("serial_no") + no_batch_selected = not d.get("batch_no") + + + if serialized and batched and (no_batch_selected or no_serial_selected): + frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.' + .format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) + if serialized and no_serial_selected: + frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.' + .format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) + if batched and no_batch_selected: + frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.' + .format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) + + def validate_return_items(self): + if not self.get("is_return"): return + + for d in self.get("items"): + if d.get("qty") > 0: + frappe.throw(_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.") + .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) + + def validate_pos_paid_amount(self): + if len(self.payments) == 0 and self.is_pos: + frappe.throw(_("At least one mode of payment is required for POS invoice.")) + + def validate_change_account(self): + if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company: + frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company)) + + def validate_change_amount(self): + grand_total = flt(self.rounded_total) or flt(self.grand_total) + base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total) + if not flt(self.change_amount) and grand_total < flt(self.paid_amount): + self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount)) + self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount)) + + if flt(self.change_amount) and not self.account_for_change_amount: + msgprint(_("Please enter Account for Change Amount"), raise_exception=1) + + def verify_payment_amount(self): + for entry in self.payments: + if not self.is_return and entry.amount < 0: + frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx)) + if self.is_return and entry.amount > 0: + frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx)) + + def validate_pos_return(self): + if self.is_pos and self.is_return: + total_amount_in_payments = 0 + for payment in self.payments: + total_amount_in_payments += payment.amount + invoice_total = self.rounded_total or self.grand_total + if total_amount_in_payments < invoice_total: + frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total))) + + def validate_loyalty_transaction(self): + if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center): + expense_account, cost_center = frappe.db.get_value('Loyalty Program', self.loyalty_program, ["expense_account", "cost_center"]) + if not self.loyalty_redemption_account: + self.loyalty_redemption_account = expense_account + if not self.loyalty_redemption_cost_center: + self.loyalty_redemption_cost_center = cost_center + + if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: + validate_loyalty_points(self, self.loyalty_points) + + def set_status(self, update=False, status=None, update_modified=True): + if self.is_new(): + if self.get('amended_from'): + self.status = 'Draft' + return + + if not status: + if self.docstatus == 2: + status = "Cancelled" + elif self.docstatus == 1: + if self.consolidated_invoice: + self.status = "Consolidated" + elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed': + self.status = "Overdue and Discounted" + elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()): + self.status = "Overdue" + elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed': + self.status = "Unpaid and Discounted" + elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()): + self.status = "Unpaid" + elif flt(self.outstanding_amount) <= 0 and self.is_return == 0 and frappe.db.get_value('POS Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): + self.status = "Credit Note Issued" + elif self.is_return == 1: + self.status = "Return" + elif flt(self.outstanding_amount)<=0: + self.status = "Paid" + else: + self.status = "Submitted" + else: + self.status = "Draft" + + if update: + self.db_set('status', self.status, update_modified = update_modified) + + def set_pos_fields(self, for_validate=False): + """Set retail related fields from POS Profiles""" + from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile + if not self.pos_profile: + pos_profile = get_pos_profile(self.company) or {} + self.pos_profile = pos_profile.get('name') + + pos = {} + if self.pos_profile: + pos = frappe.get_doc('POS Profile', self.pos_profile) + + if not self.get('payments') and not for_validate: + update_multi_mode_option(self, pos) + + if not self.account_for_change_amount: + self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') + + if pos: + if not for_validate: + self.tax_category = pos.get("tax_category") + + if not for_validate and not self.customer: + self.customer = pos.customer + + 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') + if pos.get('warehouse'): + self.set_warehouse = pos.get('warehouse') + + for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name', + 'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges', + 'write_off_cost_center', 'apply_discount_on', 'cost_center'): + if (not for_validate) or (for_validate and not self.get(fieldname)): + self.set(fieldname, pos.get(fieldname)) + + if pos.get("company_address"): + self.company_address = pos.get("company_address") + + if self.customer: + customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group']) + 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 pos.get('selling_price_list') + else: + selling_price_list = pos.get('selling_price_list') + + if selling_price_list: + self.set('selling_price_list', selling_price_list) + + if not for_validate: + self.update_stock = cint(pos.get("update_stock")) + + # set pos values in items + for item in self.get("items"): + if item.get('item_code'): + profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos) + for fname, val in iteritems(profile_details): + if (not for_validate) or (for_validate and not item.get(fname)): + item.set(fname, val) + + # fetch terms + if self.tc_name and not self.terms: + self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms") + + # fetch charges + if self.taxes_and_charges and not len(self.get("taxes")): + self.set_taxes() + + return pos + + def set_missing_values(self, for_validate=False): + pos = self.set_pos_fields(for_validate) + + if not self.debit_to: + self.debit_to = get_party_account("Customer", self.customer, self.company) + self.party_account_currency = frappe.db.get_value("Account", self.debit_to, "account_currency", cache=True) + if not self.due_date and self.customer: + self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company) + + super(SalesInvoice, self).set_missing_values(for_validate) + + print_format = pos.get("print_format") if pos else None + if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')): + print_format = 'POS Invoice' + + if pos: + return { + "print_format": print_format, + "allow_edit_rate": pos.get("allow_user_to_edit_rate"), + "allow_edit_discount": pos.get("allow_user_to_edit_discount"), + "campaign": pos.get("campaign"), + "allow_print_before_pay": pos.get("allow_print_before_pay") + } + + def set_account_for_mode_of_payment(self): + self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default] + for pay in self.payments: + if not pay.account: + pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account") + +@frappe.whitelist() +def get_stock_availability(item_code, warehouse): + latest_sle = frappe.db.sql("""select qty_after_transaction + from `tabStock Ledger Entry` + where item_code = %s and warehouse = %s + order by posting_date desc, posting_time desc + limit 1""", (item_code, warehouse), as_dict=1) + + pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty + from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item + where p.name = p_item.parent + and p.consolidated_invoice is NULL + and p.docstatus = 1 + and p_item.docstatus = 1 + and p_item.item_code = %s + and p_item.warehouse = %s + """, (item_code, warehouse), as_dict=1) + + sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 + pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0 + + if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty: + return sle_qty - pos_sales_qty + else: + # when sle_qty is 0 + # when sle_qty > 0 and pos_sales_qty is 0 + return sle_qty + +@frappe.whitelist() +def make_sales_return(source_name, target_doc=None): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return make_return_doc("POS Invoice", source_name, target_doc) + +@frappe.whitelist() +def make_merge_log(invoices): + import json + from six import string_types + + if isinstance(invoices, string_types): + invoices = json.loads(invoices) + + if len(invoices) == 0: + frappe.throw(_('Atleast one invoice has to be selected.')) + + merge_log = frappe.new_doc("POS Invoice Merge Log") + merge_log.posting_date = getdate(nowdate()) + for inv in invoices: + inv_data = frappe.db.get_values("POS Invoice", inv.get('name'), + ["customer", "posting_date", "grand_total"], as_dict=1)[0] + merge_log.customer = inv_data.customer + merge_log.append("pos_invoices", { + 'pos_invoice': inv.get('name'), + 'customer': inv_data.customer, + 'posting_date': inv_data.posting_date, + 'grand_total': inv_data.grand_total + }) + + if merge_log.get('pos_invoices'): + return merge_log.as_dict() \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js new file mode 100644 index 0000000000..2dbf2a4fcd --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js @@ -0,0 +1,42 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +// render +frappe.listview_settings['POS Invoice'] = { + add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company", + "currency", "is_return"], + get_indicator: function(doc) { + var status_color = { + "Draft": "red", + "Unpaid": "orange", + "Paid": "green", + "Submitted": "blue", + "Consolidated": "green", + "Return": "darkgrey", + "Unpaid and Discounted": "orange", + "Overdue and Discounted": "red", + "Overdue": "red" + + }; + return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; + }, + right_column: "grand_total", + onload: function(me) { + me.page.add_action_item('Make Merge Log', function() { + const invoices = me.get_checked_items(); + frappe.call({ + method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_merge_log", + freeze: true, + args:{ + "invoices": invoices + }, + callback: function (r) { + if (r.message) { + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + } + }); + }); + }, +}; diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py new file mode 100644 index 0000000000..f29572542c --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest, copy, time +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile +from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return + +class TestPOSInvoice(unittest.TestCase): + def test_timestamp_change(self): + w = create_pos_invoice(do_not_save=1) + w.docstatus = 0 + w.insert() + + w2 = frappe.get_doc(w.doctype, w.name) + + import time + time.sleep(1) + w.save() + + import time + time.sleep(1) + self.assertRaises(frappe.TimestampMismatchError, w2.save) + + def test_change_naming_series(self): + inv = create_pos_invoice(do_not_submit=1) + inv.naming_series = 'TEST-' + + self.assertRaises(frappe.CannotChangeConstantError, inv.save) + + def test_discount_and_inclusive_tax(self): + inv = create_pos_invoice(qty=100, rate=50, do_not_save=1) + inv.append("taxes", { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 14, + 'included_in_print_rate': 1 + }) + inv.insert() + + self.assertEqual(inv.net_total, 4385.96) + self.assertEqual(inv.grand_total, 5000) + + inv.reload() + + inv.discount_amount = 100 + inv.apply_discount_on = 'Net Total' + inv.payment_schedule = [] + + inv.save() + + self.assertEqual(inv.net_total, 4285.96) + self.assertEqual(inv.grand_total, 4885.99) + + inv.reload() + + inv.discount_amount = 100 + inv.apply_discount_on = 'Grand Total' + inv.payment_schedule = [] + + inv.save() + + self.assertEqual(inv.net_total, 4298.25) + self.assertEqual(inv.grand_total, 4900.00) + + def test_tax_calculation_with_multiple_items(self): + inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True) + item_row = inv.get("items")[0] + for qty in (54, 288, 144, 430): + item_row_copy = copy.deepcopy(item_row) + item_row_copy.qty = qty + inv.append("items", item_row_copy) + + inv.append("taxes", { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "rate": 19 + }) + inv.insert() + + self.assertEqual(inv.net_total, 4600) + + self.assertEqual(inv.get("taxes")[0].tax_amount, 874.0) + self.assertEqual(inv.get("taxes")[0].total, 5474.0) + + self.assertEqual(inv.grand_total, 5474.0) + + def test_tax_calculation_with_item_tax_template(self): + inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1) + item_row = inv.get("items")[0] + + add_items = [ + (54, '_Test Account Excise Duty @ 12'), + (288, '_Test Account Excise Duty @ 15'), + (144, '_Test Account Excise Duty @ 20'), + (430, '_Test Item Tax Template 1') + ] + for qty, item_tax_template in add_items: + item_row_copy = copy.deepcopy(item_row) + item_row_copy.qty = qty + item_row_copy.item_tax_template = item_tax_template + inv.append("items", item_row_copy) + + inv.append("taxes", { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "rate": 11 + }) + inv.append("taxes", { + "account_head": "_Test Account Education Cess - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "rate": 0 + }) + inv.append("taxes", { + "account_head": "_Test Account S&H Education Cess - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "S&H Education Cess", + "doctype": "Sales Taxes and Charges", + "rate": 3 + }) + inv.insert() + + self.assertEqual(inv.net_total, 4600) + + self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41) + self.assertEqual(inv.get("taxes")[0].total, 5102.41) + + self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80) + self.assertEqual(inv.get("taxes")[1].total, 5300.21) + + self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36) + self.assertEqual(inv.get("taxes")[2].total, 5675.57) + + self.assertEqual(inv.grand_total, 5675.57) + self.assertEqual(inv.rounding_adjustment, 0.43) + self.assertEqual(inv.rounded_total, 5676.0) + + def test_tax_calculation_with_multiple_items_and_discount(self): + inv = create_pos_invoice(qty=1, rate=75, do_not_save=True) + item_row = inv.get("items")[0] + for rate in (500, 200, 100, 50, 50): + item_row_copy = copy.deepcopy(item_row) + item_row_copy.price_list_rate = rate + item_row_copy.rate = rate + inv.append("items", item_row_copy) + + inv.apply_discount_on = "Net Total" + inv.discount_amount = 75.0 + + inv.append("taxes", { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "rate": 24 + }) + inv.insert() + + self.assertEqual(inv.total, 975) + self.assertEqual(inv.net_total, 900) + + self.assertEqual(inv.get("taxes")[0].tax_amount, 216.0) + self.assertEqual(inv.get("taxes")[0].total, 1116.0) + + self.assertEqual(inv.grand_total, 1116.0) + + def test_pos_returns_with_repayment(self): + pos = create_pos_invoice(qty = 10, do_not_save=True) + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500}) + pos.insert() + pos.submit() + + pos_return = make_sales_return(pos.name) + + pos_return.insert() + pos_return.submit() + + self.assertEqual(pos_return.get('payments')[0].amount, -500) + self.assertEqual(pos_return.get('payments')[1].amount, -500) + + def test_pos_change_amount(self): + pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC", + income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105, + cost_center = "Main - _TC", do_not_save=True) + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60}) + + pos.insert() + pos.submit() + + self.assertEqual(pos.grand_total, 105.0) + self.assertEqual(pos.change_amount, 5.0) + + def test_without_payment(self): + inv = create_pos_invoice(do_not_save=1) + # Check that the invoice cannot be submitted without payments + inv.payments = [] + self.assertRaises(frappe.ValidationError, inv.insert) + + def test_serialized_item_transaction(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + pos = create_pos_invoice(item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + pos.get("items")[0].serial_no = serial_nos[0] + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) + + pos.insert() + pos.submit() + + pos2 = create_pos_invoice(item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + pos2.get("items")[0].serial_no = serial_nos[0] + pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) + + self.assertRaises(frappe.ValidationError, pos2.insert) + + def test_loyalty_points(self): + from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records + from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points + + create_records() + frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty") + before_lp_details = get_loyalty_program_details_with_points("Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty") + + inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000) + + lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'POS Invoice', 'invoice': inv.name, 'customer': inv.customer}) + after_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) + + self.assertEqual(inv.get('loyalty_program'), "Test Single Loyalty") + self.assertEqual(lpe.loyalty_points, 10) + self.assertEqual(after_lp_details.loyalty_points, before_lp_details.loyalty_points + 10) + + inv.cancel() + after_cancel_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) + self.assertEqual(after_cancel_lp_details.loyalty_points, before_lp_details.loyalty_points) + + def test_loyalty_points_redeemption(self): + from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points + # add 10 loyalty points + create_pos_invoice(customer="Test Loyalty Customer", rate=10000) + + before_lp_details = get_loyalty_program_details_with_points("Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty") + + inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1) + inv.redeem_loyalty_points = 1 + inv.loyalty_points = before_lp_details.loyalty_points + inv.loyalty_amount = inv.loyalty_points * before_lp_details.conversion_factor + inv.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 10000 - inv.loyalty_amount}) + inv.paid_amount = 10000 + inv.submit() + + after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) + self.assertEqual(after_redeem_lp_details.loyalty_points, 9) + +def create_pos_invoice(**args): + args = frappe._dict(args) + pos_profile = None + if not args.pos_profile: + pos_profile = make_pos_profile() + pos_profile.save() + + pos_inv = frappe.new_doc("POS Invoice") + pos_inv.update_stock = 1 + pos_inv.is_pos = 1 + pos_inv.pos_profile = args.pos_profile or pos_profile.name + + pos_inv.set_missing_values() + + if args.posting_date: + pos_inv.set_posting_time = 1 + pos_inv.posting_date = args.posting_date or frappe.utils.nowdate() + + pos_inv.company = args.company or "_Test Company" + pos_inv.customer = args.customer or "_Test Customer" + pos_inv.debit_to = args.debit_to or "Debtors - _TC" + pos_inv.is_return = args.is_return + pos_inv.return_against = args.return_against + pos_inv.currency=args.currency or "INR" + pos_inv.conversion_rate = args.conversion_rate or 1 + pos_inv.account_for_change_amount = "Cash - _TC" + + pos_inv.append("items", { + "item_code": args.item or args.item_code or "_Test Item", + "warehouse": args.warehouse or "_Test Warehouse - _TC", + "qty": args.qty or 1, + "rate": args.rate if args.get("rate") is not None else 100, + "income_account": args.income_account or "Sales - _TC", + "expense_account": args.expense_account or "Cost of Goods Sold - _TC", + "cost_center": args.cost_center or "_Test Cost Center - _TC", + "serial_no": args.serial_no + }) + + if not args.do_not_save: + pos_inv.insert() + if not args.do_not_submit: + pos_inv.submit() + else: + pos_inv.payment_schedule = [] + else: + pos_inv.payment_schedule = [] + + return pos_inv \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py b/erpnext/accounts/doctype/pos_invoice_item/__init__.py similarity index 100% rename from erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py rename to erpnext/accounts/doctype/pos_invoice_item/__init__.py diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json new file mode 100644 index 0000000000..2b6e7de118 --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -0,0 +1,805 @@ +{ + "actions": [], + "autoname": "hash", + "creation": "2020-01-27 13:04:55.229516", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "barcode", + "item_code", + "col_break1", + "item_name", + "customer_item_code", + "description_section", + "description", + "item_group", + "brand", + "image_section", + "image", + "image_view", + "quantity_and_rate", + "qty", + "stock_uom", + "col_break2", + "uom", + "conversion_factor", + "stock_qty", + "section_break_17", + "price_list_rate", + "base_price_list_rate", + "discount_and_margin", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_19", + "discount_percentage", + "discount_amount", + "base_rate_with_margin", + "section_break1", + "rate", + "amount", + "item_tax_template", + "col_break3", + "base_rate", + "base_amount", + "pricing_rules", + "is_free_item", + "section_break_21", + "net_rate", + "net_amount", + "column_break_24", + "base_net_rate", + "base_net_amount", + "drop_ship", + "delivered_by_supplier", + "accounting", + "income_account", + "is_fixed_asset", + "asset", + "finance_book", + "col_break4", + "expense_account", + "deferred_revenue", + "deferred_revenue_account", + "service_stop_date", + "enable_deferred_revenue", + "column_break_50", + "service_start_date", + "service_end_date", + "section_break_18", + "weight_per_unit", + "total_weight", + "column_break_21", + "weight_uom", + "warehouse_and_reference", + "warehouse", + "target_warehouse", + "quality_inspection", + "batch_no", + "col_break5", + "allow_zero_valuation_rate", + "serial_no", + "item_tax_rate", + "actual_batch_qty", + "actual_qty", + "edit_references", + "sales_order", + "so_detail", + "column_break_74", + "delivery_note", + "dn_detail", + "delivered_qty", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", + "section_break_54", + "page_break" + ], + "fields": [ + { + "fieldname": "barcode", + "fieldtype": "Data", + "label": "Barcode", + "print_hide": 1 + }, + { + "bold": 1, + "columns": 4, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "search_index": 1 + }, + { + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "customer_item_code", + "fieldtype": "Data", + "hidden": 1, + "label": "Customer's Item Code", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "description_section", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "200px", + "reqd": 1, + "width": "200px" + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 1, + "label": "Item Group", + "oldfieldname": "item_group", + "oldfieldtype": "Link", + "options": "Item Group", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "brand", + "fieldtype": "Data", + "hidden": 1, + "label": "Brand Name", + "oldfieldname": "brand", + "oldfieldtype": "Data", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "image_section", + "fieldtype": "Section Break", + "label": "Image" + }, + { + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image" + }, + { + "fieldname": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "print_hide": 1 + }, + { + "fieldname": "quantity_and_rate", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "columns": 2, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "oldfieldname": "qty", + "oldfieldtype": "Currency" + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "col_break2", + "fieldtype": "Column Break" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", + "reqd": 1 + }, + { + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "UOM Conversion Factor", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "stock_qty", + "fieldtype": "Float", + "label": "Qty as per Stock UOM", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_17", + "fieldtype": "Section Break" + }, + { + "fieldname": "price_list_rate", + "fieldtype": "Currency", + "label": "Price List Rate", + "oldfieldname": "ref_rate", + "oldfieldtype": "Currency", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_price_list_rate", + "fieldtype": "Currency", + "label": "Price List Rate (Company Currency)", + "oldfieldname": "base_ref_rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "discount_and_margin", + "fieldtype": "Section Break", + "label": "Discount and Margin" + }, + { + "depends_on": "price_list_rate", + "fieldname": "margin_type", + "fieldtype": "Select", + "label": "Margin Type", + "options": "\nPercentage\nAmount", + "print_hide": 1 + }, + { + "depends_on": "eval:doc.margin_type && doc.price_list_rate", + "fieldname": "margin_rate_or_amount", + "fieldtype": "Float", + "label": "Margin Rate or Amount", + "print_hide": 1 + }, + { + "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", + "fieldname": "rate_with_margin", + "fieldtype": "Currency", + "label": "Rate With Margin", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "depends_on": "price_list_rate", + "fieldname": "discount_percentage", + "fieldtype": "Percent", + "label": "Discount (%) on Price List Rate with Margin", + "oldfieldname": "adj_rate", + "oldfieldtype": "Float", + "precision": "2", + "print_hide": 1 + }, + { + "depends_on": "price_list_rate", + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Discount Amount", + "options": "currency" + }, + { + "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", + "fieldname": "base_rate_with_margin", + "fieldtype": "Currency", + "label": "Rate With Margin (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break1", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "columns": 2, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "oldfieldname": "export_rate", + "oldfieldtype": "Currency", + "options": "currency", + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "oldfieldname": "export_amount", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "label": "Item Tax Template", + "options": "Item Tax Template", + "print_hide": 1 + }, + { + "fieldname": "col_break3", + "fieldtype": "Column Break" + }, + { + "fieldname": "base_rate", + "fieldtype": "Currency", + "label": "Rate (Company Currency)", + "oldfieldname": "basic_rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Amount (Company Currency)", + "oldfieldname": "amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "pricing_rules", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Pricing Rules", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_free_item", + "fieldtype": "Check", + "label": "Is Free Item", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_21", + "fieldtype": "Section Break" + }, + { + "fieldname": "net_rate", + "fieldtype": "Currency", + "label": "Net Rate", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "net_amount", + "fieldtype": "Currency", + "label": "Net Amount", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "base_net_rate", + "fieldtype": "Currency", + "label": "Net Rate (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_net_amount", + "fieldtype": "Currency", + "label": "Net Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.delivered_by_supplier==1", + "fieldname": "drop_ship", + "fieldtype": "Section Break", + "label": "Drop Ship" + }, + { + "default": "0", + "fieldname": "delivered_by_supplier", + "fieldtype": "Check", + "label": "Delivered By Supplier", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "oldfieldname": "income_account", + "oldfieldtype": "Link", + "options": "Account", + "print_hide": 1, + "print_width": "120px", + "reqd": 1, + "width": "120px" + }, + { + "default": "0", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Fixed Asset", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "asset", + "fieldtype": "Link", + "label": "Asset", + "no_copy": 1, + "options": "Asset" + }, + { + "depends_on": "asset", + "fieldname": "finance_book", + "fieldtype": "Link", + "label": "Finance Book", + "options": "Finance Book" + }, + { + "fieldname": "col_break4", + "fieldtype": "Column Break" + }, + { + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Expense Account", + "options": "Account", + "print_hide": 1, + "width": "120px" + }, + { + "collapsible": 1, + "fieldname": "deferred_revenue", + "fieldtype": "Section Break", + "label": "Deferred Revenue" + }, + { + "depends_on": "enable_deferred_revenue", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", + "label": "Deferred Revenue Account", + "options": "Account" + }, + { + "allow_on_submit": 1, + "depends_on": "enable_deferred_revenue", + "fieldname": "service_stop_date", + "fieldtype": "Date", + "label": "Service Stop Date", + "no_copy": 1 + }, + { + "default": "0", + "fieldname": "enable_deferred_revenue", + "fieldtype": "Check", + "label": "Enable Deferred Revenue" + }, + { + "fieldname": "column_break_50", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_deferred_revenue", + "fieldname": "service_start_date", + "fieldtype": "Date", + "label": "Service Start Date", + "no_copy": 1 + }, + { + "depends_on": "enable_deferred_revenue", + "fieldname": "service_end_date", + "fieldtype": "Date", + "label": "Service End Date", + "no_copy": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_18", + "fieldtype": "Section Break", + "label": "Item Weight Details" + }, + { + "fieldname": "weight_per_unit", + "fieldtype": "Float", + "label": "Weight Per Unit", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "total_weight", + "fieldtype": "Float", + "label": "Total Weight", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "fieldname": "weight_uom", + "fieldtype": "Link", + "label": "Weight UOM", + "options": "UOM", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.serial_no || doc.batch_no", + "fieldname": "warehouse_and_reference", + "fieldtype": "Section Break", + "label": "Stock Details" + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Warehouse", + "oldfieldname": "warehouse", + "oldfieldtype": "Link", + "options": "Warehouse", + "print_hide": 1 + }, + { + "fieldname": "target_warehouse", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 1, + "label": "Customer Warehouse (Optional)", + "no_copy": 1, + "options": "Warehouse", + "print_hide": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "quality_inspection", + "fieldtype": "Link", + "label": "Quality Inspection", + "options": "Quality Inspection" + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Batch No", + "options": "Batch", + "print_hide": 1 + }, + { + "fieldname": "col_break5", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "allow_zero_valuation_rate", + "fieldtype": "Check", + "label": "Allow Zero Valuation Rate", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "serial_no", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Serial No", + "oldfieldname": "serial_no", + "oldfieldtype": "Small Text" + }, + { + "fieldname": "item_tax_rate", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Item Tax Rate", + "oldfieldname": "item_tax_rate", + "oldfieldtype": "Small Text", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "actual_batch_qty", + "fieldtype": "Float", + "label": "Available Batch Qty at Warehouse", + "no_copy": 1, + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "width": "150px" + }, + { + "allow_on_submit": 1, + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": "Available Qty at Warehouse", + "oldfieldname": "actual_qty", + "oldfieldtype": "Currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "edit_references", + "fieldtype": "Section Break", + "label": "References" + }, + { + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "no_copy": 1, + "oldfieldname": "sales_order", + "oldfieldtype": "Link", + "options": "Sales Order", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "so_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Sales Order Item", + "no_copy": 1, + "oldfieldname": "so_detail", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_74", + "fieldtype": "Column Break" + }, + { + "fieldname": "delivery_note", + "fieldtype": "Link", + "label": "Delivery Note", + "no_copy": 1, + "oldfieldname": "delivery_note", + "oldfieldtype": "Link", + "options": "Delivery Note", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "dn_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Delivery Note Item", + "no_copy": 1, + "oldfieldname": "dn_detail", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "delivered_qty", + "fieldtype": "Float", + "label": "Delivered Qty", + "oldfieldname": "delivered_qty", + "oldfieldtype": "Currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "default": ":Company", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "oldfieldname": "cost_center", + "oldfieldtype": "Link", + "options": "Cost Center", + "print_hide": 1, + "print_width": "120px", + "reqd": 1, + "width": "120px" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_54", + "fieldtype": "Section Break" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "page_break", + "fieldtype": "Check", + "label": "Page Break", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-22 13:40:34.418346", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Invoice Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py similarity index 60% rename from erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py rename to erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py index a2d488b2f8..92ce61be52 100644 --- a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# 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 POSClosingVoucherInvoices(Document): +class POSInvoiceItem(Document): pass diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/__init__.py b/erpnext/accounts/doctype/pos_invoice_merge_log/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js new file mode 100644 index 0000000000..cd08efc55f --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js @@ -0,0 +1,16 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('POS Invoice Merge Log', { + setup: function(frm) { + frm.set_query("pos_invoice", "pos_invoices", doc => { + return{ + filters: { + 'docstatus': 1, + 'customer': doc.customer, + 'consolidated_invoice': '' + } + } + }); + } +}); diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json new file mode 100644 index 0000000000..8f97639bbc --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json @@ -0,0 +1,147 @@ +{ + "actions": [], + "creation": "2020-01-28 11:56:33.945372", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "posting_date", + "customer", + "section_break_3", + "pos_invoices", + "references_section", + "consolidated_invoice", + "column_break_7", + "consolidated_credit_note", + "amended_from" + ], + "fields": [ + { + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "pos_invoices", + "fieldtype": "Table", + "label": "POS Invoices", + "options": "POS Invoice Reference", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "references_section", + "fieldtype": "Section Break", + "label": "References" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "POS Invoice Merge Log", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "consolidated_invoice", + "fieldtype": "Link", + "label": "Consolidated Sales Invoice", + "options": "Sales Invoice", + "read_only": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "consolidated_credit_note", + "fieldtype": "Link", + "label": "Consolidated Credit Note", + "options": "Sales Invoice", + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-05-29 15:08:41.317100", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Invoice Merge Log", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py new file mode 100644 index 0000000000..00dbad5fa0 --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -0,0 +1,180 @@ +# -*- 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 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 six import iteritems + +class POSInvoiceMergeLog(Document): + def validate(self): + self.validate_customer() + self.validate_pos_invoice_status() + + def validate_customer(self): + for d in self.pos_invoices: + if d.customer != self.customer: + frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer)) + + def validate_pos_invoice_status(self): + for d in self.pos_invoices: + status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus']) + if docstatus != 1: + frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice)) + if status in ['Consolidated']: + frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status)) + + def on_submit(self): + pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices] + + returns = [d for d in pos_invoice_docs if d.get('is_return') == 1] + sales = [d for d in pos_invoice_docs if d.get('is_return') == 0] + + sales_invoice = self.process_merging_into_sales_invoice(sales) + + if len(returns): + credit_note = self.process_merging_into_credit_note(returns) + else: + credit_note = "" + + self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log + + self.update_pos_invoices(sales_invoice, credit_note) + + def process_merging_into_sales_invoice(self, data): + sales_invoice = self.get_new_sales_invoice() + + sales_invoice = self.merge_pos_invoice_into(sales_invoice, data) + + sales_invoice.is_consolidated = 1 + sales_invoice.save() + sales_invoice.submit() + self.consolidated_invoice = sales_invoice.name + + return sales_invoice.name + + def process_merging_into_credit_note(self, data): + credit_note = self.get_new_sales_invoice() + credit_note.is_return = 1 + + credit_note = self.merge_pos_invoice_into(credit_note, data) + + 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.save() + credit_note.submit() + self.consolidated_credit_note = credit_note.name + + return credit_note.name + + def merge_pos_invoice_into(self, invoice, data): + items, payments, taxes = [], [], [] + loyalty_amount_sum, loyalty_points_sum = 0, 0 + for doc in data: + map_doc(doc, invoice, table_map={ "doctype": invoice.doctype }) + + if doc.redeem_loyalty_points: + invoice.loyalty_redemption_account = doc.loyalty_redemption_account + invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center + loyalty_points_sum += doc.loyalty_points + loyalty_amount_sum += doc.loyalty_amount + + for item in doc.get('items'): + items.append(item) + + for tax in doc.get('taxes'): + found = False + for t in taxes: + if t.account_head == tax.account_head and t.cost_center == tax.cost_center and t.rate == tax.rate: + t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount) + t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount) + found = True + if not found: + tax.charge_type = 'Actual' + taxes.append(tax) + + for payment in doc.get('payments'): + found = False + for pay in payments: + if pay.account == payment.account and pay.mode_of_payment == payment.mode_of_payment: + pay.amount = flt(pay.amount) + flt(payment.amount) + pay.base_amount = flt(pay.base_amount) + flt(payment.base_amount) + found = True + if not found: + payments.append(payment) + + if loyalty_points_sum: + invoice.redeem_loyalty_points = 1 + invoice.loyalty_points = loyalty_points_sum + invoice.loyalty_amount = loyalty_amount_sum + + invoice.set('items', items) + invoice.set('payments', payments) + invoice.set('taxes', taxes) + + return invoice + + def get_new_sales_invoice(self): + sales_invoice = frappe.new_doc('Sales Invoice') + sales_invoice.customer = self.customer + sales_invoice.is_pos = 1 + # date can be pos closing date? + sales_invoice.posting_date = getdate(nowdate()) + + 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}) + doc.set_status(update=True) + doc.save() + +def get_all_invoices(): + filters = { + 'consolidated_invoice': [ 'in', [ '', None ]], + 'status': ['not in', ['Consolidated']], + 'docstatus': 1 + } + pos_invoices = frappe.db.get_all('POS Invoice', filters=filters, + fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer']) + + return pos_invoices + +def get_invoices_customer_map(pos_invoices): + # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] } + pos_invoice_customer_map = {} + for invoice in pos_invoices: + customer = invoice.get('customer') + pos_invoice_customer_map.setdefault(customer, []) + pos_invoice_customer_map[customer].append(invoice) + + 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 create_merge_logs(pos_invoice_customer_map): + for customer, invoices in iteritems(pos_invoice_customer_map): + merge_log = frappe.new_doc('POS Invoice Merge Log') + merge_log.posting_date = getdate(nowdate()) + merge_log.customer = customer + + merge_log.set('pos_invoices', invoices) + merge_log.save(ignore_permissions=True) + merge_log.submit() + diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py new file mode 100644 index 0000000000..0f34272eb4 --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +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_closing_entry.test_pos_closing_entry import init_user_and_profile + +class TestPOSInvoiceMergeLog(unittest.TestCase): + def test_consolidated_invoice_creation(self): + frappe.db.sql("delete from `tabPOS Invoice`") + + test_user, pos_profile = init_user_and_profile() + + pos_inv = create_pos_invoice(rate=300, do_not_submit=1) + pos_inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 + }) + pos_inv.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() + + pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) + pos_inv3.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 + }) + pos_inv3.submit() + + merge_pos_invoices() + + pos_inv.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) + + pos_inv3.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) + + self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) + + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") + + def test_consolidated_credit_note_creation(self): + frappe.db.sql("delete from `tabPOS Invoice`") + + test_user, pos_profile = init_user_and_profile() + + pos_inv = create_pos_invoice(rate=300, do_not_submit=1) + pos_inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 + }) + pos_inv.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() + + pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) + pos_inv3.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 + }) + pos_inv3.submit() + + pos_inv_cn = make_sales_return(pos_inv.name) + pos_inv_cn.set("payments", []) + pos_inv_cn.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300 + }) + pos_inv_cn.paid_amount = -300 + pos_inv_cn.submit() + + merge_pos_invoices() + + pos_inv.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) + + pos_inv3.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) + + pos_inv_cn.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice)) + self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return")) + + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") + + diff --git a/erpnext/accounts/doctype/pos_invoice_reference/__init__.py b/erpnext/accounts/doctype/pos_invoice_reference/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json new file mode 100644 index 0000000000..205c4ede90 --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json @@ -0,0 +1,65 @@ +{ + "actions": [], + "creation": "2020-01-28 11:54:47.149392", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "pos_invoice", + "posting_date", + "column_break_3", + "customer", + "grand_total" + ], + "fields": [ + { + "fieldname": "pos_invoice", + "fieldtype": "Link", + "in_list_view": 1, + "label": "POS Invoice", + "options": "POS Invoice", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "pos_invoice.customer", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer", + "read_only": 1, + "reqd": 1 + }, + { + "fetch_from": "pos_invoice.posting_date", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1 + }, + { + "fetch_from": "pos_invoice.grand_total", + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-29 15:08:42.194979", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Invoice Reference", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.py b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.py new file mode 100644 index 0000000000..4c45265f60 --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.py @@ -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 POSInvoiceReference(Document): + pass diff --git a/erpnext/accounts/doctype/pos_opening_entry/__init__.py b/erpnext/accounts/doctype/pos_opening_entry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js new file mode 100644 index 0000000000..372e75649b --- /dev/null +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js @@ -0,0 +1,56 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('POS Opening Entry', { + setup(frm) { + if (frm.doc.docstatus == 0) { + frm.trigger('set_posting_date_read_only'); + frm.set_value('period_start_date', frappe.datetime.now_datetime()); + frm.set_value('user', frappe.session.user); + } + + frm.set_query("user", function(doc) { + return { + query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers", + filters: { 'parent': doc.pos_profile } + }; + }); + }, + + refresh(frm) { + // set default posting date / time + if(frm.doc.docstatus == 0) { + if(!frm.doc.posting_date) { + frm.set_value('posting_date', frappe.datetime.nowdate()); + } + frm.trigger('set_posting_date_read_only'); + } + }, + + set_posting_date_read_only(frm) { + if(frm.doc.docstatus == 0 && frm.doc.set_posting_date) { + frm.set_df_property('posting_date', 'read_only', 0); + } else { + frm.set_df_property('posting_date', 'read_only', 1); + } + }, + + set_posting_date(frm) { + frm.trigger('set_posting_date_read_only'); + }, + + pos_profile: (frm) => { + if (frm.doc.pos_profile) { + frappe.db.get_doc("POS Profile", frm.doc.pos_profile) + .then(({ payments }) => { + if (payments.length) { + frm.doc.balance_details = []; + payments.forEach(({ mode_of_payment }) => { + frm.add_child("balance_details", { mode_of_payment }); + }) + frm.refresh_field("balance_details"); + } + }); + } + } +}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.json b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.json new file mode 100644 index 0000000000..de729cec60 --- /dev/null +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.json @@ -0,0 +1,185 @@ +{ + "actions": [], + "autoname": "POS-OPE-.YYYY.-.#####", + "creation": "2020-03-05 16:58:53.083708", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "period_start_date", + "period_end_date", + "status", + "column_break_3", + "posting_date", + "set_posting_date", + "section_break_5", + "company", + "pos_profile", + "pos_closing_entry", + "column_break_7", + "user", + "opening_balance_details_section", + "balance_details", + "section_break_9", + "amended_from" + ], + "fields": [ + { + "fieldname": "period_start_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Period Start Date", + "reqd": 1 + }, + { + "fieldname": "period_end_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Period End Date", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "pos_profile", + "fieldtype": "Link", + "in_list_view": 1, + "label": "POS Profile", + "options": "POS Profile", + "reqd": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "label": "Cashier", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "POS Opening Entry", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "set_posting_date", + "fieldtype": "Check", + "label": "Set Posting Date" + }, + { + "allow_on_submit": 1, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "label": "Status", + "options": "Draft\nOpen\nClosed\nCancelled", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "pos_closing_entry", + "fieldtype": "Data", + "label": "POS Closing Entry", + "read_only": 1 + }, + { + "fieldname": "opening_balance_details_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "balance_details", + "fieldtype": "Table", + "label": "Opening Balance Details", + "options": "POS Opening Entry Detail", + "reqd": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-05-29 15:08:40.955310", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Opening Entry", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py new file mode 100644 index 0000000000..15f23b63dc --- /dev/null +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py @@ -0,0 +1,25 @@ +# -*- 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 import _ +from frappe.utils import cint +from frappe.model.document import Document +from erpnext.controllers.status_updater import StatusUpdater + +class POSOpeningEntry(StatusUpdater): + def validate(self): + self.validate_pos_profile_and_cashier() + self.set_status() + + def validate_pos_profile_and_cashier(self): + if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"): + frappe.throw(_("POS Profile {} does not belongs to company {}".format(self.pos_profile, self.company))) + + if not cint(frappe.db.get_value("User", self.user, "enabled")): + frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user))) + + def on_submit(self): + self.set_status(update=True) \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js new file mode 100644 index 0000000000..6c26dedc54 --- /dev/null +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js @@ -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 Opening Entry'] = { + get_indicator: function(doc) { + var status_color = { + "Draft": "grey", + "Open": "orange", + "Closed": "green", + "Cancelled": "red" + + }; + return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; + } +}; diff --git a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py new file mode 100644 index 0000000000..2e36391714 --- /dev/null +++ b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestPOSOpeningEntry(unittest.TestCase): + pass + +def create_opening_entry(pos_profile, user): + entry = frappe.new_doc("POS Opening Entry") + entry.pos_profile = pos_profile.name + entry.user = user + entry.company = pos_profile.company + entry.period_start_date = frappe.utils.get_datetime() + + balance_details = []; + for d in pos_profile.payments: + balance_details.append(frappe._dict({ + 'mode_of_payment': d.mode_of_payment + })) + + entry.set("balance_details", balance_details) + entry.submit() + + return entry.as_dict() diff --git a/erpnext/accounts/doctype/pos_opening_entry_detail/__init__.py b/erpnext/accounts/doctype/pos_opening_entry_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.json b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.json new file mode 100644 index 0000000000..c23e3df8a7 --- /dev/null +++ b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "creation": "2020-04-28 16:44:32.440794", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "mode_of_payment", + "opening_amount" + ], + "fields": [ + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Mode of Payment", + "options": "Mode of Payment", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "opening_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Opening Amount", + "options": "company:company_currency", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-29 15:08:41.949378", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Opening Entry Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.py b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.py new file mode 100644 index 0000000000..555706227f --- /dev/null +++ b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.py @@ -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 POSOpeningEntryDetail(Document): + pass diff --git a/erpnext/accounts/doctype/pos_payment_method/__init__.py b/erpnext/accounts/doctype/pos_payment_method/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.json b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.json new file mode 100644 index 0000000000..4d5e1eb798 --- /dev/null +++ b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "creation": "2020-04-30 14:37:08.148707", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "default", + "mode_of_payment" + ], + "fields": [ + { + "default": "0", + "depends_on": "eval:parent.doctype == 'POS Profile'", + "fieldname": "default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Default" + }, + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Mode of Payment", + "options": "Mode of Payment", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-29 15:08:41.704844", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Payment Method", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.py b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.py new file mode 100644 index 0000000000..8a46d84bfe --- /dev/null +++ b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.py @@ -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 POSPaymentMethod(Document): + pass diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js index 5e94118d60..ef431d7d41 100755 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.js +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js @@ -28,7 +28,7 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) { frappe.ui.form.on('POS Profile', { setup: function(frm) { - frm.set_query("print_format_for_online", function() { + frm.set_query("print_format", function() { return { filters: [ ['Print Format', 'doc_type', '=', 'Sales Invoice'], @@ -49,12 +49,6 @@ frappe.ui.form.on('POS Profile', { return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} }; }); - frappe.db.get_value('POS Settings', 'POS Settings', 'use_pos_in_offline_mode', (r) => { - const is_offline = r && cint(r.use_pos_in_offline_mode) - frm.toggle_display('offline_pos_section', is_offline); - frm.toggle_display('print_format_for_online', !is_offline); - }); - frm.set_query('company_address', function(doc) { if(!doc.company) { frappe.throw(__('Please set Company')); diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index fba1bed9dd..454c598d63 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_rename": 1, "autoname": "Prompt", "creation": "2013-05-24 12:15:51", @@ -11,17 +12,12 @@ "customer", "company", "country", - "warehouse", - "campaign", - "company_address", "column_break_9", "update_stock", "ignore_pricing_rule", - "allow_delete", - "allow_user_to_edit_rate", - "allow_user_to_edit_discount", - "allow_print_before_pay", - "display_items_in_stock", + "warehouse", + "campaign", + "company_address", "section_break_15", "applicable_for_users", "section_break_11", @@ -31,16 +27,11 @@ "column_break_16", "customer_groups", "section_break_16", - "print_format_for_online", + "print_format", "letter_head", "column_break0", "tc_name", "select_print_heading", - "offline_pos_section", - "territory", - "column_break_31", - "print_format", - "customer_group", "section_break_19", "selling_price_list", "currency", @@ -104,15 +95,6 @@ "fieldtype": "Read Only", "label": "Country" }, - { - "depends_on": "update_stock", - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Warehouse", - "oldfieldname": "warehouse", - "oldfieldtype": "Link", - "options": "Warehouse" - }, { "fieldname": "campaign", "fieldtype": "Link", @@ -129,48 +111,6 @@ "fieldname": "column_break_9", "fieldtype": "Column Break" }, - { - "default": "1", - "fieldname": "update_stock", - "fieldtype": "Check", - "label": "Update Stock" - }, - { - "default": "0", - "fieldname": "ignore_pricing_rule", - "fieldtype": "Check", - "label": "Ignore Pricing Rule" - }, - { - "default": "0", - "fieldname": "allow_delete", - "fieldtype": "Check", - "label": "Allow Delete" - }, - { - "default": "0", - "fieldname": "allow_user_to_edit_rate", - "fieldtype": "Check", - "label": "Allow user to edit Rate" - }, - { - "default": "0", - "fieldname": "allow_user_to_edit_discount", - "fieldtype": "Check", - "label": "Allow user to edit Discount" - }, - { - "default": "0", - "fieldname": "allow_print_before_pay", - "fieldtype": "Check", - "label": "Allow Print Before Pay" - }, - { - "default": "0", - "fieldname": "display_items_in_stock", - "fieldtype": "Check", - "label": "Display Items In Stock" - }, { "fieldname": "section_break_15", "fieldtype": "Section Break", @@ -185,13 +125,13 @@ { "fieldname": "section_break_11", "fieldtype": "Section Break", - "label": "Mode of Payment" + "label": "Payment Methods" }, { "fieldname": "payments", "fieldtype": "Table", - "label": "Sales Invoice Payment", - "options": "Sales Invoice Payment" + "options": "POS Payment Method", + "reqd": 1 }, { "fieldname": "section_break_14", @@ -220,12 +160,6 @@ "fieldtype": "Section Break", "label": "Print Settings" }, - { - "fieldname": "print_format_for_online", - "fieldtype": "Link", - "label": "Print Format for Online", - "options": "Print Format" - }, { "allow_on_submit": 1, "fieldname": "letter_head", @@ -258,39 +192,6 @@ "oldfieldtype": "Select", "options": "Print Heading" }, - { - "fieldname": "offline_pos_section", - "fieldtype": "Section Break", - "label": "Offline POS Settings" - }, - { - "fieldname": "territory", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Territory", - "oldfieldname": "territory", - "oldfieldtype": "Link", - "options": "Territory", - "reqd": 1 - }, - { - "fieldname": "column_break_31", - "fieldtype": "Column Break" - }, - { - "default": "Point of Sale", - "fieldname": "print_format", - "fieldtype": "Link", - "label": "Print Format", - "options": "Print Format" - }, - { - "fieldname": "customer_group", - "fieldtype": "Link", - "label": "Customer Group", - "options": "Customer Group", - "reqd": 1 - }, { "fieldname": "section_break_19", "fieldtype": "Section Break", @@ -380,20 +281,49 @@ "fieldtype": "Section Break", "label": "Accounting Dimensions" }, - { - "fieldname": "dimension_col_break", - "fieldtype": "Column Break" - }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", "options": "Tax Category" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "print_format", + "fieldtype": "Link", + "label": "Print Format", + "options": "Print Format" + }, + { + "depends_on": "update_stock", + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "oldfieldname": "warehouse", + "oldfieldtype": "Link", + "options": "Warehouse", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "update_stock", + "fieldtype": "Check", + "label": "Update Stock" + }, + { + "default": "0", + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule" } ], "icon": "icon-cog", "idx": 1, - "modified": "2020-01-24 15:52:03.797701", + "links": [], + "modified": "2020-06-29 12:20:30.977272", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index f1869671ae..8655b4bf3a 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -5,8 +5,6 @@ from __future__ import unicode_literals import frappe from frappe import msgprint, _ from frappe.utils import cint, now -from erpnext.accounts.doctype.sales_invoice.pos import get_child_nodes -from erpnext.accounts.doctype.sales_invoice.sales_invoice import set_account_for_mode_of_payment from six import iteritems from frappe.model.document import Document @@ -16,7 +14,6 @@ class POSProfile(Document): self.validate_all_link_fields() self.validate_duplicate_groups() self.check_default_payment() - self.validate_customer_territory_group() def validate_default_profile(self): for row in self.applicable_for_users: @@ -64,19 +61,6 @@ class POSProfile(Document): if len(default_mode_of_payment) > 1: frappe.throw(_("Multiple default mode of payment is not allowed")) - def validate_customer_territory_group(self): - if not frappe.db.get_single_value('POS Settings', 'use_pos_in_offline_mode'): - return - - if not self.territory: - frappe.throw(_("Territory is Required in POS Profile"), title="Mandatory Field") - - if not self.customer_group: - frappe.throw(_("Customer Group is Required in POS Profile"), title="Mandatory Field") - - def before_save(self): - set_account_for_mode_of_payment(self) - def on_update(self): self.set_defaults() @@ -111,9 +95,14 @@ def get_item_groups(pos_profile): return list(set(item_groups)) +def get_child_nodes(group_type, root): + lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) + return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where + lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) + @frappe.whitelist() def get_series(): - return frappe.get_meta("Sales Invoice").get_field("naming_series").options or "" + return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s" @frappe.whitelist() def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py b/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py index e28bf73075..2e4632a8d5 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py @@ -8,7 +8,7 @@ def get_data(): 'fieldname': 'pos_profile', 'transactions': [ { - 'items': ['Sales Invoice', 'POS Closing Voucher'] + 'items': ['Sales Invoice', 'POS Closing Entry', 'POS Opening Entry'] } ] } diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 64d347de84..8a4050cf9e 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import unittest from erpnext.stock.get_item_details import get_pos_profile -from erpnext.accounts.doctype.sales_invoice.pos import get_items_list, get_customers_list +from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes class TestPOSProfile(unittest.TestCase): def test_pos_profile(self): @@ -29,6 +29,44 @@ class TestPOSProfile(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") +def get_customers_list(pos_profile={}): + cond = "1=1" + customer_groups = [] + if pos_profile.get('customer_groups'): + # Get customers based on the customer groups defined in the POS profile + for d in pos_profile.get('customer_groups'): + customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))]) + cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups))) + + return frappe.db.sql(""" select name, customer_name, customer_group, + territory, customer_pos_id from tabCustomer where disabled = 0 + and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} + +def get_items_list(pos_profile, company): + cond = "" + args_list = [] + if pos_profile.get('item_groups'): + # Get items based on the item groups defined in the POS profile + for d in pos_profile.get('item_groups'): + args_list.extend([d.name for d in get_child_nodes('Item Group', d.item_group)]) + if args_list: + cond = "and i.item_group in (%s)" % (', '.join(['%s'] * len(args_list))) + + return frappe.db.sql(""" + select + i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no, + i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image, + id.expense_account, id.selling_cost_center, id.default_warehouse, + i.sales_uom, c.conversion_factor + from + `tabItem` i + left join `tabItem Default` id on id.parent = i.name and id.company = %s + left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom + where + i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.is_fixed_asset = 0 + {cond} + """.format(cond=cond), tuple([company] + args_list), as_dict=1) + def make_pos_profile(**args): frappe.db.sql("delete from `tabPOS Profile`") @@ -50,6 +88,12 @@ def make_pos_profile(**args): "write_off_account": args.write_off_account or "_Test Write Off - _TC", "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC" }) + + payments = [{ + 'mode_of_payment': 'Cash', + 'default': 1 + }] + pos_profile.set("payments", payments) if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"): pos_profile.insert() diff --git a/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json b/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json index 59a673e3a5..c8f3f5e2f9 100644 --- a/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json +++ b/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json @@ -26,7 +26,7 @@ ], "istable": 1, "links": [], - "modified": "2020-05-01 09:46:47.599173", + "modified": "2020-05-13 23:57:33.627305", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile User", diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index f5b681bd41..504941d8b6 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -6,27 +6,19 @@ frappe.ui.form.on('POS Settings', { frm.trigger("get_invoice_fields"); }, - use_pos_in_offline_mode: function(frm) { - frm.trigger("get_invoice_fields"); - }, - get_invoice_fields: function(frm) { - if (!frm.doc.use_pos_in_offline_mode) { - frappe.model.with_doctype("Sales Invoice", () => { - var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) { - if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || - d.fieldtype === 'Table') { - return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; - } else { - return null; - } - }); - - frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields); + frappe.model.with_doctype("Sales Invoice", () => { + var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) { + if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || + d.fieldtype === 'Table') { + return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; + } else { + return null; + } }); - } else { - frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""]; - } + + frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields); + }); } }); diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json index 1d55880415..35395889a6 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.json +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json @@ -5,24 +5,11 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "use_pos_in_offline_mode", - "section_break_2", - "fields" + "invoice_fields" ], "fields": [ { - "default": "0", - "fieldname": "use_pos_in_offline_mode", - "fieldtype": "Check", - "label": "Use POS in Offline Mode" - }, - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, - { - "depends_on": "eval:!doc.use_pos_in_offline_mode", - "fieldname": "fields", + "fieldname": "invoice_fields", "fieldtype": "Table", "label": "POS Field", "options": "POS Field" @@ -30,7 +17,7 @@ ], "issingle": 1, "links": [], - "modified": "2019-12-26 11:50:47.122997", + "modified": "2020-06-01 15:46:41.478928", "modified_by": "Administrator", "module": "Accounts", "name": "POS Settings", diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py deleted file mode 100755 index c49ac292be..0000000000 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ /dev/null @@ -1,626 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -import json - -import frappe -from erpnext.accounts.party import get_party_account_currency -from erpnext.controllers.accounts_controller import get_taxes_and_charges -from erpnext.setup.utils import get_exchange_rate -from erpnext.stock.get_item_details import get_pos_profile -from frappe import _ -from frappe.core.doctype.communication.email import make -from frappe.utils import nowdate, cint - -from six import string_types, iteritems - - -@frappe.whitelist() -def get_pos_data(): - doc = frappe.new_doc('Sales Invoice') - doc.is_pos = 1 - pos_profile = get_pos_profile(doc.company) or {} - if not pos_profile: - frappe.throw(_("POS Profile is required to use Point-of-Sale")) - - if not doc.company: - doc.company = pos_profile.get('company') - - doc.update_stock = pos_profile.get('update_stock') - - if pos_profile.get('name'): - pos_profile = frappe.get_doc('POS Profile', pos_profile.get('name')) - pos_profile.validate() - - company_data = get_company_data(doc.company) - update_pos_profile_data(doc, pos_profile, company_data) - update_multi_mode_option(doc, pos_profile) - default_print_format = pos_profile.get('print_format') or "Point of Sale" - print_template = frappe.db.get_value('Print Format', default_print_format, 'html') - items_list = get_items_list(pos_profile, doc.company) - customers = get_customers_list(pos_profile) - - doc.plc_conversion_rate = update_plc_conversion_rate(doc, pos_profile) - - return { - 'doc': doc, - 'default_customer': pos_profile.get('customer'), - 'items': items_list, - 'item_groups': get_item_groups(pos_profile), - 'customers': customers, - 'address': get_customers_address(customers), - 'contacts': get_contacts(customers), - 'serial_no_data': get_serial_no_data(pos_profile, doc.company), - 'batch_no_data': get_batch_no_data(), - 'barcode_data': get_barcode_data(items_list), - 'tax_data': get_item_tax_data(), - 'price_list_data': get_price_list_data(doc.selling_price_list, doc.plc_conversion_rate), - 'customer_wise_price_list': get_customer_wise_price_list(), - 'bin_data': get_bin_data(pos_profile), - 'pricing_rules': get_pricing_rule_data(doc), - 'print_template': print_template, - 'pos_profile': pos_profile, - 'meta': get_meta() - } - -def update_plc_conversion_rate(doc, pos_profile): - conversion_rate = 1.0 - - price_list_currency = frappe.get_cached_value("Price List", doc.selling_price_list, "currency") - if pos_profile.get("currency") != price_list_currency: - conversion_rate = get_exchange_rate(price_list_currency, - pos_profile.get("currency"), nowdate(), args="for_selling") or 1.0 - - return conversion_rate - -def get_meta(): - doctype_meta = { - 'customer': frappe.get_meta('Customer'), - 'invoice': frappe.get_meta('Sales Invoice') - } - - for row in frappe.get_all('DocField', fields=['fieldname', 'options'], - filters={'parent': 'Sales Invoice', 'fieldtype': 'Table'}): - doctype_meta[row.fieldname] = frappe.get_meta(row.options) - - return doctype_meta - - -def get_company_data(company): - return frappe.get_all('Company', fields=["*"], filters={'name': company})[0] - - -def update_pos_profile_data(doc, pos_profile, company_data): - doc.campaign = pos_profile.get('campaign') - if pos_profile and not pos_profile.get('country'): - pos_profile.country = company_data.country - - doc.write_off_account = pos_profile.get('write_off_account') or \ - company_data.write_off_account - doc.change_amount_account = pos_profile.get('change_amount_account') or \ - company_data.default_cash_account - doc.taxes_and_charges = pos_profile.get('taxes_and_charges') - if doc.taxes_and_charges: - update_tax_table(doc) - - doc.currency = pos_profile.get('currency') or company_data.default_currency - doc.conversion_rate = 1.0 - - if doc.currency != company_data.default_currency: - doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency, doc.posting_date, args="for_selling") - - doc.selling_price_list = pos_profile.get('selling_price_list') or \ - frappe.db.get_value('Selling Settings', None, 'selling_price_list') - doc.naming_series = pos_profile.get('naming_series') or 'SINV-' - doc.letter_head = pos_profile.get('letter_head') or company_data.default_letter_head - doc.ignore_pricing_rule = pos_profile.get('ignore_pricing_rule') or 0 - doc.apply_discount_on = pos_profile.get('apply_discount_on') or 'Grand Total' - doc.customer_group = pos_profile.get('customer_group') or get_root('Customer Group') - doc.territory = pos_profile.get('territory') or get_root('Territory') - doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or '' - doc.offline_pos_name = '' - - -def get_root(table): - root = frappe.db.sql(""" select name from `tab%(table)s` having - min(lft)""" % {'table': table}, as_dict=1) - - return root[0].name - - -def update_multi_mode_option(doc, pos_profile): - from frappe.model import default_fields - - if not pos_profile or not pos_profile.get('payments'): - for payment in get_mode_of_payment(doc): - payments = doc.append('payments', {}) - payments.mode_of_payment = payment.parent - payments.account = payment.default_account - payments.type = payment.type - - return - - for payment_mode in pos_profile.payments: - payment_mode = payment_mode.as_dict() - - for fieldname in default_fields: - if fieldname in payment_mode: - del payment_mode[fieldname] - - doc.append('payments', payment_mode) - - -def get_mode_of_payment(doc): - return frappe.db.sql(""" - select mpa.default_account, mpa.parent, mp.type as type - from `tabMode of Payment Account` mpa,`tabMode of Payment` mp - where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", - {'company': doc.company}, as_dict=1) - - -def update_tax_table(doc): - taxes = get_taxes_and_charges('Sales Taxes and Charges Template', doc.taxes_and_charges) - for tax in taxes: - doc.append('taxes', tax) - - -def get_items_list(pos_profile, company): - cond = "" - args_list = [] - if pos_profile.get('item_groups'): - # Get items based on the item groups defined in the POS profile - for d in pos_profile.get('item_groups'): - args_list.extend([d.name for d in get_child_nodes('Item Group', d.item_group)]) - if args_list: - cond = "and i.item_group in (%s)" % (', '.join(['%s'] * len(args_list))) - - return frappe.db.sql(""" - select - i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no, - i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image, - id.expense_account, id.selling_cost_center, id.default_warehouse, - i.sales_uom, c.conversion_factor - from - `tabItem` i - left join `tabItem Default` id on id.parent = i.name and id.company = %s - left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom - where - i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 - {cond} - """.format(cond=cond), tuple([company] + args_list), as_dict=1) - - -def get_item_groups(pos_profile): - item_group_dict = {} - item_groups = frappe.db.sql("""Select name, - lft, rgt from `tabItem Group` order by lft""", as_dict=1) - - for data in item_groups: - item_group_dict[data.name] = [data.lft, data.rgt] - return item_group_dict - - -def get_customers_list(pos_profile={}): - cond = "1=1" - customer_groups = [] - if pos_profile.get('customer_groups'): - # Get customers based on the customer groups defined in the POS profile - for d in pos_profile.get('customer_groups'): - customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))]) - cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups))) - - return frappe.db.sql(""" select name, customer_name, customer_group, - territory, customer_pos_id from tabCustomer where disabled = 0 - and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} - - -def get_customers_address(customers): - customer_address = {} - if isinstance(customers, string_types): - customers = [frappe._dict({'name': customers})] - - for data in customers: - address = frappe.db.sql(""" select name, address_line1, address_line2, city, state, - email_id, phone, fax, pincode from `tabAddress` where is_primary_address =1 and name in - (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s - and parenttype = 'Address')""", data.name, as_dict=1) - address_data = {} - if address: - address_data = address[0] - - address_data.update({'full_name': data.customer_name, 'customer_pos_id': data.customer_pos_id}) - customer_address[data.name] = address_data - - return customer_address - - -def get_contacts(customers): - customer_contact = {} - if isinstance(customers, string_types): - customers = [frappe._dict({'name': customers})] - - for data in customers: - contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` - where is_primary_contact=1 and name in - (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s - and parenttype = 'Contact')""", data.name, as_dict=1) - if contact: - customer_contact[data.name] = contact[0] - - return customer_contact - - -def get_child_nodes(group_type, root): - lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) - return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where - lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) - - -def get_serial_no_data(pos_profile, company): - # get itemwise serial no data - # example {'Nokia Lumia 1020': {'SN0001': 'Pune'}} - # where Nokia Lumia 1020 is item code, SN0001 is serial no and Pune is warehouse - - cond = "1=1" - if pos_profile.get('update_stock') and pos_profile.get('warehouse'): - cond = "warehouse = %(warehouse)s" - - serial_nos = frappe.db.sql("""select name, warehouse, item_code - from `tabSerial No` where {0} and company = %(company)s """.format(cond),{ - 'company': company, 'warehouse': frappe.db.escape(pos_profile.get('warehouse')) - }, as_dict=1) - - itemwise_serial_no = {} - for sn in serial_nos: - if sn.item_code not in itemwise_serial_no: - itemwise_serial_no.setdefault(sn.item_code, {}) - itemwise_serial_no[sn.item_code][sn.name] = sn.warehouse - - return itemwise_serial_no - - -def get_batch_no_data(): - # get itemwise batch no data - # exmaple: {'LED-GRE': [Batch001, Batch002]} - # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse - - itemwise_batch = {} - batches = frappe.db.sql("""select name, item from `tabBatch` - where ifnull(expiry_date, '4000-10-10') >= curdate()""", as_dict=1) - - for batch in batches: - if batch.item not in itemwise_batch: - itemwise_batch.setdefault(batch.item, []) - itemwise_batch[batch.item].append(batch.name) - - return itemwise_batch - - -def get_barcode_data(items_list): - # get itemwise batch no data - # exmaple: {'LED-GRE': [Batch001, Batch002]} - # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse - - itemwise_barcode = {} - for item in items_list: - barcodes = frappe.db.sql(""" - select barcode from `tabItem Barcode` where parent = %s - """, item.item_code, as_dict=1) - - for barcode in barcodes: - if item.item_code not in itemwise_barcode: - itemwise_barcode.setdefault(item.item_code, []) - itemwise_barcode[item.item_code].append(barcode.get("barcode")) - - return itemwise_barcode - - -def get_item_tax_data(): - # get default tax of an item - # example: {'Consulting Services': {'Excise 12 - TS': '12.000'}} - - itemwise_tax = {} - taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1) - - for tax in taxes: - if tax.parent not in itemwise_tax: - itemwise_tax.setdefault(tax.parent, {}) - itemwise_tax[tax.parent][tax.tax_type] = tax.tax_rate - - return itemwise_tax - - -def get_price_list_data(selling_price_list, conversion_rate): - itemwise_price_list = {} - price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate, - item_code from `tabItem Price` ip where price_list = %(price_list)s""", - {'price_list': selling_price_list}, as_dict=1) - - for item in price_lists: - itemwise_price_list[item.item_code] = item.price_list_rate * conversion_rate - - return itemwise_price_list - -def get_customer_wise_price_list(): - customer_wise_price = {} - customer_price_list_mapping = frappe._dict(frappe.get_all('Customer',fields = ['default_price_list', 'name'], as_list=1)) - - price_lists = frappe.db.sql(""" Select ifnull(price_list_rate, 0) as price_list_rate, - item_code, price_list from `tabItem Price` """, as_dict=1) - - for item in price_lists: - if item.price_list and customer_price_list_mapping.get(item.price_list): - - customer_wise_price.setdefault(customer_price_list_mapping.get(item.price_list),{}).setdefault( - item.item_code, item.price_list_rate - ) - - return customer_wise_price - -def get_bin_data(pos_profile): - itemwise_bin_data = {} - filters = { 'actual_qty': ['>', 0] } - if pos_profile.get('warehouse'): - filters.update({ 'warehouse': pos_profile.get('warehouse') }) - - bin_data = frappe.db.get_all('Bin', fields = ['item_code', 'warehouse', 'actual_qty'], filters=filters) - - for bins in bin_data: - if bins.item_code not in itemwise_bin_data: - itemwise_bin_data.setdefault(bins.item_code, {}) - itemwise_bin_data[bins.item_code][bins.warehouse] = bins.actual_qty - - return itemwise_bin_data - - -def get_pricing_rule_data(doc): - pricing_rules = "" - if doc.ignore_pricing_rule == 0: - pricing_rules = frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2 - and ifnull(for_price_list, '') in (%(price_list)s, '') and selling = 1 - and ifnull(company, '') in (%(company)s, '') and disable = 0 and %(date)s - between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31') - order by priority desc, name desc""", - {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1) - return pricing_rules - - -@frappe.whitelist() -def make_invoice(pos_profile, doc_list={}, email_queue_list={}, customers_list={}): - import json - - if isinstance(doc_list, string_types): - doc_list = json.loads(doc_list) - - if isinstance(email_queue_list, string_types): - email_queue_list = json.loads(email_queue_list) - - if isinstance(customers_list, string_types): - customers_list = json.loads(customers_list) - - customers_list = make_customer_and_address(customers_list) - name_list = [] - for docs in doc_list: - for name, doc in iteritems(docs): - if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): - if isinstance(doc, dict): - validate_records(doc) - si_doc = frappe.new_doc('Sales Invoice') - si_doc.offline_pos_name = name - si_doc.update(doc) - si_doc.set_posting_time = 1 - si_doc.customer = get_customer_id(doc) - si_doc.due_date = doc.get('posting_date') - name_list = submit_invoice(si_doc, name, doc, name_list) - else: - doc.due_date = doc.get('posting_date') - doc.customer = get_customer_id(doc) - doc.set_posting_time = 1 - doc.offline_pos_name = name - name_list = submit_invoice(doc, name, doc, name_list) - else: - name_list.append(name) - - email_queue = make_email_queue(email_queue_list) - - if isinstance(pos_profile, string_types): - pos_profile = json.loads(pos_profile) - - customers = get_customers_list(pos_profile) - return { - 'invoice': name_list, - 'email_queue': email_queue, - 'customers': customers_list, - 'synced_customers_list': customers, - 'synced_address': get_customers_address(customers), - 'synced_contacts': get_contacts(customers) - } - - -def validate_records(doc): - validate_item(doc) - - -def get_customer_id(doc, customer=None): - cust_id = None - if doc.get('customer_pos_id'): - cust_id = frappe.db.get_value('Customer',{'customer_pos_id': doc.get('customer_pos_id')}, 'name') - - if not cust_id: - customer = customer or doc.get('customer') - if frappe.db.exists('Customer', customer): - cust_id = customer - else: - cust_id = add_customer(doc) - - return cust_id - -def make_customer_and_address(customers): - customers_list = [] - for customer, data in iteritems(customers): - data = json.loads(data) - cust_id = get_customer_id(data, customer) - if not cust_id: - cust_id = add_customer(data) - else: - frappe.db.set_value("Customer", cust_id, "customer_name", data.get('full_name')) - - make_contact(data, cust_id) - make_address(data, cust_id) - customers_list.append(customer) - frappe.db.commit() - return customers_list - -def add_customer(data): - customer = data.get('full_name') or data.get('customer') - if frappe.db.exists("Customer", customer.strip()): - return customer.strip() - - customer_doc = frappe.new_doc('Customer') - customer_doc.customer_name = data.get('full_name') or data.get('customer') - customer_doc.customer_pos_id = data.get('customer_pos_id') - customer_doc.customer_type = 'Company' - customer_doc.customer_group = get_customer_group(data) - customer_doc.territory = get_territory(data) - customer_doc.flags.ignore_mandatory = True - customer_doc.save(ignore_permissions=True) - frappe.db.commit() - return customer_doc.name - -def get_territory(data): - if data.get('territory'): - return data.get('territory') - - return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories') - -def get_customer_group(data): - if data.get('customer_group'): - return data.get('customer_group') - - return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name') - -def make_contact(args, customer): - if args.get('email_id') or args.get('phone'): - name = frappe.db.get_value('Dynamic Link', - {'link_doctype': 'Customer', 'link_name': customer, 'parenttype': 'Contact'}, 'parent') - - args = { - 'first_name': args.get('full_name'), - 'email_id': args.get('email_id'), - 'phone': args.get('phone') - } - - doc = frappe.new_doc('Contact') - if name: - doc = frappe.get_doc('Contact', name) - - doc.update(args) - doc.is_primary_contact = 1 - if not name: - doc.append('links', { - 'link_doctype': 'Customer', - 'link_name': customer - }) - doc.flags.ignore_mandatory = True - doc.save(ignore_permissions=True) - -def make_address(args, customer): - if not args.get('address_line1'): - return - - name = args.get('name') - - if not name: - data = get_customers_address(customer) - name = data[customer].get('name') if data else None - - if name: - address = frappe.get_doc('Address', name) - else: - address = frappe.new_doc('Address') - if args.get('company'): - address.country = frappe.get_cached_value('Company', - args.get('company'), 'country') - - address.append('links', { - 'link_doctype': 'Customer', - 'link_name': customer - }) - - address.is_primary_address = 1 - address.is_shipping_address = 1 - address.update(args) - address.flags.ignore_mandatory = True - address.save(ignore_permissions=True) - -def make_email_queue(email_queue): - name_list = [] - - for key, data in iteritems(email_queue): - name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name') - if not name: continue - - data = json.loads(data) - sender = frappe.session.user - print_format = "POS Invoice" if not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')) else None - - attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)] - - make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'), - sender=sender, attachments=attachments, send_email=True, - doctype='Sales Invoice', name=name) - name_list.append(key) - - return name_list - -def validate_item(doc): - for item in doc.get('items'): - if not frappe.db.exists('Item', item.get('item_code')): - item_doc = frappe.new_doc('Item') - item_doc.name = item.get('item_code') - item_doc.item_code = item.get('item_code') - item_doc.item_name = item.get('item_name') - item_doc.description = item.get('description') - item_doc.stock_uom = item.get('stock_uom') - item_doc.uom = item.get('uom') - item_doc.item_group = item.get('item_group') - item_doc.append('item_defaults', { - "company": doc.get("company"), - "default_warehouse": item.get('warehouse') - }) - item_doc.save(ignore_permissions=True) - frappe.db.commit() - -def submit_invoice(si_doc, name, doc, name_list): - try: - si_doc.insert() - si_doc.submit() - frappe.db.commit() - name_list.append(name) - except Exception as e: - if frappe.message_log: - frappe.message_log.pop() - frappe.db.rollback() - frappe.log_error(frappe.get_traceback()) - name_list = save_invoice(doc, name, name_list) - - return name_list - -def save_invoice(doc, name, name_list): - try: - if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): - si = frappe.new_doc('Sales Invoice') - si.update(doc) - si.set_posting_time = 1 - si.customer = get_customer_id(doc) - si.due_date = doc.get('posting_date') - si.flags.ignore_mandatory = True - si.insert(ignore_permissions=True) - frappe.db.commit() - name_list.append(name) - except Exception: - frappe.db.rollback() - frappe.log_error(frappe.get_traceback()) - - return name_list diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 061ce1cbb9..9af584e0b1 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -282,7 +282,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte "customer": this.frm.doc.customer }, callback: function(r) { - if(r.message && r.message.length) { + if(r.message && r.message.length > 1) { select_loyalty_program(me.frm, r.message); } } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 02b4206544..4c1d407f56 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -13,6 +13,7 @@ "customer_name", "tax_id", "is_pos", + "is_consolidated", "pos_profile", "offline_pos_name", "is_return", @@ -1921,6 +1922,13 @@ "hide_days": 1, "hide_seconds": 1 }, + { + "default": "0", + "fieldname": "is_consolidated", + "fieldtype": "Check", + "label": "Is Consolidated", + "read_only": 1 + }, { "default": "0", "fetch_from": "customer.is_internal_customer", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 89843484f9..3dab054014 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -8,8 +8,6 @@ from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_d from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date from frappe.model.mapper import get_mapped_doc -from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option - from erpnext.controllers.selling_controller import SellingController from erpnext.accounts.utils import get_account_currency from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so @@ -133,7 +131,7 @@ class SalesInvoice(SellingController): if self.is_pos and self.is_return: self.verify_payment_amount_is_negative() - if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: + if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points and not self.is_consolidated: validate_loyalty_points(self, self.loyalty_points) def validate_fixed_asset(self): @@ -200,13 +198,13 @@ class SalesInvoice(SellingController): update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) # create the loyalty point ledger entry if the customer is enrolled in any loyalty program - if not self.is_return and self.loyalty_program: + if not self.is_return and not self.is_consolidated and self.loyalty_program: self.make_loyalty_point_entry() - elif self.is_return and self.return_against and self.loyalty_program: + elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program: against_si_doc = frappe.get_doc("Sales Invoice", self.return_against) against_si_doc.delete_loyalty_point_entry() against_si_doc.make_loyalty_point_entry() - if self.redeem_loyalty_points and self.loyalty_points: + if self.redeem_loyalty_points and not self.is_consolidated and self.loyalty_points: self.apply_loyalty_points() # Healthcare Service Invoice. @@ -265,9 +263,9 @@ class SalesInvoice(SellingController): if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction": update_company_current_month_sales(self.company) self.update_project() - if not self.is_return and self.loyalty_program: + if not self.is_return and not self.is_consolidated and self.loyalty_program: self.delete_loyalty_point_entry() - elif self.is_return and self.return_against and self.loyalty_program: + elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program: against_si_doc = frappe.get_doc("Sales Invoice", self.return_against) against_si_doc.delete_loyalty_point_entry() against_si_doc.make_loyalty_point_entry() @@ -347,7 +345,7 @@ class SalesInvoice(SellingController): super(SalesInvoice, self).set_missing_values(for_validate) - print_format = pos.get("print_format_for_online") if pos else None + print_format = pos.get("print_format") if pos else None if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')): print_format = 'POS Invoice' @@ -420,8 +418,6 @@ class SalesInvoice(SellingController): self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') if pos: - self.allow_print_before_pay = pos.allow_print_before_pay - if not for_validate: self.tax_category = pos.get("tax_category") @@ -432,8 +428,8 @@ class SalesInvoice(SellingController): if pos.get('account_for_change_amount'): self.account_for_change_amount = pos.get('account_for_change_amount') - for fieldname in ('territory', 'naming_series', 'currency', 'letter_head', 'tc_name', - 'company', 'select_print_heading', 'cash_bank_account', 'write_off_account', 'taxes_and_charges', + for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name', + 'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges', 'write_off_cost_center', 'apply_discount_on', 'cost_center'): if (not for_validate) or (for_validate and not self.get(fieldname)): self.set(fieldname, pos.get(fieldname)) @@ -1123,7 +1119,8 @@ class SalesInvoice(SellingController): "loyalty_program": lp_details.loyalty_program, "loyalty_program_tier": lp_details.tier_name, "customer": self.customer, - "sales_invoice": self.name, + "invoice_type": self.doctype, + "invoice": self.name, "loyalty_points": points_earned, "purchase_amount": eligible_amount, "expiry_date": add_days(self.posting_date, lp_details.expiry_duration), @@ -1135,18 +1132,18 @@ class SalesInvoice(SellingController): # valdite the redemption and then delete the loyalty points earned on cancel of the invoice def delete_loyalty_point_entry(self): - lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where sales_invoice=%s", + lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where invoice=%s", (self.name), as_dict=1) if not lp_entry: return - against_lp_entry = frappe.db.sql('''select name, sales_invoice from `tabLoyalty Point Entry` + against_lp_entry = frappe.db.sql('''select name, invoice from `tabLoyalty Point Entry` where redeem_against=%s''', (lp_entry[0].name), as_dict=1) if against_lp_entry: - invoice_list = ", ".join([d.sales_invoice for d in against_lp_entry]) - frappe.throw(_('''Sales Invoice can't be cancelled since the Loyalty Points earned has been redeemed. - First cancel the Sales Invoice No {0}''').format(invoice_list)) + invoice_list = ", ".join([d.invoice for d in against_lp_entry]) + frappe.throw(_('''{} can't be cancelled since the Loyalty Points earned has been redeemed. + First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list)) else: - frappe.db.sql('''delete from `tabLoyalty Point Entry` where sales_invoice=%s''', (self.name)) + frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name)) # Set loyalty program self.set_loyalty_program_tier() @@ -1172,7 +1169,9 @@ class SalesInvoice(SellingController): points_to_redeem = self.loyalty_points for lp_entry in loyalty_point_entries: - if lp_entry.sales_invoice == self.name: + if lp_entry.invoice_type != self.doctype or lp_entry.invoice == self.name: + # redeemption should be done against same doctype + # also it shouldn't be against itself continue available_points = lp_entry.loyalty_points - flt(redemption_details.get(lp_entry.name)) if available_points > points_to_redeem: @@ -1185,7 +1184,8 @@ class SalesInvoice(SellingController): "loyalty_program": self.loyalty_program, "loyalty_program_tier": lp_entry.loyalty_program_tier, "customer": self.customer, - "sales_invoice": self.name, + "invoice_type": self.doctype, + "invoice": self.name, "redeem_against": lp_entry.name, "loyalty_points": -1*redeemed_points, "purchase_amount": self.grand_total, @@ -1576,13 +1576,13 @@ def get_loyalty_programs(customer): from erpnext.selling.doctype.customer.customer import get_loyalty_programs customer = frappe.get_doc('Customer', customer) - if customer.loyalty_program: return + if customer.loyalty_program: return [customer.loyalty_program] lp_details = get_loyalty_programs(customer) if len(lp_details) == 1: frappe.db.set(customer, 'loyalty_program', lp_details[0]) - return [] + return lp_details else: return lp_details @@ -1603,7 +1603,41 @@ def create_invoice_discounting(source_name, target_doc=None): return invoice_discounting -@frappe.whitelist() +def update_multi_mode_option(doc, pos_profile): + def append_payment(payment_mode): + payment = doc.append('payments', {}) + payment.default = payment_mode.default + payment.mode_of_payment = payment_mode.parent + payment.account = payment_mode.default_account + payment.type = payment_mode.type + + doc.set('payments', []) + if not pos_profile or not pos_profile.get('payments'): + for payment_mode in get_all_mode_of_payments(doc): + append_payment(payment_mode) + return + + for pos_payment_method in pos_profile.get('payments'): + pos_payment_method = pos_payment_method.as_dict() + + payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) + payment_mode[0].default = pos_payment_method.default + append_payment(payment_mode[0]) + +def get_all_mode_of_payments(doc): + return frappe.db.sql(""" + select mpa.default_account, mpa.parent, mp.type as type + from `tabMode of Payment Account` mpa,`tabMode of Payment` mp + where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", + {'company': doc.company}, as_dict=1) + +def get_mode_of_payment_info(mode_of_payment, company): + return frappe.db.sql(""" + select mpa.default_account, mpa.parent, mp.type as type + from `tabMode of Payment Account` mpa,`tabMode of Payment` mp + where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""", + (company, mode_of_payment), as_dict=1) + def create_dunning(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount @@ -1635,4 +1669,4 @@ def create_dunning(source_name, target_doc=None): "doctype": "Dunning", } }, target_doc, set_missing_values) - return doclist \ No newline at end of file + return doclist diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ff4d6136e9..964566a17e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -706,37 +706,15 @@ class TestSalesInvoice(unittest.TestCase): self.pos_gl_entry(si, pos, 50) - def test_pos_returns_without_repayment(self): - pos_profile = make_pos_profile() - - pos = create_sales_invoice(qty = 10, do_not_save=True) - pos.is_pos = 1 - pos.pos_profile = pos_profile.name - - pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500}) - pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500}) - pos.insert() - pos.submit() - - pos_return = create_sales_invoice(is_return=1, - return_against=pos.name, qty=-5, do_not_save=True) - - pos_return.is_pos = 1 - pos_return.pos_profile = pos_profile.name - - pos_return.insert() - pos_return.submit() - - self.assertFalse(pos_return.is_pos) - self.assertFalse(pos_return.get('payments')) - def test_pos_returns_with_repayment(self): + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return + pos_profile = make_pos_profile() + pos_profile.payments = [] pos_profile.append('payments', { 'default': 1, - 'mode_of_payment': 'Cash', - 'amount': 0.0 + 'mode_of_payment': 'Cash' }) pos_profile.save() @@ -751,18 +729,12 @@ class TestSalesInvoice(unittest.TestCase): pos.insert() pos.submit() - pos_return = create_sales_invoice(is_return=1, - return_against=pos.name, qty=-5, do_not_save=True) + pos_return = make_sales_return(pos.name) - pos_return.is_pos = 1 - pos_return.pos_profile = pos_profile.name pos_return.insert() pos_return.submit() - self.assertEqual(pos_return.get('payments')[0].amount, -500) - pos_profile.payments = [] - pos_profile.save() - + self.assertEqual(pos_return.get('payments')[0].amount, -1000) def test_pos_change_amount(self): make_pos_profile() @@ -788,82 +760,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(pos.grand_total, 100.0) self.assertEqual(pos.write_off_amount, -5) - def test_make_pos_invoice(self): - from erpnext.accounts.doctype.sales_invoice.pos import make_invoice - - pos_profile = make_pos_profile() - - pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", - item_code= "_Test FG Item", - warehouse= "Stores - TCP1", cost_center= "Main - TCP1") - - pos = create_sales_invoice(company= "_Test Company with perpetual inventory", - debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", - income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", - cost_center = "Main - TCP1", do_not_save=True) - - pos.is_pos = 1 - pos.update_stock = 1 - - pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) - pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50}) - - taxes = get_taxes_and_charges() - pos.taxes = [] - for tax in taxes: - pos.append("taxes", tax) - - invoice_data = [{'09052016142': pos}] - si = make_invoice(pos_profile, invoice_data).get('invoice') - self.assertEqual(si[0], '09052016142') - - sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': '09052016142', 'docstatus': 1}) - si = frappe.get_doc('Sales Invoice', sales_invoice[0].name) - - self.assertEqual(si.grand_total, 100) - - self.pos_gl_entry(si, pos, 50) - - def test_make_pos_invoice_in_draft(self): - from erpnext.accounts.doctype.sales_invoice.pos import make_invoice - from erpnext.stock.doctype.item.test_item import make_item - - allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') - if allow_negative_stock: - frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0) - - pos_profile = make_pos_profile() - timestamp = cint(time.time()) - - item = make_item("_Test POS Item") - pos = copy.deepcopy(test_records[1]) - pos['items'][0]['item_code'] = item.name - pos['items'][0]['warehouse'] = "_Test Warehouse - _TC" - pos["is_pos"] = 1 - pos["offline_pos_name"] = timestamp - pos["update_stock"] = 1 - pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, - {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}] - - invoice_data = [{timestamp: pos}] - si = make_invoice(pos_profile, invoice_data).get('invoice') - self.assertEqual(si[0], timestamp) - - sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp}) - self.assertEqual(sales_invoice[0].docstatus, 0) - - timestamp = cint(time.time()) - pos["offline_pos_name"] = timestamp - invoice_data = [{timestamp: pos}] - si1 = make_invoice(pos_profile, invoice_data).get('invoice') - self.assertEqual(si1[0], timestamp) - - sales_invoice1 = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp}) - self.assertEqual(sales_invoice1[0].docstatus, 0) - - if allow_negative_stock: - frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1) - def pos_gl_entry(self, si, pos, cash_amount): # check stock ledger entries sle = frappe.db.sql("""select * from `tabStock Ledger Entry` diff --git a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json index 52cf810ae4..2f9d381c92 100644 --- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json +++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json @@ -1,314 +1,90 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-05-08 23:49:38.842621", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-05-08 23:49:38.842621", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "default", + "mode_of_payment", + "amount", + "column_break_3", + "account", + "type", + "base_amount", + "clearance_date" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:parent.doctype == 'POS Profile'", - "fetch_if_empty": 0, - "fieldname": "default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Mode of Payment", + "options": "Mode of Payment", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Mode of Payment", - "length": 0, - "no_copy": 0, - "options": "Mode of Payment", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:parent.doctype == 'Sales Invoice'", + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "depends_on": "eval:parent.doctype == 'Sales Invoice'", - "fetch_if_empty": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "mode_of_payment.type", + "fieldname": "type", + "fieldtype": "Read Only", + "label": "Type" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "mode_of_payment.type", - "fetch_if_empty": 0, - "fieldname": "type", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Type", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Base Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Base Amount (Company Currency)", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "clearance_date", + "fieldtype": "Date", + "label": "Clearance Date", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "clearance_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Clearance Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fieldname": "default", + "fieldtype": "Check", + "hidden": 1, + "label": "Default", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-03-19 14:54:56.524556", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Invoice Payment", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-05-05 16:51:20.091441", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Payment", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js deleted file mode 100755 index 24fcb41a5d..0000000000 --- a/erpnext/accounts/page/pos/pos.js +++ /dev/null @@ -1,2105 +0,0 @@ -frappe.provide("erpnext.pos"); -{% include "erpnext/public/js/controllers/taxes_and_totals.js" %} - -frappe.pages['pos'].on_page_load = function (wrapper) { - var page = frappe.ui.make_app_page({ - parent: wrapper, - title: __('Point of Sale'), - single_column: true - }); - - frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => { - if (r && r.use_pos_in_offline_mode && cint(r.use_pos_in_offline_mode)) { - // offline - wrapper.pos = new erpnext.pos.PointOfSale(wrapper); - cur_pos = wrapper.pos; - } else { - // online - frappe.flags.is_online = true - frappe.set_route('point-of-sale'); - } - }); -} - -frappe.pages['pos'].refresh = function (wrapper) { - window.onbeforeunload = function () { - return wrapper.pos.beforeunload() - } - - if (frappe.flags.is_online) { - frappe.set_route('point-of-sale'); - } -} - -erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ - init: function (wrapper) { - this.page_len = 20; - this.freeze = false; - this.page = wrapper.page; - this.wrapper = $(wrapper).find('.page-content'); - this.set_indicator(); - this.onload(); - this.make_menu_list(); - this.bind_events(); - this.bind_items_event(); - this.si_docs = this.get_doc_from_localstorage(); - }, - - beforeunload: function (e) { - if (this.connection_status == false && frappe.get_route()[0] == "pos") { - e = e || window.event; - - // For IE and Firefox prior to version 4 - if (e) { - e.returnValue = __("You are in offline mode. You will not be able to reload until you have network."); - return - } - - // For Safari - return __("You are in offline mode. You will not be able to reload until you have network."); - } - }, - - check_internet_connection: function () { - var me = this; - //Check Internet connection after every 30 seconds - setInterval(function () { - me.set_indicator(); - }, 5000) - }, - - set_indicator: function () { - var me = this; - // navigator.onLine - this.connection_status = false; - this.page.set_indicator(__("Offline"), "grey") - frappe.call({ - method: "frappe.handler.ping", - callback: function (r) { - if (r.message) { - me.connection_status = true; - me.page.set_indicator(__("Online"), "green") - } - } - }) - }, - - onload: function () { - var me = this; - this.get_data_from_server(function () { - me.make_control(); - me.create_new(); - me.make(); - }); - }, - - make_menu_list: function () { - var me = this; - this.page.clear_menu(); - - // for mobile - this.page.add_menu_item(__("Pay"), function () { - me.validate(); - me.update_paid_amount_status(true); - me.create_invoice(); - me.make_payment(); - }).addClass('visible-xs'); - - this.page.add_menu_item(__("New Sales Invoice"), function () { - me.save_previous_entry(); - me.create_new(); - }) - - this.page.add_menu_item(__("Sync Master Data"), function () { - me.get_data_from_server(function () { - me.load_data(false); - me.make_item_list(); - me.set_missing_values(); - }) - }); - - this.page.add_menu_item(__("Sync Offline Invoices"), function () { - me.freeze_screen = true; - me.sync_sales_invoice() - }); - - this.page.add_menu_item(__("Cashier Closing"), function () { - frappe.set_route('List', 'Cashier Closing'); - }); - - this.page.add_menu_item(__("POS Profile"), function () { - frappe.set_route('List', 'POS Profile'); - }); - }, - - email_prompt: function() { - var me = this; - var fields = [{label:__("To"), fieldtype:"Data", reqd: 0, fieldname:"recipients",length:524288}, - {fieldtype: "Section Break", collapsible: 1, label: "CC & Email Template"}, - {fieldtype: "Section Break"}, - {label:__("Subject"), fieldtype:"Data", reqd: 1, - fieldname:"subject",length:524288}, - {fieldtype: "Section Break"}, - {label:__("Message"), fieldtype:"Text Editor", reqd: 1, - fieldname:"content"}, - {fieldtype: "Section Break"}, - {fieldtype: "Column Break"}]; - - this.email_dialog = new frappe.ui.Dialog({ - title: "Email", - fields: fields, - primary_action_label: __("Send"), - primary_action: function() { - me.send_action(); - } - }); - - this.email_dialog.show() - }, - - send_action: function() { - this.email_queue = this.get_email_queue() - this.email_queue[this.frm.doc.offline_pos_name] = JSON.stringify(this.email_dialog.get_values()) - this.update_email_queue() - this.email_dialog.hide() - }, - - update_email_queue: function () { - try { - localStorage.setItem('email_queue', JSON.stringify(this.email_queue)); - } catch (e) { - frappe.throw(__("LocalStorage is full, did not save")) - } - }, - - get_email_queue: function () { - try { - return JSON.parse(localStorage.getItem('email_queue')) || {}; - } catch (e) { - return {} - } - }, - - get_customers_details: function () { - try { - return JSON.parse(localStorage.getItem('customer_details')) || {}; - } catch (e) { - return {} - } - }, - - edit_record: function () { - var me = this; - - doc_data = this.get_invoice_doc(this.si_docs); - if (doc_data) { - this.frm.doc = doc_data[0][this.frm.doc.offline_pos_name]; - this.set_missing_values(); - this.refresh(false); - this.toggle_input_field(); - this.list_dialog && this.list_dialog.hide(); - } - }, - - delete_records: function () { - var me = this; - this.validate_list() - this.remove_doc_from_localstorage() - this.update_localstorage(); - this.toggle_delete_button(); - }, - - validate_list: function() { - var me = this; - this.si_docs = this.get_submitted_invoice() - $.each(this.removed_items, function(index, pos_name){ - $.each(me.si_docs, function(key, data){ - if(me.si_docs[key][pos_name] && me.si_docs[key][pos_name].offline_pos_name == pos_name ){ - frappe.throw(__("Submitted orders can not be deleted")) - } - }) - }) - }, - - toggle_delete_button: function () { - var me = this; - if(this.pos_profile_data["allow_delete"]) { - if (this.removed_items && this.removed_items.length > 0) { - $(this.page.wrapper).find('.btn-danger').show(); - } else { - $(this.page.wrapper).find('.btn-danger').hide(); - } - } - }, - - get_doctype_status: function (doc) { - if (doc.docstatus == 0) { - return { status: "Draft", indicator: "red" } - } else if (doc.outstanding_amount == 0) { - return { status: "Paid", indicator: "green" } - } else { - return { status: "Submitted", indicator: "blue" } - } - }, - - set_missing_values: function () { - var me = this; - doc = JSON.parse(localStorage.getItem('doc')) - if (this.frm.doc.payments.length == 0) { - this.frm.doc.payments = doc.payments; - this.calculate_outstanding_amount(); - } - - this.set_customer_value_in_party_field(); - - if (!this.frm.doc.write_off_account) { - this.frm.doc.write_off_account = doc.write_off_account - } - - if (!this.frm.doc.account_for_change_amount) { - this.frm.doc.account_for_change_amount = doc.account_for_change_amount - } - }, - - set_customer_value_in_party_field: function() { - if (this.frm.doc.customer) { - this.party_field.$input.val(this.frm.doc.customer); - } - }, - - get_invoice_doc: function (si_docs) { - var me = this; - this.si_docs = this.get_doc_from_localstorage(); - - return $.grep(this.si_docs, function (data) { - for (key in data) { - return key == me.frm.doc.offline_pos_name; - } - }) - }, - - get_data_from_server: function (callback) { - var me = this; - frappe.call({ - method: "erpnext.accounts.doctype.sales_invoice.pos.get_pos_data", - freeze: true, - freeze_message: __("Master data syncing, it might take some time"), - callback: function (r) { - localStorage.setItem('doc', JSON.stringify(r.message.doc)); - me.init_master_data(r) - me.set_interval_for_si_sync(); - me.check_internet_connection(); - if (callback) { - callback(); - } - }, - error: () => { - setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000); - } - }) - }, - - init_master_data: function (r) { - var me = this; - this.doc = JSON.parse(localStorage.getItem('doc')); - this.meta = r.message.meta; - this.item_data = r.message.items; - this.item_groups = r.message.item_groups; - this.customers = r.message.customers; - this.serial_no_data = r.message.serial_no_data; - this.batch_no_data = r.message.batch_no_data; - this.barcode_data = r.message.barcode_data; - this.tax_data = r.message.tax_data; - this.contacts = r.message.contacts; - this.address = r.message.address || {}; - this.price_list_data = r.message.price_list_data; - this.customer_wise_price_list = r.message.customer_wise_price_list - this.bin_data = r.message.bin_data; - this.pricing_rules = r.message.pricing_rules; - this.print_template = r.message.print_template; - this.pos_profile_data = r.message.pos_profile; - this.default_customer = r.message.default_customer || null; - this.print_settings = locals[":Print Settings"]["Print Settings"]; - this.letter_head = (this.pos_profile_data.length > 0) ? frappe.boot.letter_heads[this.pos_profile_data[letter_head]] : {}; - }, - - save_previous_entry: function () { - if (this.frm.doc.docstatus < 1 && this.frm.doc.items.length > 0) { - this.create_invoice(); - } - }, - - create_new: function () { - var me = this; - this.frm = {} - this.load_data(true); - this.frm.doc.offline_pos_name = ''; - this.setup(); - this.set_default_customer() - }, - - load_data: function (load_doc) { - var me = this; - - this.items = this.item_data; - this.actual_qty_dict = {}; - - if (load_doc) { - this.frm.doc = JSON.parse(localStorage.getItem('doc')); - } - - $.each(this.meta, function (i, data) { - frappe.meta.sync(data) - locals["DocType"][data.name] = data; - }) - - this.print_template_data = frappe.render_template("print_template", { - content: this.print_template, - title: "POS", - base_url: frappe.urllib.get_base_url(), - print_css: frappe.boot.print_css, - print_settings: this.print_settings, - header: this.letter_head.header, - footer: this.letter_head.footer, - landscape: false, - columns: [] - }) - }, - - setup: function () { - this.set_primary_action(); - this.party_field.$input.attr('disabled', false); - if(this.selected_row) { - this.selected_row.hide() - } - }, - - set_default_customer: function() { - if (this.default_customer && !this.frm.doc.customer) { - this.party_field.$input.val(this.default_customer); - this.frm.doc.customer = this.default_customer; - this.numeric_keypad.show(); - this.toggle_list_customer(false) - this.toggle_item_cart(true) - } - }, - - set_transaction_defaults: function (party) { - var me = this; - this.party = party; - this.price_list = (party == "Customer" ? - this.frm.doc.selling_price_list : this.frm.doc.buying_price_list); - this.price_list_field = (party == "Customer" ? "selling_price_list" : "buying_price_list"); - this.sales_or_purchase = (party == "Customer" ? "Sales" : "Purchase"); - }, - - make: function () { - this.make_item_list(); - this.make_discount_field() - }, - - make_control: function() { - this.frm = {} - this.frm.doc = this.doc - this.set_transaction_defaults("Customer"); - this.frm.doc["allow_user_to_edit_rate"] = this.pos_profile_data["allow_user_to_edit_rate"] ? true : false; - this.frm.doc["allow_user_to_edit_discount"] = this.pos_profile_data["allow_user_to_edit_discount"] ? true : false; - this.wrapper.html(frappe.render_template("pos", this.frm.doc)); - this.make_search(); - this.make_customer(); - this.make_list_customers(); - this.bind_numeric_keypad(); - }, - - make_search: function () { - var me = this; - this.search_item = frappe.ui.form.make_control({ - df: { - "fieldtype": "Data", - "label": __("Item"), - "fieldname": "pos_item", - "placeholder": __("Search Item") - }, - parent: this.wrapper.find(".search-item"), - only_input: true, - }); - - this.search_item.make_input(); - - this.search_item.$input.on("keypress", function (event) { - - clearTimeout(me.last_search_timeout); - me.last_search_timeout = setTimeout(() => { - if((me.search_item.$input.val() != "") || (event.which == 13)) { - me.items = me.get_items(); - me.make_item_list(); - } - }, 400); - }); - - this.search_item_group = this.wrapper.find('.search-item-group'); - sorted_item_groups = this.get_sorted_item_groups() - var dropdown_html = sorted_item_groups.map(function(item_group) { - return "
  • "+item_group+"
  • "; - }).join(""); - - this.search_item_group.find('.dropdown-menu').html(dropdown_html); - - this.search_item_group.on('click', '.dropdown-menu a', function() { - me.selected_item_group = $(this).attr('data-value'); - me.search_item_group.find('.dropdown-text').text(me.selected_item_group); - - me.page_len = 20; - me.items = me.get_items(); - me.make_item_list(); - }) - - me.toggle_more_btn(); - - this.wrapper.on("click", ".btn-more", function() { - me.page_len += 20; - me.items = me.get_items(); - me.make_item_list(); - me.toggle_more_btn(); - }); - - this.page.wrapper.on("click", ".edit-customer-btn", function() { - me.update_customer() - }) - }, - - get_sorted_item_groups: function() { - list = {} - $.each(this.item_groups, function(i, data) { - list[i] = data[0] - }) - - return Object.keys(list).sort(function(a,b){return list[a]-list[b]}) - }, - - toggle_more_btn: function() { - if(!this.items || this.items.length <= this.page_len) { - this.wrapper.find(".btn-more").hide(); - } else { - this.wrapper.find(".btn-more").show(); - } - }, - - toggle_totals_area: function(show) { - - if(show === undefined) { - show = this.is_totals_area_collapsed; - } - - var totals_area = this.wrapper.find('.totals-area'); - totals_area.find('.net-total-area, .tax-area, .discount-amount-area') - .toggle(show); - - if(show) { - totals_area.find('.collapse-btn i') - .removeClass('octicon-chevron-down') - .addClass('octicon-chevron-up'); - } else { - totals_area.find('.collapse-btn i') - .removeClass('octicon-chevron-up') - .addClass('octicon-chevron-down'); - } - - this.is_totals_area_collapsed = !show; - }, - - make_list_customers: function () { - var me = this; - this.list_customers_btn = this.page.wrapper.find('.list-customers-btn'); - this.add_customer_btn = this.wrapper.find('.add-customer-btn'); - this.pos_bill = this.wrapper.find('.pos-bill-wrapper').hide(); - this.list_customers = this.wrapper.find('.list-customers'); - this.numeric_keypad = this.wrapper.find('.numeric_keypad'); - this.list_customers_btn.addClass("view_customer") - - me.render_list_customers(); - me.toggle_totals_area(false); - - this.page.wrapper.on('click', '.list-customers-btn', function() { - $(this).toggleClass("view_customer"); - if($(this).hasClass("view_customer")) { - me.render_list_customers(); - me.list_customers.show(); - me.pos_bill.hide(); - me.numeric_keypad.hide(); - me.toggle_delete_button() - } else { - if(me.frm.doc.docstatus == 0) { - me.party_field.$input.attr('disabled', false); - } - me.pos_bill.show(); - me.toggle_totals_area(false); - me.toggle_delete_button() - me.list_customers.hide(); - me.numeric_keypad.show(); - } - }); - this.add_customer_btn.on('click', function() { - me.save_previous_entry(); - me.create_new(); - me.refresh(); - me.set_focus(); - }); - this.pos_bill.on('click', '.collapse-btn', function() { - me.toggle_totals_area(); - }); - }, - - bind_numeric_keypad: function() { - var me = this; - $(this.numeric_keypad).find('.pos-operation').on('click', function(){ - me.numeric_val = ''; - }) - - $(this.numeric_keypad).find('.numeric-keypad').on('click', function(){ - me.numeric_id = $(this).attr("id") || me.numeric_id; - me.val = $(this).attr("val") - if(me.numeric_id) { - me.selected_field = $(me.wrapper).find('.selected-item').find('.' + me.numeric_id) - } - - if(me.val && me.numeric_id) { - me.numeric_val += me.val; - me.selected_field.val(flt(me.numeric_val)) - me.selected_field.trigger("change") - // me.render_selected_item() - } - - if(me.numeric_id && $(this).hasClass('pos-operation')) { - me.numeric_keypad.find('button.pos-operation').removeClass('active'); - $(this).addClass('active'); - - me.selected_row.find('.pos-list-row').removeClass('active'); - me.selected_field.closest('.pos-list-row').addClass('active'); - } - }) - - $(this.numeric_keypad).find('.numeric-del').click(function(){ - if(me.numeric_id) { - me.selected_field = $(me.wrapper).find('.selected-item').find('.' + me.numeric_id) - me.numeric_val = cstr(flt(me.selected_field.val())).slice(0, -1); - me.selected_field.val(me.numeric_val); - me.selected_field.trigger("change") - } else { - //Remove an item from the cart, if focus is at selected item - me.remove_selected_item() - } - }) - - $(this.numeric_keypad).find('.pos-pay').click(function(){ - me.validate(); - me.update_paid_amount_status(true); - me.create_invoice(); - me.make_payment(); - }) - }, - - remove_selected_item: function() { - this.remove_item = [] - idx = $(this.wrapper).find(".pos-selected-item-action").attr("data-idx") - this.remove_item.push(idx) - this.remove_zero_qty_items_from_cart() - this.update_paid_amount_status(false) - }, - - render_list_customers: function () { - var me = this; - - this.removed_items = []; - // this.list_customers.empty(); - this.si_docs = this.get_doc_from_localstorage(); - if (!this.si_docs.length) { - this.list_customers.find('.list-customers-table').html(""); - return; - } - - var html = ""; - if(this.si_docs.length) { - this.si_docs.forEach(function (data, i) { - for (var key in data) { - html += frappe.render_template("pos_invoice_list", { - sr: i + 1, - name: key, - customer: data[key].customer, - paid_amount: format_currency(data[key].paid_amount, me.frm.doc.currency), - grand_total: format_currency(data[key].grand_total, me.frm.doc.currency), - data: me.get_doctype_status(data[key]) - }); - } - }); - } - this.list_customers.find('.list-customers-table').html(html); - - this.list_customers.on('click', '.customer-row', function () { - me.list_customers.hide(); - me.numeric_keypad.show(); - me.list_customers_btn.toggleClass("view_customer"); - me.pos_bill.show(); - me.list_customers_btn.show(); - me.frm.doc.offline_pos_name = $(this).parents().attr('invoice-name'); - me.edit_record(); - }) - - //actions - $(this.wrapper).find('.list-select-all').click(function () { - me.list_customers.find('.list-delete').prop("checked", $(this).is(":checked")) - me.removed_items = []; - if ($(this).is(":checked")) { - $.each(me.si_docs, function (index, data) { - for (key in data) { - me.removed_items.push(key) - } - }); - } - - me.toggle_delete_button(); - }); - - $(this.wrapper).find('.list-delete').click(function () { - me.frm.doc.offline_pos_name = $(this).parent().parent().attr('invoice-name'); - if ($(this).is(":checked")) { - me.removed_items.push(me.frm.doc.offline_pos_name); - } else { - me.removed_items.pop(me.frm.doc.offline_pos_name) - } - - me.toggle_delete_button(); - }); - }, - - bind_delete_event: function() { - var me = this; - - $(this.page.wrapper).on('click', '.btn-danger', function(){ - frappe.confirm(__("Delete permanently?"), function () { - me.delete_records(); - me.list_customers.find('.list-customers-table').html(""); - me.render_list_customers(); - }) - }) - }, - - set_focus: function () { - if (this.default_customer || this.frm.doc.customer) { - this.set_customer_value_in_party_field(); - this.search_item.$input.focus(); - } else { - this.party_field.$input.focus(); - } - }, - - make_customer: function () { - var me = this; - - if(!this.party_field) { - if(this.page.wrapper.find('.pos-bill-toolbar').length === 0) { - $(frappe.render_template('customer_toolbar', { - allow_delete: this.pos_profile_data["allow_delete"] - })).insertAfter(this.page.$title_area.hide()); - } - - this.party_field = frappe.ui.form.make_control({ - df: { - "fieldtype": "Data", - "options": this.party, - "label": this.party, - "fieldname": this.party.toLowerCase(), - "placeholder": __("Select or add new customer") - }, - parent: this.page.wrapper.find(".party-area"), - only_input: true, - }); - - this.party_field.make_input(); - setTimeout(this.set_focus.bind(this), 500); - me.toggle_delete_button(); - } - - this.party_field.awesomeplete = - new Awesomplete(this.party_field.$input.get(0), { - minChars: 0, - maxItems: 99, - autoFirst: true, - list: [], - filter: function (item, input) { - if (item.value.includes('is_action')) { - return true; - } - - input = input.toLowerCase(); - item = this.get_item(item.value); - result = item ? item.searchtext.includes(input) : ''; - if(!result) { - me.prepare_customer_mapper(input); - } else { - return result; - } - }, - item: function (item, input) { - var d = this.get_item(item.value); - var html = "" + __(d.label || d.value) + ""; - if(d.customer_name) { - html += '
    ' + __(d.customer_name) + ''; - } - - return $('
  • ') - .data('item.autocomplete', d) - .html('

    ' + html + '

    ') - .get(0); - } - }); - - this.prepare_customer_mapper() - this.autocomplete_customers(); - - this.party_field.$input - .on('input', function (e) { - if(me.customers_mapper.length <= 1) { - me.prepare_customer_mapper(e.target.value); - } - me.party_field.awesomeplete.list = me.customers_mapper; - }) - .on('awesomplete-select', function (e) { - var customer = me.party_field.awesomeplete - .get_item(e.originalEvent.text.value); - if (!customer) return; - // create customer link - if (customer.action) { - customer.action.apply(me); - return; - } - me.toggle_list_customer(false); - me.toggle_edit_button(true); - me.update_customer_data(customer); - me.refresh(); - me.set_focus(); - me.list_customers_btn.removeClass("view_customer"); - }) - .on('focus', function (e) { - $(e.target).val('').trigger('input'); - me.toggle_edit_button(false); - - if(me.frm.doc.items.length) { - me.toggle_list_customer(false) - me.toggle_item_cart(true) - } else { - me.toggle_list_customer(true) - me.toggle_item_cart(false) - } - }) - .on("awesomplete-selectcomplete", function (e) { - var item = me.party_field.awesomeplete - .get_item(e.originalEvent.text.value); - // clear text input if item is action - if (item.action) { - $(this).val(""); - } - me.make_item_list(item.customer_name); - }); - }, - - prepare_customer_mapper: function(key) { - var me = this; - var customer_data = ''; - - if (key) { - key = key.toLowerCase().trim(); - var re = new RegExp('%', 'g'); - var reg = new RegExp(key.replace(re, '\\w*\\s*[a-zA-Z0-9]*')); - - customer_data = $.grep(this.customers, function(data) { - contact = me.contacts[data.name]; - if(reg.test(data.name.toLowerCase()) - || reg.test(data.customer_name.toLowerCase()) - || (contact && reg.test(contact["phone"])) - || (contact && reg.test(contact["mobile_no"])) - || (data.customer_group && reg.test(data.customer_group.toLowerCase()))){ - return data; - } - }) - } else { - customer_data = this.customers; - } - - this.customers_mapper = []; - - customer_data.forEach(function (c, index) { - if(index < 30) { - contact = me.contacts[c.name]; - if(contact && !c['phone']) { - c["phone"] = contact["phone"]; - c["email_id"] = contact["email_id"]; - c["mobile_no"] = contact["mobile_no"]; - } - - me.customers_mapper.push({ - label: c.name, - value: c.name, - customer_name: c.customer_name, - customer_group: c.customer_group, - territory: c.territory, - phone: contact ? contact["phone"] : '', - mobile_no: contact ? contact["mobile_no"] : '', - email_id: contact ? contact["email_id"] : '', - searchtext: ['customer_name', 'customer_group', 'name', 'value', - 'label', 'email_id', 'phone', 'mobile_no'] - .map(key => c[key]).join(' ') - .toLowerCase() - }); - } else { - return; - } - }); - - this.customers_mapper.push({ - label: "" - + " " - + __("Create a new Customer") - + "", - value: 'is_action', - action: me.add_customer - }); - }, - - autocomplete_customers: function() { - this.party_field.awesomeplete.list = this.customers_mapper; - }, - - toggle_edit_button: function(flag) { - this.page.wrapper.find('.edit-customer-btn').toggle(flag); - }, - - toggle_list_customer: function(flag) { - this.list_customers.toggle(flag); - }, - - toggle_item_cart: function(flag) { - this.wrapper.find('.pos-bill-wrapper').toggle(flag); - }, - - add_customer: function() { - this.frm.doc.customer = ""; - this.update_customer(true); - this.numeric_keypad.show(); - }, - - update_customer: function (new_customer) { - var me = this; - - this.customer_doc = new frappe.ui.Dialog({ - 'title': 'Customer', - fields: [ - { - "label": __("Full Name"), - "fieldname": "full_name", - "fieldtype": "Data", - "reqd": 1 - }, - { - "fieldtype": "Section Break" - }, - { - "label": __("Email Id"), - "fieldname": "email_id", - "fieldtype": "Data" - }, - { - "fieldtype": "Column Break" - }, - { - "label": __("Contact Number"), - "fieldname": "phone", - "fieldtype": "Data" - }, - { - "fieldtype": "Section Break" - }, - { - "label": __("Address Name"), - "read_only": 1, - "fieldname": "name", - "fieldtype": "Data" - }, - { - "label": __("Address Line 1"), - "fieldname": "address_line1", - "fieldtype": "Data" - }, - { - "label": __("Address Line 2"), - "fieldname": "address_line2", - "fieldtype": "Data" - }, - { - "fieldtype": "Column Break" - }, - { - "label": __("City"), - "fieldname": "city", - "fieldtype": "Data" - }, - { - "label": __("State"), - "fieldname": "state", - "fieldtype": "Data" - }, - { - "label": __("ZIP Code"), - "fieldname": "pincode", - "fieldtype": "Data" - }, - { - "label": __("Customer POS Id"), - "fieldname": "customer_pos_id", - "fieldtype": "Data", - "hidden": 1 - } - ] - }) - this.customer_doc.show() - this.render_address_data() - - this.customer_doc.set_primary_action(__("Save"), function () { - me.make_offline_customer(new_customer); - me.pos_bill.show(); - me.list_customers.hide(); - }); - }, - - render_address_data: function() { - var me = this; - this.address_data = this.address[this.frm.doc.customer] || {}; - if(!this.address_data.email_id || !this.address_data.phone) { - this.address_data = this.contacts[this.frm.doc.customer]; - } - - this.customer_doc.set_values(this.address_data) - if(!this.customer_doc.fields_dict.full_name.$input.val()) { - this.customer_doc.set_value("full_name", this.frm.doc.customer) - } - - if(!this.customer_doc.fields_dict.customer_pos_id.value) { - this.customer_doc.set_value("customer_pos_id", frappe.datetime.now_datetime()) - } - }, - - get_address_from_localstorage: function() { - this.address_details = this.get_customers_details() - return this.address_details[this.frm.doc.customer] - }, - - make_offline_customer: function(new_customer) { - this.frm.doc.customer = this.frm.doc.customer || this.customer_doc.get_values().full_name; - this.frm.doc.customer_pos_id = this.customer_doc.fields_dict.customer_pos_id.value; - this.customer_details = this.get_customers_details(); - this.customer_details[this.frm.doc.customer] = this.get_prompt_details(); - this.party_field.$input.val(this.frm.doc.customer); - this.update_address_and_customer_list(new_customer) - this.autocomplete_customers(); - this.update_customer_in_localstorage() - this.update_customer_in_localstorage() - this.customer_doc.hide() - }, - - update_address_and_customer_list: function(new_customer) { - var me = this; - if(new_customer) { - this.customers_mapper.push({ - label: this.frm.doc.customer, - value: this.frm.doc.customer, - customer_group: "", - territory: "" - }); - } - - this.address[this.frm.doc.customer] = JSON.parse(this.get_prompt_details()) - }, - - get_prompt_details: function() { - this.prompt_details = this.customer_doc.get_values(); - this.prompt_details['country'] = this.pos_profile_data.country; - this.prompt_details['territory'] = this.pos_profile_data["territory"]; - this.prompt_details['customer_group'] = this.pos_profile_data["customer_group"]; - this.prompt_details['customer_pos_id'] = this.customer_doc.fields_dict.customer_pos_id.value; - return JSON.stringify(this.prompt_details) - }, - - update_customer_data: function (doc) { - var me = this; - this.frm.doc.customer = doc.label || doc.name; - this.frm.doc.customer_name = doc.customer_name; - this.frm.doc.customer_group = doc.customer_group; - this.frm.doc.territory = doc.territory; - this.pos_bill.show(); - this.numeric_keypad.show(); - }, - - make_item_list: function (customer) { - var me = this; - if (!this.price_list) { - frappe.msgprint(__("Price List not found or disabled")); - return; - } - - me.item_timeout = null; - - var $wrap = me.wrapper.find(".item-list"); - me.wrapper.find(".item-list").empty(); - - if (this.items.length > 0) { - $.each(this.items, function(index, obj) { - let customer_price_list = me.customer_wise_price_list[customer]; - let item_price - if (customer && customer_price_list && customer_price_list[obj.name]) { - item_price = format_currency(customer_price_list[obj.name], me.frm.doc.currency); - } else { - item_price = format_currency(me.price_list_data[obj.name], me.frm.doc.currency); - } - if(index < me.page_len) { - $(frappe.render_template("pos_item", { - item_code: obj.name, - item_price: item_price, - item_name: obj.name === obj.item_name ? "" : obj.item_name, - item_image: obj.image, - item_stock: __('Stock Qty') + ": " + me.get_actual_qty(obj), - item_uom: obj.stock_uom, - color: frappe.get_palette(obj.item_name), - abbr: frappe.get_abbr(obj.item_name) - })).tooltip().appendTo($wrap); - } - }); - - $wrap.append(` -
    -
    - -
    Load more items
    -
    -
    - `); - - me.toggle_more_btn(); - } else { - $("

    " - +__("Not items found")+"

    ").appendTo($wrap) - } - - if (this.items.length == 1 - && this.search_item.$input.val()) { - this.search_item.$input.val(""); - this.add_to_cart(); - } - }, - - get_items: function (item_code) { - // To search item as per the key enter - - var me = this; - this.item_serial_no = {}; - this.item_batch_no = {}; - - if (item_code) { - return $.grep(this.item_data, function (item) { - if (item.item_code == item_code) { - return true - } - }) - } - - this.items_list = this.apply_category(); - - key = this.search_item.$input.val().toLowerCase().replace(/[&\/\\#,+()\[\]$~.'":*?<>{}]/g, '\\$&'); - var re = new RegExp('%', 'g'); - var reg = new RegExp(key.replace(re, '[\\w*\\s*[a-zA-Z0-9]*]*')) - search_status = true - - if (key) { - return $.grep(this.items_list, function (item) { - if (search_status) { - if (me.batch_no_data[item.item_code] && - in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) { - search_status = false; - return me.item_batch_no[item.item_code] = me.search_item.$input.val() - } else if (me.serial_no_data[item.item_code] - && in_list(Object.keys(me.serial_no_data[item.item_code]), me.search_item.$input.val())) { - search_status = false; - me.item_serial_no[item.item_code] = [me.search_item.$input.val(), me.serial_no_data[item.item_code][me.search_item.$input.val()]] - return true - } else if (me.barcode_data[item.item_code] && - in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) { - search_status = false; - return true; - } else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) || - reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) { - return true - } - } - }) - } else { - return this.items_list; - } - }, - - apply_category: function() { - var me = this; - category = this.selected_item_group || "All Item Groups"; - if(category == 'All Item Groups') { - return this.item_data - } else { - return this.item_data.filter(function(element, index, array){ - return element.item_group == category; - }); - } - }, - - bind_items_event: function() { - var me = this; - $(this.wrapper).on('click', '.pos-bill-item', function() { - $(me.wrapper).find('.pos-bill-item').removeClass('active'); - $(this).addClass('active'); - me.numeric_val = ""; - me.numeric_id = "" - me.item_code = $(this).attr("data-item-code"); - me.render_selected_item() - me.bind_qty_event() - me.update_rate() - $(me.wrapper).find(".selected-item").scrollTop(1000); - }) - }, - - bind_qty_event: function () { - var me = this; - - $(this.wrapper).on("change", ".pos-item-qty", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); - var qty = $(this).val(); - me.update_qty(item_code, qty); - me.update_value(); - }) - - $(this.wrapper).on("focusout", ".pos-item-qty", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); - var qty = $(this).val(); - me.update_qty(item_code, qty, true); - me.update_value(); - }) - - $(this.wrapper).find("[data-action='increase-qty']").on("click", function () { - var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); - var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) + 1; - me.update_qty(item_code, qty); - }) - - $(this.wrapper).find("[data-action='decrease-qty']").on("click", function () { - var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); - var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) - 1; - me.update_qty(item_code, qty); - }) - - $(this.wrapper).on("change", ".pos-item-disc", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); - var discount = $(this).val(); - if(discount > 100){ - discount = $(this).val(''); - frappe.show_alert({ - indicator: 'red', - message: __('Discount amount cannot be greater than 100%') - }); - me.update_discount(item_code, discount); - }else{ - me.update_discount(item_code, discount); - me.update_value(); - } - }) - }, - - bind_events: function() { - var me = this; - // if form is local then allow this function - // $(me.wrapper).find(".pos-item-wrapper").on("click", function () { - $(this.wrapper).on("click", ".pos-item-wrapper", function () { - me.item_code = ''; - me.customer_validate(); - if($(me.pos_bill).is(":hidden")) return; - - if (me.frm.doc.docstatus == 0) { - me.items = me.get_items($(this).attr("data-item-code")) - me.add_to_cart(); - me.clear_selected_row(); - } - }); - - me.bind_delete_event() - }, - - update_qty: function (item_code, qty, remove_zero_qty_items) { - var me = this; - this.items = this.get_items(item_code); - this.validate_serial_no() - this.set_item_details(item_code, "qty", qty, remove_zero_qty_items); - }, - - update_discount: function(item_code, discount) { - var me = this; - this.items = this.get_items(item_code); - this.set_item_details(item_code, "discount_percentage", discount); - }, - - update_rate: function () { - var me = this; - $(this.wrapper).on("change", ".pos-item-price", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); - me.set_item_details(item_code, "rate", $(this).val()); - me.update_value() - }) - }, - - update_value: function() { - var me = this; - var fields = {qty: ".pos-item-qty", "discount_percentage": ".pos-item-disc", - "rate": ".pos-item-price", "amount": ".pos-amount"} - this.child_doc = this.get_child_item(this.item_code); - - if(me.child_doc.length) { - $.each(fields, function(key, field) { - $(me.selected_row).find(field).val(me.child_doc[0][key]) - }) - } else { - this.clear_selected_row(); - } - }, - - clear_selected_row: function() { - $(this.wrapper).find('.selected-item').empty(); - }, - - render_selected_item: function() { - this.child_doc = this.get_child_item(this.item_code); - $(this.wrapper).find('.selected-item').empty(); - if(this.child_doc.length) { - this.child_doc[0]["allow_user_to_edit_rate"] = this.pos_profile_data["allow_user_to_edit_rate"] ? true : false, - this.child_doc[0]["allow_user_to_edit_discount"] = this.pos_profile_data["allow_user_to_edit_discount"] ? true : false; - this.selected_row = $(frappe.render_template("pos_selected_item", this.child_doc[0])) - $(this.wrapper).find('.selected-item').html(this.selected_row) - } - - $(this.selected_row).find('.form-control').click(function(){ - $(this).select(); - }) - }, - - get_child_item: function(item_code) { - var me = this; - return $.map(me.frm.doc.items, function(doc){ - if(doc.item_code == item_code) { - return doc - } - }) - }, - - set_item_details: function (item_code, field, value, remove_zero_qty_items) { - var me = this; - if (value < 0) { - frappe.throw(__("Enter value must be positive")); - } - - this.remove_item = [] - $.each(this.frm.doc["items"] || [], function (i, d) { - if (d.item_code == item_code) { - if (d.serial_no && field == 'qty') { - me.validate_serial_no_qty(d, item_code, field, value) - } - - d[field] = flt(value); - d.amount = flt(d.rate) * flt(d.qty); - if (d.qty == 0 && remove_zero_qty_items) { - me.remove_item.push(d.idx) - } - - if(field=="discount_percentage" && value == 0) { - d.rate = d.price_list_rate; - } - } - }); - - if (field == 'qty') { - this.remove_zero_qty_items_from_cart(); - } - - this.update_paid_amount_status(false) - }, - - remove_zero_qty_items_from_cart: function () { - var me = this; - var idx = 0; - this.items = [] - $.each(this.frm.doc["items"] || [], function (i, d) { - if (!in_list(me.remove_item, d.idx)) { - d.idx = idx; - me.items.push(d); - idx++; - } - }); - - this.frm.doc["items"] = this.items; - }, - - make_discount_field: function () { - var me = this; - - this.wrapper.find('input.discount-percentage').on("change", function () { - me.frm.doc.additional_discount_percentage = flt($(this).val(), precision("additional_discount_percentage")); - - if(me.frm.doc.additional_discount_percentage && me.frm.doc.discount_amount) { - // Reset discount amount - me.frm.doc.discount_amount = 0; - } - - var total = me.frm.doc.grand_total - - if (me.frm.doc.apply_discount_on == 'Net Total') { - total = me.frm.doc.net_total - } - - me.frm.doc.discount_amount = flt(total * flt(me.frm.doc.additional_discount_percentage) / 100, precision("discount_amount")); - me.refresh(); - me.wrapper.find('input.discount-amount').val(me.frm.doc.discount_amount) - }); - - this.wrapper.find('input.discount-amount').on("change", function () { - me.frm.doc.discount_amount = flt($(this).val(), precision("discount_amount")); - me.frm.doc.additional_discount_percentage = 0.0; - me.refresh(); - me.wrapper.find('input.discount-percentage').val(0); - }); - }, - - customer_validate: function () { - var me = this; - if (!this.frm.doc.customer || this.party_field.get_value() == "") { - frappe.throw(__("Please select customer")) - } - }, - - add_to_cart: function () { - var me = this; - var caught = false; - var no_of_items = me.wrapper.find(".pos-bill-item").length; - - this.customer_validate(); - this.mandatory_batch_no(); - this.validate_serial_no(); - this.validate_warehouse(); - - if (no_of_items != 0) { - $.each(this.frm.doc["items"] || [], function (i, d) { - if (d.item_code == me.items[0].item_code) { - caught = true; - d.qty += 1; - d.amount = flt(d.rate) * flt(d.qty); - if (me.item_serial_no[d.item_code]) { - d.serial_no += '\n' + me.item_serial_no[d.item_code][0] - d.warehouse = me.item_serial_no[d.item_code][1] - } - - if (me.item_batch_no.length) { - d.batch_no = me.item_batch_no[d.item_code] - } - } - }); - } - - // if item not found then add new item - if (!caught) - this.add_new_item_to_grid(); - - this.update_paid_amount_status(false) - this.wrapper.find(".item-cart-items").scrollTop(1000); - }, - - add_new_item_to_grid: function () { - var me = this; - this.child = frappe.model.add_child(this.frm.doc, this.frm.doc.doctype + " Item", "items"); - this.child.item_code = this.items[0].item_code; - this.child.item_name = this.items[0].item_name; - this.child.stock_uom = this.items[0].stock_uom; - this.child.uom = this.items[0].sales_uom || this.items[0].stock_uom; - this.child.conversion_factor = this.items[0].conversion_factor || 1; - this.child.brand = this.items[0].brand; - this.child.description = this.items[0].description || this.items[0].item_name; - this.child.discount_percentage = 0.0; - this.child.qty = 1; - this.child.item_group = this.items[0].item_group; - this.child.cost_center = this.pos_profile_data['cost_center'] || this.items[0].cost_center; - this.child.income_account = this.pos_profile_data['income_account'] || this.items[0].income_account; - this.child.warehouse = (this.item_serial_no[this.child.item_code] - ? this.item_serial_no[this.child.item_code][1] : (this.pos_profile_data['warehouse'] || this.items[0].default_warehouse)); - - customer = this.frm.doc.customer; - let rate; - - customer_price_list = this.customer_wise_price_list[customer] - if (customer_price_list && customer_price_list[this.child.item_code]){ - rate = flt(this.customer_wise_price_list[customer][this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9); - } - else{ - rate = flt(this.price_list_data[this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9); - } - - this.child.price_list_rate = rate; - this.child.rate = rate; - this.child.actual_qty = me.get_actual_qty(this.items[0]); - this.child.amount = flt(this.child.qty) * flt(this.child.rate); - this.child.batch_no = this.item_batch_no[this.child.item_code]; - this.child.serial_no = (this.item_serial_no[this.child.item_code] - ? this.item_serial_no[this.child.item_code][0] : ''); - this.child.item_tax_rate = JSON.stringify(this.tax_data[this.child.item_code]); - }, - - update_paid_amount_status: function (update_paid_amount) { - if (this.frm.doc.offline_pos_name) { - update_paid_amount = update_paid_amount ? false : true; - } - - this.refresh(update_paid_amount); - }, - - refresh: function (update_paid_amount) { - var me = this; - this.refresh_fields(update_paid_amount); - this.set_primary_action(); - }, - - refresh_fields: function (update_paid_amount) { - this.apply_pricing_rule(); - this.discount_amount_applied = false; - this._calculate_taxes_and_totals(); - this.calculate_discount_amount(); - this.show_items_in_item_cart(); - this.set_taxes(); - this.calculate_outstanding_amount(update_paid_amount); - this.set_totals(); - this.update_total_qty(); - }, - - get_company_currency: function () { - return erpnext.get_currency(this.frm.doc.company); - }, - - show_items_in_item_cart: function () { - var me = this; - var $items = this.wrapper.find(".items").empty(); - var $no_items_message = this.wrapper.find(".no-items-message"); - $no_items_message.toggle(this.frm.doc.items.length === 0); - - var $totals_area = this.wrapper.find('.totals-area'); - $totals_area.toggle(this.frm.doc.items.length > 0); - - $.each(this.frm.doc.items || [], function (i, d) { - $(frappe.render_template("pos_bill_item_new", { - item_code: d.item_code, - item_name: (d.item_name === d.item_code || !d.item_name) ? "" : ("
    " + d.item_name), - qty: d.qty, - discount_percentage: d.discount_percentage || 0.0, - actual_qty: me.actual_qty_dict[d.item_code] || 0.0, - projected_qty: d.projected_qty, - rate: format_currency(d.rate, me.frm.doc.currency), - amount: format_currency(d.amount, me.frm.doc.currency), - selected_class: (me.item_code == d.item_code) ? "active" : "" - })).appendTo($items); - }); - - this.wrapper.find("input.pos-item-qty").on("focus", function () { - $(this).select(); - }); - - this.wrapper.find("input.pos-item-disc").on("focus", function () { - $(this).select(); - }); - - this.wrapper.find("input.pos-item-price").on("focus", function () { - $(this).select(); - }); - }, - - set_taxes: function () { - var me = this; - me.frm.doc.total_taxes_and_charges = 0.0 - - var taxes = this.frm.doc.taxes || []; - $(this.wrapper) - .find(".tax-area").toggleClass("hide", (taxes && taxes.length) ? false : true) - .find(".tax-table").empty(); - - $.each(taxes, function (i, d) { - if (d.tax_amount && cint(d.included_in_print_rate) == 0) { - $(frappe.render_template("pos_tax_row", { - description: d.description, - tax_amount: format_currency(flt(d.tax_amount_after_discount_amount), - me.frm.doc.currency) - })).appendTo(me.wrapper.find(".tax-table")); - } - }); - }, - - set_totals: function () { - var me = this; - this.wrapper.find(".net-total").text(format_currency(me.frm.doc.total, me.frm.doc.currency)); - this.wrapper.find(".grand-total").text(format_currency(me.frm.doc.grand_total, me.frm.doc.currency)); - this.wrapper.find('input.discount-percentage').val(this.frm.doc.additional_discount_percentage); - this.wrapper.find('input.discount-amount').val(this.frm.doc.discount_amount); - }, - - update_total_qty: function() { - var me = this; - var qty_total = 0; - $.each(this.frm.doc["items"] || [], function (i, d) { - if (d.item_code) { - qty_total += d.qty; - } - }); - this.frm.doc.qty_total = qty_total; - this.wrapper.find('.qty-total').text(this.frm.doc.qty_total); - }, - - set_primary_action: function () { - var me = this; - this.page.set_primary_action(__("New Cart"), function () { - me.make_new_cart() - me.make_menu_list() - }, "fa fa-plus") - - if (this.frm.doc.docstatus == 1 || this.pos_profile_data["allow_print_before_pay"]) { - this.page.set_secondary_action(__("Print"), function () { - me.create_invoice(); - var html = frappe.render(me.print_template_data, me.frm.doc) - me.print_document(html) - }) - } - - if (this.frm.doc.docstatus == 1) { - this.page.add_menu_item(__("Email"), function () { - me.email_prompt() - }) - } - }, - - make_new_cart: function (){ - this.item_code = ''; - this.page.clear_secondary_action(); - this.save_previous_entry(); - this.create_new(); - this.refresh(); - this.toggle_input_field(); - this.render_list_customers(); - this.set_focus(); - }, - - print_dialog: function () { - var me = this; - - this.msgprint = frappe.msgprint( - `${__('Print')} - ${__('New')}`); - - this.msgprint.msg_area.find('.print_doc').on('click', function() { - var html = frappe.render(me.print_template_data, me.frm.doc); - me.print_document(html); - }) - - this.msgprint.msg_area.find('.new_doc').on('click', function() { - me.msgprint.hide(); - me.make_new_cart(); - }) - - }, - - print_document: function (html) { - var w = window.open(); - w.document.write(html); - w.document.close(); - setTimeout(function () { - w.print(); - w.close(); - }, 1000); - }, - - submit_invoice: function () { - var me = this; - this.change_status(); - this.update_serial_no() - if (this.frm.doc.docstatus == 1) { - this.print_dialog() - } - }, - - update_serial_no: function() { - var me = this; - - //Remove the sold serial no from the cache - $.each(this.frm.doc.items, function(index, data) { - var sn = data.serial_no.split('\n') - if(sn.length) { - var serial_no_list = me.serial_no_data[data.item_code] - if(serial_no_list) { - $.each(sn, function(i, serial_no) { - if(in_list(Object.keys(serial_no_list), serial_no)) { - delete serial_no_list[serial_no] - } - }) - me.serial_no_data[data.item_code] = serial_no_list; - } - } - }) - }, - - change_status: function () { - if (this.frm.doc.docstatus == 0) { - this.frm.doc.docstatus = 1; - this.update_invoice(); - this.toggle_input_field(); - } - }, - - toggle_input_field: function () { - var pointer_events = 'inherit' - var disabled = this.frm.doc.docstatus == 1 ? true: false; - $(this.wrapper).find('input').attr("disabled", disabled); - $(this.wrapper).find('select').attr("disabled", disabled); - $(this.wrapper).find('input').attr("disabled", disabled); - $(this.wrapper).find('select').attr("disabled", disabled); - $(this.wrapper).find('button').attr("disabled", disabled); - this.party_field.$input.attr('disabled', disabled); - - if (this.frm.doc.docstatus == 1) { - pointer_events = 'none'; - } - - $(this.wrapper).find('.pos-bill').css('pointer-events', pointer_events); - $(this.wrapper).find('.pos-items-section').css('pointer-events', pointer_events); - this.set_primary_action(); - - $(this.wrapper).find('#pos-item-disc').prop('disabled', - this.pos_profile_data.allow_user_to_edit_discount ? false : true); - - $(this.wrapper).find('#pos-item-price').prop('disabled', - this.pos_profile_data.allow_user_to_edit_rate ? false : true); - }, - - create_invoice: function () { - var me = this; - var existing_pos_list = []; - var invoice_data = {}; - this.si_docs = this.get_doc_from_localstorage(); - - if(this.si_docs) { - this.si_docs.forEach((row) => { - existing_pos_list.push(Object.keys(row)[0]); - }); - } - - if (this.frm.doc.offline_pos_name - && in_list(existing_pos_list, cstr(this.frm.doc.offline_pos_name))) { - this.update_invoice() - } else if(!this.frm.doc.offline_pos_name) { - this.frm.doc.offline_pos_name = frappe.datetime.now_datetime(); - this.frm.doc.posting_date = frappe.datetime.get_today(); - this.frm.doc.posting_time = frappe.datetime.now_time(); - this.frm.doc.pos_total_qty = this.frm.doc.qty_total; - this.frm.doc.pos_profile = this.pos_profile_data['name']; - invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc; - this.si_docs.push(invoice_data); - this.update_localstorage(); - this.set_primary_action(); - } - return invoice_data; - }, - - update_invoice: function () { - var me = this; - this.si_docs = this.get_doc_from_localstorage(); - $.each(this.si_docs, function (index, data) { - for (var key in data) { - if (key == me.frm.doc.offline_pos_name) { - me.si_docs[index][key] = me.frm.doc; - me.update_localstorage(); - } - } - }); - }, - - update_localstorage: function () { - try { - localStorage.setItem('sales_invoice_doc', JSON.stringify(this.si_docs)); - } catch (e) { - frappe.throw(__("LocalStorage is full , did not save")) - } - }, - - get_doc_from_localstorage: function () { - try { - return JSON.parse(localStorage.getItem('sales_invoice_doc')) || []; - } catch (e) { - return [] - } - }, - - set_interval_for_si_sync: function () { - var me = this; - setInterval(function () { - me.freeze_screen = false; - me.sync_sales_invoice() - }, 180000) - }, - - sync_sales_invoice: function () { - var me = this; - this.si_docs = this.get_submitted_invoice() || []; - this.email_queue_list = this.get_email_queue() || {}; - this.customers_list = this.get_customers_details() || {}; - - if (this.si_docs.length || this.email_queue_list || this.customers_list) { - frappe.call({ - method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice", - freeze: true, - args: { - pos_profile: me.pos_profile_data, - doc_list: me.si_docs, - email_queue_list: me.email_queue_list, - customers_list: me.customers_list - }, - callback: function (r) { - if (r.message) { - me.freeze = false; - me.customers = r.message.synced_customers_list; - me.address = r.message.synced_address; - me.contacts = r.message.synced_contacts; - me.removed_items = r.message.invoice; - me.removed_email = r.message.email_queue; - me.removed_customers = r.message.customers; - me.remove_doc_from_localstorage(); - me.remove_email_queue_from_localstorage(); - me.remove_customer_from_localstorage(); - me.prepare_customer_mapper(); - me.autocomplete_customers(); - me.render_list_customers(); - } - } - }) - } - }, - - get_submitted_invoice: function () { - var invoices = []; - var index = 1; - var docs = this.get_doc_from_localstorage(); - if (docs) { - invoices = $.map(docs, function (data) { - for (var key in data) { - if (data[key].docstatus == 1 && index < 50) { - index++ - data[key].docstatus = 0; - return data - } - } - }); - } - - return invoices - }, - - remove_doc_from_localstorage: function () { - var me = this; - this.si_docs = this.get_doc_from_localstorage(); - this.new_si_docs = []; - if (this.removed_items) { - $.each(this.si_docs, function (index, data) { - for (var key in data) { - if (!in_list(me.removed_items, key)) { - me.new_si_docs.push(data); - } - } - }) - this.removed_items = []; - this.si_docs = this.new_si_docs; - this.update_localstorage(); - } - }, - - remove_email_queue_from_localstorage: function() { - var me = this; - this.email_queue = this.get_email_queue() - if (this.removed_email) { - $.each(this.email_queue_list, function (index, data) { - if (in_list(me.removed_email, index)) { - delete me.email_queue[index] - } - }) - this.update_email_queue(); - } - }, - - remove_customer_from_localstorage: function() { - var me = this; - this.customer_details = this.get_customers_details() - if (this.removed_customers) { - $.each(this.customers_list, function (index, data) { - if (in_list(me.removed_customers, index)) { - delete me.customer_details[index] - } - }) - this.update_customer_in_localstorage(); - } - }, - - validate: function () { - var me = this; - this.customer_validate(); - this.validate_zero_qty_items(); - this.item_validate(); - this.validate_mode_of_payments(); - }, - - validate_zero_qty_items: function() { - this.remove_item = []; - - this.frm.doc.items.forEach(d => { - if (d.qty == 0) { - this.remove_item.push(d.idx); - } - }); - - if(this.remove_item) { - this.remove_zero_qty_items_from_cart(); - } - }, - - item_validate: function () { - if (this.frm.doc.items.length == 0) { - frappe.throw(__("Select items to save the invoice")) - } - }, - - validate_mode_of_payments: function () { - if (this.frm.doc.payments.length === 0) { - frappe.throw(__("Payment Mode is not configured. Please check, whether account has been set on Mode of Payments or on POS Profile.")) - } - }, - - validate_serial_no: function () { - var me = this; - var item_code = '' - var serial_no = ''; - for (var key in this.item_serial_no) { - item_code = key; - serial_no = me.item_serial_no[key][0]; - } - - if (this.items && this.items[0].has_serial_no && serial_no == "") { - this.refresh(); - frappe.throw(__(repl("Error: Serial no is mandatory for item %(item)s", { - 'item': this.items[0].item_code - }))) - } - - if (item_code && serial_no) { - $.each(this.frm.doc.items, function (index, data) { - if (data.item_code == item_code) { - if (in_list(data.serial_no.split('\n'), serial_no)) { - frappe.throw(__(repl("Serial no %(serial_no)s is already taken", { - 'serial_no': serial_no - }))) - } - } - }) - } - }, - - validate_serial_no_qty: function (args, item_code, field, value) { - var me = this; - if (args.item_code == item_code && args.serial_no - && field == 'qty' && cint(value) != value) { - args.qty = 0.0; - this.refresh(); - frappe.throw(__("Serial no item cannot be a fraction")) - } - - if (args.item_code == item_code && args.serial_no && args.serial_no.split('\n').length != cint(value)) { - args.qty = 0.0; - args.serial_no = '' - this.refresh(); - frappe.throw(__(repl("Total nos of serial no is not equal to quantity for item %(item)s.", { - 'item': item_code - }))) - } - }, - - mandatory_batch_no: function () { - var me = this; - if (this.items[0].has_batch_no && !this.item_batch_no[this.items[0].item_code]) { - frappe.prompt([{ - 'fieldname': 'batch', - 'fieldtype': 'Select', - 'label': __('Batch No'), - 'reqd': 1, - 'options': this.batch_no_data[this.items[0].item_code] - }], - function(values){ - me.item_batch_no[me.items[0].item_code] = values.batch; - const item = me.frm.doc.items.find( - ({ item_code }) => item_code === me.items[0].item_code - ); - if (item) { - item.batch_no = values.batch; - } - }, - __('Select Batch No')) - } - }, - - apply_pricing_rule: function () { - var me = this; - $.each(this.frm.doc["items"], function (n, item) { - var pricing_rule = me.get_pricing_rule(item) - me.validate_pricing_rule(pricing_rule) - if (pricing_rule.length) { - item.pricing_rule = pricing_rule[0].name; - item.margin_type = pricing_rule[0].margin_type; - item.price_list_rate = pricing_rule[0].price || item.price_list_rate; - item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount; - item.discount_percentage = pricing_rule[0].discount_percentage || 0.0; - me.apply_pricing_rule_on_item(item) - } else if (item.pricing_rule) { - item.price_list_rate = me.price_list_data[item.item_code] - item.margin_rate_or_amount = 0.0; - item.discount_percentage = 0.0; - item.pricing_rule = null; - me.apply_pricing_rule_on_item(item) - } - - if(item.discount_percentage > 0) { - me.apply_pricing_rule_on_item(item) - } - }) - }, - - get_pricing_rule: function (item) { - var me = this; - return $.grep(this.pricing_rules, function (data) { - if (item.qty >= data.min_qty && (item.qty <= (data.max_qty ? data.max_qty : item.qty))) { - if (me.validate_item_condition(data, item)) { - if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) { - return me.validate_condition(data) - } else { - return true - } - } - } - }) - }, - - validate_item_condition: function (data, item) { - var apply_on = frappe.model.scrub(data.apply_on); - - return (data.apply_on == 'Item Group') - ? this.validate_item_group(data.item_group, item.item_group) : (data[apply_on] == item[apply_on]); - }, - - validate_item_group: function (pr_item_group, cart_item_group) { - //pr_item_group = pricing rule's item group - //cart_item_group = cart item's item group - //this.item_groups has information about item group's lft and rgt - //for example: {'Foods': [12, 19]} - - pr_item_group = this.item_groups[pr_item_group] - cart_item_group = this.item_groups[cart_item_group] - - return (cart_item_group[0] >= pr_item_group[0] && - cart_item_group[1] <= pr_item_group[1]) - }, - - validate_condition: function (data) { - //This method check condition based on applicable for - var condition = this.get_mapper_for_pricing_rule(data)[data.applicable_for] - if (in_list(condition[1], condition[0])) { - return true - } - }, - - get_mapper_for_pricing_rule: function (data) { - return { - 'Customer': [data.customer, [this.frm.doc.customer]], - 'Customer Group': [data.customer_group, [this.frm.doc.customer_group, 'All Customer Groups']], - 'Territory': [data.territory, [this.frm.doc.territory, 'All Territories']], - 'Campaign': [data.campaign, [this.frm.doc.campaign]], - } - }, - - validate_pricing_rule: function (pricing_rule) { - //This method validate duplicate pricing rule - var pricing_rule_name = ''; - var priority = 0; - var pricing_rule_list = []; - var priority_list = [] - - if (pricing_rule.length > 1) { - - $.each(pricing_rule, function (index, data) { - pricing_rule_name += data.name + ',' - priority_list.push(data.priority) - if (priority <= data.priority) { - priority = data.priority - pricing_rule_list.push(data) - } - }) - - var count = 0 - $.each(priority_list, function (index, value) { - if (value == priority) { - count++ - } - }) - - if (priority == 0 || count > 1) { - frappe.throw(__(repl("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: %(pricing_rule)s", { - 'pricing_rule': pricing_rule_name - }))) - } - - return pricing_rule_list - } - }, - - validate_warehouse: function () { - if (this.items[0].is_stock_item && !this.items[0].default_warehouse && !this.pos_profile_data['warehouse']) { - frappe.throw(__("Default warehouse is required for selected item")) - } - }, - - get_actual_qty: function (item) { - this.actual_qty = 0.0; - - var warehouse = this.pos_profile_data['warehouse'] || item.default_warehouse; - if (warehouse && this.bin_data[item.item_code]) { - this.actual_qty = this.bin_data[item.item_code][warehouse] || 0; - this.actual_qty_dict[item.item_code] = this.actual_qty - } - - return this.actual_qty - }, - - update_customer_in_localstorage: function() { - var me = this; - try { - localStorage.setItem('customer_details', JSON.stringify(this.customer_details)); - } catch (e) { - frappe.throw(__("LocalStorage is full , did not save")) - } - } -}) \ No newline at end of file diff --git a/erpnext/accounts/page/pos/pos.json b/erpnext/accounts/page/pos/pos.json deleted file mode 100644 index abd918a4f5..0000000000 --- a/erpnext/accounts/page/pos/pos.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "content": null, - "creation": "2014-08-08 02:45:55.931022", - "docstatus": 0, - "doctype": "Page", - "icon": "fa fa-th", - "modified": "2014-08-08 05:59:33.045012", - "modified_by": "Administrator", - "module": "Accounts", - "name": "pos", - "owner": "Administrator", - "page_name": "pos", - "roles": [ - { - "role": "Sales User" - }, - { - "role": "Purchase User" - }, - { - "role": "Accounts User" - } - ], - "script": null, - "standard": "Yes", - "style": null, - "title": "POS" -} \ No newline at end of file diff --git a/erpnext/accounts/page/pos/test_pos.js b/erpnext/accounts/page/pos/test_pos.js deleted file mode 100644 index e5524a2d92..0000000000 --- a/erpnext/accounts/page/pos/test_pos.js +++ /dev/null @@ -1,52 +0,0 @@ -QUnit.test("test:Sales Invoice", function(assert) { - assert.expect(3); - let done = assert.async(); - - frappe.run_serially([ - () => { - return frappe.tests.make("POS Profile", [ - {naming_series: "SINV"}, - {pos_profile_name: "_Test POS Profile"}, - {country: "India"}, - {currency: "INR"}, - {write_off_account: "Write Off - FT"}, - {write_off_cost_center: "Main - FT"}, - {payments: [ - [ - {"default": 1}, - {"mode_of_payment": "Cash"} - ]] - } - ]); - }, - () => cur_frm.save(), - () => frappe.timeout(2), - () => { - assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested"); - }, - () => frappe.timeout(1), - () => { - return frappe.tests.make("Sales Invoice", [ - {customer: "Test Customer 2"}, - {is_pos: 1}, - {posting_date: frappe.datetime.get_today()}, - {due_date: frappe.datetime.get_today()}, - {items: [ - [ - {"item_code": "Test Product 1"}, - {"qty": 5}, - {"warehouse":'Stores - FT'} - ]] - } - ]); - }, - () => frappe.timeout(2), - () => cur_frm.save(), - () => frappe.timeout(2), - () => { - assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested"); - assert.equal(cur_frm.doc.payments[0].mode_of_payment, "Cash", "Default mode of payment tested"); - }, - () => done() - ]); -}); \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index b764eff12c..28a6519650 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -184,7 +184,7 @@ def set_price_list(party_details, party, party_type, given_price_list, pos=None) def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype): - if doctype not in ["Sales Invoice", "Purchase Invoice"]: + if doctype not in ["POS Invoice", "Sales Invoice", "Purchase Invoice"]: # not an invoice return { party_type.lower(): party diff --git a/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json index 1c5a195132..1aa1c02968 100644 --- a/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json +++ b/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json @@ -7,10 +7,10 @@ "docstatus": 0, "doctype": "Print Format", "font": "Default", - "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n

    \n\t{{ doc.company }}
    \n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
    GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
    \n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
    \n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n\t{% endif %}\n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{{ _(\"Customer\") }}:
    \n\t\t{{ doc.customer_name }}
    \n\t\t{{ customer_address }}\n\t{% endif %}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
    {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t
    {{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
    {{ _(\"Serial No\") }}: {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.rate }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- if doc.change_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n

    \n\t{{ doc.company }}
    \n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
    GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
    \n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
    \n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n\t{% endif %}\n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{{ _(\"Customer\") }}:
    \n\t\t{{ doc.customer_name }}
    \n\t\t{{ customer_address }}\n\t{% endif %}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
    {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t
    {{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
    {{ _(\"Serial No\") }}: {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.rate }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- if doc.change_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", "idx": 0, "line_breaks": 0, - "modified": "2019-12-09 17:39:23.356573", + "modified": "2020-04-29 16:39:12.936215", "modified_by": "Administrator", "module": "Accounts", "name": "GST POS Invoice", diff --git a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json index be699228c5..13a973d234 100644 --- a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json @@ -6,10 +6,10 @@ "doc_type": "Sales Invoice", "docstatus": 0, "doctype": "Print Format", - "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

    \n\t{{ doc.company }}
    \n\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
    {{ item.item_name }}{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.get_formatted(\"rate\") }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
    \n
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

    \n\t{{ doc.company }}
    \n\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
    {{ item.item_name }}{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.get_formatted(\"rate\") }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
    \n
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", "idx": 1, "line_breaks": 0, - "modified": "2019-12-09 17:40:53.183574", + "modified": "2020-04-29 16:35:07.043058", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 90c67f1e52..3f127a201e 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -213,7 +213,7 @@ def make_return_doc(doctype, source_name, target_doc=None): doc.return_against = source.name doc.ignore_pricing_rule = 1 doc.set_warehouse = "" - if doctype == "Sales Invoice": + if doctype == "Sales Invoice" or doctype == "POS Invoice": doc.is_pos = source.is_pos # look for Print Heading "Credit Note" @@ -229,7 +229,7 @@ def make_return_doc(doctype, source_name, target_doc=None): tax.tax_amount = -1 * tax.tax_amount if doc.get("is_return"): - if doc.doctype == 'Sales Invoice': + if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice': doc.set('payments', []) for data in source.payments: paid_amount = 0.00 @@ -241,8 +241,11 @@ def make_return_doc(doctype, source_name, target_doc=None): 'mode_of_payment': data.mode_of_payment, 'type': data.type, 'amount': -1 * paid_amount, - 'base_amount': -1 * base_paid_amount + 'base_amount': -1 * base_paid_amount, + 'account': data.account }) + if doc.is_pos: + doc.paid_amount = -1 * source.paid_amount elif doc.doctype == 'Purchase Invoice': doc.paid_amount = -1 * source.paid_amount doc.base_paid_amount = -1 * source.base_paid_amount @@ -287,7 +290,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.dn_detail = source_doc.name if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return - elif doctype == "Sales Invoice": + elif doctype == "Sales Invoice" or doctype == "POS Invoice": target_doc.sales_order = source_doc.sales_order target_doc.delivery_note = source_doc.delivery_note target_doc.so_detail = source_doc.so_detail diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index b465a106f0..0dc9878afd 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -85,6 +85,12 @@ status_map = { "Bank Transaction": [ ["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"], ["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"] + ], + "POS Opening Entry": [ + ["Draft", None], + ["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"], ] } diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 6449c71edd..572e1ca239 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -370,7 +370,7 @@ class calculate_taxes_and_totals(object): self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"]) - if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: + if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]: self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \ if self.doc.total_taxes_and_charges else self.doc.base_net_total else: @@ -619,17 +619,14 @@ class calculate_taxes_and_totals(object): self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc) def update_paid_amount_for_return(self, total_amount_to_pay): - default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment', - {'parent': self.doc.pos_profile, 'default': 1}, - ['mode_of_payment', 'type', 'account'], as_dict=1) + default_mode_of_payment = frappe.db.get_value('POS Payment Method', + {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1) self.doc.payments = [] if default_mode_of_payment: self.doc.append('payments', { 'mode_of_payment': default_mode_of_payment.mode_of_payment, - 'type': default_mode_of_payment.type, - 'account': default_mode_of_payment.account, 'amount': total_amount_to_pay }) else: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ea2611f698..2fb9d7f870 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -14,6 +14,7 @@ erpnext.patches.v4_0.apply_user_permissions erpnext.patches.v4_0.move_warehouse_user_to_restrictions erpnext.patches.v4_0.global_defaults_to_system_settings erpnext.patches.v4_0.update_incharge_name_to_sales_person_in_maintenance_schedule +execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28 execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16 execute:frappe.reload_doc('stock', 'doctype', 'warehouse') # 2017-04-24 execute:frappe.reload_doc('accounts', 'doctype', 'sales_invoice') # 2016-08-31 @@ -437,7 +438,6 @@ erpnext.patches.v8_5.remove_project_type_property_setter erpnext.patches.v8_7.sync_india_custom_fields erpnext.patches.v8_7.fix_purchase_receipt_status erpnext.patches.v8_6.rename_bom_update_tool -erpnext.patches.v8_7.set_offline_in_pos_settings #11-09-17 erpnext.patches.v8_9.add_setup_progress_actions #08-09-2017 #26-09-2017 #22-11-2017 #15-12-2017 erpnext.patches.v8_9.rename_company_sales_target_field erpnext.patches.v8_8.set_bom_rate_as_per_uom @@ -677,6 +677,8 @@ erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign 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.v12_0.retain_permission_rules_for_video_doctype 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 @@ -695,6 +697,7 @@ erpnext.patches.v12_0.update_bom_in_so_mr execute:frappe.delete_doc("Report", "Department Analytics") execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True) erpnext.patches.v12_0.update_uom_conversion_factor +execute:frappe.delete_doc_if_exists("Page", "pos") #29-05-2020 erpnext.patches.v13_0.delete_old_purchase_reports erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions erpnext.patches.v13_0.update_subscription @@ -708,6 +711,7 @@ execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 +erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020 erpnext.patches.v12_0.add_taxjar_integration_field erpnext.patches.v13_0.delete_report_requested_items_to_order erpnext.patches.v12_0.update_item_tax_template_company diff --git a/erpnext/patches/v11_0/refactor_autoname_naming.py b/erpnext/patches/v11_0/refactor_autoname_naming.py index d67c7235e8..5dc5d3bf0c 100644 --- a/erpnext/patches/v11_0/refactor_autoname_naming.py +++ b/erpnext/patches/v11_0/refactor_autoname_naming.py @@ -54,7 +54,7 @@ doctype_series_map = { 'Payroll Entry': 'HR-PRUN-.YYYY.-.#####', 'Period Closing Voucher': 'ACC-PCV-.YYYY.-.#####', 'Plant Analysis': 'AG-PLA-.YYYY.-.#####', - 'POS Closing Voucher': 'POS-CLO-.YYYY.-.#####', + 'POS Closing Entry': 'POS-CLO-.YYYY.-.#####', 'Prepared Report': 'SYS-PREP-.YYYY.-.#####', 'Program Enrollment': 'EDU-ENR-.YYYY.-.#####', 'Quotation Item': '', diff --git a/erpnext/patches/v12_0/rename_pos_closing_doctype.py b/erpnext/patches/v12_0/rename_pos_closing_doctype.py new file mode 100644 index 0000000000..8ca92ef65c --- /dev/null +++ b/erpnext/patches/v12_0/rename_pos_closing_doctype.py @@ -0,0 +1,25 @@ +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + if frappe.db.table_exists("POS Closing Voucher"): + if not frappe.db.exists("DocType", "POS Closing Entry"): + frappe.rename_doc('DocType', 'POS Closing Voucher', 'POS Closing Entry', force=True) + + if not frappe.db.exists('DocType', 'POS Closing Entry Taxes'): + frappe.rename_doc('DocType', 'POS Closing Voucher Taxes', 'POS Closing Entry Taxes', force=True) + + if not frappe.db.exists('DocType', 'POS Closing Voucher Details'): + frappe.rename_doc('DocType', 'POS Closing Voucher Details', 'POS Closing Entry Details', force=True) + + frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry') + frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Taxes') + frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Details') + + if frappe.db.exists("DocType", "POS Closing Voucher"): + frappe.delete_doc("DocType", "POS Closing Voucher") + frappe.delete_doc("DocType", "POS Closing Voucher Taxes") + frappe.delete_doc("DocType", "POS Closing Voucher Details") + frappe.delete_doc("DocType", "POS Closing Voucher Invoices") \ No newline at end of file diff --git a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py new file mode 100644 index 0000000000..ee7734053c --- /dev/null +++ b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py @@ -0,0 +1,20 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe + +def execute(): + '''`sales_invoice` field from loyalty point entry is splitted into `invoice_type` & `invoice` fields''' + + frappe.reload_doc("Accounts", "doctype", "loyalty_point_entry") + + if not frappe.db.has_column('Loyalty Point Entry', 'sales_invoice'): + return + + frappe.db.sql( + """UPDATE `tabLoyalty Point Entry` lpe + SET lpe.`invoice_type` = 'Sales Invoice', lpe.`invoice` = lpe.`sales_invoice` + WHERE lpe.`sales_invoice` IS NOT NULL + AND (lpe.`invoice` IS NULL OR lpe.`invoice` = '')""") \ No newline at end of file diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py new file mode 100644 index 0000000000..4a621b6a51 --- /dev/null +++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py @@ -0,0 +1,29 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe + +def execute(): + frappe.reload_doc("Selling", "doctype", "POS Payment Method") + pos_profiles = frappe.get_all("POS Profile") + + for pos_profile in pos_profiles: + if not pos_profile.get("payments"): return + + payments = frappe.db.sql(""" + select idx, parentfield, parenttype, parent, mode_of_payment, `default` from `tabSales Invoice Payment` where parent=%s + """, pos_profile.name, as_dict=1) + if payments: + for payment_mode in payments: + pos_payment_method = frappe.new_doc("POS Payment Method") + pos_payment_method.idx = payment_mode.idx + pos_payment_method.default = payment_mode.default + pos_payment_method.mode_of_payment = payment_mode.mode_of_payment + pos_payment_method.parent = payment_mode.parent + pos_payment_method.parentfield = payment_mode.parentfield + pos_payment_method.parenttype = payment_mode.parenttype + pos_payment_method.db_insert() + + frappe.db.sql("""delete from `tabSales Invoice Payment` where parent=%s""", pos_profile.name) diff --git a/erpnext/patches/v8_7/set_offline_in_pos_settings.py b/erpnext/patches/v8_7/set_offline_in_pos_settings.py deleted file mode 100644 index 7d2882e064..0000000000 --- a/erpnext/patches/v8_7/set_offline_in_pos_settings.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'pos_field') - frappe.reload_doc('accounts', 'doctype', 'pos_settings') - - doc = frappe.get_doc('POS Settings') - doc.use_pos_in_offline_mode = 1 - doc.save() \ No newline at end of file diff --git a/erpnext/public/css/pos.css b/erpnext/public/css/pos.css index 613a5ffa6e..e80e3ed126 100644 --- a/erpnext/public/css/pos.css +++ b/erpnext/public/css/pos.css @@ -1,179 +1,216 @@ -[data-route="point-of-sale"] .layout-main-section-wrapper { - margin-bottom: 0; -} -[data-route="point-of-sale"] .pos-items-wrapper { - max-height: calc(100vh - 210px); -} -.pos { - padding: 15px; -} -.list-item { - min-height: 40px; - height: auto; -} -.cart-container { - padding: 0 15px; - display: inline-block; - width: 39%; - vertical-align: top; -} -.item-container { - padding: 0 15px; - display: inline-block; - width: 60%; - vertical-align: top; -} -.search-field { - width: 60%; -} -.search-field input::placeholder { - font-size: 12px; -} -.item-group-field { - width: 40%; - margin-left: 15px; -} -.cart-wrapper { - margin-bottom: 12px; -} -.cart-wrapper .list-item__content:not(:first-child) { - justify-content: flex-end; -} -.cart-wrapper .list-item--head .list-item__content:nth-child(2) { - flex: 1.5; -} -.cart-items { - height: 150px; - overflow: auto; -} -.cart-items .list-item.current-item { - background-color: #fffce7; -} -.cart-items .list-item.current-item.qty input { - border: 1px solid #5E64FF; - font-weight: bold; -} -.cart-items .list-item.current-item.disc .discount { - font-weight: bold; -} -.cart-items .list-item.current-item.rate .rate { - font-weight: bold; -} -.cart-items .list-item .quantity { - flex: 1.5; -} -.cart-items input { - text-align: right; - height: 22px; - font-size: 12px; -} -.fields { - display: flex; -} -.pos-items-wrapper { - max-height: 480px; - overflow-y: auto; -} -.pos-items { - overflow: hidden; -} -.pos-item-wrapper { - display: flex; - flex-direction: column; - position: relative; - width: 25%; -} -.image-view-container { - display: block; -} -.image-view-container .image-field { - height: auto; -} -.empty-state { - height: 100%; - position: relative; -} -.empty-state span { - position: absolute; - color: #8D99A6; - font-size: 12px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} -@keyframes yellow-fade { - 0% { - background-color: #fffce7; - } - 100% { - background-color: transparent; - } -} -.highlight { - animation: yellow-fade 1s ease-in 1; -} -input[type=number]::-webkit-inner-spin-button, -input[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} -.number-pad { - border-collapse: collapse; - cursor: pointer; - display: table; -} -.num-row { - display: table-row; -} -.num-col { - display: table-cell; - border: 1px solid #d1d8dd; -} -.num-col > div { - width: 50px; - height: 50px; - text-align: center; - line-height: 50px; -} -.num-col.active { - background-color: #fffce7; -} -.num-col.brand-primary { - background-color: #5E64FF; - color: #ffffff; -} -.discount-amount .discount-inputs { - display: flex; - flex-direction: column; - padding: 15px 0; -} -.discount-amount input:first-child { - margin-bottom: 10px; -} -.taxes-and-totals { - border-top: 1px solid #d1d8dd; -} -.taxes-and-totals .taxes { - display: flex; - flex-direction: column; - padding: 15px 0; - align-items: flex-end; -} -.taxes-and-totals .taxes > div:first-child { - margin-bottom: 10px; -} -.grand-total { - border-top: 1px solid #d1d8dd; -} -.grand-total .list-item { - height: 60px; -} -.grand-total .grand-total-value { - font-size: 18px; -} -.rounded-total-value { - font-size: 18px; -} -.quantity-total { - font-size: 18px; -} +[data-route="point-of-sale"] .layout-main-section { border: none; font-size: 12px; } +[data-route="point-of-sale"] .layout-main-section-wrapper { margin-bottom: 0; } +[data-route="point-of-sale"] .pos-items-wrapper { max-height: calc(100vh - 210px); } +:root { --border-color: #d1d8dd; --text-color: #8d99a6; --primary: #5e64ff; } +[data-route="point-of-sale"] .flex { display: flex; } +[data-route="point-of-sale"] .grid { display: grid; } +[data-route="point-of-sale"] .absolute { position: absolute; } +[data-route="point-of-sale"] .relative { position: relative; } +[data-route="point-of-sale"] .abs-center { top: 50%; left: 50%; transform: translate(-50%, -50%); } +[data-route="point-of-sale"] .inline { display: inline; } +[data-route="point-of-sale"] .float-right { float: right; } +[data-route="point-of-sale"] .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } +[data-route="point-of-sale"] .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +[data-route="point-of-sale"] .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } +[data-route="point-of-sale"] .grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } +[data-route="point-of-sale"] .grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } +[data-route="point-of-sale"] .grid-cols-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } +[data-route="point-of-sale"] .gap-2 { grid-gap: 0.5rem; gap: 0.5rem; } +[data-route="point-of-sale"] .gap-4 { grid-gap: 1rem; gap: 1rem; } +[data-route="point-of-sale"] .gap-6 { grid-gap: 1.25rem; gap: 1.25rem; } +[data-route="point-of-sale"] .gap-8 { grid-gap: 1.5rem; gap: 1.5rem; } +[data-route="point-of-sale"] .row-gap-2 { grid-row-gap: 0.5rem; row-gap: 0.5rem; } +[data-route="point-of-sale"] .col-gap-4 { grid-column-gap: 1rem; column-gap: 1rem; } +[data-route="point-of-sale"] .col-span-2 { grid-column: span 2 / span 2; } +[data-route="point-of-sale"] .col-span-3 { grid-column: span 3 / span 3; } +[data-route="point-of-sale"] .col-span-4 { grid-column: span 4 / span 4; } +[data-route="point-of-sale"] .col-span-6 { grid-column: span 6 / span 6; } +[data-route="point-of-sale"] .col-span-10 { grid-column: span 10 / span 10; } +[data-route="point-of-sale"] .row-span-2 { grid-row: span 2 / span 2; } +[data-route="point-of-sale"] .grid-auto-row { grid-auto-rows: 5.5rem; } +[data-route="point-of-sale"] .d-none { display: none; } +[data-route="point-of-sale"] .flex-wrap { flex-wrap: wrap; } +[data-route="point-of-sale"] .flex-row { flex-direction: row; } +[data-route="point-of-sale"] .flex-col { flex-direction: column; } +[data-route="point-of-sale"] .flex-row-rev { flex-direction: row-reverse; } +[data-route="point-of-sale"] .flex-col-rev { flex-direction: column-reverse; } +[data-route="point-of-sale"] .flex-1 { flex: 1 1 0%; } +[data-route="point-of-sale"] .items-center { align-items: center; } +[data-route="point-of-sale"] .items-end { align-items: flex-end; } +[data-route="point-of-sale"] .f-grow-1 { flex-grow: 1; } +[data-route="point-of-sale"] .f-grow-2 { flex-grow: 2; } +[data-route="point-of-sale"] .f-grow-3 { flex-grow: 3; } +[data-route="point-of-sale"] .f-grow-4 { flex-grow: 4; } +[data-route="point-of-sale"] .f-shrink-0 { flex-shrink: 0; } +[data-route="point-of-sale"] .f-shrink-1 { flex-shrink: 1; } +[data-route="point-of-sale"] .f-shrink-2 { flex-shrink: 2; } +[data-route="point-of-sale"] .f-shrink-3 { flex-shrink: 3; } +[data-route="point-of-sale"] .shadow { box-shadow: 0 0px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 2px 0 rgba(0, 0, 0, 0.06); } +[data-route="point-of-sale"] .shadow-sm { box-shadow: 0 0.5px 3px 0 rgba(0, 0, 0, 0.125); } +[data-route="point-of-sale"] .shadow-inner { box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1); } +[data-route="point-of-sale"] .rounded { border-radius: 0.3rem; } +[data-route="point-of-sale"] .rounded-b { border-bottom-left-radius: 0.3rem; border-bottom-right-radius: 0.3rem; } +[data-route="point-of-sale"] .p-8 { padding: 2rem; } +[data-route="point-of-sale"] .p-16 { padding: 4rem; } +[data-route="point-of-sale"] .p-32 { padding: 8rem; } +[data-route="point-of-sale"] .p-6 { padding: 1.5rem; } +[data-route="point-of-sale"] .p-4 { padding: 1rem; } +[data-route="point-of-sale"] .p-3 { padding: 0.75rem; } +[data-route="point-of-sale"] .p-2 { padding: 0.5rem; } +[data-route="point-of-sale"] .m-8 { margin: 2rem; } +[data-route="point-of-sale"] .p-1 { padding: 0.25rem; } +[data-route="point-of-sale"] .pr-0 { padding-right: 0rem; } +[data-route="point-of-sale"] .pl-0 { padding-left: 0rem; } +[data-route="point-of-sale"] .pt-0 { padding-top: 0rem; } +[data-route="point-of-sale"] .pb-0 { padding-bottom: 0rem; } +[data-route="point-of-sale"] .mr-0 { margin-right: 0rem; } +[data-route="point-of-sale"] .ml-0 { margin-left: 0rem; } +[data-route="point-of-sale"] .mt-0 { margin-top: 0rem; } +[data-route="point-of-sale"] .mb-0 { margin-bottom: 0rem; } +[data-route="point-of-sale"] .pr-2 { padding-right: 0.5rem; } +[data-route="point-of-sale"] .pl-2 { padding-left: 0.5rem; } +[data-route="point-of-sale"] .pt-2 { padding-top: 0.5rem; } +[data-route="point-of-sale"] .pb-2 { padding-bottom: 0.5rem; } +[data-route="point-of-sale"] .pr-3 { padding-right: 0.75rem; } +[data-route="point-of-sale"] .pl-3 { padding-left: 0.75rem; } +[data-route="point-of-sale"] .pt-3 { padding-top: 0.75rem; } +[data-route="point-of-sale"] .pb-3 { padding-bottom: 0.75rem; } +[data-route="point-of-sale"] .pr-4 { padding-right: 1rem; } +[data-route="point-of-sale"] .pl-4 { padding-left: 1rem; } +[data-route="point-of-sale"] .pt-4 { padding-top: 1rem; } +[data-route="point-of-sale"] .pb-4 { padding-bottom: 1rem; } +[data-route="point-of-sale"] .mr-4 { margin-right: 1rem; } +[data-route="point-of-sale"] .ml-4 { margin-left: 1rem; } +[data-route="point-of-sale"] .mt-4 { margin-top: 1rem; } +[data-route="point-of-sale"] .mb-4 { margin-bottom: 1rem; } +[data-route="point-of-sale"] .mr-2 { margin-right: 0.5rem; } +[data-route="point-of-sale"] .ml-2 { margin-left: 0.5rem; } +[data-route="point-of-sale"] .mt-2 { margin-top: 0.5rem; } +[data-route="point-of-sale"] .mb-2 { margin-bottom: 0.5rem; } +[data-route="point-of-sale"] .mr-1 { margin-right: 0.25rem; } +[data-route="point-of-sale"] .ml-1 { margin-left: 0.25rem; } +[data-route="point-of-sale"] .mt-1 { margin-top: 0.25rem; } +[data-route="point-of-sale"] .mb-1 { margin-bottom: 0.25rem; } +[data-route="point-of-sale"] .mr-auto { margin-right: auto; } +[data-route="point-of-sale"] .ml-auto { margin-left: auto; } +[data-route="point-of-sale"] .mt-auto { margin-top: auto; } +[data-route="point-of-sale"] .mb-auto { margin-bottom: auto; } +[data-route="point-of-sale"] .pr-6 { padding-right: 1.5rem; } +[data-route="point-of-sale"] .pl-6 { padding-left: 1.5rem; } +[data-route="point-of-sale"] .pt-6 { padding-top: 1.5rem; } +[data-route="point-of-sale"] .pb-6 { padding-bottom: 1.5rem; } +[data-route="point-of-sale"] .mr-6 { margin-right: 1.5rem; } +[data-route="point-of-sale"] .ml-6 { margin-left: 1.5rem; } +[data-route="point-of-sale"] .mt-6 { margin-top: 1.5rem; } +[data-route="point-of-sale"] .mb-6 { margin-bottom: 1.5rem; } +[data-route="point-of-sale"] .mr-8 { margin-right: 2rem; } +[data-route="point-of-sale"] .ml-8 { margin-left: 2rem; } +[data-route="point-of-sale"] .mt-8 { margin-top: 2rem; } +[data-route="point-of-sale"] .mb-8 { margin-bottom: 2rem; } +[data-route="point-of-sale"] .pr-8 { padding-right: 2rem; } +[data-route="point-of-sale"] .pl-8 { padding-left: 2rem; } +[data-route="point-of-sale"] .pt-8 { padding-top: 2rem; } +[data-route="point-of-sale"] .pb-8 { padding-bottom: 2rem; } +[data-route="point-of-sale"] .pr-16 { padding-right: 4rem; } +[data-route="point-of-sale"] .pl-16 { padding-left: 4rem; } +[data-route="point-of-sale"] .pt-16 { padding-top: 4rem; } +[data-route="point-of-sale"] .pb-16 { padding-bottom: 4rem; } +[data-route="point-of-sale"] .w-full { width: 100%; } +[data-route="point-of-sale"] .h-full { height: 100%; } +[data-route="point-of-sale"] .w-quarter { width: 25%; } +[data-route="point-of-sale"] .w-half { width: 50%; } +[data-route="point-of-sale"] .w-66 { width: 66.66%; } +[data-route="point-of-sale"] .w-33 { width: 33.33%; } +[data-route="point-of-sale"] .w-60 { width: 60%; } +[data-route="point-of-sale"] .w-40 { width: 40%; } +[data-route="point-of-sale"] .w-fit { width: fit-content; } +[data-route="point-of-sale"] .w-6 { width: 2rem; } +[data-route="point-of-sale"] .h-6 { min-height: 2rem; height: 2rem; } +[data-route="point-of-sale"] .w-8 { width: 2.5rem; } +[data-route="point-of-sale"] .h-8 { min-height: 2.5rem; height: 2.5rem; } +[data-route="point-of-sale"] .w-10 { width: 3rem; } +[data-route="point-of-sale"] .h-10 { min-height:3rem; height: 3rem; } +[data-route="point-of-sale"] .h-12 { min-height: 3.3rem; height: 3.3rem; } +[data-route="point-of-sale"] .w-12 { width: 3.3rem; } +[data-route="point-of-sale"] .h-14 { min-height: 4.2rem; height: 4.2rem; } +[data-route="point-of-sale"] .h-16 { min-height: 4.6rem; height: 4.6rem; } +[data-route="point-of-sale"] .h-18 { min-height: 5rem; height: 5rem; } +[data-route="point-of-sale"] .w-18 { width: 5.4rem; } +[data-route="point-of-sale"] .w-24 { width: 7.2rem; } +[data-route="point-of-sale"] .w-26 { width: 8.4rem; } +[data-route="point-of-sale"] .h-24 { min-height: 7.2rem; height: 7.2rem; } +[data-route="point-of-sale"] .h-32 { min-height: 9.6rem; height: 9.6rem; } +[data-route="point-of-sale"] .w-46 { width: 15rem; } +[data-route="point-of-sale"] .h-46 { min-height:15rem; height: 15rem; } +[data-route="point-of-sale"] .h-100 { height: 100vh; } +[data-route="point-of-sale"] .mx-h-70 { max-height: 67rem; } +[data-route="point-of-sale"] .border-grey-300 { border-color: #e2e8f0; } +[data-route="point-of-sale"] .border-grey { border: 1px solid #d1d8dd; } +[data-route="point-of-sale"] .border-white { border: 1px solid #fff; } +[data-route="point-of-sale"] .border-b-grey { border-bottom: 1px solid #d1d8dd; } +[data-route="point-of-sale"] .border-t-grey { border-top: 1px solid #d1d8dd; } +[data-route="point-of-sale"] .border-r-grey { border-right: 1px solid #d1d8dd; } +[data-route="point-of-sale"] .text-dark-grey { color: #5f5f5f; } +[data-route="point-of-sale"] .text-grey { color: #8d99a6; } +[data-route="point-of-sale"] .text-grey-100 { color: #d1d8dd; } +[data-route="point-of-sale"] .text-grey-200 { color: #a0aec0; } +[data-route="point-of-sale"] .bg-green-200 { background-color: #c6f6d5; } +[data-route="point-of-sale"] .text-bold { font-weight: bold; } +[data-route="point-of-sale"] .italic { font-style: italic; } +[data-route="point-of-sale"] .font-weight-450 { font-weight: 450; } +[data-route="point-of-sale"] .justify-around { justify-content: space-around; } +[data-route="point-of-sale"] .justify-between { justify-content: space-between; } +[data-route="point-of-sale"] .justify-center { justify-content: center; } +[data-route="point-of-sale"] .justify-end { justify-content: flex-end; } +[data-route="point-of-sale"] .bg-white { background-color: white; } +[data-route="point-of-sale"] .bg-light-grey { background-color: #f0f4f7; } +[data-route="point-of-sale"] .bg-grey-100 { background-color: #f7fafc; } +[data-route="point-of-sale"] .bg-grey-200 { background-color: #edf2f7; } +[data-route="point-of-sale"] .bg-grey { background-color: #f4f5f6; } +[data-route="point-of-sale"] .text-center { text-align: center; } +[data-route="point-of-sale"] .text-right { text-align: right; } +[data-route="point-of-sale"] .text-sm { font-size: 1rem; } +[data-route="point-of-sale"] .text-md-0 { font-size: 1.25rem; } +[data-route="point-of-sale"] .text-md { font-size: 1.4rem; } +[data-route="point-of-sale"] .text-lg { font-size: 1.6rem; } +[data-route="point-of-sale"] .text-xl { font-size: 2.2rem; } +[data-route="point-of-sale"] .text-2xl { font-size: 2.8rem; } +[data-route="point-of-sale"] .text-2-5xl { font-size: 3rem; } +[data-route="point-of-sale"] .text-3xl { font-size: 3.8rem; } +[data-route="point-of-sale"] .text-6xl { font-size: 4.8rem; } +[data-route="point-of-sale"] .line-through { text-decoration: line-through; } +[data-route="point-of-sale"] .text-primary { color: #5e64ff; } +[data-route="point-of-sale"] .text-white { color: #fff; } +[data-route="point-of-sale"] .text-green-500 { color: #48bb78; } +[data-route="point-of-sale"] .bg-primary { background-color: #5e64ff; } +[data-route="point-of-sale"] .border-primary { border-color: #5e64ff; } +[data-route="point-of-sale"] .text-danger { color: #e53e3e; } +[data-route="point-of-sale"] .scroll-x { overflow-x: scroll;overflow-y: hidden; } +[data-route="point-of-sale"] .scroll-y { overflow-y: scroll;overflow-x: hidden; } +[data-route="point-of-sale"] .overflow-hidden { overflow: hidden; } +[data-route="point-of-sale"] .whitespace-nowrap { white-space: nowrap; } +[data-route="point-of-sale"] .sticky { position: sticky; top: -1px; } +[data-route="point-of-sale"] .bg-white { background-color: #fff; } +[data-route="point-of-sale"] .bg-selected { background-color: #fffdf4; } +[data-route="point-of-sale"] .border-dashed { border-width:1px; border-style: dashed; } +[data-route="point-of-sale"] .z-100 { z-index: 100; } + +[data-route="point-of-sale"] .frappe-control { margin: 0 !important; width: 100%; } +[data-route="point-of-sale"] .form-control { font-size: 12px; } +[data-route="point-of-sale"] .form-group { margin: 0 !important; } +[data-route="point-of-sale"] .pointer { cursor: pointer; } +[data-route="point-of-sale"] .no-select { user-select: none; } +[data-route="point-of-sale"] .item-wrapper:hover { transform: scale(1.02, 1.02); } +[data-route="point-of-sale"] .hover-underline:hover { text-decoration: underline; } +[data-route="point-of-sale"] .item-wrapper { transition: scale 0.2s ease-in-out; } +[data-route="point-of-sale"] .cart-items-section .cart-item-wrapper:not(:first-child) { border-top: none; } +[data-route="point-of-sale"] .customer-transactions .invoice-wrapper:not(:first-child) { border-top: none; } + +[data-route="point-of-sale"] .payment-summary-wrapper:last-child { border-bottom: none; } +[data-route="point-of-sale"] .item-summary-wrapper:last-child { border-bottom: none; } +[data-route="point-of-sale"] .total-summary-wrapper:last-child { border-bottom: none; } +[data-route="point-of-sale"] .invoices-container .invoice-wrapper:last-child { border-bottom: none; } +[data-route="point-of-sale"] .summary-btns:last-child { margin-right: 0px; } +[data-route="point-of-sale"] ::-webkit-scrollbar { width: 1px } + +[data-route="point-of-sale"] .indicator.grey::before { background-color: #8d99a6; } \ No newline at end of file diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b72ceb2113..405a33c72a 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -34,12 +34,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.calculate_discount_amount(); // Advance calculation applicable to Sales /Purchase Invoice - if(in_list(["Sales Invoice", "Purchase Invoice"], this.frm.doc.doctype) + if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype) && this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) { this.calculate_total_advance(update_paid_amount); } - if (this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_pos && + if (in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_pos && this.frm.doc.is_return) { this.update_paid_amount_for_return(); } @@ -425,7 +425,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ ? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment) : this.frm.doc.net_total); - if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) { + if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) { this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ? flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total; } else { @@ -604,7 +604,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ // NOTE: // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice // total_advance is only for non POS Invoice - if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_return){ + if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){ this.calculate_paid_amount(); } @@ -612,7 +612,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); - if(in_list(["Sales Invoice", "Purchase Invoice"], this.frm.doc.doctype)) { + if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) { var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; if(this.frm.doc.party_account_currency == this.frm.doc.currency) { @@ -634,7 +634,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.frm.refresh_field("base_paid_amount"); } - if(this.frm.doc.doctype == "Sales Invoice") { + if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) { let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount) ? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total")) : total_amount_to_pay; @@ -691,11 +691,13 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) { $.each(this.frm.doc['payments'] || [], function(index, data) { if(data.default && payment_status && total_amount_to_pay > 0) { - data.base_amount = flt(total_amount_to_pay, precision("base_amount")); - data.amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount")); + let base_amount = flt(total_amount_to_pay, precision("base_amount", data)); + frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount); + let amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data)); + frappe.model.set_value(data.doctype, data.name, "amount", amount); payment_status = false; } else if(me.frm.doc.paid_amount) { - data.amount = 0.0; + frappe.model.set_value(data.doctype, data.name, "amount", 0.0); } }); } @@ -707,7 +709,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ var base_paid_amount = 0.0; if(this.frm.doc.is_pos) { $.each(this.frm.doc['payments'] || [], function(index, data){ - data.base_amount = flt(data.amount * me.frm.doc.conversion_rate, precision("base_amount")); + data.base_amount = flt(data.amount * me.frm.doc.conversion_rate, precision("base_amount", data)); paid_amount += data.amount; base_paid_amount += data.base_amount; }); @@ -719,14 +721,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ paid_amount += flt(this.frm.doc.loyalty_amount / me.frm.doc.conversion_rate, precision("paid_amount")); } - this.frm.doc.paid_amount = flt(paid_amount, precision("paid_amount")); - this.frm.doc.base_paid_amount = flt(base_paid_amount, precision("base_paid_amount")); + this.frm.set_value('paid_amount', flt(paid_amount, precision("paid_amount"))); + this.frm.set_value('base_paid_amount', flt(base_paid_amount, precision("base_paid_amount"))); }, calculate_change_amount: function(){ this.frm.doc.change_amount = 0.0; this.frm.doc.base_change_amount = 0.0; - if(this.frm.doc.doctype == "Sales Invoice" + if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) { var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; }); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3c56a636bd..4e50f3d7f6 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -651,7 +651,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ let child = frappe.model.add_child(me.frm.doc, "taxes"); child.charge_type = "On Net Total"; child.account_head = tax; - child.rate = 0; + child.rate = rate; } }); } diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index d75633e5a9..42f9cabc27 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -43,6 +43,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ label: __(me.warehouse_details.type), default: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '', onchange: function(e) { + me.warehouse_details.name = this.get_value(); if(me.has_batch && !me.has_serial_no) { fields = fields.concat(me.get_batch_fields()); @@ -50,7 +51,6 @@ erpnext.SerialNoBatchSelector = Class.extend({ fields = fields.concat(me.get_serial_no_fields()); } - me.warehouse_details.name = this.get_value(); var batches = this.layout.fields_dict.batches; if(batches) { batches.grid.df.data = []; @@ -98,8 +98,13 @@ erpnext.SerialNoBatchSelector = Class.extend({ numbers.then((data) => { let auto_fetched_serial_numbers = data.message; let records_length = auto_fetched_serial_numbers.length; + if (!records_length) { + const warehouse = me.dialog.fields_dict.warehouse.get_value().bold(); + frappe.msgprint(__(`Serial numbers unavailable for Item ${me.item.item_code.bold()} + under warehouse ${warehouse}. Please try changing warehouse.`)); + } if (records_length < qty) { - frappe.msgprint(`Fetched only ${records_length} serial numbers.`); + frappe.msgprint(__(`Fetched only ${records_length} available serial numbers.`)); } let serial_no_list_field = this.dialog.fields_dict.serial_no; numbers = auto_fetched_serial_numbers.join('\n'); @@ -445,6 +450,28 @@ erpnext.SerialNoBatchSelector = Class.extend({ serial_no_filters['warehouse'] = me.warehouse_details.name; } + if (me.frm.doc.doctype === 'POS Invoice' && !this.showing_reserved_serial_nos_error) { + frappe.call({ + method: "erpnext.stock.doctype.serial_no.serial_no.get_pos_reserved_serial_nos", + args: { + item_code: me.item_code, + warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '' + } + }).then((data) => { + if (!data.message[1].length) { + this.showing_reserved_serial_nos_error = true; + const warehouse = me.dialog.fields_dict.warehouse.get_value().bold(); + const d = frappe.msgprint(__(`Serial numbers unavailable for Item ${me.item.item_code.bold()} + under warehouse ${warehouse}. Please try changing warehouse.`)); + d.get_close_btn().on('click', () => { + this.showing_reserved_serial_nos_error = false; + d.hide(); + }); + } + serial_no_filters['name'] = ["not in", data.message[0]] + }) + } + return [ {fieldtype: 'Section Break', label: __('Serial Numbers')}, { diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js deleted file mode 100644 index f24caf767f..0000000000 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('POS Closing Voucher', { - onload: function(frm) { - frm.set_query("pos_profile", function(doc) { - return { - filters: { - 'user': doc.user - } - }; - }); - - frm.set_query("user", function(doc) { - return { - query: "erpnext.selling.doctype.pos_closing_voucher.pos_closing_voucher.get_cashiers", - filters: { - 'parent': doc.pos_profile - } - }; - }); - }, - - total_amount: function(frm) { - get_difference_amount(frm); - }, - custody_amount: function(frm){ - get_difference_amount(frm); - }, - expense_amount: function(frm){ - get_difference_amount(frm); - }, - refresh: function(frm) { - get_closing_voucher_details(frm); - }, - period_start_date: function(frm) { - get_closing_voucher_details(frm); - }, - period_end_date: function(frm) { - get_closing_voucher_details(frm); - }, - company: function(frm) { - get_closing_voucher_details(frm); - }, - pos_profile: function(frm) { - get_closing_voucher_details(frm); - }, - user: function(frm) { - get_closing_voucher_details(frm); - }, -}); - -frappe.ui.form.on('POS Closing Voucher Details', { - collected_amount: function(doc, cdt, cdn) { - var row = locals[cdt][cdn]; - frappe.model.set_value(cdt, cdn, "difference", row.collected_amount - row.expected_amount); - } -}); - -var get_difference_amount = function(frm){ - frm.doc.difference = frm.doc.total_amount - frm.doc.custody_amount - frm.doc.expense_amount; - refresh_field("difference"); -}; - -var get_closing_voucher_details = function(frm) { - if (frm.doc.period_end_date && frm.doc.period_start_date && frm.doc.company && frm.doc.pos_profile && frm.doc.user) { - frappe.call({ - method: "get_closing_voucher_details", - doc: frm.doc, - callback: function(r) { - if (r.message) { - refresh_field("payment_reconciliation"); - refresh_field("sales_invoices_summary"); - refresh_field("taxes"); - - refresh_field("grand_total"); - refresh_field("net_total"); - refresh_field("total_quantity"); - refresh_field("total_amount"); - - frm.get_field("payment_reconciliation_details").$wrapper.html(r.message); - } - } - }); - } - -}; diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json deleted file mode 100644 index 2ac57794b4..0000000000 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json +++ /dev/null @@ -1,1016 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "POS-CLO-.YYYY.-.#####", - "beta": 0, - "creation": "2018-05-28 19:06:40.830043", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "period_start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Period Start Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "period_end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Period End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pos_profile", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "POS Profile", - "length": 0, - "no_copy": 0, - "options": "POS Profile", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cashier", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expense_details_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Expense Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expense_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Expense Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "custody_amount", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amount in Custody", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_13", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Collected Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "difference", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Difference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_reconciliation_details", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_11", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Modes of Payment", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_reconciliation", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Reconciliation", - "length": 0, - "no_copy": 0, - "options": "POS Closing Voucher Details", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "section_break_13", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "grand_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Grand Total", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "net_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Total", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_quantity", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes", - "length": 0, - "no_copy": 0, - "options": "POS Closing Voucher Taxes", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "section_break_12", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Linked Invoices", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_invoices_summary", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Invoices Summary", - "length": 0, - "no_copy": 0, - "options": "POS Closing Voucher Invoices", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_14", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "POS Closing Voucher", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-28 12:33:45.217813", - "modified_by": "Administrator", - "module": "Selling", - "name": "POS Closing Voucher", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py deleted file mode 100644 index bb5f83ed05..0000000000 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.model.document import Document -from collections import defaultdict -from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data -import json - -class POSClosingVoucher(Document): - def get_closing_voucher_details(self): - filters = { - 'doc': self.name, - 'from_date': self.period_start_date, - 'to_date': self.period_end_date, - 'company': self.company, - 'pos_profile': self.pos_profile, - 'user': self.user, - 'is_pos': 1 - } - - invoice_list = get_invoices(filters) - self.set_invoice_list(invoice_list) - - sales_summary = get_sales_summary(invoice_list) - self.set_sales_summary_values(sales_summary) - self.total_amount = sales_summary['grand_total'] - - if not self.get('payment_reconciliation'): - mop = get_mode_of_payment_details(invoice_list) - self.set_mode_of_payments(mop) - - taxes = get_tax_details(invoice_list) - self.set_taxes(taxes) - - return self.get_payment_reconciliation_details() - - def validate(self): - user = frappe.get_all('POS Closing Voucher', - filters = { - 'user': self.user, - 'docstatus': 1 - }, - or_filters = { - 'period_start_date': ('between', [self.period_start_date, self.period_end_date]), - 'period_end_date': ('between', [self.period_start_date, self.period_end_date]) - }) - - if user: - frappe.throw(_("POS Closing Voucher alreday exists for {0} between date {1} and {2}") - .format(self.user, self.period_start_date, self.period_end_date)) - - def set_invoice_list(self, invoice_list): - self.sales_invoices_summary = [] - for invoice in invoice_list: - self.append('sales_invoices_summary', { - 'invoice': invoice['name'], - 'qty_of_items': invoice['pos_total_qty'], - 'grand_total': invoice['grand_total'] - }) - - def set_sales_summary_values(self, sales_summary): - self.grand_total = sales_summary['grand_total'] - self.net_total = sales_summary['net_total'] - self.total_quantity = sales_summary['total_qty'] - - def set_mode_of_payments(self, mop): - self.payment_reconciliation = [] - for m in mop: - self.append('payment_reconciliation', { - 'mode_of_payment': m['name'], - 'expected_amount': m['amount'] - }) - - def set_taxes(self, taxes): - self.taxes = [] - for tax in taxes: - self.append('taxes', { - 'rate': tax['rate'], - 'amount': tax['amount'] - }) - - def get_payment_reconciliation_details(self): - currency = get_company_currency(self) - return frappe.render_template("erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html", - {"data": self, "currency": currency}) - -@frappe.whitelist() -def get_cashiers(doctype, txt, searchfield, start, page_len, filters): - cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) - cashiers = [cashier for cashier in set(c['user'] for c in cashiers_list)] - return [[c] for c in cashiers] - -def get_mode_of_payment_details(invoice_list): - mode_of_payment_details = [] - invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list]) - if invoice_list: - inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date, - ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount - from `tabSales Invoice` a, `tabSales Invoice Payment` b - where a.name = b.parent - and a.name in ({invoice_list_names}) - group by a.owner, a.posting_date, mode_of_payment - union - select a.owner,a.posting_date, - ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_paid_amount) as paid_amount - from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c - where a.name = c.reference_name - and b.name = c.parent - and a.name in ({invoice_list_names}) - group by a.owner, a.posting_date, mode_of_payment - union - select a.owner, a.posting_date, - ifnull(a.voucher_type,'') as mode_of_payment, sum(b.credit) - from `tabJournal Entry` a, `tabJournal Entry Account` b - where a.name = b.parent - and a.docstatus = 1 - and b.reference_type = "Sales Invoice" - and b.reference_name in ({invoice_list_names}) - group by a.owner, a.posting_date, mode_of_payment - """.format(invoice_list_names=invoice_list_names), as_dict=1) - - inv_change_amount = frappe.db.sql("""select a.owner, a.posting_date, - ifnull(b.mode_of_payment, '') as mode_of_payment, sum(a.base_change_amount) as change_amount - from `tabSales Invoice` a, `tabSales Invoice Payment` b - where a.name = b.parent - and a.name in ({invoice_list_names}) - and b.mode_of_payment = 'Cash' - and a.base_change_amount > 0 - group by a.owner, a.posting_date, mode_of_payment""".format(invoice_list_names=invoice_list_names), as_dict=1) - - for d in inv_change_amount: - for det in inv_mop_detail: - if det["owner"] == d["owner"] and det["posting_date"] == d["posting_date"] and det["mode_of_payment"] == d["mode_of_payment"]: - paid_amount = det["paid_amount"] - d["change_amount"] - det["paid_amount"] = paid_amount - - payment_details = defaultdict(int) - for d in inv_mop_detail: - payment_details[d.mode_of_payment] += d.paid_amount - - for m in payment_details: - mode_of_payment_details.append({'name': m, 'amount': payment_details[m]}) - - return mode_of_payment_details - -def get_tax_details(invoice_list): - tax_breakup = [] - tax_details = defaultdict(int) - for invoice in invoice_list: - doc = frappe.get_doc("Sales Invoice", invoice.name) - itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) - - if itemised_tax: - for a in itemised_tax: - for b in itemised_tax[a]: - for c in itemised_tax[a][b]: - if c == 'tax_rate': - tax_details[itemised_tax[a][b][c]] += itemised_tax[a][b]['tax_amount'] - - for t in tax_details: - tax_breakup.append({'rate': t, 'amount': tax_details[t]}) - - return tax_breakup - -def get_sales_summary(invoice_list): - net_total = sum(item['net_total'] for item in invoice_list) - grand_total = sum(item['grand_total'] for item in invoice_list) - total_qty = sum(item['pos_total_qty'] for item in invoice_list) - - return {'net_total': net_total, 'grand_total': grand_total, 'total_qty': total_qty} - -def get_company_currency(doc): - currency = frappe.get_cached_value('Company', doc.company, "default_currency") - return frappe.get_doc('Currency', currency) - -def get_invoices(filters): - return frappe.db.sql("""select a.name, a.base_grand_total as grand_total, - a.base_net_total as net_total, a.pos_total_qty - from `tabSales Invoice` a - where a.docstatus = 1 and a.posting_date >= %(from_date)s - and a.posting_date <= %(to_date)s and a.company=%(company)s - and a.pos_profile = %(pos_profile)s and a.is_pos = %(is_pos)s - and a.owner = %(user)s""", - filters, as_dict=1) diff --git a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py b/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py deleted file mode 100644 index 8899aaff41..0000000000 --- a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals -import frappe -import unittest -from frappe.utils import nowdate -from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile - -class TestPOSClosingVoucher(unittest.TestCase): - def test_pos_closing_voucher(self): - old_user = frappe.session.user - user = 'test@example.com' - test_user = frappe.get_doc('User', user) - - roles = ("Accounts Manager", "Accounts User", "Sales Manager") - test_user.add_roles(*roles) - frappe.set_user(user) - - pos_profile = make_pos_profile() - pos_profile.append('applicable_for_users', { - 'default': 1, - 'user': user - }) - - pos_profile.save() - - si1 = create_sales_invoice(is_pos=1, rate=3500, do_not_submit=1) - si1.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500 - }) - si1.submit() - - si2 = create_sales_invoice(is_pos=1, rate=3200, do_not_submit=1) - si2.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 - }) - si2.submit() - - pcv_doc = create_pos_closing_voucher(user=user, - pos_profile=pos_profile.name, collected_amount=6700) - - pcv_doc.get_closing_voucher_details() - - self.assertEqual(pcv_doc.total_quantity, 2) - self.assertEqual(pcv_doc.net_total, 6700) - - payment = pcv_doc.payment_reconciliation[0] - self.assertEqual(payment.mode_of_payment, 'Cash') - - si1.load_from_db() - si1.cancel() - - si2.load_from_db() - si2.cancel() - - test_user.load_from_db() - test_user.remove_roles(*roles) - - frappe.set_user(old_user) - frappe.db.sql("delete from `tabPOS Profile`") - -def create_pos_closing_voucher(**args): - args = frappe._dict(args) - - doc = frappe.get_doc({ - 'doctype': 'POS Closing Voucher', - 'period_start_date': args.period_start_date or nowdate(), - 'period_end_date': args.period_end_date or nowdate(), - 'posting_date': args.posting_date or nowdate(), - 'company': args.company or "_Test Company", - 'pos_profile': args.pos_profile, - 'user': args.user or "Administrator", - }) - - doc.get_closing_voucher_details() - if doc.get('payment_reconciliation'): - doc.payment_reconciliation[0].collected_amount = (args.collected_amount or - doc.payment_reconciliation[0].expected_amount) - - doc.save() - return doc \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json b/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json deleted file mode 100644 index a52688462a..0000000000 --- a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-05-28 19:10:47.580174", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Mode of Payment", - "length": 0, - "no_copy": 0, - "options": "Mode of Payment", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.0", - "fieldname": "collected_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Collected Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expected_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Expected Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "difference", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Difference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-29 17:47:16.311557", - "modified_by": "Administrator", - "module": "Selling", - "name": "POS Closing Voucher Details", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json b/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json deleted file mode 100644 index 7304550784..0000000000 --- a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-05-29 14:50:08.687453", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "invoice", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Invoices", - "length": 0, - "no_copy": 0, - "options": "Sales Invoice", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty_of_items", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Quantity of Items", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "grand_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Grand Total", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-29 17:46:46.539993", - "modified_by": "Administrator", - "module": "Selling", - "name": "POS Closing Voucher Invoices", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json b/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json deleted file mode 100644 index 3089e0621f..0000000000 --- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-05-30 09:11:22.535470", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-30 09:11:22.535470", - "modified_by": "Administrator", - "module": "Selling", - "name": "POS Closing Voucher Taxes", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/onscan.js b/erpnext/selling/page/point_of_sale/onscan.js new file mode 100644 index 0000000000..428dc75cf8 --- /dev/null +++ b/erpnext/selling/page/point_of_sale/onscan.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t()):e.onScan=t()}(this,function(){var d={attachTo:function(e,t){if(void 0!==e.scannerDetectionData)throw new Error("onScan.js is already initialized for DOM element "+e);var n={onScan:function(e,t){},onScanError:function(e){},onKeyProcess:function(e,t){},onKeyDetect:function(e,t){},onPaste:function(e,t){},keyCodeMapper:function(e){return d.decodeKeyEvent(e)},onScanButtonLongPress:function(){},scanButtonKeyCode:!1,scanButtonLongPressTime:500,timeBeforeScanTest:100,avgTimeByChar:30,minLength:6,suffixKeyCodes:[9,13],prefixKeyCodes:[],ignoreIfFocusOn:!1,stopPropagation:!1,preventDefault:!1,captureEvents:!1,reactToKeydown:!0,reactToPaste:!1,singleScanQty:1};return t=this._mergeOptions(n,t),e.scannerDetectionData={options:t,vars:{firstCharTime:0,lastCharTime:0,accumulatedString:"",testTimer:!1,longPressTimeStart:0,longPressed:!1}},!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste,t.captureEvents),!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp,t.captureEvents),!0!==t.reactToKeydown&&!1===t.scanButtonKeyCode||e.addEventListener("keydown",this._handleKeyDown,t.captureEvents),this},detachFrom:function(e){e.scannerDetectionData.options.reactToPaste&&e.removeEventListener("paste",this._handlePaste),!1!==e.scannerDetectionData.options.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp),e.removeEventListener("keydown",this._handleKeyDown),e.scannerDetectionData=void 0},getOptions:function(e){return e.scannerDetectionData.options},setOptions:function(e,t){switch(e.scannerDetectionData.options.reactToPaste){case!0:!1===t.reactToPaste&&e.removeEventListener("paste",this._handlePaste);break;case!1:!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste)}switch(e.scannerDetectionData.options.scanButtonKeyCode){case!1:!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp);break;default:!1===t.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp)}return e.scannerDetectionData.options=this._mergeOptions(e.scannerDetectionData.options,t),this._reinitialize(e),this},decodeKeyEvent:function(e){var t=this._getNormalizedKeyNum(e);switch(!0){case 48<=t&&t<=90:case 106<=t&&t<=111:if(void 0!==e.key&&""!==e.key)return e.key;var n=String.fromCharCode(t);switch(e.shiftKey){case!1:n=n.toLowerCase();break;case!0:n=n.toUpperCase()}return n;case 96<=t&&t<=105:return t-96}return""},simulate:function(e,t){return this._reinitialize(e),Array.isArray(t)?t.forEach(function(e){var t={};"object"!=typeof e&&"function"!=typeof e||null===e?t.keyCode=parseInt(e):t=e;var n=new KeyboardEvent("keydown",t);document.dispatchEvent(n)}):this._validateScanCode(e,t),this},_reinitialize:function(e){var t=e.scannerDetectionData.vars;t.firstCharTime=0,t.lastCharTime=0,t.accumulatedString=""},_isFocusOnIgnoredElement:function(e){var t=e.scannerDetectionData.options.ignoreIfFocusOn;if(!t)return!1;var n=document.activeElement;if(Array.isArray(t)){for(var a=0;at.length*i.avgTimeByChar:c={message:"Receieved code was not entered in time"};break;default:return i.onScan.call(e,t,o),n=new CustomEvent("scan",{detail:{scanCode:t,qty:o}}),e.dispatchEvent(n),d._reinitialize(e),!0}return c.scanCode=t,c.scanDuration=s-r,c.avgTimeByChar=i.avgTimeByChar,c.minLength=i.minLength,i.onScanError.call(e,c),n=new CustomEvent("scanError",{detail:c}),e.dispatchEvent(n),d._reinitialize(e),!1},_mergeOptions:function(e,t){var n,a={};for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(a[n]=e[n]);for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(a[n]=t[n]);return a},_getNormalizedKeyNum:function(e){return e.which||e.keyCode},_handleKeyDown:function(e){var t=d._getNormalizedKeyNum(e),n=this.scannerDetectionData.options,a=this.scannerDetectionData.vars,i=!1;if(!1!==n.onKeyDetect.call(this,t,e)&&!d._isFocusOnIgnoredElement(this))if(!1===n.scanButtonKeyCode||t!=n.scanButtonKeyCode){switch(!0){case a.firstCharTime&&-1!==n.suffixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!0;break;case!a.firstCharTime&&-1!==n.prefixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!1;break;default:var o=n.keyCodeMapper.call(this,e);if(null===o)return;a.accumulatedString+=o,n.preventDefault&&e.preventDefault(),n.stopPropagation&&e.stopImmediatePropagation(),i=!1}a.firstCharTime||(a.firstCharTime=Date.now()),a.lastCharTime=Date.now(),a.testTimer&&clearTimeout(a.testTimer),i?(d._validateScanCode(this,a.accumulatedString),a.testTimer=!1):a.testTimer=setTimeout(d._validateScanCode,n.timeBeforeScanTest,this,a.accumulatedString),n.onKeyProcess.call(this,o,e)}else a.longPressed||(a.longPressTimer=setTimeout(n.onScanButtonLongPress,n.scanButtonLongPressTime,this),a.longPressed=!0)},_handlePaste:function(e){if(!d._isFocusOnIgnoredElement(this)){e.preventDefault(),oOptions.stopPropagation&&e.stopImmediatePropagation();var t=(event.clipboardData||window.clipboardData).getData("text");this.scannerDetectionData.options.onPaste.call(this,t,event);var n=this.scannerDetectionData.vars;n.firstCharTime=0,n.lastCharTime=0,d._validateScanCode(this,t)}},_handleKeyUp:function(e){d._isFocusOnIgnoredElement(this)||d._getNormalizedKeyNum(e)==this.scannerDetectionData.options.scanButtonKeyCode&&(clearTimeout(this.scannerDetectionData.vars.longPressTimer),this.scannerDetectionData.vars.longPressed=!1)},isScanInProgressFor:function(e){return 0 { - if (r && !cint(r.use_pos_in_offline_mode)) { - // online - wrapper.pos = new erpnext.pos.PointOfSale(wrapper); - window.cur_pos = wrapper.pos; - } else { - // offline - frappe.flags.is_offline = true; - frappe.set_route('pos'); - } - }); -}; - -frappe.pages['point-of-sale'].refresh = function(wrapper) { - if (wrapper.pos) { - wrapper.pos.make_new_invoice(); - } - - if (frappe.flags.is_offline) { - frappe.set_route('pos'); - } -} - -erpnext.pos.PointOfSale = class PointOfSale { - constructor(wrapper) { - this.wrapper = $(wrapper).find('.layout-main-section'); - this.page = wrapper.page; - - const assets = [ - 'assets/erpnext/js/pos/clusterize.js', - 'assets/erpnext/css/pos.css' - ]; - - frappe.require(assets, () => { - this.make(); - }); - } - - make() { - return frappe.run_serially([ - () => frappe.dom.freeze(), - () => { - this.prepare_dom(); - this.prepare_menu(); - this.set_online_status(); - }, - () => this.make_new_invoice(), - () => { - if(!this.frm.doc.company) { - this.setup_company() - .then((company) => { - this.frm.doc.company = company; - this.get_pos_profile(); - }); - } - }, - () => { - frappe.dom.unfreeze(); - }, - () => this.page.set_title(__('Point of Sale')) - ]); - } - - get_pos_profile() { - return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile", - {'company': this.frm.doc.company}) - .then((r) => { - if(r) { - this.frm.doc.pos_profile = r.name; - this.set_pos_profile_data() - .then(() => { - this.on_change_pos_profile(); - }); - } else { - this.raise_exception_for_pos_profile(); - } - }); - } - - set_online_status() { - this.connection_status = false; - this.page.set_indicator(__("Offline"), "grey"); - frappe.call({ - method: "frappe.handler.ping", - callback: r => { - if (r.message) { - this.connection_status = true; - this.page.set_indicator(__("Online"), "green"); - } - } - }); - } - - raise_exception_for_pos_profile() { - setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000); - frappe.throw(__("POS Profile is required to use Point-of-Sale")); - } - - prepare_dom() { - this.wrapper.append(` -
    -
    - -
    -
    - -
    -
    - `); - } - - make_cart() { - this.cart = new POSCart({ - frm: this.frm, - wrapper: this.wrapper.find('.cart-container'), - events: { - on_customer_change: (customer) => { - this.frm.set_value('customer', customer); - }, - on_field_change: (item_code, field, value, batch_no) => { - this.update_item_in_cart(item_code, field, value, batch_no); - }, - on_numpad: (value) => { - if (value == __('Pay')) { - if (!this.payment) { - this.make_payment_modal(); - } else { - this.frm.doc.payments.map(p => { - this.payment.dialog.set_value(p.mode_of_payment, p.amount); - }); - - this.payment.set_title(); - } - this.payment.open_modal(); - } - }, - on_select_change: () => { - this.cart.numpad.set_inactive(); - this.set_form_action(); - }, - get_item_details: (item_code) => { - return this.items.get(item_code); - }, - get_loyalty_details: () => { - var me = this; - if (this.frm.doc.customer) { - frappe.call({ - method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details", - args: { - "customer": me.frm.doc.customer, - "expiry_date": me.frm.doc.posting_date, - "company": me.frm.doc.company, - "silent": true - }, - callback: function(r) { - if (r.message.loyalty_program && r.message.loyalty_points) { - me.cart.events.set_loyalty_details(r.message, true); - } - if (!r.message.loyalty_program) { - var loyalty_details = { - loyalty_points: 0, - loyalty_program: '', - expense_account: '', - cost_center: '' - } - me.cart.events.set_loyalty_details(loyalty_details, false); - } - } - }); - } - }, - set_loyalty_details: (details, view_status) => { - if (view_status) { - this.cart.available_loyalty_points.$wrapper.removeClass("hide"); - } else { - this.cart.available_loyalty_points.$wrapper.addClass("hide"); - } - this.cart.available_loyalty_points.set_value(details.loyalty_points); - this.cart.available_loyalty_points.refresh_input(); - this.frm.set_value("loyalty_program", details.loyalty_program); - this.frm.set_value("loyalty_redemption_account", details.expense_account); - this.frm.set_value("loyalty_redemption_cost_center", details.cost_center); - } - } - }); - - frappe.ui.form.on('Sales Invoice', 'selling_price_list', (frm) => { - if(this.items && frm.doc.pos_profile) { - this.items.reset_items(); - } - }) - } - - toggle_editing(flag) { - let disabled; - if (flag !== undefined) { - disabled = !flag; - } else { - disabled = this.frm.doc.docstatus == 1 ? true: false; - } - const pointer_events = disabled ? 'none' : 'inherit'; - - this.wrapper.find('input, button, select').prop("disabled", disabled); - this.wrapper.find('.number-pad-container').toggleClass("hide", disabled); - - this.wrapper.find('.cart-container').css('pointer-events', pointer_events); - this.wrapper.find('.item-container').css('pointer-events', pointer_events); - - this.page.clear_actions(); - } - - make_items() { - this.items = new POSItems({ - wrapper: this.wrapper.find('.item-container'), - frm: this.frm, - events: { - update_cart: (item, field, value) => { - if(!this.frm.doc.customer) { - frappe.throw(__('Please select a customer')); - } - this.update_item_in_cart(item, field, value); - this.cart && this.cart.unselect_all(); - } - } - }); - } - - update_item_in_cart(item_code, field='qty', value=1, batch_no) { - frappe.dom.freeze(); - if(this.cart.exists(item_code, batch_no)) { - const search_field = batch_no ? 'batch_no' : 'item_code'; - const search_value = batch_no || item_code; - const item = this.frm.doc.items.find(i => i[search_field] === search_value); - frappe.flags.hide_serial_batch_dialog = false; - - if (typeof value === 'string' && !in_list(['serial_no', 'batch_no'], field)) { - // value can be of type '+1' or '-1' - value = item[field] + flt(value); - } - - if(field === 'serial_no') { - value = item.serial_no + '\n'+ value; - } - - // if actual_batch_qty and actual_qty if there is only one batch. In such - // a case, no point showing the dialog - const show_dialog = item.has_serial_no || item.has_batch_no; - - if (show_dialog && field == 'qty' && ((!item.batch_no && item.has_batch_no) || - (item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) { - this.select_batch_and_serial_no(item); - } else { - this.update_item_in_frm(item, field, value) - .then(() => { - frappe.dom.unfreeze(); - frappe.run_serially([ - () => { - let items = this.frm.doc.items.map(item => item.name); - if (items && items.length > 0 && items.includes(item.name)) { - this.frm.doc.items.forEach(item_row => { - // update cart - this.on_qty_change(item_row); - }); - } else { - this.on_qty_change(item); - } - }, - () => this.post_qty_change(item) - ]); - }); - } - return; - } - - let args = { item_code: item_code }; - if (in_list(['serial_no', 'batch_no'], field)) { - args[field] = value; - } - - // add to cur_frm - const item = this.frm.add_child('items', args); - frappe.flags.hide_serial_batch_dialog = true; - - frappe.run_serially([ - () => { - return this.frm.script_manager.trigger('item_code', item.doctype, item.name) - .then(() => { - this.frm.script_manager.trigger('qty', item.doctype, item.name) - .then(() => { - frappe.run_serially([ - () => { - let items = this.frm.doc.items.map(i => i.name); - if (items && items.length > 0 && items.includes(item.name)) { - this.frm.doc.items.forEach(item_row => { - // update cart - this.on_qty_change(item_row); - }); - } else { - this.on_qty_change(item); - } - }, - () => this.post_qty_change(item) - ]); - }); - }); - }, - () => { - const show_dialog = item.has_serial_no || item.has_batch_no; - - // if actual_batch_qty and actual_qty if then there is only one batch. In such - // a case, no point showing the dialog - if (show_dialog && field == 'qty' && ((!item.batch_no && item.has_batch_no) || - (item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) { - // check has serial no/batch no and update cart - this.select_batch_and_serial_no(item); - } - } - ]); - } - - on_qty_change(item) { - frappe.run_serially([ - () => this.update_cart_data(item), - ]); - } - - post_qty_change(item) { - this.cart.update_taxes_and_totals(); - this.cart.update_grand_total(); - this.cart.update_qty_total(); - this.cart.scroll_to_item(item.item_code); - this.set_form_action(); - } - - select_batch_and_serial_no(row) { - frappe.dom.unfreeze(); - - erpnext.show_serial_batch_selector(this.frm, row, () => { - this.frm.doc.items.forEach(item => { - this.update_item_in_frm(item, 'qty', item.qty) - .then(() => { - // update cart - frappe.run_serially([ - () => { - if (item.qty === 0) { - frappe.model.clear_doc(item.doctype, item.name); - } - }, - () => this.update_cart_data(item), - () => this.post_qty_change(item) - ]); - }); - }) - }, () => { - this.on_close(row); - }, true); - } - - on_close(item) { - if (!this.cart.exists(item.item_code, item.batch_no) && item.qty) { - frappe.model.clear_doc(item.doctype, item.name); - } - } - - update_cart_data(item) { - this.cart.add_item(item); - frappe.dom.unfreeze(); - } - - update_item_in_frm(item, field, value) { - if (field == 'qty' && value < 0) { - frappe.msgprint(__("Quantity must be positive")); - value = item.qty; - } else { - if (in_list(["qty", "serial_no", "batch"], field)) { - item[field] = value; - if (field == "serial_no" && value) { - let serial_nos = value.split("\n"); - item["qty"] = serial_nos.filter(d => { - return d!==""; - }).length; - } - } else { - return frappe.model.set_value(item.doctype, item.name, field, value); - } - } - - return this.frm.script_manager.trigger('qty', item.doctype, item.name) - .then(() => { - if (field === 'qty' && item.qty === 0) { - frappe.model.clear_doc(item.doctype, item.name); - } - }) - - return Promise.resolve(); - } - - make_payment_modal() { - this.payment = new Payment({ - frm: this.frm, - events: { - submit_form: () => { - this.submit_sales_invoice(); - } - } - }); - } - - submit_sales_invoice() { - this.frm.savesubmit() - .then((r) => { - if (r && r.doc) { - this.frm.doc.docstatus = r.doc.docstatus; - frappe.show_alert({ - indicator: 'green', - message: __(`Sales invoice ${r.doc.name} created succesfully`) - }); - - this.toggle_editing(); - this.set_form_action(); - this.set_primary_action_in_modal(); - } - }); - } - - set_primary_action_in_modal() { - if (!this.frm.msgbox) { - this.frm.msgbox = frappe.msgprint( - ` - ${__('Print')} - - ${__('New')}` - ); - - $(this.frm.msgbox.body).find('.btn-default').on('click', () => { - this.frm.msgbox.hide(); - this.make_new_invoice(); - }) - } - } - - change_pos_profile() { - return new Promise((resolve) => { - const on_submit = ({ company, pos_profile, set_as_default }) => { - if (pos_profile) { - this.pos_profile = pos_profile; - } - - if (set_as_default) { - frappe.call({ - method: "erpnext.accounts.doctype.pos_profile.pos_profile.set_default_profile", - args: { - 'pos_profile': pos_profile, - 'company': company - } - }).then(() => { - this.on_change_pos_profile(); - }); - } else { - this.on_change_pos_profile(); - } - } - - - let me = this; - - var dialog = frappe.prompt([{ - fieldtype: 'Link', - label: __('Company'), - options: 'Company', - fieldname: 'company', - default: me.frm.doc.company, - reqd: 1, - onchange: function(e) { - me.get_default_pos_profile(this.value).then((r) => { - dialog.set_value('pos_profile', (r && r.name)? r.name : ''); - }); - } - }, - { - fieldtype: 'Link', - label: __('POS Profile'), - options: 'POS Profile', - fieldname: 'pos_profile', - default: me.frm.doc.pos_profile, - reqd: 1, - get_query: () => { - return { - query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { - company: dialog.get_value('company') - } - }; - } - }, { - fieldtype: 'Check', - label: __('Set as default'), - fieldname: 'set_as_default' - }], - on_submit, - __('Select POS Profile') - ); - }); - } - - on_change_pos_profile() { - return frappe.run_serially([ - () => this.make_sales_invoice_frm(), - () => { - this.frm.doc.pos_profile = this.pos_profile; - this.set_pos_profile_data() - .then(() => { - this.reset_cart(); - if (this.items) { - this.items.reset_items(); - } - }); - } - ]); - } - - get_default_pos_profile(company) { - return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile", - {'company': company}) - } - - setup_company() { - return new Promise(resolve => { - if(!this.frm.doc.company) { - frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link", - label: __("Select Company"), reqd: 1}, (data) => { - this.company = data.company; - resolve(this.company); - }, __("Select Company")); - } else { - resolve(); - } - }) - } - - make_new_invoice() { - return frappe.run_serially([ - () => this.make_sales_invoice_frm(), - () => this.set_pos_profile_data(), - () => { - if (this.cart) { - this.cart.frm = this.frm; - this.cart.reset(); - this.cart.reset_pos_field_value(); - } else { - this.make_items(); - this.make_cart(); - } - this.toggle_editing(true); - }, - ]); - } - - reset_cart() { - this.cart.frm = this.frm; - this.cart.reset(); - this.items.reset_search_field(); - } - - make_sales_invoice_frm() { - const doctype = 'Sales Invoice'; - return new Promise(resolve => { - if (this.frm) { - this.frm = get_frm(this.frm); - if(this.company) { - this.frm.doc.company = this.company; - } - - resolve(); - } else { - frappe.model.with_doctype(doctype, () => { - this.frm = get_frm(); - resolve(); - }); - } - }); - - function get_frm(_frm) { - const page = $('
    '); - const frm = _frm || new frappe.ui.form.Form(doctype, page, false); - const name = frappe.model.make_new_doc_and_get_name(doctype, true); - frm.refresh(name); - frm.doc.items = []; - frm.doc.is_pos = 1; - - return frm; - } - } - - set_pos_profile_data() { - if (this.company) { - this.frm.doc.company = this.company; - } - - if (!this.frm.doc.company) { - return; - } - - return new Promise(resolve => { - return this.frm.call({ - doc: this.frm.doc, - method: "set_missing_values", - }).then((r) => { - if(!r.exc) { - if (!this.frm.doc.pos_profile) { - frappe.dom.unfreeze(); - this.raise_exception_for_pos_profile(); - } - this.frm.script_manager.trigger("update_stock"); - frappe.model.set_default_values(this.frm.doc); - this.frm.cscript.calculate_taxes_and_totals(); - - if (r.message) { - this.frm.meta.default_print_format = r.message.print_format || ""; - this.frm.allow_edit_rate = r.message.allow_edit_rate; - this.frm.allow_edit_discount = r.message.allow_edit_discount; - this.frm.doc.campaign = r.message.campaign; - this.frm.allow_print_before_pay = r.message.allow_print_before_pay; - } - } - - resolve(); - }); - }); - } - - prepare_menu() { - var me = this; - this.page.clear_menu(); - - this.page.add_menu_item(__("Form View"), function () { - frappe.model.sync(me.frm.doc); - frappe.set_route("Form", me.frm.doc.doctype, me.frm.doc.name); - }); - - this.page.add_menu_item(__("POS Profile"), function () { - frappe.set_route('List', 'POS Profile'); - }); - - this.page.add_menu_item(__('POS Settings'), function() { - frappe.set_route('Form', 'POS Settings'); - }); - - this.page.add_menu_item(__('Change POS Profile'), function() { - me.change_pos_profile(); - }); - this.page.add_menu_item(__('Close the POS'), function() { - var voucher = frappe.model.get_new_doc('POS Closing Voucher'); - voucher.pos_profile = me.frm.doc.pos_profile; - voucher.user = frappe.session.user; - voucher.company = me.frm.doc.company; - voucher.period_start_date = me.frm.doc.posting_date; - voucher.period_end_date = me.frm.doc.posting_date; - voucher.posting_date = me.frm.doc.posting_date; - frappe.set_route('Form', 'POS Closing Voucher', voucher.name); - }); - } - - set_form_action() { - if(this.frm.doc.docstatus == 1 || (this.frm.allow_print_before_pay == 1 && this.frm.doc.items.length > 0)){ - this.page.set_secondary_action(__("Print"), async() => { - if(this.frm.doc.docstatus != 1 ){ - await this.frm.save(); - } - this.frm.print_preview.printit(true); - }); - } - if(this.frm.doc.items.length == 0){ - this.page.clear_secondary_action(); - } - - if (this.frm.doc.docstatus == 1) { - this.page.set_primary_action(__("New"), () => { - this.make_new_invoice(); - }); - this.page.add_menu_item(__("Email"), () => { - this.frm.email_doc(); - }); - } - } -}; - -const [Qty,Disc,Rate,Del,Pay] = [__("Qty"), __('Disc'), __('Rate'), __('Del'), __('Pay')]; - -class POSCart { - constructor({frm, wrapper, events}) { - this.frm = frm; - this.item_data = {}; - this.wrapper = wrapper; - this.events = events; - this.make(); - this.bind_events(); - } - - make() { - this.make_dom(); - this.make_customer_field(); - this.make_pos_fields(); - this.make_loyalty_points(); - this.make_numpad(); - } - - make_dom() { - this.wrapper.append(` -
    -
    -
    - -
    -
    -
    -
    ${__('Item Name')}
    -
    ${__('Quantity')}
    -
    ${__('Discount')}
    -
    ${__('Rate')}
    -
    -
    -
    - ${__('No Items added to cart')} -
    -
    -
    - ${this.get_taxes_and_totals()} -
    -
    `+ - (!this.frm.allow_edit_discount ? `` : `${this.get_discount_amount()}`)+ - `
    -
    - ${this.get_grand_total()} -
    -
    - ${this.get_item_qty_total()} -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - `); - - - this.$cart_items = this.wrapper.find('.cart-items'); - this.$empty_state = this.wrapper.find('.cart-items .empty-state'); - this.$taxes_and_totals = this.wrapper.find('.taxes-and-totals'); - this.$discount_amount = this.wrapper.find('.discount-amount'); - this.$grand_total = this.wrapper.find('.grand-total'); - this.$qty_total = this.wrapper.find('.quantity-total'); - // this.$loyalty_button = this.wrapper.find('.loyalty-button'); - - // this.$loyalty_button.on('click', () => { - // this.loyalty_button.show(); - // }) - - this.toggle_taxes_and_totals(false); - this.$grand_total.on('click', () => { - this.toggle_taxes_and_totals(); - }); - } - - reset() { - this.$cart_items.find('.list-item').remove(); - this.$empty_state.show(); - this.$taxes_and_totals.html(this.get_taxes_and_totals()); - this.numpad && this.numpad.reset_value(); - this.customer_field.set_value(""); - this.frm.msgbox = ""; - - let total_item_qty = 0.0; - this.frm.set_value("pos_total_qty",total_item_qty); - - this.$discount_amount.find('input:text').val(''); - this.wrapper.find('.grand-total-value').text( - format_currency(this.frm.doc.grand_total, this.frm.currency)); - this.wrapper.find('.rounded-total-value').text( - format_currency(this.frm.doc.rounded_total, this.frm.currency)); - this.$qty_total.find(".quantity-total").text(total_item_qty); - - const customer = this.frm.doc.customer; - this.customer_field.set_value(customer); - - if (this.numpad) { - const disable_btns = this.disable_numpad_control() - const enable_btns = [__('Rate'), __('Disc')] - - if (disable_btns) { - enable_btns.filter(btn => !disable_btns.includes(btn)) - } - - this.numpad.enable_buttons(enable_btns); - } - } - - reset_pos_field_value() { - let value = ''; - if (this.custom_pos_fields) { - this.custom_pos_fields.forEach(r => { - value = this.frm.doc[r.fieldname] || r.default_value || ''; - - if (this.fields) { - this.fields[r.fieldname].set_value(value); - } - }) - } - - this.wrapper.find('.pos-fields').toggle(false); - this.wrapper.find('.pos-fields-octicon').toggle(true); - } - - get_grand_total() { - let total = this.get_total_template('Grand Total', 'grand-total-value'); - - if (!cint(frappe.sys_defaults.disable_rounded_total)) { - total += this.get_total_template('Rounded Total', 'rounded-total-value'); - } - - return total; - } - - get_item_qty_total() { - let total = this.get_total_template('Total Qty', 'quantity-total'); - return total; - } - - get_total_template(label, class_name) { - return ` -
    -
    ${__(label)}
    -
    0.00
    -
    - `; - } - - get_discount_amount() { - const get_currency_symbol = window.get_currency_symbol; - - return ` -
    -
    ${__('Discount')}
    -
    - - -
    -
    - `; - } - - get_taxes_and_totals() { - return ` -
    -
    ${__('Net Total')}
    -
    0.00
    -
    -
    -
    ${__('Taxes')}
    -
    0.00
    -
    - `; - } - - toggle_taxes_and_totals(flag) { - if (flag !== undefined) { - this.tax_area_is_shown = flag; - } else { - this.tax_area_is_shown = !this.tax_area_is_shown; - } - - this.$taxes_and_totals.toggle(this.tax_area_is_shown); - this.$discount_amount.toggle(this.tax_area_is_shown); - } - - update_taxes_and_totals() { - if (!this.frm.doc.taxes) { return; } - - const currency = this.frm.doc.currency; - this.frm.refresh_field('taxes'); - - // Update totals - this.$taxes_and_totals.find('.net-total') - .html(format_currency(this.frm.doc.total, currency)); - - // Update taxes - const taxes_html = this.frm.doc.taxes.map(tax => { - return ` -
    - ${tax.description} - - ${format_currency(tax.tax_amount, currency)} - -
    - `; - }).join(""); - this.$taxes_and_totals.find('.taxes').html(taxes_html); - } - - update_grand_total() { - this.$grand_total.find('.grand-total-value').text( - format_currency(this.frm.doc.grand_total, this.frm.currency) - ); - - this.$grand_total.find('.rounded-total-value').text( - format_currency(this.frm.doc.rounded_total, this.frm.currency) - ); - } - - update_qty_total() { - var total_item_qty = 0; - $.each(this.frm.doc["items"] || [], function (i, d) { - if (d.qty > 0) { - total_item_qty += d.qty; - } - }); - this.$qty_total.find('.quantity-total').text(total_item_qty); - this.frm.set_value("pos_total_qty",total_item_qty); - } - - make_customer_field() { - this.customer_field = frappe.ui.form.make_control({ - df: { - fieldtype: 'Link', - label: 'Customer', - fieldname: 'customer', - options: 'Customer', - reqd: 1, - get_query: function() { - return { - query: 'erpnext.controllers.queries.customer_query' - } - }, - onchange: () => { - this.events.on_customer_change(this.customer_field.get_value()); - this.events.get_loyalty_details(); - } - }, - parent: this.wrapper.find('.customer-field'), - render_input: true - }); - - this.customer_field.set_value(this.frm.doc.customer); - } - - make_pos_fields() { - const me = this; - - this.fields = {}; - this.wrapper.find('.pos-fields-octicon, .more-fields-section').click(() => { - this.wrapper.find('.pos-fields').toggle(); - this.wrapper.find('.pos-fields-octicon').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up'); - }); - this.wrapper.find('.pos-fields').toggle(false); - - return new Promise(res => { - frappe.call({ - method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_fields", - freeze: true, - }).then(r => { - if(r.message.length) { - this.wrapper.find('.pos-field-section').css('display','block'); - this.custom_pos_fields = r.message; - if (r.message.length < 3) { - this.wrapper.find('.pos-fields').toggle(true); - this.wrapper.find('.pos-fields-octicon').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up'); - } - - r.message.forEach(field => { - this.fields[field.fieldname] = frappe.ui.form.make_control({ - df: { - fieldtype: field.fieldtype, - label: field.label, - fieldname: field.fieldname, - options: field.options, - reqd: field.reqd || 0, - read_only: field.read_only || 0, - default: field.default_value, - onchange: function() { - if (this.value) { - me.frm.set_value(this.df.fieldname, this.value); - } - }, - get_query: () => { - return this.get_query_for_pos_fields(field.fieldname) - }, - }, - parent: this.wrapper.find('.pos-fields'), - render_input: true - }); - - if (this.frm.doc[field.fieldname]) { - this.fields[field.fieldname].set_value(this.frm.doc[field.fieldname]); - } - }); - } - }); - }); - } - - get_query_for_pos_fields(field) { - if (this.frm.fields_dict && this.frm.fields_dict[field] - && this.frm.fields_dict[field].get_query) { - return this.frm.fields_dict[field].get_query(this.frm.doc); - } - } - - make_loyalty_points() { - this.available_loyalty_points = frappe.ui.form.make_control({ - df: { - fieldtype: 'Int', - label: 'Available Loyalty Points', - read_only: 1, - fieldname: 'available_loyalty_points' - }, - parent: this.wrapper.find('.loyalty-program-field') - }); - this.available_loyalty_points.set_value(this.frm.doc.loyalty_points); - } - - - disable_numpad_control() { - let disabled_btns = []; - if(!this.frm.allow_edit_rate) { - disabled_btns.push(__('Rate')); - } - if(!this.frm.allow_edit_discount) { - disabled_btns.push(__('Disc')); - } - return disabled_btns; - } - - - make_numpad() { - - var pay_class = {} - pay_class[__('Pay')]='brand-primary' - this.numpad = new NumberPad({ - button_array: [ - [1, 2, 3, Qty], - [4, 5, 6, Disc], - [7, 8, 9, Rate], - [Del, 0, '.', Pay] - ], - add_class: pay_class, - disable_highlight: [Qty, Disc, Rate, Pay], - reset_btns: [Qty, Disc, Rate, Pay], - del_btn: Del, - disable_btns: this.disable_numpad_control(), - wrapper: this.wrapper.find('.number-pad-container'), - onclick: (btn_value) => { - // on click - - if (!this.selected_item && btn_value !== Pay) { - frappe.show_alert({ - indicator: 'red', - message: __('Please select an item in the cart') - }); - return; - } - if ([Qty, Disc, Rate].includes(btn_value)) { - this.set_input_active(btn_value); - } else if (btn_value !== Pay) { - if (!this.selected_item.active_field) { - frappe.show_alert({ - indicator: 'red', - message: __('Please select a field to edit from numpad') - }); - return; - } - - if (this.selected_item.active_field == 'discount_percentage' && this.numpad.get_value() > cint(100)) { - frappe.show_alert({ - indicator: 'red', - message: __('Discount amount cannot be greater than 100%') - }); - this.numpad.reset_value(); - } else { - const item_code = unescape(this.selected_item.attr('data-item-code')); - const batch_no = this.selected_item.attr('data-batch-no'); - const field = this.selected_item.active_field; - const value = this.numpad.get_value(); - - this.events.on_field_change(item_code, field, value, batch_no); - } - } - - this.events.on_numpad(btn_value); - } - }); - } - - set_input_active(btn_value) { - this.selected_item.removeClass('qty disc rate'); - - this.numpad.set_active(btn_value); - if (btn_value === Qty) { - this.selected_item.addClass('qty'); - this.selected_item.active_field = 'qty'; - } else if (btn_value == Disc) { - this.selected_item.addClass('disc'); - this.selected_item.active_field = 'discount_percentage'; - } else if (btn_value == Rate) { - this.selected_item.addClass('rate'); - this.selected_item.active_field = 'rate'; - } - } - - add_item(item) { - this.$empty_state.hide(); - - if (this.exists(item.item_code, item.batch_no)) { - // update quantity - this.update_item(item); - } else if (flt(item.qty) > 0.0) { - // add to cart - const $item = $(this.get_item_html(item)); - $item.appendTo(this.$cart_items); - } - this.highlight_item(item.item_code); - } - - update_item(item) { - const item_selector = item.batch_no ? - `[data-batch-no="${item.batch_no}"]` : `[data-item-code="${escape(item.item_code)}"]`; - - const $item = this.$cart_items.find(item_selector); - - if(item.qty > 0) { - const is_stock_item = this.get_item_details(item.item_code).is_stock_item; - const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red'; - const remove_class = indicator_class == 'green' ? 'red' : 'green'; - - $item.find('.quantity input').val(item.qty); - $item.find('.discount').text(item.discount_percentage + '%'); - $item.find('.rate').text(format_currency(item.rate, this.frm.doc.currency)); - $item.addClass(indicator_class); - $item.removeClass(remove_class); - } else { - $item.remove(); - } - } - - get_item_html(item) { - const is_stock_item = this.get_item_details(item.item_code).is_stock_item; - const rate = format_currency(item.rate, this.frm.doc.currency); - const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red'; - const batch_no = item.batch_no || ''; - - return ` -
    -
    - ${item.item_name} -
    -
    - ${get_quantity_html(item.qty)} -
    -
    - ${item.discount_percentage}% -
    -
    - ${rate} -
    -
    - `; - - function get_quantity_html(value) { - return ` -
    - - - - - - - - - -
    - `; - } - } - - get_item_details(item_code) { - if (!this.item_data[item_code]) { - this.item_data[item_code] = this.events.get_item_details(item_code); - } - - return this.item_data[item_code]; - } - - exists(item_code, batch_no) { - const is_exists = batch_no ? - `[data-batch-no="${batch_no}"]` : `[data-item-code="${escape(item_code)}"]`; - - let $item = this.$cart_items.find(is_exists); - - return $item.length > 0; - } - - highlight_item(item_code) { - const $item = this.$cart_items.find(`[data-item-code="${escape(item_code)}"]`); - $item.addClass('highlight'); - setTimeout(() => $item.removeClass('highlight'), 1000); - } - - scroll_to_item(item_code) { - const $item = this.$cart_items.find(`[data-item-code="${escape(item_code)}"]`); - if ($item.length === 0) return; - const scrollTop = $item.offset().top - this.$cart_items.offset().top + this.$cart_items.scrollTop(); - this.$cart_items.animate({ scrollTop }); - } - - bind_events() { - const me = this; - const events = this.events; - - // quantity change - this.$cart_items.on('click', - '[data-action="increment"], [data-action="decrement"]', function() { - const $btn = $(this); - const $item = $btn.closest('.list-item[data-item-code]'); - const item_code = unescape($item.attr('data-item-code')); - const action = $btn.attr('data-action'); - - if(action === 'increment') { - events.on_field_change(item_code, 'qty', '+1'); - } else if(action === 'decrement') { - events.on_field_change(item_code, 'qty', '-1'); - } - }); - - this.$cart_items.on('change', '.quantity input', function() { - const $input = $(this); - const $item = $input.closest('.list-item[data-item-code]'); - const item_code = unescape($item.attr('data-item-code')); - events.on_field_change(item_code, 'qty', flt($input.val())); - }); - - // current item - this.$cart_items.on('click', '.list-item', function() { - me.set_selected_item($(this)); - }); - - this.wrapper.find('.additional_discount_percentage').on('change', (e) => { - const discount_percentage = flt(e.target.value, - precision("additional_discount_percentage")); - - frappe.model.set_value(this.frm.doctype, this.frm.docname, - 'additional_discount_percentage', discount_percentage) - .then(() => { - let discount_wrapper = this.wrapper.find('.discount_amount'); - discount_wrapper.val(flt(this.frm.doc.discount_amount, - precision('discount_amount'))); - discount_wrapper.trigger('change'); - }); - }); - - this.wrapper.find('.discount_amount').on('change', (e) => { - const discount_amount = flt(e.target.value, precision('discount_amount')); - frappe.model.set_value(this.frm.doctype, this.frm.docname, - 'discount_amount', discount_amount); - this.frm.trigger('discount_amount') - .then(() => { - this.update_discount_fields(); - this.update_taxes_and_totals(); - this.update_grand_total(); - }); - }); - } - - update_discount_fields() { - let discount_wrapper = this.wrapper.find('.additional_discount_percentage'); - let discount_amt_wrapper = this.wrapper.find('.discount_amount'); - discount_wrapper.val(flt(this.frm.doc.additional_discount_percentage, - precision('additional_discount_percentage'))); - discount_amt_wrapper.val(flt(this.frm.doc.discount_amount, - precision('discount_amount'))); - } - - set_selected_item($item) { - this.selected_item = $item; - this.$cart_items.find('.list-item').removeClass('current-item qty disc rate'); - this.selected_item.addClass('current-item'); - this.events.on_select_change(); - } - - unselect_all() { - this.$cart_items.find('.list-item').removeClass('current-item qty disc rate'); - this.selected_item = null; - this.events.on_select_change(); - } -} - -class POSItems { - constructor({wrapper, frm, events}) { - this.wrapper = wrapper; - this.frm = frm; - this.items = {}; - this.events = events; - this.currency = this.frm.doc.currency; - - frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name", (r) => { - this.parent_item_group = r.name; - this.make_dom(); - this.make_fields(); - - this.init_clusterize(); - this.bind_events(); - this.load_items_data(); - }) - } - - load_items_data() { - // bootstrap with 20 items - this.get_items() - .then(({ items }) => { - this.all_items = items; - this.items = items; - this.render_items(items); - }); - } - - reset_items() { - this.wrapper.find('.pos-items').empty(); - this.init_clusterize(); - this.load_items_data(); - } - - make_dom() { - this.wrapper.html(` -
    -
    -
    -
    -
    -
    -
    -
    - `); - - this.items_wrapper = this.wrapper.find('.items-wrapper'); - this.items_wrapper.append(` -
    -
    -
    -
    - `); - } - - make_fields() { - // Search field - const me = this; - this.search_field = frappe.ui.form.make_control({ - df: { - fieldtype: 'Data', - label: __('Search Item (Ctrl + i)'), - placeholder: __('Search by item code, serial number, batch no or barcode') - }, - parent: this.wrapper.find('.search-field'), - render_input: true, - }); - - frappe.ui.keys.on('ctrl+i', () => { - this.search_field.set_focus(); - }); - - this.search_field.$input.on('input', (e) => { - clearTimeout(this.last_search); - this.last_search = setTimeout(() => { - const search_term = e.target.value; - const item_group = this.item_group_field ? - this.item_group_field.get_value() : ''; - - this.filter_items({ search_term:search_term, item_group: item_group}); - }, 300); - }); - - this.item_group_field = frappe.ui.form.make_control({ - df: { - fieldtype: 'Link', - label: 'Item Group', - options: 'Item Group', - default: me.parent_item_group, - onchange: () => { - const item_group = this.item_group_field.get_value(); - if (item_group) { - this.filter_items({ item_group: item_group }); - } - }, - get_query: () => { - return { - query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query', - filters: { - pos_profile: this.frm.doc.pos_profile - } - }; - } - }, - parent: this.wrapper.find('.item-group-field'), - render_input: true - }); - } - - init_clusterize() { - this.clusterize = new Clusterize({ - scrollElem: this.wrapper.find('.pos-items-wrapper')[0], - contentElem: this.wrapper.find('.pos-items')[0], - rows_in_block: 6 - }); - } - - render_items(items) { - let _items = items || this.items; - - const all_items = Object.values(_items).map(item => this.get_item_html(item)); - let row_items = []; - - const row_container = '
    '; - let curr_row = row_container; - - for (let i=0; i < all_items.length; i++) { - // wrap 4 items in a div to emulate - // a row for clusterize - if(i % 4 === 0 && i !== 0) { - curr_row += '
    '; - row_items.push(curr_row); - curr_row = row_container; - } - curr_row += all_items[i]; - - if(i == all_items.length - 1) { - row_items.push(curr_row); - } - } - - this.clusterize.update(row_items); - } - - filter_items({ search_term='', item_group=this.parent_item_group }={}) { - if (search_term) { - search_term = search_term.toLowerCase(); - - // memoize - this.search_index = this.search_index || {}; - if (this.search_index[search_term]) { - const items = this.search_index[search_term]; - this.items = items; - this.render_items(items); - this.set_item_in_the_cart(items); - return; - } - } else if (item_group == this.parent_item_group) { - this.items = this.all_items; - return this.render_items(this.all_items); - } - - this.get_items({search_value: search_term, item_group }) - .then(({ items, serial_no, batch_no, barcode }) => { - if (search_term && !barcode) { - this.search_index[search_term] = items; - } - - this.items = items; - this.render_items(items); - this.set_item_in_the_cart(items, serial_no, batch_no, barcode); - }); - } - - set_item_in_the_cart(items, serial_no, batch_no, barcode) { - if (serial_no) { - this.events.update_cart(items[0].item_code, - 'serial_no', serial_no); - this.reset_search_field(); - return; - } - - if (batch_no) { - this.events.update_cart(items[0].item_code, - 'batch_no', batch_no); - this.reset_search_field(); - return; - } - - if (items.length === 1 && (serial_no || batch_no || barcode)) { - this.events.update_cart(items[0].item_code, - 'qty', '+1'); - this.reset_search_field(); - } - } - - reset_search_field() { - this.search_field.set_value(''); - this.search_field.$input.trigger("input"); - } - - bind_events() { - var me = this; - this.wrapper.on('click', '.pos-item-wrapper', function() { - const $item = $(this); - const item_code = unescape($item.attr('data-item-code')); - me.events.update_cart(item_code, 'qty', '+1'); - }); - } - - get(item_code) { - let item = {}; - this.items.map(data => { - if (data.item_code === item_code) { - item = data; - } - }) - - return item - } - - get_all() { - return this.items; - } - - get_item_html(item) { - const price_list_rate = format_currency(item.price_list_rate, this.currency); - const { item_code, item_name, item_image} = item; - const item_title = item_name || item_code; - - const template = ` - - `; - - return template; - } - - get_items({start = 0, page_length = 40, search_value='', item_group=this.parent_item_group}={}) { - const price_list = this.frm.doc.selling_price_list; - return new Promise(res => { - frappe.call({ - method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items", - freeze: true, - args: { - start, - page_length, - price_list, - item_group, - search_value, - pos_profile: this.frm.doc.pos_profile - } - }).then(r => { - // const { items, serial_no, batch_no } = r.message; - - // this.serial_no = serial_no || ""; - res(r.message); - }); - }); - } -} - -class NumberPad { - constructor({ - wrapper, onclick, button_array, - add_class={}, disable_highlight=[], - reset_btns=[], del_btn='', disable_btns - }) { - this.wrapper = wrapper; - this.onclick = onclick; - this.button_array = button_array; - this.add_class = add_class; - this.disable_highlight = disable_highlight; - this.reset_btns = reset_btns; - this.del_btn = del_btn; - this.disable_btns = disable_btns || []; - this.make_dom(); - this.bind_events(); - this.value = ''; - } - - make_dom() { - if (!this.button_array) { - this.button_array = [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - ['', 0, ''] - ]; - } - - this.wrapper.html(` -
    - ${this.button_array.map(get_row).join("")} -
    - `); - - function get_row(row) { - return '
    ' + row.map(get_col).join("") + '
    '; - } - - function get_col(col) { - return `
    ${col}
    `; - } - - this.set_class(); - - if(this.disable_btns) { - this.disable_btns.forEach((btn) => { - const $btn = this.get_btn(btn); - $btn.prop("disabled", true) - $btn.hover(() => { - $btn.css('cursor','not-allowed'); - }) - }) - } - } - - enable_buttons(btns) { - btns.forEach((btn) => { - const $btn = this.get_btn(btn); - $btn.prop("disabled", false) - $btn.hover(() => { - $btn.css('cursor','pointer'); - }) - }) - } - - set_class() { - for (const btn in this.add_class) { - const class_name = this.add_class[btn]; - this.get_btn(btn).addClass(class_name); - } - } - - bind_events() { - // bind click event - const me = this; - this.wrapper.on('click', '.num-col', function() { - const $btn = $(this); - const btn_value = $btn.attr('data-value'); - if (!me.disable_highlight.includes(btn_value)) { - me.highlight_button($btn); - } - if (me.reset_btns.includes(btn_value)) { - me.reset_value(); - } else { - if (btn_value === me.del_btn) { - me.value = me.value.substr(0, me.value.length - 1); - } else { - me.value += btn_value; - } - } - me.onclick(btn_value); - }); - } - - reset_value() { - this.value = ''; - } - - get_value() { - return flt(this.value); - } - - get_btn(btn_value) { - return this.wrapper.find(`.num-col[data-value="${btn_value}"]`); - } - - highlight_button($btn) { - $btn.addClass('highlight'); - setTimeout(() => $btn.removeClass('highlight'), 1000); - } - - set_active(btn_value) { - const $btn = this.get_btn(btn_value); - this.wrapper.find('.num-col').removeClass('active'); - $btn.addClass('active'); - } - - set_inactive() { - this.wrapper.find('.num-col').removeClass('active'); - } -} - -class Payment { - constructor({frm, events}) { - this.frm = frm; - this.events = events; - this.make(); - this.bind_events(); - this.set_primary_action(); - } - - open_modal() { - this.dialog.show(); - } - - make() { - this.set_flag(); - this.dialog = new frappe.ui.Dialog({ - fields: this.get_fields(), - width: 800, - invoice_frm: this.frm - }); - - this.set_title(); - - this.$body = this.dialog.body; - - this.numpad = new NumberPad({ - wrapper: $(this.$body).find('[data-fieldname="numpad"]'), - button_array: [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - [__('Del'), 0, '.'], - ], - onclick: () => { - if(this.fieldname) { - this.dialog.set_value(this.fieldname, this.numpad.get_value()); - } - } - }); - } - - set_title() { - let title = __('Total Amount {0}', - [format_currency(this.frm.doc.rounded_total || this.frm.doc.grand_total, - this.frm.doc.currency)]); - - this.dialog.set_title(title); - } - - bind_events() { - var me = this; - $(this.dialog.body).find('.input-with-feedback').focusin(function() { - me.numpad.reset_value(); - me.fieldname = $(this).prop('dataset').fieldname; - if (me.frm.doc.outstanding_amount > 0 && - !in_list(['write_off_amount', 'change_amount'], me.fieldname)) { - me.frm.doc.payments.forEach((data) => { - if (data.mode_of_payment == me.fieldname && !data.amount) { - me.dialog.set_value(me.fieldname, - me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate); - return; - } - }) - } - }); - } - - set_primary_action() { - var me = this; - - this.dialog.set_primary_action(__("Submit"), function() { - me.dialog.hide(); - me.events.submit_form(); - }); - } - - get_fields() { - const me = this; - - let fields = this.frm.doc.payments.map(p => { - return { - fieldtype: 'Currency', - label: __(p.mode_of_payment), - options: me.frm.doc.currency, - fieldname: p.mode_of_payment, - default: p.amount, - onchange: () => { - const value = this.dialog.get_value(this.fieldname) || 0; - me.update_payment_value(this.fieldname, value); - } - }; - }); - - fields = fields.concat([ - { - fieldtype: 'Column Break', - }, - { - fieldtype: 'HTML', - fieldname: 'numpad' - }, - { - fieldtype: 'Section Break', - depends_on: 'eval: this.invoice_frm.doc.loyalty_program' - }, - { - fieldtype: 'Check', - label: 'Redeem Loyalty Points', - fieldname: 'redeem_loyalty_points', - onchange: () => { - me.update_cur_frm_value("redeem_loyalty_points", () => { - frappe.flags.redeem_loyalty_points = false; - me.update_loyalty_points(); - }); - } - }, - { - fieldtype: 'Column Break', - }, - { - fieldtype: 'Int', - fieldname: "loyalty_points", - label: __("Loyalty Points"), - depends_on: "redeem_loyalty_points", - onchange: () => { - me.update_cur_frm_value("loyalty_points", () => { - frappe.flags.loyalty_points = false; - me.update_loyalty_points(); - }); - } - }, - { - fieldtype: 'Currency', - label: __("Loyalty Amount"), - fieldname: "loyalty_amount", - options: me.frm.doc.currency, - read_only: 1, - depends_on: "redeem_loyalty_points" - }, - { - fieldtype: 'Section Break', - }, - { - fieldtype: 'Currency', - label: __("Write off Amount"), - options: me.frm.doc.currency, - fieldname: "write_off_amount", - default: me.frm.doc.write_off_amount, - onchange: () => { - me.update_cur_frm_value('write_off_amount', () => { - frappe.flags.change_amount = false; - me.update_change_amount(); - }); - } - }, - { - fieldtype: 'Column Break', - }, - { - fieldtype: 'Currency', - label: __("Change Amount"), - options: me.frm.doc.currency, - fieldname: "change_amount", - default: me.frm.doc.change_amount, - onchange: () => { - me.update_cur_frm_value('change_amount', () => { - frappe.flags.write_off_amount = false; - me.update_write_off_amount(); - }); - } - }, - { - fieldtype: 'Section Break', - }, - { - fieldtype: 'Currency', - label: __("Paid Amount"), - options: me.frm.doc.currency, - fieldname: "paid_amount", - default: me.frm.doc.paid_amount, - read_only: 1 - }, - { - fieldtype: 'Column Break', - }, - { - fieldtype: 'Currency', - label: __("Outstanding Amount"), - options: me.frm.doc.currency, - fieldname: "outstanding_amount", - default: me.frm.doc.outstanding_amount, - read_only: 1 - }, - ]); - - return fields; - } - - set_flag() { - frappe.flags.write_off_amount = true; - frappe.flags.change_amount = true; - frappe.flags.loyalty_points = true; - frappe.flags.redeem_loyalty_points = true; - frappe.flags.payment_method = true; - } - - update_cur_frm_value(fieldname, callback) { - if (frappe.flags[fieldname]) { - const value = this.dialog.get_value(fieldname); - this.frm.set_value(fieldname, value) - .then(() => { - callback(); - }); - } - - frappe.flags[fieldname] = true; - } - - update_payment_value(fieldname, value) { - var me = this; - $.each(this.frm.doc.payments, function(i, data) { - if (__(data.mode_of_payment) == __(fieldname)) { - frappe.model.set_value('Sales Invoice Payment', data.name, 'amount', value) - .then(() => { - me.update_change_amount(); - me.update_write_off_amount(); - }); - } - }); - } - - update_change_amount() { - this.dialog.set_value("change_amount", this.frm.doc.change_amount); - this.show_paid_amount(); - } - - update_write_off_amount() { - this.dialog.set_value("write_off_amount", this.frm.doc.write_off_amount); - } - - show_paid_amount() { - this.dialog.set_value("paid_amount", this.frm.doc.paid_amount); - this.dialog.set_value("outstanding_amount", this.frm.doc.outstanding_amount); - } - - update_payment_amount() { - var me = this; - $.each(this.frm.doc.payments, function(i, data) { - console.log("setting the ", data.mode_of_payment, " for the value", data.amount); - me.dialog.set_value(data.mode_of_payment, data.amount); - }); - } - - update_loyalty_points() { - if (this.dialog.get_value("redeem_loyalty_points")) { - this.dialog.set_value("loyalty_points", this.frm.doc.loyalty_points); - this.dialog.set_value("loyalty_amount", this.frm.doc.loyalty_amount); - this.update_payment_amount(); - this.show_paid_amount(); - } - } - -} + // online + wrapper.pos = new erpnext.PointOfSale.Controller(wrapper); + window.cur_pos = wrapper.pos; +}; \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.json b/erpnext/selling/page/point_of_sale/point_of_sale.json index 6d2f5f2f8d..99b86e42c2 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.json +++ b/erpnext/selling/page/point_of_sale/point_of_sale.json @@ -1,33 +1,33 @@ { - "content": null, - "creation": "2017-08-07 17:08:56.737947", - "docstatus": 0, - "doctype": "Page", - "idx": 0, - "modified": "2017-09-11 13:49:05.415211", - "modified_by": "Administrator", - "module": "Selling", - "name": "point-of-sale", - "owner": "Administrator", - "page_name": "Point of Sale", - "restrict_to_domain": "Retail", + "content": null, + "creation": "2020-01-28 22:05:44.819140", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2020-06-01 15:41:06.348380", + "modified_by": "Administrator", + "module": "Selling", + "name": "point-of-sale", + "owner": "Administrator", + "page_name": "Point of Sale", + "restrict_to_domain": "Retail", "roles": [ { "role": "Accounts User" - }, + }, { "role": "Accounts Manager" - }, + }, { "role": "Sales User" - }, + }, { "role": "Sales Manager" } - ], - "script": null, - "standard": "Yes", - "style": null, - "system_page": 0, - "title": "Point of Sale" + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Point Of Sale" } \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 1ae1fde588..f7b7ed8b89 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -6,6 +6,7 @@ import frappe, json from frappe.utils.nestedset import get_root_of from frappe.utils import cint from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups +from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability from six import string_types @@ -43,6 +44,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p SELECT name AS item_code, item_name, + description, stock_uom, image AS item_image, idx AS idx, @@ -53,10 +55,11 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p disabled = 0 AND has_variants = 0 AND is_sales_item = 1 + AND is_fixed_asset = 0 AND item_group in (SELECT name FROM `tabItem Group` WHERE lft >= {lft} AND rgt <= {rgt}) AND {condition} ORDER BY - idx desc + name asc LIMIT {start}, {page_length}""" .format( @@ -73,32 +76,14 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p fields = ["item_code", "price_list_rate", "currency"], filters = {'price_list': price_list, 'item_code': ['in', items]}) - item_prices, bin_data = {}, {} + item_prices = {} for d in item_prices_data: item_prices[d.item_code] = d - # prepare filter for bin query - bin_filters = {'item_code': ['in', items]} - if warehouse: - bin_filters['warehouse'] = warehouse - if display_items_in_stock: - bin_filters['actual_qty'] = [">", 0] - - # query item bin - bin_data = frappe.get_all( - 'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'], - filters=bin_filters, group_by='item_code' - ) - - # convert list of dict into dict as {item_code: actual_qty} - bin_dict = {} - for b in bin_data: - bin_dict[b.get('item_code')] = b.get('actual_qty') - for item in items_data: item_code = item.item_code item_price = item_prices.get(item_code) or {} - item_stock_qty = bin_dict.get(item_code) + item_stock_qty = get_stock_availability(item_code, warehouse) if display_items_in_stock and not item_stock_qty: pass @@ -116,6 +101,13 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p 'items': result } + if len(res['items']) == 1: + res['items'][0].setdefault('serial_no', serial_no) + res['items'][0].setdefault('batch_no', batch_no) + res['items'][0].setdefault('barcode', barcode) + + return res + if serial_no: res.update({ 'serial_no': serial_no @@ -186,6 +178,73 @@ def item_group_query(doctype, txt, searchfield, start, page_len, filters): {'txt': '%%%s%%' % txt}) @frappe.whitelist() -def get_pos_fields(): - return frappe.get_all("POS Field", fields=["label", "fieldname", - "fieldtype", "default_value", "reqd", "read_only", "options"]) +def check_opening_entry(user): + open_vouchers = frappe.db.get_all("POS Opening Entry", + filters = { + "user": user, + "pos_closing_entry": ["in", ["", None]], + "docstatus": 1 + }, + fields = ["name", "company", "pos_profile", "period_start_date"], + order_by = "period_start_date desc" + ) + + return open_vouchers + +@frappe.whitelist() +def create_opening_voucher(pos_profile, company, balance_details): + import json + balance_details = json.loads(balance_details) + + new_pos_opening = frappe.get_doc({ + 'doctype': 'POS Opening Entry', + "period_start_date": frappe.utils.get_datetime(), + "posting_date": frappe.utils.getdate(), + "user": frappe.session.user, + "pos_profile": pos_profile, + "company": company, + }) + new_pos_opening.set("balance_details", balance_details) + new_pos_opening.submit() + + return new_pos_opening.as_dict() + +@frappe.whitelist() +def get_past_order_list(search_term, status, limit=20): + fields = ['name', 'grand_total', 'currency', 'customer', 'posting_time', 'posting_date'] + invoice_list = [] + + if search_term and status: + invoices_by_customer = frappe.db.get_all('POS Invoice', filters={ + 'customer': ['like', '%{}%'.format(search_term)], + 'status': status + }, fields=fields) + invoices_by_name = frappe.db.get_all('POS Invoice', filters={ + 'name': ['like', '%{}%'.format(search_term)], + 'status': status + }, fields=fields) + + invoice_list = invoices_by_customer + invoices_by_name + elif status: + invoice_list = frappe.db.get_all('POS Invoice', filters={ + 'status': status + }, fields=fields) + + return invoice_list + +@frappe.whitelist() +def set_customer_info(fieldname, customer, value=""): + if fieldname == 'loyalty_program': + frappe.db.set_value('Customer', customer, 'loyalty_program', value) + + contact = frappe.get_cached_value('Customer', customer, 'customer_primary_contact') + + if contact: + contact_doc = frappe.get_doc('Contact', contact) + if fieldname == 'email_id': + contact_doc.set('email_ids', [{ 'email_id': value, 'is_primary': 1}]) + frappe.db.set_value('Customer', customer, 'email_id', value) + elif fieldname == 'mobile_no': + contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}]) + frappe.db.set_value('Customer', customer, 'mobile_no', value) + contact_doc.save() \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js new file mode 100644 index 0000000000..483ef78d64 --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -0,0 +1,714 @@ +{% include "erpnext/selling/page/point_of_sale/onscan.js" %} +{% include "erpnext/selling/page/point_of_sale/pos_item_selector.js" %} +{% include "erpnext/selling/page/point_of_sale/pos_item_cart.js" %} +{% include "erpnext/selling/page/point_of_sale/pos_item_details.js" %} +{% include "erpnext/selling/page/point_of_sale/pos_payment.js" %} +{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %} +{% include "erpnext/selling/page/point_of_sale/pos_past_order_list.js" %} +{% include "erpnext/selling/page/point_of_sale/pos_past_order_summary.js" %} + +erpnext.PointOfSale.Controller = class { + constructor(wrapper) { + this.wrapper = $(wrapper).find('.layout-main-section'); + this.page = wrapper.page; + + this.load_assets(); + } + + load_assets() { + // after loading assets first check if opening entry has been made + frappe.require(['assets/erpnext/css/pos.css'], this.check_opening_entry.bind(this)); + } + + check_opening_entry() { + return frappe.call("erpnext.selling.page.point_of_sale.point_of_sale.check_opening_entry", { "user": frappe.session.user }) + .then((r) => { + if (r.message.length) { + // assuming only one opening voucher is available for the current user + this.prepare_app_defaults(r.message[0]); + } else { + this.create_opening_voucher(); + } + }); + } + + create_opening_voucher() { + const table_fields = [ + { fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 }, + { fieldname: "opening_amount", fieldtype: "Currency", in_list_view: 1, label: "Opening Amount", options: "company:company_currency", reqd: 1 } + ]; + + const dialog = new frappe.ui.Dialog({ + title: __('Create POS Opening Entry'), + fields: [ + { + fieldtype: 'Link', label: __('Company'), default: frappe.defaults.get_default('company'), + options: 'Company', fieldname: 'company', reqd: 1 + }, + { + fieldtype: 'Link', label: __('POS Profile'), + options: 'POS Profile', fieldname: 'pos_profile', reqd: 1, + onchange: () => { + const pos_profile = dialog.fields_dict.pos_profile.get_value(); + const company = dialog.fields_dict.company.get_value(); + const user = frappe.session.user + + if (!pos_profile || !company || !user) return; + + // auto fetch last closing entry's balance details + frappe.db.get_list("POS Closing Entry", { + filters: { company, pos_profile, user }, + limit: 1, + order_by: 'period_end_date desc' + }).then((res) => { + if (!res.length) return; + const pos_closing_entry = res[0]; + frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => { + dialog.fields_dict.balance_details.df.data = []; + payment_reconciliation.forEach(pay => { + const { mode_of_payment, closing_amount } = pay; + dialog.fields_dict.balance_details.df.data.push({ + mode_of_payment: mode_of_payment + }); + }); + dialog.fields_dict.balance_details.grid.refresh(); + }); + }); + } + }, + { + fieldname: "balance_details", + fieldtype: "Table", + label: "Opening Balance Details", + cannot_add_rows: false, + in_place_edit: true, + reqd: 1, + data: [], + fields: table_fields + } + ], + primary_action: ({ company, pos_profile, balance_details }) => { + if (!balance_details.length) { + frappe.show_alert({ + message: __("Please add Mode of payments and opening balance details."), + indicator: 'red' + }) + frappe.utils.play_sound("error"); + return; + } + frappe.dom.freeze(); + return frappe.call("erpnext.selling.page.point_of_sale.point_of_sale.create_opening_voucher", + { pos_profile, company, balance_details }) + .then((r) => { + frappe.dom.unfreeze(); + dialog.hide(); + if (r.message) { + this.prepare_app_defaults(r.message); + } + }) + }, + primary_action_label: __('Submit') + }); + dialog.show(); + } + + prepare_app_defaults(data) { + this.pos_opening = data.name; + this.company = data.company; + this.pos_profile = data.pos_profile; + this.pos_opening_time = data.period_start_date; + + frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => { + this.allow_negative_stock = flt(message.allow_negative_stock) || false; + }); + + frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => { + this.customer_groups = profile.customer_groups.map(group => group.customer_group); + this.cart.make_customer_selector(); + }); + + this.item_stock_map = {}; + + this.make_app(); + } + + set_opening_entry_status() { + this.page.set_title_sub( + ` + + Opened at ${moment(this.pos_opening_time).format("Do MMMM, h:mma")} + + `); + } + + make_app() { + return frappe.run_serially([ + () => frappe.dom.freeze(), + () => { + this.set_opening_entry_status(); + this.prepare_dom(); + this.prepare_components(); + this.prepare_menu(); + }, + () => this.make_new_invoice(), + () => frappe.dom.unfreeze(), + () => this.page.set_title(__('Point of Sale Beta')), + ]); + } + + prepare_dom() { + this.wrapper.append(` +
    ` + ); + + this.$components_wrapper = this.wrapper.find('.app'); + } + + prepare_components() { + this.init_item_selector(); + this.init_item_details(); + this.init_item_cart(); + this.init_payments(); + this.init_recent_order_list(); + this.init_order_summary(); + } + + prepare_menu() { + var me = this; + this.page.clear_menu(); + + this.page.add_menu_item(__("Form View"), function () { + frappe.model.sync(me.frm.doc); + frappe.set_route("Form", me.frm.doc.doctype, me.frm.doc.name); + }); + + this.page.add_menu_item(__("Toggle Recent Orders"), () => { + const show = this.recent_order_list.$component.hasClass('d-none'); + this.toggle_recent_order_list(show); + }); + + this.page.add_menu_item(__("Save as Draft"), this.save_draft_invoice.bind(this)); + + frappe.ui.keys.on("ctrl+s", this.save_draft_invoice.bind(this)); + + this.page.add_menu_item(__('Close the POS'), this.close_pos.bind(this)); + + frappe.ui.keys.on("shift+ctrl+s", this.close_pos.bind(this)); + } + + save_draft_invoice() { + if (!this.$components_wrapper.is(":visible")) return; + + if (this.frm.doc.items.length == 0) { + frappe.show_alert({ + message:__("You must add atleast one item to save it as draft."), + indicator:'red' + }); + frappe.utils.play_sound("error"); + return; + } + + this.frm.save(undefined, undefined, undefined, () => { + frappe.show_alert({ + message:__("There was an error saving the document."), + indicator:'red' + }); + frappe.utils.play_sound("error"); + }).then(() => { + frappe.run_serially([ + () => frappe.dom.freeze(), + () => this.make_new_invoice(), + () => frappe.dom.unfreeze(), + ]); + }) + } + + close_pos() { + if (!this.$components_wrapper.is(":visible")) return; + + let voucher = frappe.model.get_new_doc('POS Closing Entry'); + voucher.pos_profile = this.frm.doc.pos_profile; + voucher.user = frappe.session.user; + voucher.company = this.frm.doc.company; + voucher.pos_opening_entry = this.pos_opening; + voucher.period_end_date = frappe.datetime.now_datetime(); + voucher.posting_date = frappe.datetime.now_date(); + frappe.set_route('Form', 'POS Closing Entry', voucher.name); + } + + init_item_selector() { + this.item_selector = new erpnext.PointOfSale.ItemSelector({ + wrapper: this.$components_wrapper, + pos_profile: this.pos_profile, + events: { + item_selected: args => this.on_cart_update(args), + + get_frm: () => this.frm || {}, + + get_allowed_item_group: () => this.item_groups + } + }) + } + + init_item_cart() { + this.cart = new erpnext.PointOfSale.ItemCart({ + wrapper: this.$components_wrapper, + events: { + get_frm: () => this.frm, + + cart_item_clicked: (item_code, batch_no, uom) => { + const item_row = this.frm.doc.items.find( + i => i.item_code === item_code + && i.uom === uom + && (!batch_no || (batch_no && i.batch_no === batch_no)) + ); + this.item_details.toggle_item_details_section(item_row); + }, + + numpad_event: (value, action) => this.update_item_field(value, action), + + checkout: () => this.payment.checkout(), + + edit_cart: () => this.payment.edit_cart(), + + customer_details_updated: (details) => { + this.customer_details = details; + // will add/remove LP payment method + this.payment.render_loyalty_points_payment_mode(); + }, + + get_allowed_customer_group: () => this.customer_groups + } + }) + } + + init_item_details() { + this.item_details = new erpnext.PointOfSale.ItemDetails({ + wrapper: this.$components_wrapper, + events: { + get_frm: () => this.frm, + + toggle_item_selector: (minimize) => { + this.item_selector.resize_selector(minimize); + this.cart.toggle_numpad(minimize); + }, + + form_updated: async (cdt, cdn, fieldname, value) => { + const item_row = frappe.model.get_doc(cdt, cdn); + if (item_row && item_row[fieldname] != value) { + + if (fieldname === 'qty' && flt(value) == 0) { + this.remove_item_from_cart(); + return; + } + + const { item_code, batch_no, uom } = this.item_details.current_item; + const event = { + field: fieldname, + value, + item: { item_code, batch_no, uom } + } + return this.on_cart_update(event) + } + }, + + item_field_focused: (fieldname) => { + this.cart.toggle_numpad_field_edit(fieldname); + }, + set_value_in_current_cart_item: (selector, value) => { + this.cart.update_selector_value_in_cart_item(selector, value, this.item_details.current_item); + }, + clone_new_batch_item_in_frm: (batch_serial_map, current_item) => { + // called if serial nos are 'auto_selected' and if those serial nos belongs to multiple batches + // for each unique batch new item row is added in the form & cart + Object.keys(batch_serial_map).forEach(batch => { + const { item_code, batch_no } = current_item; + const item_to_clone = this.frm.doc.items.find(i => i.item_code === item_code && i.batch_no === batch_no); + const new_row = this.frm.add_child("items", { ...item_to_clone }); + // update new serialno and batch + new_row.batch_no = batch; + new_row.serial_no = batch_serial_map[batch].join(`\n`); + new_row.qty = batch_serial_map[batch].length; + this.frm.doc.items.forEach(row => { + if (item_code === row.item_code) { + this.update_cart_html(row); + } + }); + }) + }, + remove_item_from_cart: () => this.remove_item_from_cart(), + get_item_stock_map: () => this.item_stock_map, + close_item_details: () => { + this.item_details.toggle_item_details_section(undefined); + this.cart.prev_action = undefined; + this.cart.toggle_item_highlight(); + }, + get_available_stock: (item_code, warehouse) => this.get_available_stock(item_code, warehouse) + } + }); + } + + init_payments() { + this.payment = new erpnext.PointOfSale.Payment({ + wrapper: this.$components_wrapper, + events: { + get_frm: () => this.frm || {}, + + get_customer_details: () => this.customer_details || {}, + + toggle_other_sections: (show) => { + if (show) { + this.item_details.$component.hasClass('d-none') ? '' : this.item_details.$component.addClass('d-none'); + this.item_selector.$component.addClass('d-none'); + } else { + this.item_selector.$component.removeClass('d-none'); + } + }, + + submit_invoice: () => { + this.frm.savesubmit() + .then((r) => { + // this.set_invoice_status(); + this.toggle_components(false); + this.order_summary.toggle_component(true); + this.order_summary.load_summary_of(this.frm.doc, true); + frappe.show_alert({ + indicator: 'green', + message: __(`POS invoice ${r.doc.name} created succesfully`) + }); + }); + } + } + }); + } + + init_recent_order_list() { + this.recent_order_list = new erpnext.PointOfSale.PastOrderList({ + wrapper: this.$components_wrapper, + events: { + open_invoice_data: (name) => { + frappe.db.get_doc('POS Invoice', name).then((doc) => { + this.order_summary.load_summary_of(doc); + }); + }, + reset_summary: () => this.order_summary.show_summary_placeholder() + } + }) + } + + init_order_summary() { + this.order_summary = new erpnext.PointOfSale.PastOrderSummary({ + wrapper: this.$components_wrapper, + events: { + get_frm: () => this.frm, + + process_return: (name) => { + this.recent_order_list.toggle_component(false); + frappe.db.get_doc('POS Invoice', name).then((doc) => { + frappe.run_serially([ + () => this.make_return_invoice(doc), + () => this.cart.load_invoice(), + () => this.item_selector.toggle_component(true) + ]); + }); + }, + edit_order: (name) => { + this.recent_order_list.toggle_component(false); + frappe.run_serially([ + () => this.frm.refresh(name), + () => this.cart.load_invoice(), + () => this.item_selector.toggle_component(true) + ]); + }, + new_order: () => { + frappe.run_serially([ + () => frappe.dom.freeze(), + () => this.make_new_invoice(), + () => this.item_selector.toggle_component(true), + () => frappe.dom.unfreeze(), + ]); + } + } + }) + } + + + + toggle_recent_order_list(show) { + this.toggle_components(!show); + this.recent_order_list.toggle_component(show); + this.order_summary.toggle_component(show); + } + + toggle_components(show) { + this.cart.toggle_component(show); + this.item_selector.toggle_component(show); + + // do not show item details or payment if recent order is toggled off + !show ? (this.item_details.toggle_component(false) || this.payment.toggle_component(false)) : ''; + } + + make_new_invoice() { + return frappe.run_serially([ + () => this.make_sales_invoice_frm(), + () => this.set_pos_profile_data(), + () => this.set_pos_profile_status(), + () => this.cart.load_invoice(), + ]); + } + + make_sales_invoice_frm() { + const doctype = 'POS Invoice'; + return new Promise(resolve => { + if (this.frm) { + this.frm = this.get_new_frm(this.frm); + this.frm.doc.items = []; + this.frm.doc.is_pos = 1 + resolve(); + } else { + frappe.model.with_doctype(doctype, () => { + this.frm = this.get_new_frm(); + this.frm.doc.items = []; + this.frm.doc.is_pos = 1 + resolve(); + }); + } + }); + } + + get_new_frm(_frm) { + const doctype = 'POS Invoice'; + const page = $('
    '); + const frm = _frm || new frappe.ui.form.Form(doctype, page, false); + const name = frappe.model.make_new_doc_and_get_name(doctype, true); + frm.refresh(name); + + return frm; + } + + async make_return_invoice(doc) { + frappe.dom.freeze(); + this.frm = this.get_new_frm(this.frm); + this.frm.doc.items = []; + const res = await frappe.call({ + method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return", + args: { + 'source_name': doc.name, + 'target_doc': this.frm.doc + } + }); + frappe.model.sync(res.message); + await this.set_pos_profile_data(); + frappe.dom.unfreeze(); + } + + set_pos_profile_data() { + if (this.company && !this.frm.doc.company) this.frm.doc.company = this.company; + if (this.pos_profile && !this.frm.doc.pos_profile) this.frm.doc.pos_profile = this.pos_profile; + if (!this.frm.doc.company) return; + + return new Promise(resolve => { + return this.frm.call({ + doc: this.frm.doc, + method: "set_missing_values", + }).then((r) => { + if(!r.exc) { + if (!this.frm.doc.pos_profile) { + frappe.dom.unfreeze(); + this.raise_exception_for_pos_profile(); + } + this.frm.trigger("update_stock"); + this.frm.trigger('calculate_taxes_and_totals'); + if(this.frm.doc.taxes_and_charges) this.frm.script_manager.trigger("taxes_and_charges"); + frappe.model.set_default_values(this.frm.doc); + if (r.message) { + this.frm.pos_print_format = r.message.print_format || ""; + this.frm.meta.default_print_format = r.message.print_format || ""; + this.frm.allow_edit_rate = r.message.allow_edit_rate; + this.frm.allow_edit_discount = r.message.allow_edit_discount; + this.frm.doc.campaign = r.message.campaign; + } + } + resolve(); + }); + }); + } + + raise_exception_for_pos_profile() { + setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000); + frappe.throw(__("POS Profile is required to use Point-of-Sale")); + } + + set_invoice_status() { + const [status, indicator] = frappe.listview_settings["POS Invoice"].get_indicator(this.frm.doc); + this.page.set_indicator(__(`${status}`), indicator); + } + + set_pos_profile_status() { + this.page.set_indicator(__(`${this.pos_profile}`), "blue"); + } + + async on_cart_update(args) { + frappe.dom.freeze(); + try { + let { field, value, item } = args; + const { item_code, batch_no, serial_no, uom } = item; + let item_row = this.get_item_from_frm(item_code, batch_no, uom); + + const item_selected_from_selector = field === 'qty' && value === "+1" + + if (item_row) { + item_selected_from_selector && (value = item_row.qty + flt(value)) + + field === 'qty' && (value = flt(value)); + + if (field === 'qty' && value > 0 && !this.allow_negative_stock) + await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); + + if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) { + await frappe.model.set_value(item_row.doctype, item_row.name, field, value); + this.update_cart_html(item_row); + } + + } else { + if (!this.frm.doc.customer) { + frappe.dom.unfreeze(); + frappe.show_alert({ + message: __('You must select a customer before adding an item.'), + indicator: 'orange' + }); + frappe.utils.play_sound("error"); + return; + } + item_selected_from_selector && (value = flt(value)) + + const args = { item_code, batch_no, [field]: value }; + + if (serial_no) args['serial_no'] = serial_no; + + if (field === 'serial_no') args['qty'] = value.split(`\n`).length || 0; + + item_row = this.frm.add_child('items', args); + + if (field === 'qty' && value !== 0 && !this.allow_negative_stock) + await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); + + await this.trigger_new_item_events(item_row); + + this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row); + this.update_cart_html(item_row); + } + } catch (error) { + console.log(error); + } finally { + frappe.dom.unfreeze(); + } + } + + get_item_from_frm(item_code, batch_no, uom) { + const has_batch_no = batch_no; + return this.frm.doc.items.find( + i => i.item_code === item_code + && (!has_batch_no || (has_batch_no && i.batch_no === batch_no)) + && (i.uom === uom) + ); + } + + edit_item_details_of(item_row) { + this.item_details.toggle_item_details_section(item_row); + } + + is_current_item_being_edited(item_row) { + const { item_code, batch_no } = this.item_details.current_item; + + return item_code !== item_row.item_code || batch_no != item_row.batch_no ? false : true; + } + + update_cart_html(item_row, remove_item) { + this.cart.update_item_html(item_row, remove_item); + this.cart.update_totals_section(this.frm); + } + + check_serial_batch_selection_needed(item_row) { + // right now item details is shown for every type of item. + // if item details is not shown for every item then this fn will be needed + const serialized = item_row.has_serial_no; + const batched = item_row.has_batch_no; + const no_serial_selected = !item_row.serial_no; + const no_batch_selected = !item_row.batch_no; + + if ((serialized && no_serial_selected) || (batched && no_batch_selected) || + (serialized && batched && (no_batch_selected || no_serial_selected))) { + return true; + } + return false; + } + + async trigger_new_item_events(item_row) { + await this.frm.script_manager.trigger('item_code', item_row.doctype, item_row.name) + await this.frm.script_manager.trigger('qty', item_row.doctype, item_row.name) + } + + async check_stock_availability(item_row, qty_needed, warehouse) { + const available_qty = (await this.get_available_stock(item_row.item_code, warehouse)).message; + + frappe.dom.unfreeze(); + if (!(available_qty > 0)) { + frappe.model.clear_doc(item_row.doctype, item_row.name); + frappe.throw(__(`Item Code: ${item_row.item_code.bold()} is not available under warehouse ${warehouse.bold()}.`)) + } else if (available_qty < qty_needed) { + frappe.show_alert({ + message: __(`Stock quantity not enough for Item Code: ${item_row.item_code.bold()} under warehouse ${warehouse.bold()}. + Available quantity ${available_qty.toString().bold()}.`), + indicator: 'orange' + }); + frappe.utils.play_sound("error"); + this.item_details.qty_control.set_value(flt(available_qty)); + } + frappe.dom.freeze(); + } + + get_available_stock(item_code, warehouse) { + const me = this; + return frappe.call({ + method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.get_stock_availability", + args: { + 'item_code': item_code, + 'warehouse': warehouse, + }, + callback(res) { + if (!me.item_stock_map[item_code]) + me.item_stock_map[item_code] = {} + me.item_stock_map[item_code][warehouse] = res.message; + } + }); + } + + update_item_field(value, field_or_action) { + if (field_or_action === 'checkout') { + this.item_details.toggle_item_details_section(undefined); + } else if (field_or_action === 'remove') { + this.remove_item_from_cart(); + } else { + const field_control = this.item_details[`${field_or_action}_control`]; + if (!field_control) return; + field_control.set_focus(); + value != "" && field_control.set_value(value); + } + } + + remove_item_from_cart() { + frappe.dom.freeze(); + const { doctype, name, current_item } = this.item_details; + + frappe.model.set_value(doctype, name, 'qty', 0); + + this.frm.script_manager.trigger('qty', doctype, name).then(() => { + frappe.model.clear_doc(doctype, name); + this.update_cart_html(current_item, true); + this.item_details.toggle_item_details_section(undefined); + frappe.dom.unfreeze(); + }) + } +} + diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js new file mode 100644 index 0000000000..c23a6ad58f --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -0,0 +1,951 @@ +erpnext.PointOfSale.ItemCart = class { + constructor({ wrapper, events }) { + this.wrapper = wrapper; + this.events = events; + this.customer_info = undefined; + + this.init_component(); + } + + init_component() { + this.prepare_dom(); + this.init_child_components(); + this.bind_events(); + this.attach_shortcuts(); + } + + prepare_dom() { + this.wrapper.append( + `
    ` + ) + + this.$component = this.wrapper.find('.item-cart'); + } + + init_child_components() { + this.init_customer_selector(); + this.init_cart_components(); + } + + init_customer_selector() { + this.$component.append( + `
    ` + ) + this.$customer_section = this.$component.find('.customer-section'); + } + + reset_customer_selector() { + const frm = this.events.get_frm(); + frm.set_value('customer', ''); + this.$customer_section.removeClass('border pr-4 pl-4'); + this.make_customer_selector(); + this.customer_field.set_focus(); + } + + init_cart_components() { + this.$component.append( + `
    +
    +
    +
    Item
    +
    Qty
    +
    Amount
    +
    +
    +
    +
    +
    +
    ` + ); + this.$cart_container = this.$component.find('.cart-container'); + + this.make_cart_totals_section(); + this.make_cart_items_section(); + this.make_cart_numpad(); + } + + make_cart_items_section() { + this.$cart_header = this.$component.find('.cart-header'); + this.$cart_items_wrapper = this.$component.find('.cart-items-section'); + + this.make_no_items_placeholder(); + } + + make_no_items_placeholder() { + this.$cart_header.addClass('d-none'); + this.$cart_items_wrapper.html( + `
    +
    No items in cart
    +
    ` + ) + this.$cart_items_wrapper.addClass('mt-4 border-grey border-dashed'); + } + + make_cart_totals_section() { + this.$totals_section = this.$component.find('.cart-totals-section'); + + this.$totals_section.append( + `
    + + Add Discount +
    +
    +
    +
    +
    Net Total
    +
    +
    +
    0.00
    +
    +
    +
    +
    +
    +
    Grand Total
    +
    +
    +
    0.00
    +
    +
    +
    + Checkout +
    +
    + Edit Cart +
    +
    ` + ) + + this.$add_discount_elem = this.$component.find(".add-discount"); + } + + make_cart_numpad() { + this.$numpad_section = this.$component.find('.numpad-section'); + + this.number_pad = new erpnext.PointOfSale.NumberPad({ + wrapper: this.$numpad_section, + events: { + numpad_event: this.on_numpad_event.bind(this) + }, + cols: 5, + keys: [ + [ 1, 2, 3, 'Quantity' ], + [ 4, 5, 6, 'Discount' ], + [ 7, 8, 9, 'Rate' ], + [ '.', 0, 'Delete', 'Remove' ] + ], + css_classes: [ + [ '', '', '', 'col-span-2' ], + [ '', '', '', 'col-span-2' ], + [ '', '', '', 'col-span-2' ], + [ '', '', '', 'col-span-2 text-bold text-danger' ] + ], + fieldnames_map: { 'Quantity': 'qty', 'Discount': 'discount_percentage' } + }) + + this.$numpad_section.prepend( + `
    + + +
    ` + ) + + this.$numpad_section.append( + `
    + Checkout +
    ` + ) + } + + bind_events() { + const me = this; + this.$customer_section.on('click', '.add-remove-customer', function (e) { + const customer_info_is_visible = me.$cart_container.hasClass('d-none'); + customer_info_is_visible ? + me.toggle_customer_info(false) : me.reset_customer_selector(); + }); + + this.$customer_section.on('click', '.customer-header', function(e) { + // don't triggger the event if .add-remove-customer btn is clicked which is under .customer-header + if ($(e.target).closest('.add-remove-customer').length) return; + + const show = !me.$cart_container.hasClass('d-none'); + me.toggle_customer_info(show); + }); + + this.$cart_items_wrapper.on('click', '.cart-item-wrapper', function() { + const $cart_item = $(this); + + me.toggle_item_highlight(this); + + const payment_section_hidden = me.$totals_section.find('.edit-cart-btn').hasClass('d-none'); + if (!payment_section_hidden) { + // payment section is visible + // edit cart first and then open item details section + me.$totals_section.find(".edit-cart-btn").click(); + } + + const item_code = unescape($cart_item.attr('data-item-code')); + const batch_no = unescape($cart_item.attr('data-batch-no')); + const uom = unescape($cart_item.attr('data-uom')); + me.events.cart_item_clicked(item_code, batch_no, uom); + this.numpad_value = ''; + }); + + this.$component.on('click', '.checkout-btn', function() { + if (!$(this).hasClass('bg-primary')) return; + + me.events.checkout(); + me.toggle_checkout_btn(false); + + me.$add_discount_elem.removeClass("d-none"); + }); + + this.$totals_section.on('click', '.edit-cart-btn', () => { + this.events.edit_cart(); + this.toggle_checkout_btn(true); + + this.$add_discount_elem.addClass("d-none"); + }); + + this.$component.on('click', '.add-discount', () => { + const can_edit_discount = this.$add_discount_elem.find('.edit-discount').length; + + if(!this.discount_field || can_edit_discount) this.show_discount_control(); + }); + + frappe.ui.form.on("POS Invoice", "paid_amount", frm => { + // called when discount is applied + this.update_totals_section(frm); + }); + } + + attach_shortcuts() { + for (let row of this.number_pad.keys) { + for (let btn of row) { + let shortcut_key = `ctrl+${frappe.scrub(String(btn))[0]}`; + if (btn === 'Delete') shortcut_key = 'ctrl+backspace'; + if (btn === 'Remove') shortcut_key = 'shift+ctrl+backspace' + if (btn === '.') shortcut_key = 'ctrl+>'; + + // to account for fieldname map + const fieldname = this.number_pad.fieldnames[btn] ? this.number_pad.fieldnames[btn] : + typeof btn === 'string' ? frappe.scrub(btn) : btn; + + frappe.ui.keys.on(`${shortcut_key}`, () => { + const cart_is_visible = this.$component.is(":visible"); + if (cart_is_visible && this.item_is_selected && this.$numpad_section.is(":visible")) { + this.$numpad_section.find(`.numpad-btn[data-button-value="${fieldname}"]`).click(); + } + }) + } + } + + frappe.ui.keys.on("ctrl+enter", () => { + const cart_is_visible = this.$component.is(":visible"); + const payment_section_hidden = this.$totals_section.find('.edit-cart-btn').hasClass('d-none'); + if (cart_is_visible && payment_section_hidden) { + this.$component.find(".checkout-btn").click(); + } + }); + } + + toggle_item_highlight(item) { + const $cart_item = $(item); + const item_is_highlighted = $cart_item.hasClass("shadow"); + + if (!item || item_is_highlighted) { + this.item_is_selected = false; + this.$cart_container.find('.cart-item-wrapper').removeClass("shadow").css("opacity", "1"); + } else { + $cart_item.addClass("shadow"); + this.item_is_selected = true; + this.$cart_container.find('.cart-item-wrapper').css("opacity", "1"); + this.$cart_container.find('.cart-item-wrapper').not(item).removeClass("shadow").css("opacity", "0.65"); + } + // highlight with inner shadow + // $cart_item.addClass("shadow-inner bg-selected"); + // me.$cart_container.find('.cart-item-wrapper').not(this).removeClass("shadow-inner bg-selected"); + } + + make_customer_selector() { + this.$customer_section.html(`
    `); + const me = this; + const query = { query: 'erpnext.controllers.queries.customer_query' }; + const allowed_customer_group = this.events.get_allowed_customer_group() || []; + if (allowed_customer_group.length) { + query.filters = { + customer_group: ['in', allowed_customer_group] + } + } + this.customer_field = frappe.ui.form.make_control({ + df: { + label: __('Customer'), + fieldtype: 'Link', + options: 'Customer', + placeholder: __('Search by customer name, phone, email.'), + get_query: () => query, + onchange: function() { + if (this.value) { + const frm = me.events.get_frm(); + frappe.dom.freeze(); + frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'customer', this.value); + frm.script_manager.trigger('customer', frm.doc.doctype, frm.doc.name).then(() => { + frappe.run_serially([ + () => me.fetch_customer_details(this.value), + () => me.events.customer_details_updated(me.customer_info), + () => me.update_customer_section(), + () => me.update_totals_section(), + () => frappe.dom.unfreeze() + ]); + }) + } + }, + }, + parent: this.$customer_section.find('.customer-search-field'), + render_input: true, + }); + this.customer_field.toggle_label(false); + } + + fetch_customer_details(customer) { + if (customer) { + return new Promise((resolve) => { + frappe.db.get_value('Customer', customer, ["email_id", "mobile_no", "image", "loyalty_program"]).then(({ message }) => { + const { loyalty_program } = message; + // if loyalty program then fetch loyalty points too + if (loyalty_program) { + frappe.call({ + method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details_with_points", + args: { customer, loyalty_program, "silent": true }, + callback: (r) => { + const { loyalty_points, conversion_factor } = r.message; + if (!r.exc) { + this.customer_info = { ...message, customer, loyalty_points, conversion_factor }; + resolve(); + } + } + }); + } else { + this.customer_info = { ...message, customer }; + resolve(); + } + }); + }); + } else { + return new Promise((resolve) => { + this.customer_info = {} + resolve(); + }); + } + } + + show_discount_control() { + this.$add_discount_elem.removeClass("pr-4 pl-4"); + this.$add_discount_elem.html( + `
    +
    ` + ); + const me = this; + + this.discount_field = frappe.ui.form.make_control({ + df: { + label: __('Discount'), + fieldtype: 'Data', + placeholder: __('Enter discount percentage.'), + onchange: function() { + if (this.value || this.value == 0) { + const frm = me.events.get_frm(); + frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', this.value); + me.hide_discount_control(this.value); + } + }, + }, + parent: this.$add_discount_elem.find('.add-dicount-field'), + render_input: true, + }); + this.discount_field.toggle_label(false); + this.discount_field.set_focus(); + } + + hide_discount_control(discount) { + this.$add_discount_elem.addClass('pr-4 pl-4'); + this.$add_discount_elem.html( + ` + + +
    + ${String(discount).bold()}% off +
    + ` + ); + } + + update_customer_section() { + const { customer, email_id='', mobile_no='', image } = this.customer_info || {}; + + if (customer) { + this.$customer_section.addClass('border pr-4 pl-4').html( + `
    +
    + ${get_customer_image()} +
    +
    ${customer}
    + ${get_customer_description()} +
    +
    + + + +
    +
    +
    ` + ); + } else { + // reset customer selector + this.reset_customer_selector(); + } + + function get_customer_description() { + if (!email_id && !mobile_no) { + return `
    Click to add email / phone
    ` + } else if (email_id && !mobile_no) { + return `
    ${email_id}
    ` + } else if (mobile_no && !email_id) { + return `
    ${mobile_no}
    ` + } else { + return `
    ${email_id} | ${mobile_no}
    ` + } + } + + function get_customer_image() { + if (image) { + return `
    + ${image} +
    ` + } else { + return `
    + ${frappe.get_abbr(customer)} +
    ` + } + } + } + + update_totals_section(frm) { + if (!frm) frm = this.events.get_frm(); + + this.render_net_total(frm.doc.base_net_total); + this.render_grand_total(frm.doc.base_grand_total); + + const taxes = frm.doc.taxes.map(t => { return { description: t.description, rate: t.rate }}) + this.render_taxes(frm.doc.base_total_taxes_and_charges, taxes); + } + + render_net_total(value) { + const currency = this.events.get_frm().doc.currency; + this.$totals_section.find('.net-total').html( + `
    +
    Net Total
    +
    +
    +
    ${format_currency(value, currency)}
    +
    ` + ) + + this.$numpad_section.find('.numpad-net-total').html(`Net Total: ${format_currency(value, currency)}`) + } + + render_grand_total(value) { + const currency = this.events.get_frm().doc.currency; + this.$totals_section.find('.grand-total').html( + `
    +
    Grand Total
    +
    +
    +
    ${format_currency(value, currency)}
    +
    ` + ) + + this.$numpad_section.find('.numpad-grand-total').html(`Grand Total: ${format_currency(value, currency)}`) + } + + render_taxes(value, taxes) { + if (taxes.length) { + const currency = this.events.get_frm().doc.currency; + this.$totals_section.find('.taxes').html( + `
    +
    +
    Tax Charges
    +
    + ${ + taxes.map((t, i) => { + let margin_left = ''; + if (i !== 0) margin_left = 'ml-2'; + return `${t.description}` + }).join('') + } +
    +
    +
    +
    ${format_currency(value, currency)}
    +
    +
    ` + ) + } else { + this.$totals_section.find('.taxes').html('') + } + } + + get_cart_item({ item_code, batch_no, uom }) { + const batch_attr = `[data-batch-no="${escape(batch_no)}"]`; + const item_code_attr = `[data-item-code="${escape(item_code)}"]`; + const uom_attr = `[data-uom=${escape(uom)}]`; + + const item_selector = batch_no ? + `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`; + + return this.$cart_items_wrapper.find(item_selector); + } + + update_item_html(item, remove_item) { + const $item = this.get_cart_item(item); + + if (remove_item) { + $item && $item.remove(); + } else { + const { item_code, batch_no, uom } = item; + const search_field = batch_no ? 'batch_no' : 'item_code'; + const search_value = batch_no || item_code; + const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom); + + this.render_cart_item(item_row, $item); + } + + const no_of_cart_items = this.$cart_items_wrapper.children().length; + no_of_cart_items > 0 && this.highlight_checkout_btn(no_of_cart_items > 0); + + this.update_empty_cart_section(no_of_cart_items); + } + + render_cart_item(item_data, $item_to_update) { + const currency = this.events.get_frm().doc.currency; + const me = this; + + if (!$item_to_update.length) { + this.$cart_items_wrapper.append( + `
    +
    ` + ) + $item_to_update = this.get_cart_item(item_data); + } + + $item_to_update.html( + `
    +
    + ${item_data.item_name} +
    + ${get_description_html()} +
    + ${get_rate_discount_html()} +
    ` + ) + + set_dynamic_rate_header_width(); + this.scroll_to_item($item_to_update); + + function set_dynamic_rate_header_width() { + const rate_cols = Array.from(me.$cart_items_wrapper.find(".rate-col")); + me.$cart_header.find(".rate-list-header").css("width", ""); + me.$cart_items_wrapper.find(".rate-col").css("width", ""); + let max_width = rate_cols.reduce((max_width, elm) => { + if ($(elm).width() > max_width) + max_width = $(elm).width(); + return max_width; + }, 0); + + max_width += 1; + if (max_width == 1) max_width = ""; + + me.$cart_header.find(".rate-list-header").css("width", max_width); + me.$cart_items_wrapper.find(".rate-col").css("width", max_width); + } + + function get_rate_discount_html() { + if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) { + return ` +
    +
    + ${item_data.qty || 0} +
    +
    +
    ${format_currency(item_data.amount, currency)}
    +
    ${format_currency(item_data.rate, currency)}
    +
    +
    ` + } else { + return ` +
    +
    + ${item_data.qty || 0} +
    +
    +
    ${format_currency(item_data.rate, currency)}
    +
    +
    ` + } + } + + function get_description_html() { + if (item_data.description) { + if (item_data.description.indexOf('
    ') != -1) { + try { + item_data.description = $(item_data.description).text(); + } catch (error) { + item_data.description = item_data.description.replace(/
    /g, ' ').replace(/<\/div>/g, ' ').replace(/ +/g, ' '); + } + } + item_data.description = frappe.ellipsis(item_data.description, 45); + return `
    ${item_data.description}
    ` + } + return ``; + } + } + + scroll_to_item($item) { + if ($item.length === 0) return; + const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop(); + this.$cart_items_wrapper.animate({ scrollTop }); + } + + update_selector_value_in_cart_item(selector, value, item) { + const $item_to_update = this.get_cart_item(item); + $item_to_update.attr(`data-${selector}`, value); + } + + toggle_checkout_btn(show_checkout) { + if (show_checkout) { + this.$totals_section.find('.checkout-btn').removeClass('d-none'); + this.$totals_section.find('.edit-cart-btn').addClass('d-none'); + } else { + this.$totals_section.find('.checkout-btn').addClass('d-none'); + this.$totals_section.find('.edit-cart-btn').removeClass('d-none'); + } + } + + highlight_checkout_btn(toggle) { + const has_primary_class = this.$totals_section.find('.checkout-btn').hasClass('bg-primary'); + if (toggle && !has_primary_class) { + this.$totals_section.find('.checkout-btn').addClass('bg-primary text-white text-lg'); + } else if (!toggle && has_primary_class) { + this.$totals_section.find('.checkout-btn').removeClass('bg-primary text-white text-lg'); + } + } + + update_empty_cart_section(no_of_cart_items) { + const $no_item_element = this.$cart_items_wrapper.find('.no-item-wrapper'); + + // if cart has items and no item is present + no_of_cart_items > 0 && $no_item_element && $no_item_element.remove() + && this.$cart_items_wrapper.removeClass('mt-4 border-grey border-dashed') && this.$cart_header.removeClass('d-none'); + + no_of_cart_items === 0 && !$no_item_element.length && this.make_no_items_placeholder(); + } + + on_numpad_event($btn) { + const current_action = $btn.attr('data-button-value'); + const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action); + + this.highlight_numpad_btn($btn, current_action); + + const action_is_pressed_twice = this.prev_action === current_action; + const first_click_event = !this.prev_action; + const field_to_edit_changed = this.prev_action && this.prev_action != current_action; + + if (action_is_field_edit) { + + if (first_click_event || field_to_edit_changed) { + this.prev_action = current_action; + } else if (action_is_pressed_twice) { + this.prev_action = undefined; + } + this.numpad_value = ''; + + } else if (current_action === 'checkout') { + this.prev_action = undefined; + this.toggle_item_highlight(); + this.events.numpad_event(undefined, current_action); + return; + } else if (current_action === 'remove') { + this.prev_action = undefined; + this.toggle_item_highlight(); + this.events.numpad_event(undefined, current_action); + return; + } else { + this.numpad_value = current_action === 'delete' ? this.numpad_value.slice(0, -1) : this.numpad_value + current_action; + this.numpad_value = this.numpad_value || 0; + } + + const first_click_event_is_not_field_edit = !action_is_field_edit && first_click_event; + + if (first_click_event_is_not_field_edit) { + frappe.show_alert({ + indicator: 'red', + message: __('Please select a field to edit from numpad') + }); + frappe.utils.play_sound("error"); + return; + } + + if (flt(this.numpad_value) > 100 && this.prev_action === 'discount_percentage') { + frappe.show_alert({ + message: __('Discount cannot be greater than 100%'), + indicator: 'orange' + }); + frappe.utils.play_sound("error"); + this.numpad_value = current_action; + } + + this.events.numpad_event(this.numpad_value, this.prev_action); + } + + highlight_numpad_btn($btn, curr_action) { + const curr_action_is_highlighted = $btn.hasClass('shadow-inner'); + const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action); + + if (!curr_action_is_highlighted) { + $btn.addClass('shadow-inner bg-selected'); + } + if (this.prev_action === curr_action && curr_action_is_highlighted) { + // if Qty is pressed twice + $btn.removeClass('shadow-inner bg-selected'); + } + if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) { + // Order: Qty -> Rate then remove Qty highlight + const prev_btn = $(`[data-button-value='${this.prev_action}']`); + prev_btn.removeClass('shadow-inner bg-selected'); + } + if (!curr_action_is_action || curr_action === 'done') { + // if numbers are clicked + setTimeout(() => { + $btn.removeClass('shadow-inner bg-selected'); + }, 100); + } + } + + toggle_numpad(show) { + if (show) { + this.$totals_section.addClass('d-none'); + this.$numpad_section.removeClass('d-none'); + } else { + this.$totals_section.removeClass('d-none'); + this.$numpad_section.addClass('d-none'); + } + this.reset_numpad(); + } + + reset_numpad() { + this.numpad_value = ''; + this.prev_action = undefined; + this.$numpad_section.find('.shadow-inner').removeClass('shadow-inner bg-selected'); + } + + toggle_numpad_field_edit(fieldname) { + if (['qty', 'discount_percentage', 'rate'].includes(fieldname)) { + this.$numpad_section.find(`[data-button-value="${fieldname}"]`).click(); + } + } + + toggle_customer_info(show) { + if (show) { + this.$cart_container.addClass('d-none') + this.$customer_section.addClass('flex-1 scroll-y').removeClass('mb-0 border pr-4 pl-4') + this.$customer_section.find('.icon').addClass('w-24 h-24 text-2xl').removeClass('w-12 h-12 text-md') + this.$customer_section.find('.customer-header').removeClass('h-18'); + this.$customer_section.find('.customer-details').addClass('sticky z-100 bg-white'); + + this.$customer_section.find('.customer-name').html( + `
    ${this.customer_info.customer}
    +
    ` + ) + + this.$customer_section.find('.customer-details').append( + `
    +
    CONTACT DETAILS
    +
    + +
    +
    +
    +
    +
    RECENT TRANSACTIONS
    +
    ` + ) + // transactions need to be in diff div from sticky elem for scrolling + this.$customer_section.append(`
    `) + + this.render_customer_info_form(); + this.fetch_customer_transactions(); + + } else { + this.$cart_container.removeClass('d-none'); + this.$customer_section.removeClass('flex-1 scroll-y').addClass('mb-0 border pr-4 pl-4'); + this.$customer_section.find('.icon').addClass('w-12 h-12 text-md').removeClass('w-24 h-24 text-2xl'); + this.$customer_section.find('.customer-header').addClass('h-18') + this.$customer_section.find('.customer-details').removeClass('sticky z-100 bg-white'); + + this.update_customer_section(); + } + } + + render_customer_info_form() { + const $customer_form = this.$customer_section.find('.customer-form'); + + const dfs = [{ + fieldname: 'email_id', + label: __('Email'), + fieldtype: 'Data', + options: 'email', + placeholder: __("Enter customer's email") + },{ + fieldname: 'mobile_no', + label: __('Phone Number'), + fieldtype: 'Data', + placeholder: __("Enter customer's phone number") + },{ + fieldname: 'loyalty_program', + label: __('Loyalty Program'), + fieldtype: 'Link', + options: 'Loyalty Program', + placeholder: __("Select Loyalty Program") + },{ + fieldname: 'loyalty_points', + label: __('Loyalty Points'), + fieldtype: 'Int', + read_only: 1 + }]; + + const me = this; + dfs.forEach(df => { + this[`customer_${df.fieldname}_field`] = frappe.ui.form.make_control({ + df: { ...df, + onchange: handle_customer_field_change, + }, + parent: $customer_form.find(`.${df.fieldname}-field`), + render_input: true, + }); + this[`customer_${df.fieldname}_field`].set_value(this.customer_info[df.fieldname]); + }) + + function handle_customer_field_change() { + const current_value = me.customer_info[this.df.fieldname]; + const current_customer = me.customer_info.customer; + + if (this.value && current_value != this.value && this.df.fieldname != 'loyalty_points') { + frappe.call({ + method: 'erpnext.selling.page.point_of_sale.point_of_sale.set_customer_info', + args: { + fieldname: this.df.fieldname, + customer: current_customer, + value: this.value + }, + callback: (r) => { + if(!r.exc) { + me.customer_info[this.df.fieldname] = this.value; + frappe.show_alert({ + message: __("Customer contact updated successfully."), + indicator: 'green' + }); + frappe.utils.play_sound("submit"); + } + } + }); + } + } + } + + fetch_customer_transactions() { + frappe.db.get_list('POS Invoice', { + filters: { customer: this.customer_info.customer, docstatus: 1 }, + fields: ['name', 'grand_total', 'status', 'posting_date', 'posting_time', 'currency'], + limit: 20 + }).then((res) => { + const transaction_container = this.$customer_section.find('.customer-transactions'); + + if (!res.length) { + transaction_container.removeClass('flex-1 border rounded').html( + `
    No recent transactions found
    ` + ) + return; + }; + + const elapsed_time = moment(res[0].posting_date+" "+res[0].posting_time).fromNow(); + this.$customer_section.find('.last-transacted-on').html(`Last transacted ${elapsed_time}`); + + res.forEach(invoice => { + const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma"); + let indicator_color = ''; + + if (in_list(['Paid', 'Consolidated'], invoice.status)) (indicator_color = 'green'); + if (invoice.status === 'Draft') (indicator_color = 'red'); + if (invoice.status === 'Return') (indicator_color = 'grey'); + + transaction_container.append( + `
    +
    +
    ${invoice.name}
    +
    + ${posting_datetime} +
    +
    +
    +
    + ${format_currency(invoice.grand_total, invoice.currency, 0) || 0} +
    +
    ${invoice.status}
    +
    +
    ` + ) + }); + }) + } + + load_invoice() { + const frm = this.events.get_frm(); + this.fetch_customer_details(frm.doc.customer).then(() => { + this.events.customer_details_updated(this.customer_info); + this.update_customer_section(); + }) + + this.$cart_items_wrapper.html(''); + if (frm.doc.items.length) { + frm.doc.items.forEach(item => { + this.update_item_html(item); + }); + } else { + this.make_no_items_placeholder(); + this.highlight_checkout_btn(false); + } + + this.update_totals_section(frm); + + if(frm.doc.docstatus === 1) { + this.$totals_section.find('.checkout-btn').addClass('d-none'); + this.$totals_section.find('.edit-cart-btn').addClass('d-none'); + this.$totals_section.find('.grand-total').removeClass('border-b-grey'); + } else { + this.$totals_section.find('.checkout-btn').removeClass('d-none'); + this.$totals_section.find('.edit-cart-btn').addClass('d-none'); + this.$totals_section.find('.grand-total').addClass('border-b-grey'); + } + + this.toggle_component(true); + } + + toggle_component(show) { + show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + } + +} \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js new file mode 100644 index 0000000000..86a1be9faf --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -0,0 +1,394 @@ +erpnext.PointOfSale.ItemDetails = class { + constructor({ wrapper, events }) { + this.wrapper = wrapper; + this.events = events; + this.current_item = {}; + + this.init_component(); + } + + init_component() { + this.prepare_dom(); + this.init_child_components(); + this.bind_events(); + this.attach_shortcuts(); + } + + prepare_dom() { + this.wrapper.append( + `
    ` + ) + + this.$component = this.wrapper.find('.item-details'); + } + + init_child_components() { + this.$component.html( + `
    +
    +
    ITEM DETAILS
    +
    Close
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    STOCK DETAILS
    +
    +
    ` + ) + + this.$item_name = this.$component.find('.item-name'); + this.$item_description = this.$component.find('.item-description'); + this.$item_price = this.$component.find('.item-price'); + this.$item_image = this.$component.find('.item-image'); + this.$form_container = this.$component.find('.form-container'); + this.$dicount_section = this.$component.find('.discount-section'); + } + + toggle_item_details_section(item) { + const { item_code, batch_no, uom } = this.current_item; + const item_code_is_same = item && item_code === item.item_code; + const batch_is_same = item && batch_no == item.batch_no; + const uom_is_same = item && uom === item.uom; + + this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true; + + this.events.toggle_item_selector(this.item_has_changed); + this.toggle_component(this.item_has_changed); + + if (this.item_has_changed) { + this.doctype = item.doctype; + this.item_meta = frappe.get_meta(this.doctype); + this.name = item.name; + this.item_row = item; + this.currency = this.events.get_frm().doc.currency; + + this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom }; + + this.render_dom(item); + this.render_discount_dom(item); + this.render_form(item); + } else { + this.validate_serial_batch_item(); + this.current_item = {}; + } + } + + validate_serial_batch_item() { + const doc = this.events.get_frm().doc; + const item_row = doc.items.find(item => item.name === this.name); + + if (!item_row) return; + + const serialized = item_row.has_serial_no; + const batched = item_row.has_batch_no; + const no_serial_selected = !item_row.serial_no; + const no_batch_selected = !item_row.batch_no; + + if ((serialized && no_serial_selected) || (batched && no_batch_selected) || + (serialized && batched && (no_batch_selected || no_serial_selected))) { + + frappe.show_alert({ + message: __("Item will be removed since no serial / batch no selected."), + indicator: 'orange' + }); + frappe.utils.play_sound("cancel"); + this.events.remove_item_from_cart(); + } + } + + render_dom(item) { + let { item_code ,item_name, description, image, price_list_rate } = item; + + function get_description_html() { + if (description) { + description = description.indexOf('...') === -1 && description.length > 75 ? description.substr(0, 73) + '...' : description; + return description; + } + return ``; + } + + this.$item_name.html(item_name); + this.$item_description.html(get_description_html()); + this.$item_price.html(format_currency(price_list_rate, this.currency)); + if (image) { + this.$item_image.html( + `${image}` + ); + } else { + this.$item_image.html(frappe.get_abbr(item_code)); + } + + } + + render_discount_dom(item) { + if (item.discount_percentage) { + this.$dicount_section.html( + `
    + ${format_currency(item.price_list_rate, this.currency)} +
    +
    + ${item.discount_percentage}% off +
    ` + ) + this.$item_price.html(format_currency(item.rate, this.currency)); + } else { + this.$dicount_section.html(``) + } + } + + render_form(item) { + const fields_to_display = this.get_form_fields(item); + this.$form_container.html(''); + + fields_to_display.forEach((fieldname, idx) => { + this.$form_container.append( + `
    +
    +
    ` + ) + + const field_meta = this.item_meta.fields.find(df => df.fieldname === fieldname); + fieldname === 'discount_percentage' ? (field_meta.label = __('Discount (%)')) : ''; + const me = this; + + this[`${fieldname}_control`] = frappe.ui.form.make_control({ + df: { + ...field_meta, + onchange: function() { + me.events.form_updated(me.doctype, me.name, fieldname, this.value); + } + }, + parent: this.$form_container.find(`.${fieldname}-control`), + render_input: true, + }) + this[`${fieldname}_control`].set_value(item[fieldname]); + }); + + this.make_auto_serial_selection_btn(item); + + this.bind_custom_control_change_event(); + } + + get_form_fields(item) { + const fields = ['qty', 'uom', 'rate', 'price_list_rate', 'discount_percentage', 'warehouse', 'actual_qty']; + if (item.has_serial_no) fields.push('serial_no'); + if (item.has_batch_no) fields.push('batch_no'); + return fields; + } + + make_auto_serial_selection_btn(item) { + if (item.has_serial_no) { + this.$form_container.append( + `
    ` + ) + if (!item.has_batch_no) { + this.$form_container.append( + `
    ` + ) + } + this.$form_container.append( + `
    + Auto Fetch Serial Numbers +
    ` + ) + this.$form_container.find('.serial_no-control').find('textarea').css('height', '9rem'); + this.$form_container.find('.serial_no-control').parent().addClass('row-span-2'); + } + } + + bind_custom_control_change_event() { + const me = this; + if (this.rate_control) { + this.rate_control.df.onchange = function() { + if (this.value) { + me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => { + const item_row = frappe.get_doc(me.doctype, me.name); + const doc = me.events.get_frm().doc; + + me.$item_price.html(format_currency(item_row.rate, doc.currency)); + me.render_discount_dom(item_row); + }); + } + } + } + + if (this.warehouse_control) { + this.warehouse_control.df.reqd = 1; + this.warehouse_control.df.onchange = function() { + if (this.value) { + me.events.form_updated(me.doctype, me.name, 'warehouse', this.value).then(() => { + me.item_stock_map = me.events.get_item_stock_map(); + const available_qty = me.item_stock_map[me.item_row.item_code][this.value]; + if (available_qty === undefined) { + me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { + // item stock map is updated now reset warehouse + me.warehouse_control.set_value(this.value); + }) + } else if (available_qty === 0) { + me.warehouse_control.set_value(''); + frappe.throw(__(`Item Code: ${me.item_row.item_code.bold()} is not available under warehouse ${this.value.bold()}.`)); + } + me.actual_qty_control.set_value(available_qty); + }); + } + } + this.warehouse_control.refresh(); + } + + if (this.discount_percentage_control) { + this.discount_percentage_control.df.onchange = function() { + if (this.value) { + me.events.form_updated(me.doctype, me.name, 'discount_percentage', this.value).then(() => { + const item_row = frappe.get_doc(me.doctype, me.name); + me.rate_control.set_value(item_row.rate); + }); + } + } + } + + if (this.serial_no_control) { + this.serial_no_control.df.reqd = 1; + this.serial_no_control.df.onchange = async function() { + !me.current_item.batch_no && await me.auto_update_batch_no(); + me.events.form_updated(me.doctype, me.name, 'serial_no', this.value); + } + this.serial_no_control.refresh(); + } + + if (this.batch_no_control) { + this.batch_no_control.df.reqd = 1; + this.batch_no_control.df.get_query = () => { + return { + query: 'erpnext.controllers.queries.get_batch_no', + filters: { + item_code: me.item_row.item_code, + warehouse: me.item_row.warehouse + } + } + }; + this.batch_no_control.df.onchange = function() { + me.events.set_value_in_current_cart_item('batch-no', this.value); + me.events.form_updated(me.doctype, me.name, 'batch_no', this.value); + me.current_item.batch_no = this.value; + } + this.batch_no_control.refresh(); + } + + if (this.uom_control) { + this.uom_control.df.onchange = function() { + me.events.set_value_in_current_cart_item('uom', this.value); + me.events.form_updated(me.doctype, me.name, 'uom', this.value); + me.current_item.uom = this.value; + } + } + } + + async auto_update_batch_no() { + if (this.serial_no_control && this.batch_no_control) { + const selected_serial_nos = this.serial_no_control.get_value().split(`\n`).filter(s => s); + if (!selected_serial_nos.length) return; + + // find batch nos of the selected serial no + const serials_with_batch_no = await frappe.db.get_list("Serial No", { + filters: { 'name': ["in", selected_serial_nos]}, + fields: ["batch_no", "name"] + }); + const batch_serial_map = serials_with_batch_no.reduce((acc, r) => { + acc[r.batch_no] || (acc[r.batch_no] = []); + acc[r.batch_no] = [...acc[r.batch_no], r.name]; + return acc; + }, {}); + // set current item's batch no and serial no + const batch_no = Object.keys(batch_serial_map)[0]; + const batch_serial_nos = batch_serial_map[batch_no].join(`\n`); + // eg. 10 selected serial no. -> 5 belongs to first batch other 5 belongs to second batch + const serial_nos_belongs_to_other_batch = selected_serial_nos.length !== batch_serial_map[batch_no].length; + + const current_batch_no = this.batch_no_control.get_value(); + current_batch_no != batch_no && await this.batch_no_control.set_value(batch_no); + + if (serial_nos_belongs_to_other_batch) { + this.serial_no_control.set_value(batch_serial_nos); + this.qty_control.set_value(batch_serial_map[batch_no].length); + } + + delete batch_serial_map[batch_no]; + + if (serial_nos_belongs_to_other_batch) + this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item); + } + } + + bind_events() { + this.bind_auto_serial_fetch_event(); + this.bind_fields_to_numpad_fields(); + + this.$component.on('click', '.close-btn', () => { + this.events.close_item_details(); + }); + } + + attach_shortcuts() { + frappe.ui.keys.on("escape", () => { + const item_details_visible = this.$component.is(":visible"); + if (item_details_visible) { + this.events.close_item_details(); + } + }); + } + + bind_fields_to_numpad_fields() { + const me = this; + this.$form_container.on('click', '.input-with-feedback', function() { + const fieldname = $(this).attr('data-fieldname'); + if (this.last_field_focused != fieldname) { + me.events.item_field_focused(fieldname); + this.last_field_focused = fieldname; + } + }); + } + + bind_auto_serial_fetch_event() { + this.$form_container.on('click', '.auto-fetch-btn', () => { + this.batch_no_control.set_value(''); + let qty = this.qty_control.get_value(); + let numbers = frappe.call({ + method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", + args: { + qty, + item_code: this.current_item.item_code, + warehouse: this.warehouse_control.get_value() || '', + batch_nos: this.current_item.batch_no || '', + for_doctype: 'POS Invoice' + } + }); + + numbers.then((data) => { + let auto_fetched_serial_numbers = data.message; + let records_length = auto_fetched_serial_numbers.length; + if (!records_length) { + const warehouse = this.warehouse_control.get_value().bold(); + frappe.msgprint(__(`Serial numbers unavailable for Item ${this.current_item.item_code.bold()} + under warehouse ${warehouse}. Please try changing warehouse.`)); + } else if (records_length < qty) { + frappe.msgprint(`Fetched only ${records_length} available serial numbers.`); + this.qty_control.set_value(records_length); + } + numbers = auto_fetched_serial_numbers.join(`\n`); + this.serial_no_control.set_value(numbers); + }); + }) + } + + toggle_component(show) { + show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + } +} \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js new file mode 100644 index 0000000000..ee0c06d45d --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -0,0 +1,265 @@ +erpnext.PointOfSale.ItemSelector = class { + constructor({ frm, wrapper, events, pos_profile }) { + this.wrapper = wrapper; + this.events = events; + this.pos_profile = pos_profile; + + this.inti_component(); + } + + inti_component() { + this.prepare_dom(); + this.make_search_bar(); + this.load_items_data(); + this.bind_events(); + this.attach_shortcuts(); + } + + prepare_dom() { + this.wrapper.append( + `
    +
    +
    +
    +
    +
    +
    +
    ALL ITEMS
    +
    +
    +
    +
    +
    ` + ); + + this.$component = this.wrapper.find('.items-selector'); + } + + async load_items_data() { + if (!this.item_group) { + const res = await frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name"); + this.parent_item_group = res.message.name; + }; + if (!this.price_list) { + const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list"); + this.price_list = res.message.selling_price_list; + } + + this.get_items({}).then(({message}) => { + this.render_item_list(message.items); + }); + } + + get_items({start = 0, page_length = 40, search_value=''}) { + const price_list = this.events.get_frm().doc?.selling_price_list || this.price_list; + let { item_group, pos_profile } = this; + + !item_group && (item_group = this.parent_item_group); + + return frappe.call({ + method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items", + freeze: true, + args: { start, page_length, price_list, item_group, search_value, pos_profile }, + }); + } + + + render_item_list(items) { + this.$items_container = this.$component.find('.items-container'); + this.$items_container.html(''); + + items.forEach(item => { + const item_html = this.get_item_html(item); + this.$items_container.append(item_html); + }) + } + + get_item_html(item) { + const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; + const indicator_color = actual_qty > 10 ? "green" : actual_qty !== 0 ? "orange" : "red"; + + function get_item_image_html() { + if (item_image) { + return `
    + ${item_image} +
    ` + } else { + return `
    + ${frappe.get_abbr(item.item_name)} +
    ` + } + } + + return ( + `
    + ${get_item_image_html()} +
    +
    + + ${frappe.ellipsis(item.item_name, 18)} +
    +
    ${format_currency(item.price_list_rate, item.currency, 0) || 0}
    +
    +
    ` + ) + } + + make_search_bar() { + const me = this; + this.$component.find('.search-field').html(''); + this.$component.find('.item-group-field').html(''); + + this.search_field = frappe.ui.form.make_control({ + df: { + label: __('Search'), + fieldtype: 'Data', + placeholder: __('Search by item code, serial number, batch no or barcode') + }, + parent: this.$component.find('.search-field'), + render_input: true, + }); + this.item_group_field = frappe.ui.form.make_control({ + df: { + label: __('Item Group'), + fieldtype: 'Link', + options: 'Item Group', + placeholder: __('Select item group'), + onchange: function() { + me.item_group = this.value; + !me.item_group && (me.item_group = me.parent_item_group); + me.filter_items(); + }, + get_query: function () { + return { + query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query', + filters: { + pos_profile: me.events.get_frm().doc?.pos_profile + } + } + }, + }, + parent: this.$component.find('.item-group-field'), + render_input: true, + }); + this.search_field.toggle_label(false); + this.item_group_field.toggle_label(false); + } + + bind_events() { + const me = this; + onScan.attachTo(document, { + onScan: (sScancode) => { + if (this.search_field && this.$component.is(':visible')) { + this.search_field.set_focus(); + $(this.search_field.$input[0]).val(sScancode).trigger("input"); + this.barcode_scanned = true; + } + } + }); + + this.$component.on('click', '.item-wrapper', function() { + const $item = $(this); + const item_code = unescape($item.attr('data-item-code')); + let batch_no = unescape($item.attr('data-batch-no')); + let serial_no = unescape($item.attr('data-serial-no')); + let uom = unescape($item.attr('data-uom')); + + // escape(undefined) returns "undefined" then unescape returns "undefined" + batch_no = batch_no === "undefined" ? undefined : batch_no; + serial_no = serial_no === "undefined" ? undefined : serial_no; + uom = uom === "undefined" ? undefined : uom; + + me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }}); + }) + + this.search_field.$input.on('input', (e) => { + clearTimeout(this.last_search); + this.last_search = setTimeout(() => { + const search_term = e.target.value; + this.filter_items({ search_term }); + }, 300); + }); + } + + attach_shortcuts() { + frappe.ui.keys.on("ctrl+i", () => { + const selector_is_visible = this.$component.is(':visible'); + if (!selector_is_visible) return; + this.search_field.set_focus(); + }); + frappe.ui.keys.on("ctrl+g", () => { + const selector_is_visible = this.$component.is(':visible'); + if (!selector_is_visible) return; + this.item_group_field.set_focus(); + }); + // for selecting the last filtered item on search + frappe.ui.keys.on("enter", () => { + const selector_is_visible = this.$component.is(':visible'); + if (!selector_is_visible || this.search_field.get_value() === "") return; + + if (this.items.length == 1) { + this.$items_container.find(".item-wrapper").click(); + frappe.utils.play_sound("submit"); + $(this.search_field.$input[0]).val("").trigger("input"); + } else if (this.items.length == 0 && this.barcode_scanned) { + // only show alert of barcode is scanned and enter is pressed + frappe.show_alert({ + message: __("No items found. Scan barcode again."), + indicator: 'orange' + }); + frappe.utils.play_sound("error"); + this.barcode_scanned = false; + $(this.search_field.$input[0]).val("").trigger("input"); + } + }); + } + + filter_items({ search_term='' }={}) { + if (search_term) { + search_term = search_term.toLowerCase(); + + // memoize + this.search_index = this.search_index || {}; + if (this.search_index[search_term]) { + const items = this.search_index[search_term]; + this.items = items; + this.render_item_list(items); + return; + } + } + + this.get_items({ search_value: search_term }) + .then(({ message }) => { + const { items, serial_no, batch_no, barcode } = message; + if (search_term && !barcode) { + this.search_index[search_term] = items; + } + this.items = items; + this.render_item_list(items); + }); + } + + resize_selector(minimize) { + minimize ? + this.$component.find('.search-field').removeClass('mr-8') : + this.$component.find('.search-field').addClass('mr-8'); + + minimize ? + this.$component.find('.filter-section').addClass('flex-col') : + this.$component.find('.filter-section').removeClass('flex-col'); + + minimize ? + this.$component.removeClass('col-span-6').addClass('col-span-2') : + this.$component.removeClass('col-span-2').addClass('col-span-6') + + minimize ? + this.$items_container.removeClass('grid-cols-4').addClass('grid-cols-1') : + this.$items_container.removeClass('grid-cols-1').addClass('grid-cols-4') + } + + toggle_component(show) { + show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + } +} \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_number_pad.js b/erpnext/selling/page/point_of_sale/pos_number_pad.js new file mode 100644 index 0000000000..2ffc2c0229 --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_number_pad.js @@ -0,0 +1,49 @@ +erpnext.PointOfSale.NumberPad = class { + constructor({ wrapper, events, cols, keys, css_classes, fieldnames_map }) { + this.wrapper = wrapper; + this.events = events; + this.cols = cols; + this.keys = keys; + this.css_classes = css_classes || []; + this.fieldnames = fieldnames_map || {}; + + this.init_component(); + } + + init_component() { + this.prepare_dom(); + this.bind_events(); + } + + prepare_dom() { + const { cols, keys, css_classes, fieldnames } = this; + + function get_keys() { + return keys.reduce((a, row, i) => { + return a + row.reduce((a2, number, j) => { + const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : ''; + const fieldname = fieldnames && fieldnames[number] ? + fieldnames[number] : + typeof number === 'string' ? frappe.scrub(number) : number; + + return a2 + `
    ${number}
    ` + }, '') + }, ''); + } + + this.wrapper.html( + `
    + ${get_keys()} +
    ` + ) + } + + bind_events() { + const me = this; + this.wrapper.on('click', '.numpad-btn', function() { + const $btn = $(this); + me.events.numpad_event($btn); + }) + } +} \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js new file mode 100644 index 0000000000..9181ee8000 --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -0,0 +1,130 @@ +erpnext.PointOfSale.PastOrderList = class { + constructor({ wrapper, events }) { + this.wrapper = wrapper; + this.events = events; + + this.init_component(); + } + + init_component() { + this.prepare_dom(); + this.make_filter_section(); + this.bind_events(); + } + + prepare_dom() { + this.wrapper.append( + `
    +
    +
    +
    +
    +
    +
    +
    RECENT ORDERS
    +
    +
    +
    +
    ` + ) + + this.$component = this.wrapper.find('.past-order-list'); + this.$invoices_container = this.$component.find('.invoices-container'); + } + + bind_events() { + this.search_field.$input.on('input', (e) => { + clearTimeout(this.last_search); + this.last_search = setTimeout(() => { + const search_term = e.target.value; + this.refresh_list(search_term, this.status_field.get_value()); + }, 300); + }); + const me = this; + this.$invoices_container.on('click', '.invoice-wrapper', function() { + const invoice_name = unescape($(this).attr('data-invoice-name')); + + me.events.open_invoice_data(invoice_name); + }) + } + + make_filter_section() { + const me = this; + this.search_field = frappe.ui.form.make_control({ + df: { + label: __('Search'), + fieldtype: 'Data', + placeholder: __('Search by invoice id or customer name') + }, + parent: this.$component.find('.search-field'), + render_input: true, + }); + this.status_field = frappe.ui.form.make_control({ + df: { + label: __('Invoice Status'), + fieldtype: 'Select', + options: `Draft\nPaid\nConsolidated\nReturn`, + placeholder: __('Filter by invoice status'), + onchange: function() { + me.refresh_list(me.search_field.get_value(), this.value); + } + }, + parent: this.$component.find('.status-field'), + render_input: true, + }); + this.search_field.toggle_label(false); + this.status_field.toggle_label(false); + this.status_field.set_value('Paid'); + } + + toggle_component(show) { + show ? + this.$component.removeClass('d-none') && this.refresh_list() : + this.$component.addClass('d-none'); + } + + refresh_list() { + frappe.dom.freeze(); + this.events.reset_summary(); + const search_term = this.search_field.get_value(); + const status = this.status_field.get_value(); + + this.$invoices_container.html(''); + + return frappe.call({ + method: "erpnext.selling.page.point_of_sale.point_of_sale.get_past_order_list", + freeze: true, + args: { search_term, status }, + callback: (response) => { + frappe.dom.unfreeze(); + response.message.forEach(invoice => { + const invoice_html = this.get_invoice_html(invoice); + this.$invoices_container.append(invoice_html); + }); + } + }); + } + + get_invoice_html(invoice) { + const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma"); + return ( + `
    +
    +
    ${invoice.name}
    +
    +
    + + + + ${invoice.customer} +
    +
    +
    +
    +
    ${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
    +
    ${posting_datetime}
    +
    +
    ` + ) + } +} \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js new file mode 100644 index 0000000000..24326b2256 --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -0,0 +1,452 @@ +erpnext.PointOfSale.PastOrderSummary = class { + constructor({ wrapper, events }) { + this.wrapper = wrapper; + this.events = events; + + this.init_component(); + } + + init_component() { + this.prepare_dom(); + this.init_child_components(); + this.bind_events(); + this.attach_shortcuts(); + } + + prepare_dom() { + this.wrapper.append( + `
    +
    +
    +
    Select an invoice to load summary data
    +
    +
    +
    +
    +
    +
    ` + ) + + this.$component = this.wrapper.find('.past-order-summary'); + this.$summary_wrapper = this.$component.find('.summary-wrapper'); + this.$summary_container = this.$component.find('.summary-container'); + } + + init_child_components() { + this.init_upper_section(); + this.init_items_summary(); + this.init_totals_summary(); + this.init_payments_summary(); + this.init_summary_buttons(); + this.init_email_print_dialog(); + } + + init_upper_section() { + this.$summary_container.append( + `
    ` + ); + + this.$upper_section = this.$summary_container.find('.upper-section'); + } + + init_items_summary() { + this.$summary_container.append( + `
    +
    ITEMS
    +
    +
    ` + ) + + this.$items_summary_container = this.$summary_container.find('.items-summary-container'); + } + + init_totals_summary() { + this.$summary_container.append( + `
    +
    TOTALS
    +
    +
    ` + ) + + this.$totals_summary_container = this.$summary_container.find('.summary-totals-container'); + } + + init_payments_summary() { + this.$summary_container.append( + `
    +
    PAYMENTS
    +
    +
    ` + ) + + this.$payment_summary_container = this.$summary_container.find('.payments-summary-container'); + } + + init_summary_buttons() { + this.$summary_container.append( + `
    ` + ) + + this.$summary_btns = this.$summary_container.find('.summary-btns'); + } + + init_email_print_dialog() { + const email_dialog = new frappe.ui.Dialog({ + title: 'Email Receipt', + fields: [ + {fieldname:'email_id', fieldtype:'Data', options: 'Email', label:'Email ID'}, + // {fieldname:'remarks', fieldtype:'Text', label:'Remarks (if any)'} + ], + primary_action: () => { + this.send_email(); + }, + primary_action_label: __('Send'), + }); + this.email_dialog = email_dialog; + + const print_dialog = new frappe.ui.Dialog({ + title: 'Print Receipt', + fields: [ + {fieldname:'print', fieldtype:'Data', label:'Print Preview'} + ], + primary_action: () => { + this.events.get_frm().print_preview.printit(true); + }, + primary_action_label: __('Print'), + }); + this.print_dialog = print_dialog; + } + + get_upper_section_html(doc) { + const { status } = doc; let indicator_color = ''; + + in_list(['Paid', 'Consolidated'], status) && (indicator_color = 'green'); + status === 'Draft' && (indicator_color = 'red'); + status === 'Return' && (indicator_color = 'grey'); + + return `
    +
    ${doc.customer}
    +
    ${this.customer_email}
    +
    Sold by: ${doc.owner}
    +
    +
    +
    ${format_currency(doc.paid_amount, doc.currency)}
    +
    +
    ${doc.name}
    +
    ${doc.status}
    +
    +
    ` + } + + get_discount_html(doc) { + if (doc.discount_amount) { + return `
    +
    +
    + Discount +
    + (${doc.additional_discount_percentage} %) +
    +
    +
    ${format_currency(doc.discount_amount, doc.currency)}
    +
    +
    `; + } else { + return ``; + } + } + + get_net_total_html(doc) { + return `
    +
    +
    + Net Total +
    +
    +
    +
    ${format_currency(doc.net_total, doc.currency)}
    +
    +
    ` + } + + get_taxes_html(doc) { + return `
    +
    +
    Tax Charges
    +
    + ${ + doc.taxes.map((t, i) => { + let margin_left = ''; + if (i !== 0) margin_left = 'ml-2'; + return `${t.description} @${t.rate}%` + }).join('') + } +
    +
    +
    +
    ${format_currency(doc.base_total_taxes_and_charges, doc.currency)}
    +
    +
    ` + } + + get_grand_total_html(doc) { + return `
    +
    +
    + Grand Total +
    +
    +
    +
    ${format_currency(doc.grand_total, doc.currency)}
    +
    +
    ` + } + + get_item_html(doc, item_data) { + return `
    +
    + ${item_data.qty || 0} +
    +
    +
    + ${item_data.item_name} +
    +
    +
    + ${get_rate_discount_html()} +
    +
    ` + + function get_rate_discount_html() { + if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) { + return `(${item_data.discount_percentage}% off) +
    ${format_currency(item_data.rate, doc.currency)}
    ` + } else { + return `
    ${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}
    ` + } + } + } + + get_payment_html(doc, payment) { + return `
    +
    +
    + ${payment.mode_of_payment} +
    +
    +
    +
    ${format_currency(payment.amount, doc.currency)}
    +
    +
    ` + } + + bind_events() { + this.$summary_container.on('click', '.return-btn', () => { + this.events.process_return(this.doc.name); + this.toggle_component(false); + this.$component.find('.no-summary-placeholder').removeClass('d-none'); + this.$summary_wrapper.addClass('d-none'); + }); + + this.$summary_container.on('click', '.edit-btn', () => { + this.events.edit_order(this.doc.name); + this.toggle_component(false); + this.$component.find('.no-summary-placeholder').removeClass('d-none'); + this.$summary_wrapper.addClass('d-none'); + }); + + this.$summary_container.on('click', '.new-btn', () => { + this.events.new_order(); + this.toggle_component(false); + this.$component.find('.no-summary-placeholder').removeClass('d-none'); + this.$summary_wrapper.addClass('d-none'); + }); + + this.$summary_container.on('click', '.email-btn', () => { + this.email_dialog.fields_dict.email_id.set_value(this.customer_email); + this.email_dialog.show(); + }); + + this.$summary_container.on('click', '.print-btn', () => { + // this.print_dialog.show(); + const frm = this.events.get_frm(); + frm.doc = this.doc; + frm.print_preview.printit(true); + }); + } + + attach_shortcuts() { + frappe.ui.keys.on("ctrl+p", () => { + const print_btn_visible = this.$summary_container.find('.print-btn').is(":visible"); + const summary_visible = this.$component.is(":visible"); + if (!summary_visible || !print_btn_visible) return; + + this.$summary_container.find('.print-btn').click(); + }); + } + + toggle_component(show) { + show ? + this.$component.removeClass('d-none') : + this.$component.addClass('d-none'); + } + + send_email() { + const frm = this.events.get_frm(); + const recipients = this.email_dialog.get_values().recipients; + const doc = this.doc || frm.doc; + const print_format = frm.pos_print_format; + + frappe.call({ + method:"frappe.core.doctype.communication.email.make", + args: { + recipients: recipients, + subject: __(frm.meta.name) + ': ' + doc.name, + doctype: doc.doctype, + name: doc.name, + send_email: 1, + print_format, + sender_full_name: frappe.user.full_name(), + _lang : doc.language + }, + callback: r => { + if(!r.exc) { + frappe.utils.play_sound("email"); + if(r.message["emails_not_sent_to"]) { + frappe.msgprint(__("Email not sent to {0} (unsubscribed / disabled)", + [ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) ); + } else { + frappe.show_alert({ + message: __('Email sent successfully.'), + indicator: 'green' + }); + } + this.email_dialog.hide(); + } else { + frappe.msgprint(__("There were errors while sending email. Please try again.")); + } + } + }); + } + + add_summary_btns(map) { + this.$summary_btns.html(''); + map.forEach(m => { + if (m.condition) { + m.visible_btns.forEach(b => { + const class_name = b.split(' ')[0].toLowerCase(); + this.$summary_btns.append( + `
    + ${b} +
    ` + ) + }); + } + }); + this.$summary_btns.children().last().removeClass('mr-4'); + } + + show_summary_placeholder() { + this.$summary_wrapper.addClass("d-none"); + this.$component.find('.no-summary-placeholder').removeClass('d-none'); + } + + switch_to_post_submit_summary() { + // switch to full width view + this.$component.removeClass('col-span-6').addClass('col-span-10'); + this.$summary_wrapper.removeClass('w-66').addClass('w-40'); + + // switch place holder with summary container + this.$component.find('.no-summary-placeholder').addClass('d-none'); + this.$summary_wrapper.removeClass('d-none'); + } + + switch_to_recent_invoice_summary() { + // switch full width view with 60% view + this.$component.removeClass('col-span-10').addClass('col-span-6'); + this.$summary_wrapper.removeClass('w-40').addClass('w-66'); + + // switch place holder with summary container + this.$component.find('.no-summary-placeholder').addClass('d-none'); + this.$summary_wrapper.removeClass('d-none'); + } + + get_condition_btn_map(after_submission) { + if (after_submission) + return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }]; + + return [ + { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] }, + { condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']}, + { condition: this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt']} + ]; + } + + load_summary_of(doc, after_submission=false) { + this.$summary_wrapper.removeClass("d-none"); + + after_submission ? + this.switch_to_post_submit_summary() : this.switch_to_recent_invoice_summary(); + + this.doc = doc; + + this.attach_basic_info(doc); + + this.attach_items_info(doc); + + this.attach_totals_info(doc); + + this.attach_payments_info(doc); + + const condition_btns_map = this.get_condition_btn_map(after_submission); + + this.add_summary_btns(condition_btns_map); + } + + attach_basic_info(doc) { + frappe.db.get_value('Customer', this.doc.customer, 'email_id').then(({ message }) => { + this.customer_email = message.email_id || ''; + const upper_section_dom = this.get_upper_section_html(doc); + this.$upper_section.html(upper_section_dom); + }); + } + + attach_items_info(doc) { + this.$items_summary_container.html(''); + doc.items.forEach(item => { + const item_dom = this.get_item_html(doc, item); + this.$items_summary_container.append(item_dom); + }); + } + + attach_payments_info(doc) { + this.$payment_summary_container.html(''); + doc.payments.forEach(p => { + if (p.amount) { + const payment_dom = this.get_payment_html(doc, p); + this.$payment_summary_container.append(payment_dom); + } + }); + if (doc.redeem_loyalty_points && doc.loyalty_amount) { + const payment_dom = this.get_payment_html(doc, { + mode_of_payment: 'Loyalty Points', + amount: doc.loyalty_amount, + }); + this.$payment_summary_container.append(payment_dom); + } + } + + attach_totals_info(doc) { + this.$totals_summary_container.html(''); + + const discount_dom = this.get_discount_html(doc); + const net_total_dom = this.get_net_total_html(doc); + const taxes_dom = this.get_taxes_html(doc); + const grand_total_dom = this.get_grand_total_html(doc); + this.$totals_summary_container.append(discount_dom); + this.$totals_summary_container.append(net_total_dom); + this.$totals_summary_container.append(taxes_dom); + this.$totals_summary_container.append(grand_total_dom); + } + +} \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js new file mode 100644 index 0000000000..e1c54f64a7 --- /dev/null +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -0,0 +1,503 @@ +{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %} + +erpnext.PointOfSale.Payment = class { + constructor({ events, wrapper }) { + this.wrapper = wrapper; + this.events = events; + + this.init_component(); + } + + init_component() { + this.prepare_dom(); + this.initialize_numpad(); + this.bind_events(); + this.attach_shortcuts(); + + } + + prepare_dom() { + this.wrapper.append( + `
    +
    +
    + PAYMENT METHOD +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Complete Order +
    +
    +
    +
    +
    +
    ` + ) + this.$component = this.wrapper.find('.payment-section'); + this.$payment_modes = this.$component.find('.payment-modes'); + this.$totals_remarks = this.$component.find('.totals-remarks'); + this.$totals = this.$component.find('.totals'); + this.$remarks = this.$component.find('.remarks'); + this.$numpad = this.$component.find('.number-pad'); + this.$invoice_details_section = this.$component.find('.invoice-details-section'); + } + + make_invoice_fields_control() { + frappe.db.get_doc("POS Settings", undefined).then((doc) => { + const fields = doc.invoice_fields; + if (!fields.length) return; + + this.$invoice_details_section.html( + `
    + ADDITIONAL INFORMATION +
    +
    ` + ); + this.$invoice_fields = this.$invoice_details_section.find('.invoice-fields'); + const frm = this.events.get_frm(); + + fields.forEach(df => { + this.$invoice_fields.append( + `
    ` + ); + + this[`${df.fieldname}_field`] = frappe.ui.form.make_control({ + df: { + ...df, + onchange: function() { + frm.set_value(this.df.fieldname, this.value); + } + }, + parent: this.$invoice_fields.find(`.${df.fieldname}-field`), + render_input: true, + }); + this[`${df.fieldname}_field`].set_value(frm.doc[df.fieldname]); + }) + }); + } + + initialize_numpad() { + const me = this; + this.number_pad = new erpnext.PointOfSale.NumberPad({ + wrapper: this.$numpad, + events: { + numpad_event: function($btn) { + me.on_numpad_clicked($btn); + } + }, + cols: 3, + keys: [ + [ 1, 2, 3 ], + [ 4, 5, 6 ], + [ 7, 8, 9 ], + [ '.', 0, 'Delete' ] + ], + }) + + this.numpad_value = ''; + } + + on_numpad_clicked($btn) { + const me = this; + const button_value = $btn.attr('data-button-value'); + + highlight_numpad_btn($btn); + this.numpad_value = button_value === 'delete' ? this.numpad_value.slice(0, -1) : this.numpad_value + button_value; + this.selected_mode.$input.get(0).focus(); + this.selected_mode.set_value(this.numpad_value); + + function highlight_numpad_btn($btn) { + $btn.addClass('shadow-inner bg-selected'); + setTimeout(() => { + $btn.removeClass('shadow-inner bg-selected'); + }, 100); + } + } + + bind_events() { + const me = this; + + this.$payment_modes.on('click', '.mode-of-payment', function(e) { + const mode_clicked = $(this); + // if clicked element doesn't have .mode-of-payment class then return + if (!$(e.target).is(mode_clicked)) return; + + const mode = mode_clicked.attr('data-mode'); + + // hide all control fields and shortcuts + $(`.mode-of-payment-control`).addClass('d-none'); + $(`.cash-shortcuts`).addClass('d-none'); + me.$payment_modes.find(`.pay-amount`).removeClass('d-none'); + me.$payment_modes.find(`.loyalty-amount-name`).addClass('d-none'); + + // remove highlight from all mode-of-payments + $('.mode-of-payment').removeClass('border-primary'); + + if (mode_clicked.hasClass('border-primary')) { + // clicked one is selected then unselect it + mode_clicked.removeClass('border-primary'); + me.selected_mode = ''; + me.toggle_numpad(false); + } else { + // clicked one is not selected then select it + mode_clicked.addClass('border-primary'); + mode_clicked.find('.mode-of-payment-control').removeClass('d-none'); + mode_clicked.find('.cash-shortcuts').removeClass('d-none'); + me.$payment_modes.find(`.${mode}-amount`).addClass('d-none'); + me.$payment_modes.find(`.${mode}-name`).removeClass('d-none'); + me.toggle_numpad(true); + + me.selected_mode = me[`${mode}_control`]; + const doc = me.events.get_frm().doc; + me.selected_mode?.$input?.get(0).focus(); + !me.selected_mode?.get_value() ? me.selected_mode?.set_value(doc.grand_total - doc.paid_amount) : ''; + } + }) + + this.$payment_modes.on('click', '.shortcut', function(e) { + const value = $(this).attr('data-value'); + me.selected_mode.set_value(value); + }) + + // this.$totals_remarks.on('click', '.remarks', () => { + // this.toggle_remarks_control(); + // }) + + this.$component.on('click', '.submit-order', () => { + const doc = this.events.get_frm().doc; + const paid_amount = doc.paid_amount; + const items = doc.items; + + if (paid_amount == 0 || !items.length) { + const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order.") + frappe.show_alert({ message, indicator: "orange" }); + frappe.utils.play_sound("error"); + return; + } + + this.events.submit_invoice(); + }) + + frappe.ui.form.on('POS Invoice', 'paid_amount', (frm) => { + this.update_totals_section(frm.doc); + + // need to re calculate cash shortcuts after discount is applied + const is_cash_shortcuts_invisible = this.$payment_modes.find('.cash-shortcuts').hasClass('d-none'); + this.attach_cash_shortcuts(frm.doc); + !is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').removeClass('d-none'); + }) + + frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => { + const formatted_currency = format_currency(frm.doc.loyalty_amount, frm.doc.currency); + this.$payment_modes.find(`.loyalty-amount-amount`).html(formatted_currency); + }); + + frappe.ui.form.on("Sales Invoice Payment", "amount", (frm, cdt, cdn) => { + // for setting correct amount after loyalty points are redeemed + const default_mop = locals[cdt][cdn]; + const mode = default_mop.mode_of_payment.replace(' ', '_').toLowerCase(); + if (this[`${mode}_control`] && this[`${mode}_control`].get_value() != default_mop.amount) { + this[`${mode}_control`].set_value(default_mop.amount); + } + }); + + this.$component.on('click', '.invoice-details-section', function(e) { + if ($(e.target).closest('.invoice-fields').length) return; + + me.$payment_modes.addClass('d-none'); + me.$invoice_fields.toggleClass("d-none"); + me.toggle_numpad(false); + }); + this.$component.on('click', '.payment-section', () => { + this.$invoice_fields.addClass("d-none"); + this.$payment_modes.toggleClass('d-none'); + this.toggle_numpad(true); + }) + } + + attach_shortcuts() { + frappe.ui.keys.on("ctrl+enter", () => { + const payment_is_visible = this.$component.is(":visible"); + const active_mode = this.$payment_modes.find(".border-primary"); + if (payment_is_visible && active_mode.length) { + this.$component.find('.submit-order').click(); + } + }); + + frappe.ui.keys.on("tab", () => { + const payment_is_visible = this.$component.is(":visible"); + const mode_of_payments = Array.from(this.$payment_modes.find(".mode-of-payment")).map(m => $(m).attr("data-mode")); + let active_mode = this.$payment_modes.find(".border-primary"); + active_mode = active_mode.length ? active_mode.attr("data-mode") : undefined; + + if (!active_mode) return; + + const mode_index = mode_of_payments.indexOf(active_mode); + const next_mode_index = (mode_index + 1) % mode_of_payments.length; + const next_mode_to_be_clicked = this.$payment_modes.find(`.mode-of-payment[data-mode="${mode_of_payments[next_mode_index]}"]`); + + if (payment_is_visible && mode_index != next_mode_index) { + next_mode_to_be_clicked.click(); + } + }); + } + + toggle_numpad(show) { + if (show) { + this.$numpad.removeClass('d-none'); + this.$remarks.addClass('d-none'); + this.$totals_remarks.addClass('w-60 justify-center').removeClass('justify-end w-full'); + } else { + this.$numpad.addClass('d-none'); + this.$remarks.removeClass('d-none'); + this.$totals_remarks.removeClass('w-60 justify-center').addClass('justify-end w-full'); + } + } + + render_payment_section() { + this.render_payment_mode_dom(); + this.make_invoice_fields_control(); + this.update_totals_section(); + } + + edit_cart() { + this.events.toggle_other_sections(false); + this.toggle_component(false); + } + + checkout() { + this.events.toggle_other_sections(true); + this.toggle_component(true); + + this.render_payment_section(); + } + + toggle_remarks_control() { + if (this.$remarks.find('.frappe-control').length) { + this.$remarks.html('+ Add Remark'); + } else { + this.$remarks.html(''); + this[`remark_control`] = frappe.ui.form.make_control({ + df: { + label: __('Remark'), + fieldtype: 'Data', + onchange: function() {} + }, + parent: this.$totals_remarks.find(`.remarks`), + render_input: true, + }); + this[`remark_control`].set_value(''); + } + } + + render_payment_mode_dom() { + const doc = this.events.get_frm().doc; + const payments = doc.payments; + const currency = doc.currency; + + this.$payment_modes.html( + `${ + payments.map((p, i) => { + const mode = p.mode_of_payment.replace(' ', '_').toLowerCase(); + const payment_type = p.type; + const margin = i % 2 === 0 ? 'pr-2' : 'pl-2'; + const amount = p.amount > 0 ? format_currency(p.amount, currency) : ''; + + return ( + `
    +
    + ${p.mode_of_payment} +
    ${amount}
    +
    +
    +
    ` + ) + }).join('') + }` + ) + + payments.forEach(p => { + const mode = p.mode_of_payment.replace(' ', '_').toLowerCase(); + const me = this; + this[`${mode}_control`] = frappe.ui.form.make_control({ + df: { + label: __(`${p.mode_of_payment}`), + fieldtype: 'Currency', + placeholder: __(`Enter ${p.mode_of_payment} amount.`), + onchange: function() { + if (this.value || this.value == 0) { + frappe.model.set_value(p.doctype, p.name, 'amount', flt(this.value)) + .then(() => me.update_totals_section()); + + const formatted_currency = format_currency(this.value, currency); + me.$payment_modes.find(`.${mode}-amount`).html(formatted_currency); + } + } + }, + parent: this.$payment_modes.find(`.${mode}.mode-of-payment-control`), + render_input: true, + }); + this[`${mode}_control`].toggle_label(false); + this[`${mode}_control`].set_value(p.amount); + + if (p.default) { + setTimeout(() => { + this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click(); + }, 500); + } + }) + + this.render_loyalty_points_payment_mode(); + + this.attach_cash_shortcuts(doc); + } + + attach_cash_shortcuts(doc) { + const grand_total = doc.grand_total; + const currency = doc.currency; + + const shortcuts = this.get_cash_shortcuts(flt(grand_total)); + + this.$payment_modes.find('.cash-shortcuts').remove(); + this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after( + `
    + ${ + shortcuts.map(s => { + return `
    + ${format_currency(s, currency)} +
    ` + }).join('') + } +
    ` + ) + } + + get_cash_shortcuts(grand_total) { + let steps = [1, 5, 10]; + const digits = String(Math.round(grand_total)).length; + + steps = steps.map(x => x * (10 ** (digits - 2))); + + const get_nearest = (amount, x) => { + let nearest_x = Math.ceil((amount / x)) * x; + return nearest_x === amount ? nearest_x + x : nearest_x; + } + + return steps.reduce((finalArr, x) => { + let nearest_x = get_nearest(grand_total, x); + nearest_x = finalArr.indexOf(nearest_x) != -1 ? nearest_x + x : nearest_x; + return [...finalArr, nearest_x]; + }, []); + } + + render_loyalty_points_payment_mode() { + const me = this; + const doc = this.events.get_frm().doc; + const { loyalty_program, loyalty_points, conversion_factor } = this.events.get_customer_details(); + + this.$payment_modes.find(`.mode-of-payment[data-mode="loyalty-amount"]`).parent().remove(); + + if (!loyalty_program) return; + + let description, read_only, max_redeemable_amount; + if (!loyalty_points) { + description = __(`You don't have enough points to redeem.`); + read_only = true; + } else { + max_redeemable_amount = flt(flt(loyalty_points) * flt(conversion_factor), precision("loyalty_amount", doc)) + description = __(`You can redeem upto ${format_currency(max_redeemable_amount)}.`); + read_only = false; + } + + const margin = this.$payment_modes.children().length % 2 === 0 ? 'pr-2' : 'pl-2'; + const amount = doc.loyalty_amount > 0 ? format_currency(doc.loyalty_amount, doc.currency) : ''; + this.$payment_modes.append( + `
    +
    + Redeem Loyalty Points +
    ${amount}
    +
    ${loyalty_program}
    +
    +
    +
    ` + ) + + this['loyalty-amount_control'] = frappe.ui.form.make_control({ + df: { + label: __('Redeem Loyalty Points'), + fieldtype: 'Currency', + placeholder: __(`Enter amount to be redeemed.`), + options: 'company:currency', + read_only, + onchange: async function() { + if (!loyalty_points) return; + + if (this.value > max_redeemable_amount) { + frappe.show_alert({ + message: __(`You cannot redeem more than ${format_currency(max_redeemable_amount)}.`), + indicator: "red" + }); + frappe.utils.play_sound("submit"); + me['loyalty-amount_control'].set_value(0); + return; + } + const redeem_loyalty_points = this.value > 0 ? 1 : 0; + await frappe.model.set_value(doc.doctype, doc.name, 'redeem_loyalty_points', redeem_loyalty_points); + frappe.model.set_value(doc.doctype, doc.name, 'loyalty_points', parseInt(this.value / conversion_factor)); + }, + description + }, + parent: this.$payment_modes.find(`.loyalty-amount.mode-of-payment-control`), + render_input: true, + }); + this['loyalty-amount_control'].toggle_label(false); + + // this.render_add_payment_method_dom(); + } + + render_add_payment_method_dom() { + const docstatus = this.events.get_frm().doc.docstatus; + if (docstatus === 0) + this.$payment_modes.append( + `
    +
    + Add Payment Method
    +
    ` + ) + } + + update_totals_section(doc) { + if (!doc) doc = this.events.get_frm().doc; + const paid_amount = doc.paid_amount; + const remaining = doc.grand_total - doc.paid_amount; + const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined; + const currency = doc.currency + const label = change ? __('Change') : __('To Be Paid'); + + this.$totals.html( + `
    +
    Paid Amount
    +
    ${format_currency(paid_amount, currency)}
    +
    +
    +
    ${label}
    +
    ${format_currency(change || remaining, currency)}
    +
    ` + ) + } + + toggle_component(show) { + show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + } + } \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js b/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js deleted file mode 100644 index 79d1700b4e..0000000000 --- a/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js +++ /dev/null @@ -1,38 +0,0 @@ -QUnit.test("test:Point of Sales", function(assert) { - assert.expect(1); - let done = assert.async(); - - frappe.run_serially([ - () => frappe.set_route('point-of-sale'), - () => frappe.timeout(3), - () => frappe.set_control('customer', 'Test Customer 1'), - () => frappe.timeout(0.2), - () => cur_frm.set_value('customer', 'Test Customer 1'), - () => frappe.timeout(2), - () => frappe.click_link('Test Product 2'), - () => frappe.timeout(0.2), - () => frappe.click_element(`.cart-items [data-item-code="Test Product 2"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.number-pad [data-value="Rate"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.number-pad [data-value="2"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.number-pad [data-value="5"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.number-pad [data-value="0"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.number-pad [data-value="Pay"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.frappe-control [data-value="4"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.frappe-control [data-value="5"]`), - () => frappe.timeout(0.2), - () => frappe.click_element(`.frappe-control [data-value="0"]`), - () => frappe.timeout(0.2), - () => frappe.click_button('Submit'), - () => frappe.click_button('Yes'), - () => frappe.timeout(3), - () => assert.ok(cur_frm.doc.docstatus==1, "Sales invoice created successfully"), - () => done() - ]); -}); \ No newline at end of file diff --git a/erpnext/selling/print_format/__init__.py b/erpnext/selling/print_format/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/selling/print_format/gst_pos_invoice/__init__.py b/erpnext/selling/print_format/gst_pos_invoice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json new file mode 100644 index 0000000000..9094a07bcc --- /dev/null +++ b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json @@ -0,0 +1,23 @@ +{ + "align_labels_right": 0, + "creation": "2017-08-08 12:33:04.773099", + "custom_format": 1, + "disabled": 0, + "doc_type": "POS Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n

    \n\t{{ doc.company }}
    \n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
    GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
    \n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
    \n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n\t{% endif %}\n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
    \", \" \") %}\n\t\t{{ _(\"Customer\") }}:
    \n\t\t{{ doc.customer_name }}
    \n\t\t{{ customer_address }}\n\t{% endif %}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
    {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t
    {{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
    {{ _(\"SR.No\") }}:
    \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.rate }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- if doc.change_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", + "idx": 0, + "line_breaks": 0, + "modified": "2020-04-29 16:47:02.743246", + "modified_by": "Administrator", + "module": "Selling", + "name": "GST POS Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/selling/print_format/pos_invoice/__init__.py b/erpnext/selling/print_format/pos_invoice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/selling/print_format/pos_invoice/pos_invoice.json b/erpnext/selling/print_format/pos_invoice/pos_invoice.json new file mode 100644 index 0000000000..99094ed9b0 --- /dev/null +++ b/erpnext/selling/print_format/pos_invoice/pos_invoice.json @@ -0,0 +1,22 @@ +{ + "align_labels_right": 0, + "creation": "2011-12-21 11:08:55", + "custom_format": 1, + "disabled": 0, + "doc_type": "POS Invoice", + "docstatus": 0, + "doctype": "Print Format", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

    \n\t{{ doc.company }}
    \n\t{{ doc.select_print_heading or _(\"Invoice\") }}
    \n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
    {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
    {{ _(\"SR.No\") }}:
    \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.get_formatted(\"rate\") }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
    \n
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", + "idx": 1, + "line_breaks": 0, + "modified": "2020-04-29 16:45:58.942375", + "modified_by": "Administrator", + "module": "Selling", + "name": "POS Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/selling/print_format/return_pos_invoice/__init__.py b/erpnext/selling/print_format/return_pos_invoice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json b/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json new file mode 100644 index 0000000000..d7f335059c --- /dev/null +++ b/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json @@ -0,0 +1,24 @@ +{ + "align_labels_right": 0, + "creation": "2020-05-14 17:02:44.207166", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "POS Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

    \n\t{{ doc.company }}
    \n\t{{ doc.select_print_heading or _(\"Return Invoice\") }}
    \n

    \n

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Original Invoice\") }}: {{ doc.return_against }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
    {{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
    \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
    {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
    {{ _(\"SR.No\") }}:
    \n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
    {{ item.qty }}
    @ {{ item.get_formatted(\"rate\") }}
    {{ item.get_formatted(\"amount\") }}
    \n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
    \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
    \n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc)}}\n\t\t\t\t
    \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
    \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
    \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\")}}\n\t\t\t\t
    \n
    \n

    {{ doc.terms or \"\" }}

    \n

    {{ _(\"Thank you, please visit again.\") }}

    ", + "idx": 0, + "line_breaks": 0, + "modified": "2020-05-14 17:13:29.354015", + "modified_by": "Administrator", + "module": "Selling", + "name": "Return POS Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 4a7dd5ad9b..333a563aa5 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -142,7 +142,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]); // check if child doctype is Sales Order Item/Qutation Item and calculate the rate - if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), cdt) + if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item", "POS Invoice Item"]), cdt) this.apply_pricing_rule_on_item(item); else item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), @@ -312,6 +312,11 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ batch_no: function(doc, cdt, cdn) { var me = this; var item = frappe.get_doc(cdt, cdn); + + if (item.serial_no) { + return; + } + item.serial_no = null; var has_serial_no; frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_serial_no', (r) => { diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 153ce2fb67..f7ff916c5a 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +import json from frappe.model.naming import make_autoname from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate @@ -537,15 +538,54 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): return serial_nos @frappe.whitelist() -def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None): - import json +def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None, for_doctype=None): filters = { "item_code": item_code, "warehouse": warehouse, "delivery_document_no": "", "sales_invoice": "" } - if batch_nos: filters["batch_no"] = ["in", json.loads(batch_nos)] + + if batch_nos: + try: + filters["batch_no"] = ["in", json.loads(batch_nos)] + except: + filters["batch_no"] = ["in", [batch_nos]] + + if for_doctype == 'POS Invoice': + reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters, qty) + return unreserved_serial_nos serial_numbers = frappe.get_list("Serial No", filters=filters, limit=qty, order_by="creation") return [item['name'] for item in serial_numbers] + +@frappe.whitelist() +def get_pos_reserved_serial_nos(filters, qty=None): + batch_no_cond = "" + if filters.get("batch_no"): + batch_no_cond = "and item.batch_no = {}".format(frappe.db.escape(filters.get('batch_no'))) + + reserved_serial_nos_str = [d.serial_no for d in frappe.db.sql("""select item.serial_no as serial_no + from `tabPOS Invoice` p, `tabPOS Invoice Item` item + where p.name = item.parent + and p.consolidated_invoice is NULL + and p.docstatus = 1 + and item.docstatus = 1 + and item.item_code = %s + and item.warehouse = %s + {} + """.format(batch_no_cond), [filters.get('item_code'), filters.get('warehouse')], as_dict=1)] + + reserved_serial_nos = [] + for s in reserved_serial_nos_str: + if not s: continue + + serial_nos = s.split("\n") + serial_nos = ' '.join(serial_nos).split() # remove whitespaces + if len(serial_nos): reserved_serial_nos += serial_nos + + filters["name"] = ["not in", reserved_serial_nos] + serial_numbers = frappe.get_list("Serial No", filters=filters, limit=qty, order_by="creation") + unreserved_serial_nos = [item['name'] for item in serial_numbers] + + return reserved_serial_nos, unreserved_serial_nos \ No newline at end of file diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index b8554c83e2..1a7c15ebca 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -401,13 +401,30 @@ def get_item_warehouse(item, args, overwrite_warehouse, defaults={}): return warehouse def update_barcode_value(out): - from erpnext.accounts.doctype.sales_invoice.pos import get_barcode_data barcode_data = get_barcode_data([out]) # If item has one barcode then update the value of the barcode field if barcode_data and len(barcode_data.get(out.item_code)) == 1: out['barcode'] = barcode_data.get(out.item_code)[0] +def get_barcode_data(items_list): + # get itemwise batch no data + # exmaple: {'LED-GRE': [Batch001, Batch002]} + # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse + + itemwise_barcode = {} + for item in items_list: + barcodes = frappe.db.sql(""" + select barcode from `tabItem Barcode` where parent = %s + """, item.item_code, as_dict=1) + + for barcode in barcodes: + if item.item_code not in itemwise_barcode: + itemwise_barcode.setdefault(item.item_code, []) + itemwise_barcode[item.item_code].append(barcode.get("barcode")) + + return itemwise_barcode + @frappe.whitelist() def get_item_tax_info(company, tax_category, item_codes): out = {}