From e0ff9142accec3660e01bad6ffb47411bd049349 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 27 Apr 2020 13:29:46 +0530 Subject: [PATCH 0001/1264] fix: show date in the message for already marked attendance --- erpnext/hr/doctype/attendance/attendance.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index b6c80655c2..9df097923e 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -35,13 +35,14 @@ class Attendance(Document): and docstatus != 2 """, (self.employee, getdate(self.attendance_date), self.name)) if res: - frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee)) + frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format( + frappe.bold(self.employee), frappe.bold(self.attendance_date))) def check_leave_record(self): leave_record = frappe.db.sql(""" select leave_type, half_day, half_day_date from `tabLeave Application` - where employee = %s + where employee = %s and %s between from_date and to_date and status = 'Approved' and docstatus = 1 From bc346b5bea3d7c2781b793347992db95fab3e969 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 28 Apr 2020 11:17:45 +0530 Subject: [PATCH 0002/1264] fix: Default column width in Gross profit report --- .../report/gross_profit/gross_profit.py | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 6ef6d6eea0..4e22b05a81 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -55,27 +55,27 @@ def get_columns(group_wise_columns, filters): columns = [] column_map = frappe._dict({ "parent": _("Sales Invoice") + ":Link/Sales Invoice:120", - "posting_date": _("Posting Date") + ":Date", - "posting_time": _("Posting Time"), - "item_code": _("Item Code") + ":Link/Item", - "item_name": _("Item Name"), - "item_group": _("Item Group") + ":Link/Item Group", - "brand": _("Brand"), - "description": _("Description"), - "warehouse": _("Warehouse") + ":Link/Warehouse", - "qty": _("Qty") + ":Float", - "base_rate": _("Avg. Selling Rate") + ":Currency/currency", - "buying_rate": _("Valuation Rate") + ":Currency/currency", - "base_amount": _("Selling Amount") + ":Currency/currency", - "buying_amount": _("Buying Amount") + ":Currency/currency", - "gross_profit": _("Gross Profit") + ":Currency/currency", - "gross_profit_percent": _("Gross Profit %") + ":Percent", - "project": _("Project") + ":Link/Project", + "posting_date": _("Posting Date") + ":Date:100", + "posting_time": _("Posting Time") + ":Data:100", + "item_code": _("Item Code") + ":Link/Item:100", + "item_name": _("Item Name") + ":Data:100", + "item_group": _("Item Group") + ":Link/Item Group:100", + "brand": _("Brand") + ":Link/Brand:100", + "description": _("Description") +":Data:100", + "warehouse": _("Warehouse") + ":Link/Warehouse:100", + "qty": _("Qty") + ":Float:80", + "base_rate": _("Avg. Selling Rate") + ":Currency/currency:100", + "buying_rate": _("Valuation Rate") + ":Currency/currency:100", + "base_amount": _("Selling Amount") + ":Currency/currency:100", + "buying_amount": _("Buying Amount") + ":Currency/currency:100", + "gross_profit": _("Gross Profit") + ":Currency/currency:100", + "gross_profit_percent": _("Gross Profit %") + ":Percent:100", + "project": _("Project") + ":Link/Project:100", "sales_person": _("Sales person"), - "allocated_amount": _("Allocated Amount") + ":Currency/currency", - "customer": _("Customer") + ":Link/Customer", - "customer_group": _("Customer Group") + ":Link/Customer Group", - "territory": _("Territory") + ":Link/Territory" + "allocated_amount": _("Allocated Amount") + ":Currency/currency:100", + "customer": _("Customer") + ":Link/Customer:100", + "customer_group": _("Customer Group") + ":Link/Customer Group:100", + "territory": _("Territory") + ":Link/Territory:100" }) for col in group_wise_columns.get(scrub(filters.group_by)): @@ -85,7 +85,8 @@ def get_columns(group_wise_columns, filters): "fieldname": "currency", "label" : _("Currency"), "fieldtype": "Link", - "options": "Currency" + "options": "Currency", + "hidden": 1 }) return columns @@ -277,7 +278,7 @@ class GrossProfitGenerator(object): from `tabPurchase Invoice Item` a where a.item_code = %s and a.docstatus=1 and modified <= %s - order by a.modified desc limit 1""", (item_code,self.filters.to_date)) + order by a.modified desc limit 1""", (item_code, self.filters.to_date)) else: last_purchase_rate = frappe.db.sql(""" select (a.base_rate / a.conversion_factor) From ac98eb544850723ad0e5cfb730b1fd85ad1b53bb Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 28 Apr 2020 13:00:44 +0530 Subject: [PATCH 0003/1264] fix: Blanket Order in SO/PO child tables (#21443) --- .../doctype/purchase_order/purchase_order.js | 9 --------- erpnext/controllers/queries.py | 13 +++++++++++++ erpnext/public/js/controllers/transaction.js | 14 ++++++++++++++ erpnext/selling/doctype/sales_order/sales_order.js | 11 +---------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index ed054aedb5..4a8146a797 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -27,15 +27,6 @@ frappe.ui.form.on("Purchase Order", { frm.set_indicator_formatter('item_code', function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) - frm.set_query("blanket_order", "items", function() { - return { - filters: { - "company": frm.doc.company, - "docstatus": 1 - } - } - }); - frm.set_query("expense_account", "items", function() { return { query: "erpnext.controllers.queries.get_expense_account", diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index c14bb669a4..5febfd6bf2 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -371,6 +371,19 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "parent_account"], limit_start=start, limit_page_length=page_len, as_list=True) +def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): + return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date + from `tabBlanket Order` bo, `tabBlanket Order Item` boi + where + boi.parent = bo.name + and boi.item_code = {item_code} + and bo.blanket_order_type = '{blanket_order_type}' + and bo.company = {company} + and bo.docstatus = 1""" + .format(item_code = frappe.db.escape(filters.get("item")), + blanket_order_type = filters.get("blanket_order_type"), + company = frappe.db.escape(filters.get("company")) + )) @frappe.whitelist() def get_income_account(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 5843034543..c9d7728521 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -175,6 +175,20 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }; } + if (this.frm.fields_dict["items"].grid.get_field('blanket_order')) { + this.frm.set_query("blanket_order", "items", function(doc, cdt, cdn) { + var item = locals[cdt][cdn]; + return { + query: "erpnext.controllers.queries.get_blanket_orders", + filters: { + "company": doc.company, + "blanket_order_type": doc.doctype === "Sales Order" ? "Selling" : "Purchasing", + "item": item.item_code + } + } + }); + } + }, onload: function() { var me = this; diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 3c1ffe9596..45a43c5e7e 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -65,15 +65,6 @@ frappe.ui.form.on("Sales Order", { } }); - frm.set_query("blanket_order", "items", function() { - return { - filters: { - "company": frm.doc.company, - "docstatus": 1 - } - } - }); - erpnext.queries.setup_warehouse_query(frm); }, @@ -148,7 +139,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); - + const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1; const order_is_maintenance = ["Maintenance"].indexOf(doc.order_type) !== -1; // order type has been customised then show all the action buttons From c26db6a60669aaf0f13ddc04de49913fefc2c721 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 28 Apr 2020 13:12:22 +0530 Subject: [PATCH 0004/1264] fix: Report summary fix in consolidated financial statement for report type Profit and Loss --- .../consolidated_financial_statement.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index b62238b59b..c2c7207e37 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -84,6 +84,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters): def get_profit_loss_data(fiscal_year, companies, columns, filters): income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters) + company_currency = get_company_currency(filters) data = [] data.extend(income or []) @@ -93,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters): chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) - report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, True) + report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True) return data, None, chart, report_summary From 7889ca564c5baa95660245ae4696ed97e9b5aa76 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 28 Apr 2020 20:19:26 +0530 Subject: [PATCH 0005/1264] fix: Remove duplicate code from accounting dimension --- .../public/js/utils/dimension_tree_filter.js | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index 75c5a820b4..b223fc557b 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -24,7 +24,7 @@ doctypes_with_dimensions.forEach((doctype) => { onload: function(frm) { erpnext.dimension_filters.forEach((dimension) => { frappe.model.with_doctype(dimension['document_type'], () => { - if (frappe.meta.has_field(dimension['document_type'], 'is_group')) { + if(frappe.meta.has_field(dimension['document_type'], 'is_group')) { frm.set_query(dimension['fieldname'], { "is_group": 0 }); @@ -42,19 +42,21 @@ doctypes_with_dimensions.forEach((doctype) => { update_dimension: function(frm) { erpnext.dimension_filters.forEach((dimension) => { - if (frm.is_new()) { - if (frm.doc.company && Object.keys(default_dimensions || {}).length > 0 + if(frm.is_new()) { + if(frm.doc.company && Object.keys(default_dimensions || {}).length > 0 && default_dimensions[frm.doc.company]) { - if (frappe.meta.has_field(doctype, dimension['fieldname'])) { - frm.set_value(dimension['fieldname'], - default_dimensions[frm.doc.company][dimension['document_type']]); - } + let default_dimension = default_dimensions[frm.doc.company][dimension['document_type']]; - $.each(frm.doc.items || frm.doc.accounts || [], function(i, row) { - frappe.model.set_value(row.doctype, row.name, dimension['fieldname'], - default_dimensions[frm.doc.company][dimension['document_type']]) - }); + if(default_dimension) { + if (frappe.meta.has_field(doctype, dimension['fieldname'])) { + frm.set_value(dimension['fieldname'], default_dimension); + } + + $.each(frm.doc.items || frm.doc.accounts || [], function(i, row) { + frappe.model.set_value(row.doctype, row.name, dimension['fieldname'], default_dimension); + }); + } } } }); @@ -71,20 +73,6 @@ child_docs.forEach((doctype) => { }); }, - accounts_add: function(frm, cdt, cdn) { - erpnext.dimension_filters.forEach((dimension) => { - var row = frappe.get_doc(cdt, cdn); - frm.script_manager.copy_from_first_row("accounts", row, [dimension['fieldname']]); - }); - }, - - items_add: function(frm, cdt, cdn) { - erpnext.dimension_filters.forEach((dimension) => { - var row = frappe.get_doc(cdt, cdn); - frm.script_manager.copy_from_first_row("items", row, [dimension['fieldname']]); - }); - }, - accounts_add: function(frm, cdt, cdn) { erpnext.dimension_filters.forEach((dimension) => { var row = frappe.get_doc(cdt, cdn); From 769145cabaea1e71aaf65660c15c48d2524485cd Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 28 Apr 2020 21:28:53 +0530 Subject: [PATCH 0006/1264] fix: Removed Finished Product and Finished Qty columns from Stock Ledger Report --- erpnext/stock/report/stock_ledger/stock_ledger.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 28d72084de..0190f09f3d 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -46,19 +46,6 @@ def execute(filters=None): "out_qty": min(sle.actual_qty, 0) }) - # get the name of the item that was produced using this item - if sle.voucher_type == "Stock Entry": - purpose, work_order, fg_completed_qty = frappe.db.get_value(sle.voucher_type, sle.voucher_no, ["purpose", "work_order", "fg_completed_qty"]) - - if purpose == "Manufacture" and work_order: - finished_product = frappe.db.get_value("Work Order", work_order, "item_name") - finished_qty = fg_completed_qty - - sle.update({ - "finished_product": finished_product, - "finished_qty": finished_qty, - }) - data.append(sle) if include_uom: @@ -77,8 +64,6 @@ def get_columns(): {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Finished Product"), "fieldname": "finished_product", "width": 100}, - {"label": _("Finished Qty"), "fieldname": "finished_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 150}, {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 150}, {"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100}, From 60256e72999f068121ef723b263c2d877607f744 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 29 Apr 2020 12:05:56 +0530 Subject: [PATCH 0007/1264] fix: Group by filter fix in item wise sales and purchase register --- .../item_wise_purchase_register/item_wise_purchase_register.py | 2 +- .../report/item_wise_sales_register/item_wise_sales_register.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 127f3133f5..1f78c7a006 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -102,7 +102,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum data.append(row) - if filters.get('group_by'): + if filters.get('group_by') and item_list: total_row = total_row_map.get(prev_group_by_value or d.get('item_name')) total_row['percent_gt'] = flt(total_row['total']/grand_total * 100) data.append(total_row) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 0c8957ae44..92a22e62f1 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -111,7 +111,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum data.append(row) - if filters.get('group_by'): + if filters.get('group_by') and item_list: total_row = total_row_map.get(prev_group_by_value or d.get('item_name')) total_row['percent_gt'] = flt(total_row['total']/grand_total * 100) data.append(total_row) From b30c9a5735d090ccc2da234f8a70676a41528d96 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 29 Apr 2020 11:16:34 +0530 Subject: [PATCH 0008/1264] fix: reload procedure doc on completion --- .../doctype/clinical_procedure/clinical_procedure.js | 8 +++++--- .../doctype/clinical_procedure/clinical_procedure.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 5f36bdd95c..87c22ccf6f 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -80,6 +80,7 @@ frappe.ui.form.on('Clinical Procedure', { frappe.call({ method: 'complete_procedure', doc: frm.doc, + freeze: true, callback: function(r) { if (r.message) { frappe.show_alert({ @@ -87,8 +88,8 @@ frappe.ui.form.on('Clinical Procedure', { ['' + r.message + '']), indicator: 'green' }); - frm.reload_doc(); } + frm.reload_doc(); } }); } @@ -111,9 +112,10 @@ frappe.ui.form.on('Clinical Procedure', { frappe.call({ doc: frm.doc, method: 'make_material_receipt', + freeze: true, callback: function(r) { if (!r.exc) { - cur_frm.reload_doc(); + frm.reload_doc(); let doclist = frappe.model.sync(r.message); frappe.set_route('Form', doclist[0].doctype, doclist[0].name); } @@ -122,7 +124,7 @@ frappe.ui.form.on('Clinical Procedure', { } ); } else { - cur_frm.reload_doc(); + frm.reload_doc(); } } } diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index db3afc8807..d6c0893914 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -87,7 +87,8 @@ class ClinicalProcedure(Document): else: frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found')) - frappe.db.set_value('Clinical Procedure', self.name, 'status', 'Completed') + self.db_set('status', 'Completed') + if self.consume_stock and self.items: return stock_entry From 033886ae3ebaab1340ab07b5107572c38ec88dc7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 29 Apr 2020 13:06:52 +0530 Subject: [PATCH 0009/1264] fix: change Patient Medical Record subject fieldtype to Text Editor --- .../clinical_procedure/clinical_procedure.py | 2 +- .../healthcare/doctype/lab_test/lab_test.py | 18 +++++++------- .../patient_encounter/patient_encounter.py | 23 +++++++++++------- .../patient_medical_record.json | 4 ++-- .../doctype/vital_signs/vital_signs.py | 24 +++++++++---------- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index d6c0893914..297f1b9f4c 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -248,7 +248,7 @@ def make_procedure(source_name, target_doc=None): def insert_clinical_procedure_to_medical_record(doc): subject = cstr(doc.procedure_template) if doc.practitioner: - subject += ' ' + doc.practitioner + subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner if subject and doc.notes: subject += '
' + doc.notes diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index 4e4015d2f0..ea8ce25c97 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -288,23 +288,23 @@ def insert_lab_test_to_medical_record(doc): table_row = False subject = cstr(doc.lab_test_name) if doc.practitioner: - subject += " "+ doc.practitioner + subject += frappe.bold(_("Healthcare Practitioner: "))+ doc.practitioner + "
" if doc.normal_test_items: item = doc.normal_test_items[0] comment = "" if item.lab_test_comment: comment = str(item.lab_test_comment) - table_row = item.lab_test_name + table_row = frappe.bold(_("Lab Test Conducted: ")) + item.lab_test_name if item.lab_test_event: - table_row += " " + item.lab_test_event + table_row += frappe.bold(_("Lab Test Event: ")) + item.lab_test_event if item.result_value: - table_row += " " + 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] @@ -316,12 +316,12 @@ def insert_lab_test_to_medical_record(doc): 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.patient = doc.patient diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py index 767643bc73..1734c28e52 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py @@ -18,6 +18,9 @@ class PatientEncounter(Document): def after_insert(self): insert_encounter_to_medical_record(self) + def on_submit(self): + update_encounter_medical_record(self) + def on_cancel(self): if self.appointment: frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') @@ -66,22 +69,26 @@ def delete_medical_record(encounter): frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name) def set_subject_field(encounter): - subject = encounter.practitioner + '\n' + subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '
' if encounter.symptoms: - subject += _('Symptoms: ') + cstr(encounter.symptoms) + '\n' + subject += frappe.bold(_('Symptoms: ')) + '
' + for entry in encounter.symptoms: + subject += cstr(entry.complaint) + '
' else: - subject += _('No Symptoms') + '\n' + subject += frappe.bold(_('No Symptoms')) + '
' if encounter.diagnosis: - subject += _('Diagnosis: ') + cstr(encounter.diagnosis) + '\n' + subject += frappe.bold(_('Diagnosis: ')) + '
' + for entry in encounter.diagnosis: + subject += cstr(entry.diagnosis) + '
' else: - subject += _('No Diagnosis') + '\n' + subject += frappe.bold(_('No Diagnosis')) + '
' if encounter.drug_prescription: - subject += '\n' + _('Drug(s) Prescribed.') + subject += '
' + _('Drug(s) Prescribed.') if encounter.lab_test_prescription: - subject += '\n' + _('Test(s) Prescribed.') + subject += '
' + _('Test(s) Prescribed.') if encounter.procedure_prescription: - subject += '\n' + _('Procedure(s) Prescribed.') + subject += '
' + _('Procedure(s) Prescribed.') return subject diff --git a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json index 3655e24cb9..ed82355f33 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json +++ b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json @@ -57,7 +57,7 @@ }, { "fieldname": "subject", - "fieldtype": "Small Text", + "fieldtype": "Text Editor", "ignore_xss_filter": 1, "label": "Subject" }, @@ -125,7 +125,7 @@ ], "in_create": 1, "links": [], - "modified": "2020-03-23 19:26:59.308383", + "modified": "2020-04-29 12:26:57.679402", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Medical Record", diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py index 959e8504c4..b0e78e8eb9 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.py @@ -35,17 +35,17 @@ def delete_vital_signs_from_medical_record(doc): def set_subject_field(doc): subject = '' - if(doc.temperature): - subject += _('Temperature: ') + '\n'+ cstr(doc.temperature) + '. ' - if(doc.pulse): - subject += _('Pulse: ') + '\n' + cstr(doc.pulse) + '. ' - if(doc.respiratory_rate): - subject += _('Respiratory Rate: ') + '\n' + cstr(doc.respiratory_rate) + '. ' - if(doc.bp): - subject += _('BP: ') + '\n' + cstr(doc.bp) + '. ' - if(doc.bmi): - subject += _('BMI: ') + '\n' + cstr(doc.bmi) + '. ' - if(doc.nutrition_note): - subject += _('Note: ') + '\n' + cstr(doc.nutrition_note) + '. ' + if doc.temperature: + subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '
' + if doc.pulse: + subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '
' + if doc.respiratory_rate: + subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '
' + if doc.bp: + subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '
' + if doc.bmi: + subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '
' + if doc.nutrition_note: + subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '
' return subject From 589440c5ad7dc7ff0cf6399b5244113caaf0c661 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 29 Apr 2020 13:47:14 +0530 Subject: [PATCH 0010/1264] feat: create medical record for therapy sessions --- .../clinical_procedure/clinical_procedure.py | 2 +- .../doctype/therapy_plan/therapy_plan.py | 10 ++++- .../therapy_session/therapy_session.js | 11 ++--- .../therapy_session/therapy_session.json | 26 +++++++++++- .../therapy_session/therapy_session.py | 40 ++++++++++++++++++- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index 297f1b9f4c..b7d7a62a95 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -246,7 +246,7 @@ def make_procedure(source_name, target_doc=None): def insert_clinical_procedure_to_medical_record(doc): - subject = cstr(doc.procedure_template) + subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "
" if doc.practitioner: subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner if subject and doc.notes: diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index 201264f829..c19be17ba8 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -21,8 +21,14 @@ class TherapyPlan(Document): self.status = 'Completed' def set_totals(self): - total_sessions = sum([int(d.no_of_sessions) for d in self.get('therapy_plan_details')]) - total_sessions_completed = sum([int(d.sessions_completed) for d in self.get('therapy_plan_details')]) + total_sessions = 0 + total_sessions_completed = 0 + for entry in self.therapy_plan_details: + if entry.no_of_sessions: + total_sessions += entry.no_of_sessions + if entry.sessions_completed: + total_sessions_completed += entry.sessions_completed + self.db_set('total_sessions', total_sessions) self.db_set('total_sessions_completed', total_sessions_completed) diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js index bb675752bb..80fca39661 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js @@ -13,14 +13,9 @@ frappe.ui.form.on('Therapy Session', { refresh: function(frm) { if (!frm.doc.__islocal) { - let target = 0; - let completed = 0; - $.each(frm.doc.exercises, function(_i, e) { - target += e.counts_target; - completed += e.counts_completed; - }); - frm.dashboard.add_indicator(__('Counts Targetted: {0}', [target]), 'blue'); - frm.dashboard.add_indicator(__('Counts Completed: {0}', [completed]), (completed < target) ? 'orange' : 'green'); + frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue'); + frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]), + (frm.doc.total_counts_completed < frm.doc.total_counts_targeted) ? 'orange' : 'green'); } if (frm.doc.docstatus === 1) { diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json index 5ff719672f..d8c4b99435 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.json +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.json @@ -28,6 +28,10 @@ "invoiced", "exercises_section", "exercises", + "section_break_23", + "total_counts_targeted", + "column_break_25", + "total_counts_completed", "amended_from" ], "fields": [ @@ -173,11 +177,31 @@ "fieldtype": "Data", "label": "Patient Age", "read_only": 1 + }, + { + "fieldname": "total_counts_targeted", + "fieldtype": "Int", + "label": "Total Counts Targeted", + "read_only": 1 + }, + { + "fieldname": "total_counts_completed", + "fieldtype": "Int", + "label": "Total Counts Completed", + "read_only": 1 + }, + { + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-04-21 13:16:46.378798", + "modified": "2020-04-29 13:22:13.190353", "modified_by": "Administrator", "module": "Healthcare", "name": "Therapy Session", diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py index 45d2ee60e6..7e240955cf 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py @@ -6,10 +6,16 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from frappe import _ +from frappe.utils import cstr class TherapySession(Document): + def validate(self): + self.set_total_counts() + def on_submit(self): self.update_sessions_count_in_therapy_plan() + insert_session_medical_record(self) def on_cancel(self): self.update_sessions_count_in_therapy_plan(on_cancel=True) @@ -24,6 +30,18 @@ class TherapySession(Document): entry.sessions_completed += 1 therapy_plan.save() + def set_total_counts(self): + target_total = 0 + counts_completed = 0 + for entry in self.exercises: + if entry.counts_target: + target_total += entry.counts_target + if entry.counts_completed: + counts_completed += entry.counts_completed + + self.db_set('total_counts_targeted', target_total) + self.db_set('total_counts_completed', counts_completed) + @frappe.whitelist() def create_therapy_session(source_name, target_doc=None): @@ -52,4 +70,24 @@ def create_therapy_session(source_name, target_doc=None): } }, target_doc, set_missing_values) - return doc \ No newline at end of file + return doc + + +def insert_session_medical_record(doc): + subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '
' + if doc.therapy_plan: + subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '
' + if doc.practitioner: + subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner + subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '
' + subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '
' + + medical_record = frappe.new_doc('Patient Medical Record') + medical_record.patient = doc.patient + medical_record.subject = subject + medical_record.status = 'Open' + medical_record.communication_date = doc.start_date + medical_record.reference_doctype = 'Therapy Session' + medical_record.reference_name = doc.name + medical_record.reference_owner = doc.owner + medical_record.save(ignore_permissions=True) \ No newline at end of file From cf10fde5ec4e7112e7fdd58c30a6ad06be7e2f9d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 29 Apr 2020 14:19:27 +0530 Subject: [PATCH 0011/1264] fix: mark form as dirty when editing or deleting exercise card --- .../doctype/exercise_type/exercise_type.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.js b/erpnext/healthcare/doctype/exercise_type/exercise_type.js index f450c9bccb..ff99dc2d72 100644 --- a/erpnext/healthcare/doctype/exercise_type/exercise_type.js +++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.js @@ -24,6 +24,8 @@ erpnext.ExerciseEditor = Class.extend({ this.exercise_cards = $('
').appendTo(this.wrapper); + this.row = $('
').appendTo(this.exercise_cards); + let me = this; this.exercise_toolbar.find(".btn-add") @@ -32,7 +34,7 @@ erpnext.ExerciseEditor = Class.extend({ me.show_add_card_dialog(frm); }); - if (frm.doc.steps_table.length > 0) { + if (frm.doc.steps_table && frm.doc.steps_table.length > 0) { this.make_cards(frm); this.make_buttons(frm); } @@ -41,7 +43,6 @@ erpnext.ExerciseEditor = Class.extend({ make_cards: function(frm) { var me = this; $(me.exercise_cards).empty(); - this.row = $('
').appendTo(me.exercise_cards); $.each(frm.doc.steps_table, function(i, step) { $(repl(` @@ -78,6 +79,7 @@ erpnext.ExerciseEditor = Class.extend({ frm.doc.steps_table.pop(id); frm.refresh_field('steps_table'); $('#col-'+id).remove(); + frm.dirty(); }, 300); }); }, @@ -106,7 +108,10 @@ erpnext.ExerciseEditor = Class.extend({ ], primary_action: function() { let data = d.get_values(); - let i = frm.doc.steps_table.length; + let i = 0; + if (frm.doc.steps_table) { + i = frm.doc.steps_table.length; + } $(repl(`
@@ -165,9 +170,10 @@ erpnext.ExerciseEditor = Class.extend({ frm.doc.steps_table[id].image = data.image; frm.doc.steps_table[id].description = data.step_description; refresh_field('steps_table'); + frm.dirty(); new_dialog.hide(); }, - primary_action_label: __("Save"), + primary_action_label: __("Edit"), }); new_dialog.set_values({ From 5e8f748a63fef554387fb57cd899a4bee2901292 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 29 Apr 2020 17:34:36 +0530 Subject: [PATCH 0012/1264] feat: add create Sales Invoice option from Therapy Session --- .../therapy_session/therapy_session.js | 78 ++++++++++++++++++- .../therapy_session/therapy_session.json | 15 +++- .../therapy_session/therapy_session.py | 41 +++++++++- 3 files changed, 129 insertions(+), 5 deletions(-) diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js index 80fca39661..abe4defaf9 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js @@ -19,12 +19,86 @@ frappe.ui.form.on('Therapy Session', { } if (frm.doc.docstatus === 1) { - frm.add_custom_button(__('Patient Assessment'),function() { + frm.add_custom_button(__('Patient Assessment'), function() { frappe.model.open_mapped_doc({ method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment', frm: frm, }) }, 'Create'); + + frm.add_custom_button(__('Sales Invoice'), function() { + frappe.model.open_mapped_doc({ + method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session', + frm: frm, + }) + }, 'Create'); + } + }, + + patient: function(frm) { + if (frm.doc.patient) { + frappe.call({ + 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', + args: { + patient: frm.doc.patient + }, + callback: function (data) { + let age = ''; + if (data.message.dob) { + age = calculate_age(data.message.dob); + } else if (data.message.age) { + age = data.message.age; + if (data.message.age_as_on) { + age = __('{0} as on {1}', [age, data.message.age_as_on]); + } + } + frm.set_value('patient_age', age); + frm.set_value('gender', data.message.sex); + frm.set_value('patient_name', data.message.patient_name); + } + }); + } else { + frm.set_value('patient_age', ''); + frm.set_value('gender', ''); + frm.set_value('patient_name', ''); + } + }, + + appointment: function(frm) { + if (frm.doc.appointment) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Patient Appointment', + name: frm.doc.appointment + }, + callback: function(data) { + let values = { + 'patient':data.message.patient, + 'therapy_type': data.message.therapy_type, + 'therapy_plan': data.message.therapy_plan, + 'practitioner': data.message.practitioner, + 'department': data.message.department, + 'start_date': data.message.appointment_date, + 'start_time': data.message.appointment_time, + 'service_unit': data.message.service_unit, + 'company': data.message.company + }; + frm.set_value(values); + } + }); + } else { + let values = { + 'patient': '', + 'therapy_type': '', + 'therapy_plan': '', + 'practitioner': '', + 'department': '', + 'start_date': '', + 'start_time': '', + 'service_unit': '', + }; + frm.set_value(values); } }, @@ -39,6 +113,8 @@ frappe.ui.form.on('Therapy Session', { callback: function(data) { frm.set_value('duration', data.message.default_duration); frm.set_value('rate', data.message.rate); + frm.set_value('service_unit', data.message.healthcare_service_unit); + frm.set_value('department', data.message.medical_department); frm.doc.exercises = []; $.each(data.message.exercises, function(_i, e) { let exercise = frm.add_child('exercises'); diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json index d8c4b99435..00d74a0949 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.json +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.json @@ -9,9 +9,11 @@ "naming_series", "appointment", "patient", + "patient_name", "patient_age", "gender", "column_break_5", + "company", "therapy_plan", "therapy_type", "practitioner", @@ -20,7 +22,6 @@ "duration", "rate", "location", - "company", "column_break_12", "service_unit", "start_date", @@ -163,7 +164,8 @@ "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company" + "options": "Company", + "reqd": 1 }, { "default": "0", @@ -197,11 +199,18 @@ { "fieldname": "column_break_25", "fieldtype": "Column Break" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-04-29 13:22:13.190353", + "modified": "2020-04-29 16:49:16.286006", "modified_by": "Administrator", "module": "Healthcare", "name": "Therapy Session", diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py index 7e240955cf..9650183712 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py @@ -7,7 +7,8 @@ import frappe from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc from frappe import _ -from frappe.utils import cstr +from frappe.utils import cstr, getdate +from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account class TherapySession(Document): def validate(self): @@ -73,6 +74,44 @@ def create_therapy_session(source_name, target_doc=None): return doc +@frappe.whitelist() +def invoice_therapy_session(source_name, target_doc=None): + def set_missing_values(source, target): + target.customer = frappe.db.get_value('Patient', source.patient, 'customer') + target.due_date = getdate() + target.debit_to = get_receivable_account(source.company) + item = target.append('items', {}) + item = get_therapy_item(source, item) + target.set_missing_values(for_validate=True) + + doc = get_mapped_doc('Therapy Session', source_name, { + 'Therapy Session': { + 'doctype': 'Sales Invoice', + 'field_map': [ + ['patient', 'patient'], + ['referring_practitioner', 'practitioner'], + ['company', 'company'], + ['due_date', 'start_date'] + ] + } + }, target_doc, set_missing_values) + + return doc + + +def get_therapy_item(therapy, item): + item.item_code = frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item') + item.description = _('Therapy Session Charges: {0}').format(therapy.practitioner) + item.income_account = get_income_account(therapy.practitioner, therapy.company) + item.cost_center = frappe.get_cached_value('Company', therapy.company, 'cost_center') + item.rate = therapy.rate + item.amount = therapy.rate + item.qty = 1 + item.reference_dt = 'Therapy Session' + item.reference_dn = therapy.name + return item + + def insert_session_medical_record(doc): subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '
' if doc.therapy_plan: From 57fc3a5c3cced1064210e2baedc2b3d09401e2b5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 29 Apr 2020 19:23:16 +0530 Subject: [PATCH 0013/1264] fix: exercise type --- erpnext/healthcare/doctype/exercise_type/exercise_type.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.js b/erpnext/healthcare/doctype/exercise_type/exercise_type.js index ff99dc2d72..68db0477c2 100644 --- a/erpnext/healthcare/doctype/exercise_type/exercise_type.js +++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.js @@ -24,7 +24,7 @@ erpnext.ExerciseEditor = Class.extend({ this.exercise_cards = $('
').appendTo(this.wrapper); - this.row = $('
').appendTo(this.exercise_cards); + this.row = $('
').appendTo(this.wrapper); let me = this; From 9d5fabd7ac48f9ba8dd4f202ce145ae395253ebd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 29 Apr 2020 22:14:45 +0530 Subject: [PATCH 0014/1264] fix: get_period_list API change fixes --- erpnext/hr/report/vehicle_expenses/vehicle_expenses.py | 3 ++- .../item_group_wise_sales_target_variance.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py index e5622b7ae1..eab58ffbbc 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py @@ -12,7 +12,8 @@ def execute(filters=None): columns, data, chart = [], [], [] if filters.get('fiscal_year'): company = erpnext.get_default_company() - period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'),"Monthly", company) + period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'), + '', '', 'Fiscal Year', 'Monthly', company=company) columns=get_columns() data=get_log_data(filters) chart=get_chart_data(data,period_list) diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index 0cb606b277..2d0d60ac06 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -11,9 +11,10 @@ from erpnext.accounts.doctype.monthly_distribution.monthly_distribution import g def get_data_column(filters, partner_doctype): data = [] - period_list = get_period_list(filters.fiscal_year, filters.fiscal_year, - filters.period, company=filters.company) + period_list = get_period_list(filters.fiscal_year, filters.fiscal_year, '', '', + 'Fiscal Year', filters.period, company=filters.company) + print(period_list) rows = get_data(filters, period_list, partner_doctype) columns = get_columns(filters, period_list, partner_doctype) From 7c2d50fa15193a367b7f77cc19a61b3df27b2669 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 29 Apr 2020 22:16:47 +0530 Subject: [PATCH 0015/1264] fix: Remove print --- .../item_group_wise_sales_target_variance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index 2d0d60ac06..857b9823e0 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -14,7 +14,6 @@ def get_data_column(filters, partner_doctype): period_list = get_period_list(filters.fiscal_year, filters.fiscal_year, '', '', 'Fiscal Year', filters.period, company=filters.company) - print(period_list) rows = get_data(filters, period_list, partner_doctype) columns = get_columns(filters, period_list, partner_doctype) From 7f374e7ba5e653e34e583cc61733ca2fe427d2bb Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 30 Apr 2020 17:55:03 +0530 Subject: [PATCH 0016/1264] fix: pre-process both the existing and new products --- erpnext/hub_node/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 2035174c98..b260417fbf 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -157,8 +157,9 @@ def publish_selected_items(items_to_publish): existing_items = map_fields(items_to_update) try: - item_sync_preprocess(len(items)) - convert_relative_image_urls_to_absolute(items) + item_sync_preprocess(len(new_items+existing_items)) + convert_relative_image_urls_to_absolute(new_items) + convert_relative_image_urls_to_absolute(existing_items) # TODO: Publish Progress connection = get_hub_connection() From 367b644d9eeb3c9d60fed7523a1ca29174f85c56 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 30 Apr 2020 20:31:12 +0530 Subject: [PATCH 0017/1264] fix: delete hub tracked item on unpublish --- erpnext/hub_node/api.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index b260417fbf..42f90006f4 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -144,27 +144,17 @@ def publish_selected_items(items_to_publish): 'hub_category': item.get('hub_category'), 'image_list': item.get('image_list') } - if frappe.db.exists('Hub Tracked Item', item_code): - items_to_update.append(item) - hub_tracked_item = frappe.get_doc('Hub Tracked Item', item_code) - hub_tracked_item.update(hub_dict) - hub_tracked_item.save() - else: - frappe.get_doc(hub_dict).insert(ignore_if_duplicate=True) + frappe.get_doc(hub_dict).insert(ignore_if_duplicate=True) - items_to_publish = list(filter(lambda x: x not in items_to_update, items_to_publish)) - new_items = map_fields(items_to_publish) - existing_items = map_fields(items_to_update) + items = map_fields(items_to_publish) try: - item_sync_preprocess(len(new_items+existing_items)) - convert_relative_image_urls_to_absolute(new_items) - convert_relative_image_urls_to_absolute(existing_items) + item_sync_preprocess(len(items)) + convert_relative_image_urls_to_absolute(items) # TODO: Publish Progress connection = get_hub_connection() - connection.insert_many(new_items) - connection.bulk_update(existing_items) + connection.insert_many(items) item_sync_postprocess() except Exception as e: @@ -180,6 +170,7 @@ def unpublish_item(item_code, hub_item_name): if response: frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) + frappe.delete_doc('Hub Tracked Item', item_code) else: frappe.throw(_('Unable to update remote activity')) From 5ac2c49444e0240b5470b3768b868b6e61063641 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 1 May 2020 10:49:26 +0530 Subject: [PATCH 0018/1264] fix: Move Video Doctype from Education module to Core (#21234) (#21533) * fix: move Video Doctype from Education module to Core * fix: patch to retain Permissions Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> (cherry picked from commit f65fb1fcf80a3c0bbf0605c782efa192abc3236d) Co-authored-by: Rucha Mahabal --- erpnext/education/doctype/video/__init__.py | 0 erpnext/education/doctype/video/test_video.js | 23 ---- erpnext/education/doctype/video/test_video.py | 10 -- erpnext/education/doctype/video/video.js | 8 -- erpnext/education/doctype/video/video.json | 112 ------------------ erpnext/education/doctype/video/video.py | 13 -- erpnext/patches.txt | 3 +- ...tain_permission_rules_for_video_doctype.py | 21 ++++ .../operations/install_fixtures.py | 4 +- 9 files changed, 25 insertions(+), 169 deletions(-) delete mode 100644 erpnext/education/doctype/video/__init__.py delete mode 100644 erpnext/education/doctype/video/test_video.js delete mode 100644 erpnext/education/doctype/video/test_video.py delete mode 100644 erpnext/education/doctype/video/video.js delete mode 100644 erpnext/education/doctype/video/video.json delete mode 100644 erpnext/education/doctype/video/video.py create mode 100644 erpnext/patches/v12_0/retain_permission_rules_for_video_doctype.py diff --git a/erpnext/education/doctype/video/__init__.py b/erpnext/education/doctype/video/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/education/doctype/video/test_video.js b/erpnext/education/doctype/video/test_video.js deleted file mode 100644 index a82a221319..0000000000 --- a/erpnext/education/doctype/video/test_video.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Video", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Video - () => frappe.tests.make('Video', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/video/test_video.py b/erpnext/education/doctype/video/test_video.py deleted file mode 100644 index ecb09a2f9e..0000000000 --- a/erpnext/education/doctype/video/test_video.py +++ /dev/null @@ -1,10 +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 - -class TestVideo(unittest.TestCase): - pass diff --git a/erpnext/education/doctype/video/video.js b/erpnext/education/doctype/video/video.js deleted file mode 100644 index c35c19b0c1..0000000000 --- a/erpnext/education/doctype/video/video.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2018, 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/education/doctype/video/video.json b/erpnext/education/doctype/video/video.json deleted file mode 100644 index e912eb32cb..0000000000 --- a/erpnext/education/doctype/video/video.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "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": "description", - "fieldtype": "Text Editor", - "in_list_view": 1, - "label": "Description", - "reqd": 1 - }, - { - "fieldname": "duration", - "fieldtype": "Data", - "label": "Duration" - }, - { - "fieldname": "url", - "fieldtype": "Data", - "in_list_view": 1, - "label": "URL", - "reqd": 1 - }, - { - "fieldname": "publish_date", - "fieldtype": "Date", - "label": "Publish Date" - }, - { - "fieldname": "provider", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Provider", - "options": "YouTube\nVimeo", - "reqd": 1 - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_7", - "fieldtype": "Section Break" - } - ], - "modified": "2019-06-12 12:36:48.753092", - "modified_by": "Administrator", - "module": "Education", - "name": "Video", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "share": 1, - "write": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "LMS User", - "share": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/education/doctype/video/video.py b/erpnext/education/doctype/video/video.py deleted file mode 100644 index b19f81258c..0000000000 --- a/erpnext/education/doctype/video/video.py +++ /dev/null @@ -1,13 +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.model.document import Document - -class Video(Document): - - - def get_video(self): - pass diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a216f53a8b..c85e593b47 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -674,4 +674,5 @@ erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse 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 \ No newline at end of file +erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry +erpnext.patches.v12_0.retain_permission_rules_for_video_doctype diff --git a/erpnext/patches/v12_0/retain_permission_rules_for_video_doctype.py b/erpnext/patches/v12_0/retain_permission_rules_for_video_doctype.py new file mode 100644 index 0000000000..ca8a13b13c --- /dev/null +++ b/erpnext/patches/v12_0/retain_permission_rules_for_video_doctype.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + # to retain the roles and permissions from Education Module + # after moving doctype to core + permissions = frappe.db.sql(""" + SELECT + * + FROM + `tabDocPerm` + WHERE + parent='Video' + """, as_dict=True) + + frappe.reload_doc('core', 'doctype', 'video') + doc = frappe.get_doc('DocType', 'Video') + doc.permissions = [] + for perm in permissions: + doc.append('permissions', perm) + doc.save() diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index e4986e36b7..3be6f44832 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -32,7 +32,7 @@ def install(country=None): { 'doctype': 'Domain', 'domain': 'Agriculture'}, { 'doctype': 'Domain', 'domain': 'Non Profit'}, - # ensure at least an empty Address Template exists for this Country + # ensure at least an empty Address Template exists for this Country {'doctype':"Address Template", "country": country}, # item group @@ -271,7 +271,7 @@ def install(country=None): # Records for the Supplier Scorecard from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records - + make_default_records() make_records(records) set_up_address_templates(default_country=country) From 795cf87aabae1b503d00f8bc2f1411b7bc816e07 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 1 May 2020 18:31:14 +0530 Subject: [PATCH 0019/1264] fix: test case for loan --- erpnext/loan_management/doctype/loan/test_loan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 2d1ad33ed0..90b8534bc8 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -236,7 +236,7 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_term_loans(posting_date=nowdate()) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(nowdate(), 5), "Regular Payment", 89768.75) repayment_entry.submit() From cfd67a17a3e9d2f6cff235e08ffbb8360cabfcf4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 1 May 2020 19:39:49 +0530 Subject: [PATCH 0020/1264] chore: change log for version 13 beta --- erpnext/change_log/v13/v13_0_0_beta_1.md | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0_beta_1.md diff --git a/erpnext/change_log/v13/v13_0_0_beta_1.md b/erpnext/change_log/v13/v13_0_0_beta_1.md new file mode 100644 index 0000000000..f84dc24560 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0_beta_1.md @@ -0,0 +1,44 @@ +# Version 13.0.0 Beta 1 Release Notes + +## Accounting +- [Loan Management and Accounting](https://docs.erpnext.com/docs/user/manual/en/loan-management) +- [Accounting Dimensions in Budget Variance Report](https://github.com/frappe/erpnext/pull/19973) +- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile) +- [Custom Fields in POS](https://github.com/frappe/erpnext/pull/19876) +- [HSN Code Wise Item Tax](https://github.com/frappe/erpnext/pull/19478) +- Auto State-wise Taxation for GST India + - The Accounts entered in CGST and SGST accounts in GST Settings will be automatically skipped for Interstate Transaction and the Accounts in IGST Account will be skipped in Intrastate transaction. + +## Stock +- [Fetch Items from BOM in Stock Entry](https://github.com/frappe/erpnext/pull/19498) +- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note) + +## HR +- [Work From Home in Attendance](https://github.com/frappe/erpnext/pull/20464) +- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062) + +## Healthcare +- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare) +- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type) + +## CRM +- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post) +- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order) +- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280) + +## New Reports +- [Item-wise Sales Register](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) +- [Territory-wise Sales](https://github.com/frappe/erpnext/pull/20428) + +## Other Changes +- [Report Summary in Financial Statement](https://github.com/frappe/erpnext/pull/20876) +- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) +- [Allow Purchase Invoice Creation Without Purchase Receipt Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) +- [Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) +- Add / Delete Items in submitted Sales / Purchase Order +- Provision to edit Item Details from Marketplace +- Nested Set filtering for Accounting Dimension +- UX changes and better validation message in all Modules +- Scan Barcode in Purchase Receipt +- Disable Rounded Totals Checkbox for Salary Slips in HR Settings + From 1101f9779e6fd73ed92b76617392df7d31200fff Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Fri, 1 May 2020 21:04:23 +0530 Subject: [PATCH 0021/1264] fix: update version --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 786b9cfd16..930acdee7e 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.0.0-dev' +__version__ = '13.0.0' def get_default_company(user=None): '''Get default company for user''' From dba2132c91e2df075e892d947b36e9faf44d7f9b Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Fri, 1 May 2020 21:25:26 +0550 Subject: [PATCH 0022/1264] bumped to version 13.0.0-beta.1 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 930acdee7e..43c3935220 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0' +__version__ = '13.0.0-beta.1' def get_default_company(user=None): '''Get default company for user''' From a367a07bf17ab617df2f8a2a079492a88256a85c Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Sat, 2 May 2020 22:31:30 +0530 Subject: [PATCH 0023/1264] chore: Rename change log --- erpnext/change_log/v13/{v13_0_0_beta_1.md => v13_0_0_beta-1.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename erpnext/change_log/v13/{v13_0_0_beta_1.md => v13_0_0_beta-1.md} (100%) diff --git a/erpnext/change_log/v13/v13_0_0_beta_1.md b/erpnext/change_log/v13/v13_0_0_beta-1.md similarity index 100% rename from erpnext/change_log/v13/v13_0_0_beta_1.md rename to erpnext/change_log/v13/v13_0_0_beta-1.md From 33de842071a30cd58a206cf31a2af8d3f32c6e28 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Sat, 2 May 2020 19:25:49 +0200 Subject: [PATCH 0024/1264] Update v13_0_0_beta-1.md --- erpnext/change_log/v13/v13_0_0_beta-1.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/change_log/v13/v13_0_0_beta-1.md b/erpnext/change_log/v13/v13_0_0_beta-1.md index f84dc24560..5bd13dd823 100644 --- a/erpnext/change_log/v13/v13_0_0_beta-1.md +++ b/erpnext/change_log/v13/v13_0_0_beta-1.md @@ -30,6 +30,14 @@ - [Item-wise Sales Register](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) - [Territory-wise Sales](https://github.com/frappe/erpnext/pull/20428) +## Regional + +- Germany + + - [Update report DATEV Export to version 7.0](https://github.com/frappe/erpnext/pull/20582) and [allow to filter by voucher type](https://github.com/frappe/erpnext/pull/21060). + +- [Use any available Address Template](https://github.com/frappe/erpnext/pull/19862), not just your country's. + ## Other Changes - [Report Summary in Financial Statement](https://github.com/frappe/erpnext/pull/20876) - [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) From fa1e0bdda22bdd39ca6ff05cec08324fd0b22ec2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 21 May 2020 14:08:25 +0530 Subject: [PATCH 0025/1264] fix: dict object has no attribute append --- .../exponential_smoothing_forecasting.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index b5127f133c..1b49df6af8 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -45,7 +45,7 @@ class ForecastingReport(ExponentialSmoothingForecast): def execute_report(self): self.prepare_periodical_data() self.forecast_future_data() - self.data = self.period_wise_data.values() + self.prepare_final_data() self.add_total() columns = self.get_columns() @@ -108,7 +108,17 @@ class ForecastingReport(ExponentialSmoothingForecast): """.format(doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond), tuple(input_data), as_dict=1) + def prepare_final_data(self): + self.data = [] + + if not self.period_wise_data: return + + for key in self.period_wise_data: + self.data.append(self.period_wise_data.get(key)) + def add_total(self): + if not self.data: return + total_row = { "item_code": _(frappe.bold("Total Quantity")) } From 54bcda42ced713f8ec13a338e921ff84bfe1a664 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 May 2020 18:57:42 +0530 Subject: [PATCH 0026/1264] fix(patch): Handle single value in patch (#21823) (#21834) (cherry picked from commit 5cef8db4db7b7ddf4d2f789cd10dcab13ce18987) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../patches/v12_0/remove_duplicate_leave_ledger_entries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py index 98a2fcf27e..6353304d7a 100644 --- a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py @@ -40,5 +40,5 @@ def get_duplicate_records(): def delete_duplicate_ledger_entries(duplicate_records_list): """Delete duplicate leave ledger entries.""" - if duplicate_records_list: - frappe.db.sql(''' DELETE FROM `tabLeave Ledger Entry` WHERE name in {0}'''.format(tuple(duplicate_records_list))) #nosec \ No newline at end of file + if not duplicate_records_list: return + frappe.db.sql('''DELETE FROM `tabLeave Ledger Entry` WHERE name in %s''', ((tuple(duplicate_records_list)), )) \ No newline at end of file From 29742e4cbedb88736d2582fe425248fca6ac8c2c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 22 May 2020 15:09:57 +0530 Subject: [PATCH 0027/1264] feat: rename loan management to loan on Desk Page (cherry picked from commit 5cf90baf165913e99de6a17ac5d7ab65066d8e96) --- .../desk_page/loan_management/loan_management.json | 6 +++--- erpnext/patches.txt | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/desk_page/loan_management/loan_management.json b/erpnext/loan_management/desk_page/loan_management/loan_management.json index f9ea978ed6..d2a17630c9 100644 --- a/erpnext/loan_management/desk_page/loan_management/loan_management.json +++ b/erpnext/loan_management/desk_page/loan_management/loan_management.json @@ -36,11 +36,11 @@ "extends_another_page": 0, "idx": 0, "is_standard": 1, - "label": "Loan Management", - "modified": "2020-04-02 11:28:51.380509", + "label": "Loan", + "modified": "2020-05-22 11:28:51.380509", "modified_by": "Administrator", "module": "Loan Management", - "name": "Loan Management", + "name": "Loan", "owner": "Administrator", "pin_to_bottom": 0, "pin_to_top": 0, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index af7cb8eeda..4fd668c80b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -691,3 +691,4 @@ erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes 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) From c71e7eb56f861d7562631729924e4d189b2b7adb Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 20 May 2020 14:56:29 +0530 Subject: [PATCH 0028/1264] fix: Move address and contact templates to frappe --- erpnext/public/build.json | 2 - erpnext/public/js/templates/address_list.html | 22 -------- erpnext/public/js/templates/contact_list.html | 54 ------------------- 3 files changed, 78 deletions(-) delete mode 100644 erpnext/public/js/templates/address_list.html delete mode 100644 erpnext/public/js/templates/contact_list.html diff --git a/erpnext/public/build.json b/erpnext/public/build.json index e94d1ffe5c..2695502269 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -23,8 +23,6 @@ "public/js/queries.js", "public/js/sms_manager.js", "public/js/utils/party.js", - "public/js/templates/address_list.html", - "public/js/templates/contact_list.html", "public/js/controllers/stock_controller.js", "public/js/payment/payments.js", "public/js/controllers/taxes_and_totals.js", diff --git a/erpnext/public/js/templates/address_list.html b/erpnext/public/js/templates/address_list.html deleted file mode 100644 index 0f967b67a0..0000000000 --- a/erpnext/public/js/templates/address_list.html +++ /dev/null @@ -1,22 +0,0 @@ -
-{% for(var i=0, l=addr_list.length; i -

- {%= i+1 %}. {%= addr_list[i].address_title %}{% if(addr_list[i].address_type!="Other") { %} - ({%= __(addr_list[i].address_type) %}){% } %} - {% if(addr_list[i].is_primary_address) { %} - ({%= __("Primary") %}){% } %} - {% if(addr_list[i].is_shipping_address) { %} - ({%= __("Shipping") %}){% } %} - - - {%= __("Edit") %} -

-

{%= addr_list[i].display %}

-
-{% } %} -{% if(!addr_list.length) { %} -

{%= __("No address added yet.") %}

-{% } %} -

\ No newline at end of file diff --git a/erpnext/public/js/templates/contact_list.html b/erpnext/public/js/templates/contact_list.html deleted file mode 100644 index 7e6969163b..0000000000 --- a/erpnext/public/js/templates/contact_list.html +++ /dev/null @@ -1,54 +0,0 @@ -
-{% for(var i=0, l=contact_list.length; i -

- {%= contact_list[i].first_name %} {%= contact_list[i].last_name %} - {% if(contact_list[i].is_primary_contact) { %} - ({%= __("Primary") %}) - {% } %} - {% if(contact_list[i].designation){ %} - – {%= contact_list[i].designation %} - {% } %} - - {%= __("Edit") %} -

- {% if (contact_list[i].phones || contact_list[i].email_ids) { %} -

- {% if(contact_list[i].phone) { %} - {%= __("Phone") %}: {%= contact_list[i].phone %} ({%= __("Primary") %})
- {% endif %} - {% if(contact_list[i].mobile_no) { %} - {%= __("Mobile No") %}: {%= contact_list[i].mobile_no %} ({%= __("Primary") %})
- {% endif %} - {% if(contact_list[i].phone_nos) { %} - {% for(var j=0, k=contact_list[i].phone_nos.length; j - {% } %} - {% endif %} -

-

- {% if(contact_list[i].email_id) { %} - {%= __("Email") %}: {%= contact_list[i].email_id %} ({%= __("Primary") %})
- {% endif %} - {% if(contact_list[i].email_ids) { %} - {% for(var j=0, k=contact_list[i].email_ids.length; j - {% } %} - {% endif %} -

- {% endif %} -

- {% if (contact_list[i].address) { %} - {%= __("Address") %}: {%= contact_list[i].address %}
- {% endif %} -

-
-{% } %} -{% if(!contact_list.length) { %} -

{%= __("No contacts added yet.") %}

-{% } %} -

-

\ No newline at end of file From 69178492735b81681d349dfab0117e17fcf2106c Mon Sep 17 00:00:00 2001 From: anoop Date: Wed, 20 May 2020 15:41:37 +0530 Subject: [PATCH 0029/1264] fix: service unit create - set fields based on service unit type, added validations --- .../healthcare_service_unit.json | 19 +++++++++++++------ .../healthcare_service_unit.py | 10 ++++++++-- .../healthcare_service_unit_type.json | 16 ++++++++-------- .../healthcare_service_unit_type.py | 14 ++++++++++++++ 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json index ea4ae846f7..9ee865a62a 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json @@ -12,7 +12,6 @@ "engine": "InnoDB", "field_order": [ "healthcare_service_unit_name", - "parent_healthcare_service_unit", "is_group", "service_unit_type", "allow_appointments", @@ -20,8 +19,10 @@ "inpatient_occupancy", "occupancy_status", "column_break_9", - "warehouse", "company", + "warehouse", + "tree_details_section", + "parent_healthcare_service_unit", "lft", "rgt", "old_parent" @@ -51,7 +52,6 @@ "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1", "fieldname": "is_group", "fieldtype": "Check", - "in_list_view": 1, "label": "Is Group" }, { @@ -63,12 +63,12 @@ "options": "Healthcare Service Unit Type" }, { - "bold": 1, "default": "0", "depends_on": "eval:doc.is_group != 1 && doc.inpatient_occupancy != 1", "fetch_from": "service_unit_type.allow_appointments", "fieldname": "allow_appointments", "fieldtype": "Check", + "in_list_view": 1, "label": "Allow Appointments", "no_copy": 1, "read_only": 1 @@ -90,6 +90,7 @@ "fetch_from": "service_unit_type.inpatient_occupancy", "fieldname": "inpatient_occupancy", "fieldtype": "Check", + "in_list_view": 1, "label": "Inpatient Occupancy", "no_copy": 1, "read_only": 1, @@ -101,7 +102,7 @@ "fieldtype": "Select", "label": "Occupancy Status", "no_copy": 1, - "options": "\nVacant\nOccupied", + "options": "Vacant\nOccupied", "read_only": 1 }, { @@ -157,10 +158,16 @@ "options": "Healthcare Service Unit", "print_hide": 1, "report_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "tree_details_section", + "fieldtype": "Section Break", + "label": "Tree Details" } ], "links": [], - "modified": "2020-03-26 16:13:08.675952", + "modified": "2020-05-20 18:26:56.065543", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Service Unit", diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py index 13cc43d2be..9720078e32 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py @@ -27,5 +27,11 @@ class HealthcareServiceUnit(NestedSet): self.allow_appointments = 0 self.overlap_appointments = 0 self.inpatient_occupancy = 0 - elif not self.allow_appointments: - self.overlap_appointments = 0 + elif self.service_unit_type: + service_unit_type = frappe.get_doc('Healthcare Service Unit Type', self.service_unit_type) + self.allow_appointments = service_unit_type.allow_appointments + self.overlap_appointments = service_unit_type.overlap_appointments + self.inpatient_occupancy = service_unit_type.inpatient_occupancy + if self.inpatient_occupancy: + self.occupancy_status = 'Vacant' + self.overlap_appointments = 0 diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json index 5fa47d91bc..4b8503d028 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json @@ -31,6 +31,7 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Service Unit Type", + "no_copy": 1, "reqd": 1, "unique": 1 }, @@ -40,8 +41,7 @@ "depends_on": "eval:doc.inpatient_occupancy != 1", "fieldname": "allow_appointments", "fieldtype": "Check", - "label": "Allow Appointments", - "no_copy": 1 + "label": "Allow Appointments" }, { "bold": 1, @@ -49,8 +49,7 @@ "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1", "fieldname": "overlap_appointments", "fieldtype": "Check", - "label": "Allow Overlap", - "no_copy": 1 + "label": "Allow Overlap" }, { "bold": 1, @@ -58,8 +57,7 @@ "depends_on": "eval:doc.allow_appointments != 1", "fieldname": "inpatient_occupancy", "fieldtype": "Check", - "label": "Inpatient Occupancy", - "no_copy": 1 + "label": "Inpatient Occupancy" }, { "bold": 1, @@ -79,6 +77,7 @@ "fieldname": "item", "fieldtype": "Link", "label": "Item", + "no_copy": 1, "options": "Item", "read_only": 1 }, @@ -86,7 +85,8 @@ "fieldname": "item_code", "fieldtype": "Data", "label": "Item Code", - "mandatory_depends_on": "eval: doc.is_billable == 1" + "mandatory_depends_on": "eval: doc.is_billable == 1", + "no_copy": 1 }, { "fieldname": "item_group", @@ -138,7 +138,7 @@ } ], "links": [], - "modified": "2020-01-30 16:06:00.624496", + "modified": "2020-05-20 15:31:09.627516", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Service Unit Type", diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py index 286ecc0ff8..a99358cdc6 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py @@ -10,6 +10,20 @@ from frappe.model.rename_doc import rename_doc class HealthcareServiceUnitType(Document): def validate(self): + if self.allow_appointments and self.inpatient_occupancy: + frappe.msgprint( + _('Healthcare Service Unit Type cannot be both Allow Appointments and Inpatient Occupancy'), + raise_exception=1, title=_('Validation Error'), indicator='red' + ) + elif not self.allow_appointments and not self.inpatient_occupancy: + frappe.msgprint( + _('Healthcare Service Unit Type cannot be both Allow Appointments and Inpatient Occupancy'), + raise_exception=1, title=_('Validation Error'), indicator='red' + ) + + if not self.allow_appointments: + self.overlap_appointments = 0 + if self.is_billable: if self.disabled: frappe.db.set_value('Item', self.item, 'disabled', 1) From 18ace30a815a6f59741f87a98d6c1c1e25d1bebf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 May 2020 22:15:12 +0530 Subject: [PATCH 0030/1264] fix: Project filter in Trial Baalance Report --- erpnext/accounts/report/trial_balance/trial_balance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 8bd4399e60..5a699b6580 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -71,7 +71,8 @@ def get_data(filters): opening_balances = get_opening_balances(filters) #add filter inside list so that the query in financial_statements.py doesn't break - filters.project = [filters.project] + if filters.project: + filters.project = [filters.project] set_gl_entries_by_account(filters.company, filters.from_date, filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) From 08f76d73e32e3b585aeab24240ac0ecd681f1a90 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 21 May 2020 00:46:40 +0530 Subject: [PATCH 0031/1264] feat: admission and discharge schedule detials via dialog --- .../inpatient_record/inpatient_record.js | 66 ++++-- .../inpatient_record/inpatient_record.json | 219 ++++++++++++++++-- .../inpatient_record/inpatient_record.py | 101 +++++--- .../patient_encounter/patient_encounter.js | 125 ++++++++-- .../patient_encounter/patient_encounter.json | 4 +- 5 files changed, 417 insertions(+), 98 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js index 67c12f6c14..b640239b70 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js @@ -2,22 +2,37 @@ // For license information, please see license.txt frappe.ui.form.on('Inpatient Record', { + setup: function(frm) { + frm.get_field('drug_prescription').grid.editable_fields = [ + {fieldname: 'drug_code', columns: 2}, + {fieldname: 'drug_name', columns: 2}, + {fieldname: 'dosage', columns: 2}, + {fieldname: 'period', columns: 2} + ]; + }, refresh: function(frm) { - if(!frm.doc.__islocal && frm.doc.status == "Admission Scheduled"){ + if(!frm.doc.__islocal && (frm.doc.status == 'Admission Scheduled' || frm.doc.status == 'Admitted')) { + frm.enable_save(); + } else { + frm.disable_save(); + } + + if(!frm.doc.__islocal && frm.doc.status == 'Admission Scheduled') { frm.add_custom_button(__('Admit'), function() { admit_patient_dialog(frm); } ); - frm.set_df_property("btn_transfer", "hidden", 1); } - if(!frm.doc.__islocal && frm.doc.status == "Discharge Scheduled"){ + + if(!frm.doc.__islocal && frm.doc.status == 'Discharge Scheduled') { frm.add_custom_button(__('Discharge'), function() { discharge_patient(frm); } ); - frm.set_df_property("btn_transfer", "hidden", 0); } - if(!frm.doc.__islocal && (frm.doc.status == "Discharged" || frm.doc.status == "Discharge Scheduled")){ + if(!frm.doc.__islocal && frm.doc.status != 'Admitted') { frm.disable_save(); - frm.set_df_property("btn_transfer", "hidden", 1); + frm.set_df_property('btn_transfer', 'hidden', 1); + } else { + frm.set_df_property('btn_transfer', 'hidden', 0); } }, btn_transfer: function(frm) { @@ -28,14 +43,14 @@ frappe.ui.form.on('Inpatient Record', { var discharge_patient = function(frm) { frappe.call({ doc: frm.doc, - method: "discharge", + method: 'discharge', callback: function(data) { if(!data.exc){ frm.reload_doc(); } }, freeze: true, - freeze_message: "Process Discharge" + freeze_message: 'Processing Inpatient Discharge' }); }; @@ -44,12 +59,20 @@ var admit_patient_dialog = function(frm){ title: 'Admit Patient', width: 100, fields: [ - {fieldtype: "Link", label: "Service Unit Type", fieldname: "service_unit_type", options: "Healthcare Service Unit Type"}, - {fieldtype: "Link", label: "Service Unit", fieldname: "service_unit", options: "Healthcare Service Unit", reqd: 1}, - {fieldtype: "Datetime", label: "Admission Datetime", fieldname: "check_in", reqd: 1}, - {fieldtype: "Date", label: "Expected Discharge", fieldname: "expected_discharge"} + {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', + options: 'Healthcare Service Unit Type', default: frm.doc.admission_service_unit_type + }, + {fieldtype: 'Link', label: 'Service Unit', fieldname: 'service_unit', + options: 'Healthcare Service Unit', reqd: 1 + }, + {fieldtype: 'Datetime', label: 'Admission Datetime', fieldname: 'check_in', + reqd: 1, default: frappe.datetime.now_datetime() + }, + {fieldtype: 'Date', label: 'Expected Discharge', fieldname: 'expected_discharge', + default: frm.doc.expected_length_of_stay ? frappe.datetime.add_days(frappe.datetime.now_datetime(), frm.doc.expected_length_of_stay) : '' + } ], - primary_action_label: __("Admit"), + primary_action_label: __('Admit'), primary_action : function(){ var service_unit = dialog.get_value('service_unit'); var check_in = dialog.get_value('check_in'); @@ -74,27 +97,28 @@ var admit_patient_dialog = function(frm){ } }, freeze: true, - freeze_message: "Process Admission" + freeze_message: 'Processing Patient Admission' }); frm.refresh_fields(); dialog.hide(); } }); - dialog.fields_dict["service_unit_type"].get_query = function(){ + dialog.fields_dict['service_unit_type'].get_query = function() { return { filters: { - "inpatient_occupancy": 1, - "allow_appointments": 0 + 'inpatient_occupancy': 1, + 'allow_appointments': 0 } }; }; - dialog.fields_dict["service_unit"].get_query = function(){ + dialog.fields_dict['service_unit'].get_query = function() { return { filters: { - "is_group": 0, - "service_unit_type": dialog.get_value("service_unit_type"), - "occupancy_status" : "Vacant" + 'is_group': 0, + 'company': frm.doc.company, + 'service_unit_type': dialog.get_value('service_unit_type'), + 'occupancy_status' : 'Vacant' } }; }; diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index c1b516d536..d3835409d9 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -22,17 +22,41 @@ "scheduled_date", "admitted_datetime", "expected_discharge", - "discharge_date", "references", - "cb_admission", - "admission_practitioner", "admission_encounter", - "cb_discharge", - "discharge_practitioner", - "discharge_encounter", + "admission_practitioner", + "medical_department", + "admission_ordered_for", + "expected_length_of_stay", + "admission_service_unit_type", + "cb_admission", + "primary_practitioner", + "secondary_practitioner", + "admission_instruction", + "encounter_details_section", + "chief_complaint", + "column_break_29", + "diagnosis", + "medication_section", + "drug_prescription", + "investigations_section", + "lab_test_prescription", + "procedures_section", + "procedure_prescription", + "rehabilitation_section", + "therapy_plan", + "therapies", "sb_inpatient_occupancy", "inpatient_occupancies", "btn_transfer", + "sb_discharge_details", + "discharge_ordered_date", + "discharge_practitioner", + "discharge_encounter", + "discharge_date", + "cb_discharge", + "discharge_instructions", + "followup_date", "sb_discharge_note", "discharge_note" ], @@ -54,7 +78,8 @@ "in_list_view": 1, "label": "Patient", "options": "Patient", - "reqd": 1 + "reqd": 1, + "set_only_once": 1 }, { "fetch_from": "patient.patient_name", @@ -108,11 +133,31 @@ "label": "Phone", "read_only": 1 }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "label": "Medical Department", + "options": "Medical Department", + "set_only_once": 1 + }, + { + "fieldname": "primary_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner (Primary)", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "secondary_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner (Secondary)", + "options": "Healthcare Practitioner" + }, { "fieldname": "column_break_8", "fieldtype": "Column Break" }, { + "default": "Admission Scheduled", "fieldname": "status", "fieldtype": "Select", "in_list_view": 1, @@ -126,37 +171,45 @@ "fieldtype": "Date", "in_list_view": 1, "label": "Admission Schedule Date", + "read_only": 1, "reqd": 1 }, { - "default": "Today", + "fieldname": "admission_ordered_for", + "fieldtype": "Date", + "label": "Admission Ordered For", + "read_only": 1 + }, + { "fieldname": "admitted_datetime", "fieldtype": "Datetime", "in_list_view": 1, - "label": "Admitted Datetime" + "label": "Admitted Datetime", + "read_only": 1 + }, + { + "depends_on": "eval:(doc.expected_length_of_stay > 0)", + "fieldname": "expected_length_of_stay", + "fieldtype": "Int", + "label": "Expected Length of Stay", + "set_only_once": 1 }, { "fieldname": "expected_discharge", "fieldtype": "Date", "in_list_view": 1, - "label": "Expected Discharge" - }, - { - "fieldname": "discharge_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Discharge Date" + "label": "Expected Discharge", + "read_only": 1 }, { "collapsible": 1, "fieldname": "references", "fieldtype": "Section Break", - "label": "References" + "label": "Admission Order Details" }, { "fieldname": "cb_admission", - "fieldtype": "Column Break", - "label": "Admission" + "fieldtype": "Column Break" }, { "fieldname": "admission_practitioner", @@ -172,10 +225,21 @@ "options": "Patient Encounter", "read_only": 1 }, + { + "fieldname": "chief_complaint", + "fieldtype": "Table MultiSelect", + "label": "Chief Complaint", + "options": "Patient Encounter Symptom" + }, + { + "fieldname": "admission_instruction", + "fieldtype": "Small Text", + "label": "Admission Instruction", + "set_only_once": 1 + }, { "fieldname": "cb_discharge", - "fieldtype": "Column Break", - "label": "Discharge" + "fieldtype": "Column Break" }, { "fieldname": "discharge_practitioner", @@ -192,10 +256,51 @@ "read_only": 1 }, { + "collapsible": 1, + "fieldname": "medication_section", + "fieldtype": "Section Break", + "label": "Medications" + }, + { + "fieldname": "drug_prescription", + "fieldtype": "Table", + "options": "Drug Prescription" + }, + { + "collapsible": 1, + "fieldname": "investigations_section", + "fieldtype": "Section Break", + "label": "Investigations" + }, + { + "fieldname": "lab_test_prescription", + "fieldtype": "Table", + "options": "Lab Prescription" + }, + { + "collapsible": 1, + "fieldname": "procedures_section", + "fieldtype": "Section Break", + "label": "Procedures" + }, + { + "fieldname": "procedure_prescription", + "fieldtype": "Table", + "options": "Procedure Prescription" + }, + { + "depends_on": "eval:(doc.status != \"Admission Scheduled\")", "fieldname": "sb_inpatient_occupancy", "fieldtype": "Section Break", "label": "Inpatient Occupancy" }, + { + "fieldname": "admission_service_unit_type", + "fieldtype": "Link", + "label": "Admission Service Unit Type", + "options": "Healthcare Service Unit Type", + "read_only": 1 + }, { "fieldname": "inpatient_occupancies", "fieldtype": "Table", @@ -208,10 +313,10 @@ "label": "Transfer" }, { - "depends_on": "eval:doc.status != \"Admission Scheduled\"", + "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")", "fieldname": "sb_discharge_note", "fieldtype": "Section Break", - "label": "Discharge Note" + "label": "Discharge Notes" }, { "fieldname": "discharge_note", @@ -224,10 +329,76 @@ "in_standard_filter": 1, "label": "Company", "options": "Company" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:(doc.status == \"Admitted\")", + "fieldname": "encounter_details_section", + "fieldtype": "Section Break", + "label": "Encounter Impression" + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "fieldname": "diagnosis", + "fieldtype": "Table MultiSelect", + "label": "Diagnosis", + "options": "Patient Encounter Diagnosis" + }, + { + "fieldname": "followup_date", + "fieldtype": "Date", + "label": "Follow Up Date" + }, + { + "collapsible": 1, + "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")", + "fieldname": "sb_discharge_details", + "fieldtype": "Section Break", + "label": "Discharge Detials" + }, + { + "fieldname": "discharge_instructions", + "fieldtype": "Small Text", + "label": "Discharge Instructions" + }, + { + "fieldname": "discharge_ordered_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Discharge Ordered Date", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "rehabilitation_section", + "fieldtype": "Section Break", + "label": "Rehabilitation" + }, + { + "fieldname": "therapy_plan", + "fieldtype": "Link", + "hidden": 1, + "label": "Therapy Plan", + "options": "Therapy Plan", + "read_only": 1 + }, + { + "fieldname": "therapies", + "fieldtype": "Table", + "options": "Therapy Plan Detail" + }, + { + "fieldname": "discharge_date", + "fieldtype": "Date", + "label": "Discharge Date", + "read_only": 1 } ], "links": [], - "modified": "2020-04-07 13:13:39.351977", + "modified": "2020-05-21 00:37:12.939925", "modified_by": "Administrator", "module": "Healthcare", "name": "Inpatient Record", diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index 835b38bedf..e668204dcf 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +import frappe, json from frappe import _ from frappe.utils import today, now_datetime, getdate from frappe.model.document import Document @@ -11,8 +11,12 @@ from frappe.desk.reportview import get_match_cond class InpatientRecord(Document): def after_insert(self): - frappe.db.set_value("Patient", self.patient, "inpatient_status", "Admission Scheduled") - frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name) + frappe.db.set_value('Patient', self.patient, 'inpatient_record', self.name) + frappe.db.set_value('Patient', self.patient, 'inpatient_status', self.status) + + if self.admission_encounter: # Update encounter + frappe.db.set_value('Patient Encounter', self.admission_encounter, 'inpatient_record', self.name) + frappe.db.set_value('Patient Encounter', self.admission_encounter, 'inpatient_status', self.status) def validate(self): self.validate_dates() @@ -26,7 +30,7 @@ class InpatientRecord(Document): (getdate(self.admitted_datetime) < getdate(today())): frappe.throw(_("Scheduled and Admitted dates can not be less than today")) if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \ - (getdate(self.discharge_date) < getdate(self.scheduled_date)): + (getdate(self.discharge_ordered_date) < getdate(self.scheduled_date)): frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date")) def validate_already_scheduled_or_admitted(self): @@ -59,37 +63,76 @@ class InpatientRecord(Document): if service_unit: transfer_patient(self, service_unit, check_in) + @frappe.whitelist() -def schedule_inpatient(patient, encounter_id, practitioner): - patient_obj = frappe.get_doc('Patient', patient) +def schedule_inpatient(args): + admission_order = json.loads(args) # admission order via Encounter + if not admission_order or not admission_order['patient'] or not admission_order['admission_encounter']: + frappe.throw(_('Missing required details, did not create Inpatient Record')) + inpatient_record = frappe.new_doc('Inpatient Record') - inpatient_record.patient = patient - inpatient_record.patient_name = patient_obj.patient_name - inpatient_record.gender = patient_obj.sex - inpatient_record.blood_group = patient_obj.blood_group - inpatient_record.dob = patient_obj.dob - inpatient_record.mobile = patient_obj.mobile - inpatient_record.email = patient_obj.email - inpatient_record.phone = patient_obj.phone - inpatient_record.status = "Admission Scheduled" + + # Admission order details + set_details_from_ip_order(inpatient_record, admission_order) + + # Patient details + patient = frappe.get_doc('Patient', admission_order['patient']) + inpatient_record.patient = patient.name + inpatient_record.patient_name = patient.patient_name + inpatient_record.gender = patient.sex + inpatient_record.blood_group = patient.blood_group + inpatient_record.dob = patient.dob + inpatient_record.mobile = patient.mobile + inpatient_record.email = patient.email + inpatient_record.phone = patient.phone inpatient_record.scheduled_date = today() - inpatient_record.admission_practitioner = practitioner - inpatient_record.admission_encounter = encounter_id + + # Set encounter detials + encounter = frappe.get_doc('Patient Encounter', admission_order['admission_encounter']) + if encounter and encounter.symptoms: # Symptoms + set_ip_child_records(inpatient_record, 'chief_complaint', encounter.symptoms) + + if encounter and encounter.diagnosis: # Diagnosis + set_ip_child_records(inpatient_record, 'diagnosis', encounter.diagnosis) + + if encounter and encounter.drug_prescription: # Medication + set_ip_child_records(inpatient_record, 'drug_prescription', encounter.drug_prescription) + + if encounter and encounter.lab_test_prescription: # Lab Tests + set_ip_child_records(inpatient_record, 'lab_test_prescription', encounter.lab_test_prescription) + + if encounter and encounter.procedure_prescription: # Procedure Prescription + set_ip_child_records(inpatient_record, 'procedure_prescription', encounter.procedure_prescription) + + if encounter and encounter.therapies: # Therapies + inpatient_record.therapy_plan = encounter.therapy_plan + set_ip_child_records(inpatient_record, 'therapies', encounter.therapies) + + inpatient_record.status = 'Admission Scheduled' inpatient_record.save(ignore_permissions = True) @frappe.whitelist() -def schedule_discharge(patient, encounter_id=None, practitioner=None): - inpatient_record_id = frappe.db.get_value('Patient', patient, 'inpatient_record') +def schedule_discharge(args): + discharge_order = json.loads(args) + inpatient_record_id = frappe.db.get_value('Patient', discharge_order['patient'], 'inpatient_record') if inpatient_record_id: - inpatient_record = frappe.get_doc("Inpatient Record", inpatient_record_id) - inpatient_record.discharge_practitioner = practitioner - inpatient_record.discharge_encounter = encounter_id - inpatient_record.status = "Discharge Scheduled" - + inpatient_record = frappe.get_doc('Inpatient Record', inpatient_record_id) check_out_inpatient(inpatient_record) - + set_details_from_ip_order(inpatient_record, discharge_order) + inpatient_record.status = 'Discharge Scheduled' inpatient_record.save(ignore_permissions = True) - frappe.db.set_value("Patient", patient, "inpatient_status", "Discharge Scheduled") + frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status) + frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status) + +def set_details_from_ip_order(inpatient_record, ip_order): + for key in ip_order: + inpatient_record.set(key, ip_order[key]) + +def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child): + for item in encounter_child: + table = inpatient_record.append(inpatient_record_child) + for df in table.meta.get('fields'): + table.set(df.fieldname, item.get(df.fieldname)) def check_out_inpatient(inpatient_record): if inpatient_record.inpatient_occupancies: @@ -149,14 +192,14 @@ def get_inpatient_docs_not_invoiced(doc, inpatient_record): def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None): inpatient_record.admitted_datetime = check_in - inpatient_record.status = "Admitted" + inpatient_record.status = 'Admitted' inpatient_record.expected_discharge = expected_discharge inpatient_record.set('inpatient_occupancies', []) transfer_patient(inpatient_record, service_unit, check_in) - frappe.db.set_value("Patient", inpatient_record.patient, "inpatient_status", "Admitted") - frappe.db.set_value("Patient", inpatient_record.patient, "inpatient_record", inpatient_record.name) + frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted') + frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name) def transfer_patient(inpatient_record, service_unit, check_in): item_line = inpatient_record.append('inpatient_occupancies', {}) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 2410f8e10d..43e43acda4 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -180,35 +180,114 @@ frappe.ui.form.on('Patient Encounter', { } }); -let schedule_inpatient = function(frm) { - frappe.call({ - method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_inpatient', - args: {patient: frm.doc.patient, encounter_id: frm.doc.name, practitioner: frm.doc.practitioner}, - callback: function(data) { - if (!data.exc) { - frm.reload_doc(); +var schedule_inpatient = function(frm) { + var dialog = new frappe.ui.Dialog({ + title: 'Patient Admission', + fields: [ + {fieldtype: 'Link', label: 'Medical Department', fieldname: 'medical_department', options: 'Medical Department', reqd: 1}, + {fieldtype: 'Link', label: 'Healthcare Practitioner (Primary)', fieldname: 'primary_practitioner', options: 'Healthcare Practitioner', reqd: 1}, + {fieldtype: 'Link', label: 'Healthcare Practitioner (Secondary)', fieldname: 'secondary_practitioner', options: 'Healthcare Practitioner'}, + {fieldtype: 'Column Break'}, + {fieldtype: 'Date', label: 'Admission Ordered For', fieldname: 'admission_ordered_for', default: 'Today'}, + {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, + {fieldtype: 'Int', label: 'Expected Length of Stay', fieldname: 'expected_length_of_stay'}, + {fieldtype: 'Section Break', label: 'Admission Instructions'}, + {fieldtype: 'Small Text', fieldname: 'admission_instruction'} + ], + primary_action_label: __('Order Admission'), + primary_action : function() { + var args = { + patient: frm.doc.patient, + admission_encounter: frm.doc.name, + referring_practitioner: frm.doc.practitioner, + company: frm.doc.company, + medical_department: dialog.get_value('medical_department'), + primary_practitioner: dialog.get_value('primary_practitioner'), + secondary_practitioner: dialog.get_value('secondary_practitioner'), + admission_ordered_for: dialog.get_value('admission_ordered_for'), + admission_service_unit_type: dialog.get_value('service_unit_type'), + expected_length_of_stay: dialog.get_value('expected_length_of_stay'), + admission_instruction: dialog.get_value('admission_instruction') } - }, - freeze: true, - freeze_message: __('Process Inpatient Scheduling') + frappe.call({ + method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_inpatient', + args: { + args: args + }, + callback: function(data) { + if(!data.exc){ + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: 'Scheduling Patient Admission' + }); + frm.refresh_fields(); + dialog.hide(); + } }); + + dialog.set_values({ + 'medical_department': frm.doc.medical_department, + 'primary_practitioner': frm.doc.practitioner, + }); + + dialog.fields_dict['service_unit_type'].get_query = function() { + return { + filters: { + 'inpatient_occupancy': 1, + 'allow_appointments': 0 + } + }; + }; + + dialog.show(); + dialog.$wrapper.find('.modal-dialog').css('width', '800px'); }; -let schedule_discharge = function(frm) { - frappe.call({ - method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_discharge', - args: {patient: frm.doc.patient, encounter_id: frm.doc.name, practitioner: frm.doc.practitioner}, - callback: function(data) { - if (!data.exc) { - frm.reload_doc(); +var schedule_discharge = function(frm) { + var dialog = new frappe.ui.Dialog ({ + title: 'Inpatient Discharge', + fields: [ + {fieldtype: 'Date', label: 'Discharge Ordered Date', fieldname: 'discharge_ordered_date', default: 'Today', read_only: 1}, + {fieldtype: 'Date', label: 'Followup Date', fieldname: 'followup_date'}, + {fieldtype: 'Column Break'}, + {fieldtype: 'Small Text', label: 'Discharge Instructions', fieldname: 'discharge_instructions'}, + {fieldtype: 'Section Break', label:'Discharge Summary'}, + {fieldtype: 'Text Editor', label: 'Discharge Note', fieldname: 'discharge_note'} + ], + primary_action_label: __('Order Discharge'), + primary_action : function() { + var args = { + patient: frm.doc.patient, + discharge_encounter: frm.doc.name, + discharge_practitioner: frm.doc.practitioner, + discharge_ordered_date: dialog.get_value('discharge_ordered_date'), + followup_date: dialog.get_value('followup_date'), + discharge_instructions: dialog.get_value('discharge_instructions'), + discharge_note: dialog.get_value('discharge_note') } - }, - freeze: true, - freeze_message: 'Process Discharge' + frappe.call ({ + method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_discharge', + args: {args}, + callback: function(data) { + if(!data.exc){ + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: 'Scheduling Inpatient Discharge' + }); + frm.refresh_fields(); + dialog.hide(); + } }); + + dialog.show(); + dialog.$wrapper.find('.modal-dialog').css('width', '800px'); }; -let create_medical_record = function (frm) { +let create_medical_record = function(frm) { if (!frm.doc.patient) { frappe.throw(__('Please select patient')); } @@ -221,7 +300,7 @@ let create_medical_record = function (frm) { frappe.new_doc('Patient Medical Record'); }; -let create_vital_signs = function (frm) { +let create_vital_signs = function(frm) { if (!frm.doc.patient) { frappe.throw(__('Please select patient')); } @@ -233,7 +312,7 @@ let create_vital_signs = function (frm) { frappe.new_doc('Vital Signs'); }; -let create_procedure = function (frm) { +let create_procedure = function(frm) { if (!frm.doc.patient) { frappe.throw(__('Please select patient')); } diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index 05eec87398..15675f4673 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -52,6 +52,7 @@ ], "fields": [ { + "allow_on_submit": 1, "fieldname": "inpatient_record", "fieldtype": "Link", "label": "Inpatient Record", @@ -296,6 +297,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "fieldname": "inpatient_status", "fieldtype": "Data", "label": "Inpatient Status", @@ -326,7 +328,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-04-27 21:58:29.789797", + "modified": "2020-05-16 21:00:08.644531", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", From f71f9556abb6c5570dd130c8eed1579d936da2c5 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 21 May 2020 01:31:48 +0530 Subject: [PATCH 0032/1264] fix: consider only submitted docs for invoicing --- .../healthcare/doctype/inpatient_record/inpatient_record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index e668204dcf..8056074668 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -187,8 +187,8 @@ def get_pending_doc(doc, doc_name_list, pending_invoices): return pending_invoices def get_inpatient_docs_not_invoiced(doc, inpatient_record): - return frappe.db.get_list(doc, filters = {"patient": inpatient_record.patient, - "inpatient_record": inpatient_record.name, "invoiced": 0}) + return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient, + 'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0}) def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None): inpatient_record.admitted_datetime = check_in From 09ec030baa0f50e09a00ad9f8c9cfc900ad15b73 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 21 May 2020 01:34:18 +0530 Subject: [PATCH 0033/1264] fix: ip-order dialogs use long text field --- .../doctype/patient_encounter/patient_encounter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 43e43acda4..ef1068e6cb 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -191,8 +191,8 @@ var schedule_inpatient = function(frm) { {fieldtype: 'Date', label: 'Admission Ordered For', fieldname: 'admission_ordered_for', default: 'Today'}, {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, {fieldtype: 'Int', label: 'Expected Length of Stay', fieldname: 'expected_length_of_stay'}, - {fieldtype: 'Section Break', label: 'Admission Instructions'}, - {fieldtype: 'Small Text', fieldname: 'admission_instruction'} + {fieldtype: 'Section Break'}, + {fieldtype: 'Long Text', label: 'Admission Instructions', fieldname: 'admission_instruction'} ], primary_action_label: __('Order Admission'), primary_action : function() { @@ -254,7 +254,7 @@ var schedule_discharge = function(frm) { {fieldtype: 'Column Break'}, {fieldtype: 'Small Text', label: 'Discharge Instructions', fieldname: 'discharge_instructions'}, {fieldtype: 'Section Break', label:'Discharge Summary'}, - {fieldtype: 'Text Editor', label: 'Discharge Note', fieldname: 'discharge_note'} + {fieldtype: 'Long Text', label: 'Discharge Note', fieldname: 'discharge_note'} ], primary_action_label: __('Order Discharge'), primary_action : function() { From 470fe65cc1b07f56d27d3dcc1106af1a40ee3132 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 21 May 2020 02:26:55 +0530 Subject: [PATCH 0034/1264] fix: inpatient date validation removed, added role perms service unit defaults not set when created from tree, added validations on after_insert --- .../healthcare_service_unit.py | 2 +- .../inpatient_record/inpatient_record.json | 75 +++++++++++++++---- .../inpatient_record/inpatient_record.py | 5 +- 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py index 9720078e32..9e0417a2be 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py @@ -22,7 +22,7 @@ class HealthcareServiceUnit(NestedSet): super(HealthcareServiceUnit, self).on_update() self.validate_one_root() - def validate(self): + def after_insert(self): if self.is_group: self.allow_appointments = 0 self.overlap_appointments = 0 diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index d3835409d9..5ced845c1b 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -229,7 +229,8 @@ "fieldname": "chief_complaint", "fieldtype": "Table MultiSelect", "label": "Chief Complaint", - "options": "Patient Encounter Symptom" + "options": "Patient Encounter Symptom", + "permlevel": 1 }, { "fieldname": "admission_instruction", @@ -259,34 +260,40 @@ "collapsible": 1, "fieldname": "medication_section", "fieldtype": "Section Break", - "label": "Medications" + "label": "Medications", + "permlevel": 1 }, { "fieldname": "drug_prescription", "fieldtype": "Table", - "options": "Drug Prescription" + "options": "Drug Prescription", + "permlevel": 1 }, { "collapsible": 1, "fieldname": "investigations_section", "fieldtype": "Section Break", - "label": "Investigations" + "label": "Investigations", + "permlevel": 1 }, { "fieldname": "lab_test_prescription", "fieldtype": "Table", - "options": "Lab Prescription" + "options": "Lab Prescription", + "permlevel": 1 }, { "collapsible": 1, "fieldname": "procedures_section", "fieldtype": "Section Break", - "label": "Procedures" + "label": "Procedures", + "permlevel": 1 }, { "fieldname": "procedure_prescription", "fieldtype": "Table", - "options": "Procedure Prescription" + "options": "Procedure Prescription", + "permlevel": 1 }, { "depends_on": "eval:(doc.status != \"Admission Scheduled\")", @@ -320,7 +327,8 @@ }, { "fieldname": "discharge_note", - "fieldtype": "Text Editor" + "fieldtype": "Text Editor", + "permlevel": 1 }, { "fetch_from": "admission_encounter.company", @@ -335,7 +343,8 @@ "collapsible_depends_on": "eval:(doc.status == \"Admitted\")", "fieldname": "encounter_details_section", "fieldtype": "Section Break", - "label": "Encounter Impression" + "label": "Encounter Impression", + "permlevel": 1 }, { "fieldname": "column_break_29", @@ -345,7 +354,8 @@ "fieldname": "diagnosis", "fieldtype": "Table MultiSelect", "label": "Diagnosis", - "options": "Patient Encounter Diagnosis" + "options": "Patient Encounter Diagnosis", + "permlevel": 1 }, { "fieldname": "followup_date", @@ -375,7 +385,8 @@ "collapsible": 1, "fieldname": "rehabilitation_section", "fieldtype": "Section Break", - "label": "Rehabilitation" + "label": "Rehabilitation", + "permlevel": 1 }, { "fieldname": "therapy_plan", @@ -383,12 +394,14 @@ "hidden": 1, "label": "Therapy Plan", "options": "Therapy Plan", + "permlevel": 1, "read_only": 1 }, { "fieldname": "therapies", "fieldtype": "Table", - "options": "Therapy Plan Detail" + "options": "Therapy Plan Detail", + "permlevel": 1 }, { "fieldname": "discharge_date", @@ -398,7 +411,7 @@ } ], "links": [], - "modified": "2020-05-21 00:37:12.939925", + "modified": "2020-05-21 02:26:22.144575", "modified_by": "Administrator", "module": "Healthcare", "name": "Inpatient Record", @@ -415,6 +428,42 @@ "role": "Healthcare Administrator", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "Physician", + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Nursing User" } ], "restrict_to_domain": "Healthcare", diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index 8056074668..802ab414c0 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -26,12 +26,9 @@ class InpatientRecord(Document): frappe.db.set_value("Patient", self.patient, "inpatient_record", None) def validate_dates(self): - if (getdate(self.scheduled_date) < getdate(today())) or \ - (getdate(self.admitted_datetime) < getdate(today())): - frappe.throw(_("Scheduled and Admitted dates can not be less than today")) if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \ (getdate(self.discharge_ordered_date) < getdate(self.scheduled_date)): - frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date")) + frappe.throw(_('Expected and Discharge dates cannot be less than Admission Schedule date')) def validate_already_scheduled_or_admitted(self): query = """ From f579680b97fc9bd348f34fab639686a7aadd936b Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 21 May 2020 03:08:47 +0530 Subject: [PATCH 0035/1264] fix: invoiced field position, medical department field corrected in query --- .../doctype/patient_appointment/patient_appointment.json | 4 ++-- .../doctype/patient_appointment/patient_appointment.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index b8a400c6b7..ac35acc21a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -39,9 +39,9 @@ "section_break_16", "mode_of_payment", "billing_item", + "invoiced", "column_break_2", "paid_amount", - "invoiced", "ref_sales_invoice", "section_break_3", "referring_practitioner", @@ -348,7 +348,7 @@ } ], "links": [], - "modified": "2020-04-27 21:36:06.404062", + "modified": "2020-05-21 03:04:21.400893", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 9eb6e77c85..512fb48360 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -447,7 +447,7 @@ def get_prescribed_therapies(patient): """ SELECT t.therapy_type, t.name, t.parent, e.practitioner, - e.encounter_date, e.therapy_plan, e.visit_department + e.encounter_date, e.therapy_plan, e.medical_department FROM `tabPatient Encounter` e, `tabTherapy Plan Detail` t WHERE From ccbdfcbed1b1d15f4f4f113355b6685db851204c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 21 May 2020 09:02:46 +0530 Subject: [PATCH 0036/1264] fix: service unit validation and translation --- .../healthcare_service_unit_type.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py index a99358cdc6..bb86eaacc4 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py @@ -12,12 +12,14 @@ class HealthcareServiceUnitType(Document): def validate(self): if self.allow_appointments and self.inpatient_occupancy: frappe.msgprint( - _('Healthcare Service Unit Type cannot be both Allow Appointments and Inpatient Occupancy'), + _('Healthcare Service Unit Type cannot have both {0} and {1}').format( + frappe.bold('Allow Appointments'), frappe.bold('Inpatient Occupancy')), raise_exception=1, title=_('Validation Error'), indicator='red' ) elif not self.allow_appointments and not self.inpatient_occupancy: frappe.msgprint( - _('Healthcare Service Unit Type cannot be both Allow Appointments and Inpatient Occupancy'), + _('Healthcare Service Unit Type must allow atleast one among {0} and {1}').format( + frappe.bold('Allow Appointments'), frappe.bold('Inpatient Occupancy')), raise_exception=1, title=_('Validation Error'), indicator='red' ) From 4da770d6c2f9065c87da11775f2b9c39e6d3230f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 21 May 2020 09:27:42 +0530 Subject: [PATCH 0037/1264] fix(ip): code cleanup and translations --- .../inpatient_record/inpatient_record.js | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js index b640239b70..971e166067 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js @@ -11,24 +11,24 @@ frappe.ui.form.on('Inpatient Record', { ]; }, refresh: function(frm) { - if(!frm.doc.__islocal && (frm.doc.status == 'Admission Scheduled' || frm.doc.status == 'Admitted')) { + if (!frm.doc.__islocal && (frm.doc.status == 'Admission Scheduled' || frm.doc.status == 'Admitted')) { frm.enable_save(); } else { frm.disable_save(); } - if(!frm.doc.__islocal && frm.doc.status == 'Admission Scheduled') { + if (!frm.doc.__islocal && frm.doc.status == 'Admission Scheduled') { frm.add_custom_button(__('Admit'), function() { admit_patient_dialog(frm); } ); } - if(!frm.doc.__islocal && frm.doc.status == 'Discharge Scheduled') { + if (!frm.doc.__islocal && frm.doc.status == 'Discharge Scheduled') { frm.add_custom_button(__('Discharge'), function() { discharge_patient(frm); } ); } - if(!frm.doc.__islocal && frm.doc.status != 'Admitted') { + if (!frm.doc.__islocal && frm.doc.status != 'Admitted') { frm.disable_save(); frm.set_df_property('btn_transfer', 'hidden', 1); } else { @@ -40,22 +40,22 @@ frappe.ui.form.on('Inpatient Record', { } }); -var discharge_patient = function(frm) { +let discharge_patient = function(frm) { frappe.call({ doc: frm.doc, method: 'discharge', callback: function(data) { - if(!data.exc){ + if (!data.exc) { frm.reload_doc(); } }, freeze: true, - freeze_message: 'Processing Inpatient Discharge' + freeze_message: __('Processing Inpatient Discharge') }); }; -var admit_patient_dialog = function(frm){ - var dialog = new frappe.ui.Dialog({ +let admit_patient_dialog = function(frm) { + let dialog = new frappe.ui.Dialog({ title: 'Admit Patient', width: 100, fields: [ @@ -74,13 +74,13 @@ var admit_patient_dialog = function(frm){ ], primary_action_label: __('Admit'), primary_action : function(){ - var service_unit = dialog.get_value('service_unit'); - var check_in = dialog.get_value('check_in'); - var expected_discharge = null; - if(dialog.get_value('expected_discharge')){ + let service_unit = dialog.get_value('service_unit'); + let check_in = dialog.get_value('check_in'); + let expected_discharge = null; + if (dialog.get_value('expected_discharge')) { expected_discharge = dialog.get_value('expected_discharge'); } - if(!service_unit && !check_in){ + if (!service_unit && !check_in) { return; } frappe.call({ @@ -92,12 +92,12 @@ var admit_patient_dialog = function(frm){ 'expected_discharge': expected_discharge }, callback: function(data) { - if(!data.exc){ + if (!data.exc) { frm.reload_doc(); } }, freeze: true, - freeze_message: 'Processing Patient Admission' + freeze_message: __('Processing Patient Admission') }); frm.refresh_fields(); dialog.hide(); @@ -126,21 +126,21 @@ var admit_patient_dialog = function(frm){ dialog.show(); }; -var transfer_patient_dialog = function(frm){ - var dialog = new frappe.ui.Dialog({ +let transfer_patient_dialog = function(frm) { + let dialog = new frappe.ui.Dialog({ title: 'Transfer Patient', width: 100, fields: [ - {fieldtype: "Link", label: "Leave From", fieldname: "leave_from", options: "Healthcare Service Unit", reqd: 1, read_only:1}, - {fieldtype: "Link", label: "Service Unit Type", fieldname: "service_unit_type", options: "Healthcare Service Unit Type"}, - {fieldtype: "Link", label: "Transfer To", fieldname: "service_unit", options: "Healthcare Service Unit", reqd: 1}, - {fieldtype: "Datetime", label: "Check In", fieldname: "check_in", reqd: 1} + {fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1}, + {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, + {fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1}, + {fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1} ], - primary_action_label: __("Transfer"), - primary_action : function(){ - var service_unit = null; - var check_in = dialog.get_value('check_in'); - var leave_from = null; + primary_action_label: __('Transfer'), + primary_action : function() { + let service_unit = null; + let check_in = dialog.get_value('check_in'); + let leave_from = null; if(dialog.get_value('leave_from')){ leave_from = dialog.get_value('leave_from'); } @@ -159,47 +159,47 @@ var transfer_patient_dialog = function(frm){ 'leave_from': leave_from }, callback: function(data) { - if(!data.exc){ + if (!data.exc) { frm.reload_doc(); } }, freeze: true, - freeze_message: "Process Transfer" + freeze_message: __('Process Transfer') }); frm.refresh_fields(); dialog.hide(); } }); - dialog.fields_dict["leave_from"].get_query = function(){ + dialog.fields_dict['leave_from'].get_query = function(){ return { - query : "erpnext.healthcare.doctype.inpatient_record.inpatient_record.get_leave_from", + query : 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.get_leave_from', filters: {docname:frm.doc.name} }; }; - dialog.fields_dict["service_unit_type"].get_query = function(){ + dialog.fields_dict['service_unit_type'].get_query = function(){ return { filters: { - "inpatient_occupancy": 1, - "allow_appointments": 0 + 'inpatient_occupancy': 1, + 'allow_appointments': 0 } }; }; - dialog.fields_dict["service_unit"].get_query = function(){ + dialog.fields_dict['service_unit'].get_query = function(){ return { filters: { - "is_group": 0, - "service_unit_type": dialog.get_value("service_unit_type"), - "occupancy_status" : "Vacant" + 'is_group': 0, + 'service_unit_type': dialog.get_value('service_unit_type'), + 'occupancy_status' : 'Vacant' } }; }; dialog.show(); - var not_left_service_unit = null; - for(let inpatient_occupancy in frm.doc.inpatient_occupancies){ - if(frm.doc.inpatient_occupancies[inpatient_occupancy].left != 1){ + let not_left_service_unit = null; + for (let inpatient_occupancy in frm.doc.inpatient_occupancies) { + if (frm.doc.inpatient_occupancies[inpatient_occupancy].left != 1) { not_left_service_unit = frm.doc.inpatient_occupancies[inpatient_occupancy].service_unit; } } From 0d3231ebbaeb1edbdfd722ef2200e061a5f15fbd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 21 May 2020 10:02:31 +0530 Subject: [PATCH 0038/1264] fix: add title to validation dialog --- .../healthcare/doctype/inpatient_record/inpatient_record.py | 4 ++-- .../healthcare/doctype/patient_encounter/patient_encounter.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index 802ab414c0..cf63b65f4d 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -29,7 +29,7 @@ class InpatientRecord(Document): if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \ (getdate(self.discharge_ordered_date) < getdate(self.scheduled_date)): frappe.throw(_('Expected and Discharge dates cannot be less than Admission Schedule date')) - + def validate_already_scheduled_or_admitted(self): query = """ select name, status @@ -168,7 +168,7 @@ def validate_invoiced_inpatient(inpatient_record): if pending_invoices: frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", " - .join(pending_invoices))) + .join(pending_invoices)), title=_('Unbilled Invoices')) def get_pending_doc(doc, doc_name_list, pending_invoices): if doc_name_list: diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index ef1068e6cb..edcee99d4b 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -215,7 +215,7 @@ var schedule_inpatient = function(frm) { args: args }, callback: function(data) { - if(!data.exc){ + if (!data.exc) { frm.reload_doc(); } }, From 45de78bf94649e0a18f33b8284f6abb09e41792c Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 21 May 2020 11:35:00 +0530 Subject: [PATCH 0039/1264] fix: Employee Advance Return not working (#21812) --- erpnext/hr/doctype/employee_advance/employee_advance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 23e4992066..db39eff0e4 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -146,7 +146,7 @@ def create_return_through_additional_salary(doc): return additional_salary @frappe.whitelist() -def make_return_entry(employee_name, company, employee_advance_name, return_amount, mode_of_payment, advance_account): +def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, mode_of_payment=None): return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment) mode_of_payment_type = '' From 8e9f3e464582553579a7a9a4bb660959100891bb Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 21 May 2020 12:07:43 +0530 Subject: [PATCH 0040/1264] fix: plc conversion rate set infinitely (#21820) --- erpnext/manufacturing/doctype/bom/bom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 44da9cae35..898955e51b 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -265,7 +265,7 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({ plc_conversion_rate: function(doc) { if (!this.in_apply_price_list) { - this.apply_price_list(); + this.apply_price_list(null, true); } }, From 0f27be2facc07e5608999e31d5be7646faa29a11 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 May 2020 13:11:48 +0530 Subject: [PATCH 0041/1264] fix: Fetch customer into Delivery Note from Pick List --- erpnext/stock/doctype/pick_list/pick_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 231af1a022..1f8d009f92 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -300,6 +300,7 @@ def create_delivery_note(source_name, target_doc=None): set_delivery_note_missing_values(delivery_note) delivery_note.pick_list = pick_list.name + delivery_note.customer = pick_list.customer if pick_list.customer else None return delivery_note From 64301c8adb1f17c3dbbdf7c0b669634550c5e5a1 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 May 2020 14:03:29 +0530 Subject: [PATCH 0042/1264] fix: Supplier Invoice No not fetched in Import Supplier Invoice --- .../import_supplier_invoice/import_supplier_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 6784ea8a5b..6b9567c0e5 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -58,7 +58,7 @@ class ImportSupplierInvoice(Document): "naming_series": self.invoice_series, "document_type": line.TipoDocumento.text, "bill_date": get_datetime_str(line.Data.text), - "invoice_no": line.Numero.text, + "bill_no": line.Numero.text, "total_discount": 0, "items": [], "buying_price_list": self.default_buying_price_list @@ -249,7 +249,7 @@ def create_supplier(supplier_group, args): return existing_supplier_name else: - + new_supplier = frappe.new_doc("Supplier") new_supplier.supplier_name = re.sub('&', '&', args.supplier) new_supplier.supplier_group = supplier_group From ed26db899a9e6664f3a485880e7a610a96c3b788 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 21 May 2020 18:10:13 +0530 Subject: [PATCH 0043/1264] feat: BOM template (#21262) Co-authored-by: Marica --- erpnext/controllers/queries.py | 9 +- erpnext/manufacturing/doctype/bom/bom.js | 189 +++++- erpnext/manufacturing/doctype/bom/bom.json | 14 +- erpnext/manufacturing/doctype/bom/bom.py | 215 ++++-- erpnext/manufacturing/doctype/bom/bom_list.js | 6 +- .../doctype/bom_item/bom_item.json | 17 +- .../doctype/work_order/work_order.js | 26 + .../doctype/work_order/work_order.py | 50 +- .../work_order_item/work_order_item.json | 632 ++++-------------- erpnext/stock/doctype/item/item.py | 11 + 10 files changed, 546 insertions(+), 623 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index d9465917ec..9bba71d0dd 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -188,12 +188,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals # scan description only if items are less than 50000 description_cond = 'or tabItem.description LIKE %(txt)s' - extra_cond = " and tabItem.has_variants=0" - if (filters and isinstance(filters, dict) - and filters.get("doctype") == "BOM"): - extra_cond = "" - del filters["doctype"] - return frappe.db.sql("""select tabItem.name, if(length(tabItem.item_name) > 40, concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, @@ -204,10 +198,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals from tabItem where tabItem.docstatus < 2 and tabItem.disabled=0 + and tabItem.has_variants=0 and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00') and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s) {description_cond}) - {extra_cond} {fcond} {mcond} order by if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), @@ -218,7 +212,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals key=searchfield, columns=columns, scond=searchfields, - extra_cond=extra_cond, fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), mcond=get_match_cond(doctype).replace('%', '%%'), description_cond = description_cond), diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 898955e51b..47b4207241 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -29,10 +29,7 @@ frappe.ui.form.on("BOM", { frm.set_query("item", function() { return { - query: "erpnext.controllers.queries.item_query", - filters: { - "doctype": "BOM" - } + query: "erpnext.manufacturing.doctype.bom.bom.item_query" }; }); @@ -44,9 +41,12 @@ frappe.ui.form.on("BOM", { }; }); - frm.set_query("item_code", "items", function() { + frm.set_query("item_code", "items", function(doc) { return { - query: "erpnext.controllers.queries.item_query" + query: "erpnext.manufacturing.doctype.bom.bom.item_query", + filters: { + "item_code": doc.item + } }; }); @@ -96,6 +96,12 @@ frappe.ui.form.on("BOM", { frm.trigger("make_work_order"); }, __("Create")); + if (frm.doc.has_variants) { + frm.add_custom_button(__("Variant BOM"), function() { + frm.trigger("make_variant_bom"); + }, __("Create")); + } + if (frm.doc.inspection_required) { frm.add_custom_button(__("Quality Inspection"), function() { frm.trigger("make_quality_inspection"); @@ -124,7 +130,7 @@ frappe.ui.form.on("BOM", { } - if (frm.doc.__onload && frm.doc.__onload["has_variants"]) { + if (frm.doc.has_variants) { frm.set_intro(__('This is a Template BOM and will be used to make the work order for {0} of the item {1}', [ `variants`, @@ -138,9 +144,52 @@ frappe.ui.form.on("BOM", { }, make_work_order: function(frm) { + frm.events.setup_variant_prompt(frm, "Work Order", (frm, item, data, variant_items) => { + frappe.call({ + method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order", + args: { + bom_no: frm.doc.name, + item: item, + qty: data.qty || 0.0, + project: frm.doc.project, + variant_items: variant_items + }, + freeze: true, + callback: function(r) { + if(r.message) { + let doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + } + }); + }); + }, + + make_variant_bom: function(frm) { + frm.events.setup_variant_prompt(frm, "Variant BOM", (frm, item, data, variant_items) => { + frappe.call({ + method: "erpnext.manufacturing.doctype.bom.bom.make_variant_bom", + args: { + source_name: frm.doc.name, + bom_no: frm.doc.name, + item: item, + variant_items: variant_items + }, + freeze: true, + callback: function(r) { + if(r.message) { + let doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + } + }); + }, true); + }, + + setup_variant_prompt: function(frm, title, callback, skip_qty_field) { const fields = []; - if (frm.doc.__onload && frm.doc.__onload["has_variants"]) { + if (frm.doc.has_variants) { fields.push({ fieldtype: 'Link', label: __('Variant Item'), @@ -158,34 +207,106 @@ frappe.ui.form.on("BOM", { }); } - fields.push({ - fieldtype: 'Float', - label: __('Qty To Manufacture'), - fieldname: 'qty', - reqd: 1, - default: 1 + if (!skip_qty_field) { + fields.push({ + fieldtype: 'Float', + label: __('Qty To Manufacture'), + fieldname: 'qty', + reqd: 1, + default: 1 + }); + } + + var has_template_rm = frm.doc.items.filter(d => d.has_variants === 1) || []; + if (has_template_rm && has_template_rm.length > 0) { + fields.push({ + fieldname: "items", + fieldtype: "Table", + label: __("Raw Materials"), + fields: [ + { + fieldname: "item_code", + options: "Item", + label: __("Template Item"), + fieldtype: "Link", + in_list_view: 1, + reqd: 1, + }, + { + fieldname: "varint_item_code", + options: "Item", + label: __("Variant Item"), + fieldtype: "Link", + in_list_view: 1, + reqd: 1, + get_query: function(data) { + if (!data.item_code) { + frappe.throw(__("Select template item")); + } + + return { + query: "erpnext.controllers.queries.item_query", + filters: { + "variant_of": data.item_code + } + }; + } + }, + { + fieldname: "qty", + label: __("Quantity"), + fieldtype: "Float", + in_list_view: 1, + reqd: 1, + }, + { + fieldname: "source_warehouse", + label: __("Source Warehouse"), + fieldtype: "Link", + options: "Warehouse" + }, + { + fieldname: "operation", + label: __("Operation"), + fieldtype: "Data", + hidden: 1, + } + ], + in_place_edit: true, + data: [], + get_data: function () { + return []; + }, + }); + } + + let dialog = frappe.prompt(fields, data => { + let item = data.item || frm.doc.item; + let variant_items = data.items || []; + + variant_items.forEach(d => { + if (!d.varint_item_code) { + frappe.throw(__("Select variant item code for the template item {0}", [d.item_code])); + } + }) + + callback(frm, item, data, variant_items); + + }, __(title), __("Create")); + + has_template_rm.forEach(d => { + dialog.fields_dict.items.df.data.push({ + "item_code": d.item_code, + "varint_item_code": "", + "qty": d.qty, + "source_warehouse": d.source_warehouse, + "operation": d.operation + }); }); - frappe.prompt(fields, data => { - let item = data.item || frm.doc.item; - - frappe.call({ - method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order", - args: { - bom_no: frm.doc.name, - item: item, - qty: data.qty || 0.0, - project: frm.doc.project - }, - freeze: true, - callback: function(r) { - if(r.message) { - var doc = frappe.model.sync(r.message)[0]; - frappe.set_route("Form", doc.doctype, doc.name); - } - } - }); - }, __("Enter Value"), __("Create")); + if (has_template_rm) { + dialog.fields_dict.items.grid.refresh(); + } }, make_quality_inspection: function(frm) { diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 4ce0ecf3f2..f551b91597 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -53,6 +53,7 @@ "section_break_25", "description", "column_break_27", + "has_variants", "section_break0", "exploded_items", "website_section", @@ -498,6 +499,17 @@ "options": "Currency", "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "fetch_from": "item.has_variants", + "fieldname": "has_variants", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Has Variants", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-sitemap", @@ -505,7 +517,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2020-05-05 14:29:32.634952", + "modified": "2020-05-21 12:29:32.634952", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 6ac653e37a..1bdac5731e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -3,13 +3,16 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import cint, cstr, flt +from frappe.utils import cint, cstr, flt, today from frappe import _ from erpnext.setup.utils import get_exchange_rate from frappe.website.website_generator import WebsiteGenerator from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_price_list_rate from frappe.core.doctype.version.version import get_diff +from erpnext.controllers.queries import get_match_cond +from erpnext.stock.doctype.item.item import get_item_details +from frappe.model.mapper import get_mapped_doc import functools @@ -59,11 +62,6 @@ class BOM(WebsiteGenerator): self.name = name - def onload(self): - super(BOM, self).onload() - if self.get("item") and cint(frappe.db.get_value("Item", self.item, "has_variants")): - self.set_onload("has_variants", True) - def validate(self): self.route = frappe.scrub(self.name).replace('_', '-') self.clear_operations() @@ -103,9 +101,7 @@ class BOM(WebsiteGenerator): self.manage_default_bom() def get_item_det(self, item_code): - item = frappe.db.sql("""select name, item_name, docstatus, description, image, - is_sub_contracted_item, stock_uom, default_bom, last_purchase_rate, include_item_in_manufacturing - from `tabItem` where name=%s""", item_code, as_dict = 1) + item = get_item_details(item_code) if not item: frappe.throw(_("Item: {0} does not exist in the system").format(item_code)) @@ -150,10 +146,10 @@ class BOM(WebsiteGenerator): item = self.get_item_det(args['item_code']) - args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or '' + args['bom_no'] = args['bom_no'] or item and cstr(item['default_bom']) or '' args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or - item and item[0].include_item_in_manufacturing or 0) - args.update(item[0]) + item and item.include_item_in_manufacturing or 0) + args.update(item) rate = self.get_rm_rate(args) ret_item = { @@ -185,40 +181,14 @@ class BOM(WebsiteGenerator): self.rm_cost_as_per = "Valuation Rate" if arg.get('scrap_items'): - rate = self.get_valuation_rate(arg) + rate = get_valuation_rate(arg) elif arg: #Customer Provided parts will have zero rate if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'): if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom: rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1) else: - if self.rm_cost_as_per == 'Valuation Rate': - rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1) - elif self.rm_cost_as_per == 'Last Purchase Rate': - rate = flt(arg.get('last_purchase_rate') \ - or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \ - * (arg.get("conversion_factor") or 1) - elif self.rm_cost_as_per == "Price List": - if not self.buying_price_list: - frappe.throw(_("Please select Price List")) - args = frappe._dict({ - "doctype": "BOM", - "price_list": self.buying_price_list, - "qty": arg.get("qty") or 1, - "uom": arg.get("uom") or arg.get("stock_uom"), - "stock_uom": arg.get("stock_uom"), - "transaction_type": "buying", - "company": self.company, - "currency": self.currency, - "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function - "conversion_factor": arg.get("conversion_factor") or 1, - "plc_conversion_rate": 1, - "ignore_party": True - }) - item_doc = frappe.get_doc("Item", arg.get("item_code")) - out = frappe._dict() - get_price_list_rate(args, item_doc, out) - rate = out.price_list_rate + rate = get_bom_item_rate(arg, self) if not rate: if self.rm_cost_as_per == "Price List": @@ -286,31 +256,6 @@ class BOM(WebsiteGenerator): where is_active = 1 and name = %s""", bom_no, as_dict=1) return bom and bom[0]['unit_cost'] or 0 - def get_valuation_rate(self, args): - """ Get weighted average of valuation rate from all warehouses """ - - total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0 - for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin` - where item_code=%s""", args['item_code'], as_dict=1): - total_qty += flt(d.actual_qty) - total_value += flt(d.stock_value) - - if total_qty: - valuation_rate = total_value / total_qty - - if valuation_rate <= 0: - last_valuation_rate = frappe.db.sql("""select valuation_rate - from `tabStock Ledger Entry` - where item_code = %s and valuation_rate > 0 - order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code']) - - valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 - - if not valuation_rate: - valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate") - - return flt(valuation_rate) - def manage_default_bom(self): """ Uncheck others if current one is selected as default or check the current one as default if it the only bom for the selected item, @@ -624,6 +569,62 @@ class BOM(WebsiteGenerator): if not d.batch_size or d.batch_size <= 0: d.batch_size = 1 +def get_bom_item_rate(args, bom_doc): + if bom_doc.rm_cost_as_per == 'Valuation Rate': + rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1) + elif bom_doc.rm_cost_as_per == 'Last Purchase Rate': + rate = ( flt(args.get('last_purchase_rate')) \ + or frappe.db.get_value("Item", args['item_code'], "last_purchase_rate")) \ + * (args.get("conversion_factor") or 1) + elif bom_doc.rm_cost_as_per == "Price List": + if not bom_doc.buying_price_list: + frappe.throw(_("Please select Price List")) + bom_args = frappe._dict({ + "doctype": "BOM", + "price_list": bom_doc.buying_price_list, + "qty": args.get("qty") or 1, + "uom": args.get("uom") or args.get("stock_uom"), + "stock_uom": args.get("stock_uom"), + "transaction_type": "buying", + "company": bom_doc.company, + "currency": bom_doc.currency, + "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function + "conversion_factor": args.get("conversion_factor") or 1, + "plc_conversion_rate": 1, + "ignore_party": True + }) + item_doc = frappe.get_cached_doc("Item", args.get("item_code")) + out = frappe._dict() + get_price_list_rate(bom_args, item_doc, out) + rate = out.price_list_rate + + return rate + +def get_valuation_rate(args): + """ Get weighted average of valuation rate from all warehouses """ + + total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0 + for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin` + where item_code=%s""", args['item_code'], as_dict=1): + total_qty += flt(d.actual_qty) + total_value += flt(d.stock_value) + + if total_qty: + valuation_rate = total_value / total_qty + + if valuation_rate <= 0: + last_valuation_rate = frappe.db.sql("""select valuation_rate + from `tabStock Ledger Entry` + where item_code = %s and valuation_rate > 0 + order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code']) + + valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 + + if not valuation_rate: + valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate") + + return flt(valuation_rate) + def get_list_context(context): context.title = _("Bill of Materials") # context.introduction = _('Boms') @@ -639,6 +640,8 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty, item.image, bom.project, + bom_item.rate, + bom_item.amount, item.stock_uom, item.item_group, item.allow_alternative_item, @@ -655,6 +658,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite where bom_item.docstatus < 2 and bom.name = %(bom)s + and ifnull(item.has_variants, 0) = 0 and item.is_stock_item in (1, {is_stock_item}) {where_conditions} group by item_code, stock_uom @@ -897,3 +901,84 @@ def get_bom_diff(bom1, bom2): out.removed.append([df.fieldname, d.as_dict()]) return out + +def item_query(doctype, txt, searchfield, start, page_len, filters): + meta = frappe.get_meta("Item", cached=True) + searchfields = meta.get_search_fields() + + order_by = "idx desc, name, item_name" + + fields = ["name", "item_group", "item_name", "description"] + fields.extend([field for field in searchfields + if not field in ["name", "item_group", "description"]]) + + searchfields = searchfields + [field for field in [searchfield or "name", "item_code", "item_group", "item_name"] + if not field in searchfields] + + query_filters = { + "disabled": 0, + "ifnull(end_of_life, '5050-50-50')": (">", today()) + } + + or_cond_filters = {} + if txt: + for s_field in searchfields: + or_cond_filters[s_field] = ("like", "%{0}%".format(txt)) + + barcodes = frappe.get_all("Item Barcode", + fields=["distinct parent as item_code"], + filters = {"barcode": ("like", "%{0}%".format(txt))}) + + barcodes = [d.item_code for d in barcodes] + if barcodes: + or_cond_filters["name"] = ("in", barcodes) + + for cond in get_match_cond(doctype, as_condition=False): + for key, value in cond.items(): + if key == doctype: + key = "name" + + query_filters[key] = ("in", value) + + if filters and filters.get("item_code"): + has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants") + if not has_variants: + query_filters["has_variants"] = 0 + + return frappe.get_all("Item", + fields = fields, filters=query_filters, + or_filters = or_cond_filters, order_by=order_by, + limit_start=start, limit_page_length=page_len, as_list=1) + +@frappe.whitelist() +def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None): + from erpnext.manufacturing.doctype.work_order.work_order import add_variant_item + + def postprocess(source, doc): + doc.item = item + doc.quantity = 1 + + item_data = get_item_details(item) + doc.update({ + "item_name": item_data.item_name, + "description": item_data.description, + "uom": item_data.stock_uom, + "allow_alternative_item": item_data.allow_alternative_item + }) + + add_variant_item(variant_items, doc, source_name) + + doc = get_mapped_doc('BOM', source_name, { + 'BOM': { + 'doctype': 'BOM', + 'validation': { + 'docstatus': ['=', 1] + } + }, + 'BOM Item': { + 'doctype': 'BOM Item', + 'condition': lambda doc: doc.has_variants == 0 + }, + }, target_doc, postprocess) + + return doc \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom_list.js b/erpnext/manufacturing/doctype/bom/bom_list.js index 2b06ed72ed..94cb466bd8 100644 --- a/erpnext/manufacturing/doctype/bom/bom_list.js +++ b/erpnext/manufacturing/doctype/bom/bom_list.js @@ -1,7 +1,9 @@ frappe.listview_settings['BOM'] = { - add_fields: ["is_active", "is_default", "total_cost"], + add_fields: ["is_active", "is_default", "total_cost", "has_variants"], get_indicator: function(doc) { - if(doc.is_default) { + if(doc.is_active && doc.has_variants) { + return [__("Template"), "orange", "has_variants,=,Yes"]; + } else if(doc.is_default) { return [__("Default"), "green", "is_default,=,Yes"]; } else if(doc.is_active) { return [__("Active"), "blue", "is_active,=,Yes"]; diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index f094be4c64..e34be61bc7 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -1,8 +1,10 @@ { + "actions": [], "creation": "2013-02-22 01:27:49", "doctype": "DocType", "document_type": "Setup", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "item_code", "item_name", @@ -33,6 +35,7 @@ "scrap", "qty_consumed_per_unit", "section_break_27", + "has_variants", "include_item_in_manufacturing", "original_item" ], @@ -57,6 +60,7 @@ "label": "Item Name" }, { + "depends_on": "eval:parent.with_operations == 1", "fieldname": "operation", "fieldtype": "Link", "label": "Item operation", @@ -258,11 +262,22 @@ "label": "Original Item", "options": "Item", "read_only": 1 + }, + { + "default": "0", + "fetch_from": "item_code.has_variants", + "fieldname": "has_variants", + "fieldtype": "Check", + "label": "Has Variants", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-11-22 11:38:52.087303", + "links": [], + "modified": "2020-04-09 14:30:26.535546", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index c125571960..a244f582c4 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -449,6 +449,32 @@ frappe.ui.form.on("Work Order Item", { } }); } + }, + + item_code: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + + if (row.item_code) { + frappe.call({ + method: "erpnext.stock.doctype.item.item.get_item_details", + args: { + item_code: row.item_code, + company: frm.doc.company + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, { + "required_qty": 1, + "item_name": r.message.item_name, + "description": r.message.description, + "source_warehouse": r.message.default_warehouse, + "allow_alternative_item": r.message.allow_alternative_item, + "include_item_in_manufacturing": r.message.include_item_in_manufacturing + }); + } + } + }); + } } }); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index c2789559b0..e2233a3e2f 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -8,9 +8,9 @@ import math from frappe import _ from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours from frappe.model.document import Document -from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict +from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict, get_bom_item_rate from dateutil.relativedelta import relativedelta -from erpnext.stock.doctype.item.item import validate_end_of_life +from erpnext.stock.doctype.item.item import validate_end_of_life, get_item_defaults from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError from erpnext.projects.doctype.timesheet.timesheet import OverlapError from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations @@ -541,6 +541,8 @@ class WorkOrder(Document): # For instance in BOM Explosion Item child table, the items coming from sub assembly items for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999): self.append('required_items', { + 'rate': item.rate, + 'amount': item.amount, 'operation': item.operation, 'item_code': item.item_code, 'item_name': item.item_name, @@ -637,9 +639,10 @@ def get_bom_operations(doctype, txt, searchfield, start, page_len, filters): filters = filters, fields = ['operation'], as_list=1) @frappe.whitelist() -def get_item_details(item, project = None): +def get_item_details(item, project = None, skip_bom_info=False): res = frappe.db.sql(""" - select stock_uom, description + select stock_uom, description, item_name, allow_alternative_item, + include_item_in_manufacturing from `tabItem` where disabled=0 and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s) @@ -650,6 +653,7 @@ def get_item_details(item, project = None): return {} res = res[0] + if skip_bom_info: return res filters = {"item": item, "is_default": 1} @@ -681,7 +685,7 @@ def get_item_details(item, project = None): return res @frappe.whitelist() -def make_work_order(bom_no, item, qty=0, project=None): +def make_work_order(bom_no, item, qty=0, project=None, variant_items=None): if not frappe.has_permission("Work Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) @@ -696,8 +700,44 @@ def make_work_order(bom_no, item, qty=0, project=None): wo_doc.qty = flt(qty) wo_doc.get_items_and_operations_from_bom() + if variant_items: + add_variant_item(variant_items, wo_doc, bom_no, "required_items") + return wo_doc +def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"): + if isinstance(variant_items, string_types): + variant_items = json.loads(variant_items) + + for item in variant_items: + args = frappe._dict({ + "item_code": item.get("varint_item_code"), + "required_qty": item.get("qty"), + "qty": item.get("qty"), # for bom + "source_warehouse": item.get("source_warehouse"), + "operation": item.get("operation") + }) + + bom_doc = frappe.get_cached_doc("BOM", bom_no) + item_data = get_item_details(args.item_code, skip_bom_info=True) + args.update(item_data) + + args["rate"] = get_bom_item_rate({ + "item_code": args.get("item_code"), + "qty": args.get("required_qty"), + "uom": args.get("stock_uom"), + "stock_uom": args.get("stock_uom"), + "conversion_factor": 1 + }, bom_doc) + + if not args.source_warehouse: + args["source_warehouse"] = get_item_defaults(item.get("varint_item_code"), + wo_doc.company).default_warehouse + + args["amount"] = flt(args.get("required_qty")) * flt(args.get("rate")) + args["uom"] = item_data.stock_uom + wo_doc.append(table_name, args) + @frappe.whitelist() def check_if_scrap_warehouse_mandatory(bom_no): res = {"set_scrap_wh_mandatory": False } diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json index 4442162636..3acf5727d1 100644 --- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json +++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json @@ -1,526 +1,144 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-04-18 07:38:26.314642", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2016-04-18 07:38:26.314642", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "operation", + "item_code", + "source_warehouse", + "column_break_3", + "item_name", + "description", + "allow_alternative_item", + "include_item_in_manufacturing", + "qty_section", + "required_qty", + "rate", + "amount", + "column_break_11", + "transferred_qty", + "consumed_qty", + "available_qty_at_source_warehouse", + "available_qty_at_wip_warehouse" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "operation", - "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": "Operation", - "length": 0, - "no_copy": 0, - "options": "Operation", - "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": "operation", + "fieldtype": "Link", + "label": "Operation", + "options": "Operation" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "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": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "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": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "source_warehouse", - "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": "Source Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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": "source_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Source Warehouse", + "options": "Warehouse" + }, { - "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 - }, + "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, - "fieldname": "item_name", - "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": "Item 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 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text", - "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": "Description", - "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 - }, + "fieldname": "description", + "fieldtype": "Text", + "label": "Description", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty_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": "Qty", - "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": "qty_section", + "fieldtype": "Section Break", + "label": "Qty" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "required_qty", - "fieldtype": "Float", - "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": "Required Qty", - "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 - }, + "fieldname": "required_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!parent.skip_transfer", - "fieldname": "transferred_qty", - "fieldtype": "Float", - "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": "Transferred Qty", - "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 - }, + "depends_on": "eval:!parent.skip_transfer", + "fieldname": "transferred_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Transferred Qty", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allow_alternative_item", - "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 Alternative Item", - "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": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "include_item_in_manufacturing", - "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": "Include Item In Manufacturing", - "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": "include_item_in_manufacturing", + "fieldtype": "Check", + "label": "Include Item In Manufacturing" + }, { - "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 - }, + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!parent.skip_transfer", - "fieldname": "consumed_qty", - "fieldtype": "Float", - "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": "Consumed Qty", - "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 - }, + "depends_on": "eval:!parent.skip_transfer", + "fieldname": "consumed_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Consumed Qty", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "available_qty_at_source_warehouse", - "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": "Available Qty at Source Warehouse", - "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 - }, + "fieldname": "available_qty_at_source_warehouse", + "fieldtype": "Float", + "label": "Available Qty at Source Warehouse", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "available_qty_at_wip_warehouse", - "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": "Available Qty at WIP Warehouse", - "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 + "fieldname": "available_qty_at_wip_warehouse", + "fieldtype": "Float", + "label": "Available Qty at WIP Warehouse", + "read_only": 1 + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "read_only": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "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-11-20 19:04:38.508839", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Work Order Item", - "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, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-04-13 18:46:32.966416", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Item", + "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/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 7a1c1279ea..3436a5d013 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1143,6 +1143,17 @@ def set_item_default(item_code, company, fieldname, value): d.db_insert() item.clear_cache() +@frappe.whitelist() +def get_item_details(item_code, company=None): + out = frappe._dict() + if company: + out = get_item_defaults(item_code, company) or frappe._dict() + + doc = frappe.get_cached_doc("Item", item_code) + out.update(doc.as_dict()) + + return out + @frappe.whitelist() def get_uom_conv_factor(uom, stock_uom): uoms = [uom, stock_uom] From 9b8b712083d890bc98a8719cd04575a36dddb9d8 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Thu, 21 May 2020 18:35:03 +0530 Subject: [PATCH 0044/1264] fix: hide delete company transacations button if not system manager (#21839) --- erpnext/setup/doctype/company/company.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 0fbe49eab7..875904fe6f 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -107,6 +107,9 @@ frappe.ui.form.on("Company", { erpnext.company.set_chart_of_accounts_options(frm.doc); + if (!frappe.user.has_role('System Manager')) { + frm.get_field("delete_company_transactions").hide(); + } }, make_default_tax_template: function(frm) { @@ -134,7 +137,7 @@ frappe.ui.form.on("Company", { var d = frappe.prompt({ fieldtype:"Data", fieldname: "company_name", - label: __("Please re-type company name to confirm"), + label: __("Please enter the company name to confirm"), reqd: 1, description: __("Please make sure you really want to delete all the transactions for this company. Your master data will remain as it is. This action cannot be undone.") }, From 8f756d3a74bcd357fda9d81f6edfb24fd0ebe52f Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Thu, 21 May 2020 18:42:10 +0530 Subject: [PATCH 0045/1264] fix(set_serial_no_status): auto commit on many writes (#21845) --- erpnext/patches/v12_0/set_serial_no_status.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py index 4ec84ef0f9..abba37d48e 100644 --- a/erpnext/patches/v12_0/set_serial_no_status.py +++ b/erpnext/patches/v12_0/set_serial_no_status.py @@ -5,8 +5,12 @@ from frappe.utils import getdate, nowdate def execute(): frappe.reload_doc('stock', 'doctype', 'serial_no') - for serial_no in frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date from `tabSerial No` - where (status is NULL OR status='')""", as_dict = 1): + serial_no_list = frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date from `tabSerial No` + where (status is NULL OR status='')""", as_dict = 1) + if len(serial_no_list) > 20000: + frappe.db.auto_commit_on_many_writes = True + + for serial_no in serial_no_list: if serial_no.get("delivery_document_type"): status = "Delivered" elif serial_no.get("warranty_expiry_date") and getdate(serial_no.get("warranty_expiry_date")) <= getdate(nowdate()): @@ -14,4 +18,7 @@ def execute(): else: status = "Active" - frappe.db.set_value("Serial No", serial_no.get("name"), "status", status) \ No newline at end of file + frappe.db.set_value("Serial No", serial_no.get("name"), "status", status) + + if frappe.db.auto_commit_on_many_writes: + frappe.db.auto_commit_on_many_writes = False From 1b94fe444fc6c23a29c7b1eabb61e5b1e30b5f85 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 21 May 2020 18:47:39 +0530 Subject: [PATCH 0046/1264] fix: convert goals point to flt (#21844) --- .../hr/doctype/appraisal_template/appraisal_template.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template.py b/erpnext/hr/doctype/appraisal_template/appraisal_template.py index e5d3c42e1b..d0dfad4be3 100644 --- a/erpnext/hr/doctype/appraisal_template/appraisal_template.py +++ b/erpnext/hr/doctype/appraisal_template/appraisal_template.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint +from frappe.utils import cint, flt from frappe import _ from frappe.model.document import Document @@ -11,11 +11,11 @@ from frappe.model.document import Document class AppraisalTemplate(Document): def validate(self): self.check_total_points() - - def check_total_points(self): + + def check_total_points(self): total_points = 0 for d in self.get("goals"): - total_points += int(d.per_weightage or 0) + total_points += flt(d.per_weightage) if cint(total_points) != 100: frappe.throw(_("Sum of points for all goals should be 100. It is {0}").format(total_points)) From 015c1192a7070ff6b0cfbdce5144b748f52fad4f Mon Sep 17 00:00:00 2001 From: Anupam K Date: Fri, 22 May 2020 01:10:55 +0530 Subject: [PATCH 0047/1264] adding report card in education desk --- erpnext/education/desk_page/education/education.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/education/desk_page/education/education.json b/erpnext/education/desk_page/education/education.json index fc2697f0d7..b341ec4b99 100644 --- a/erpnext/education/desk_page/education/education.json +++ b/erpnext/education/desk_page/education/education.json @@ -64,6 +64,11 @@ "hidden": 0, "label": "Assessment Reports", "links": "[\n {\n \"dependencies\": [\n \"Assessment Result\"\n ],\n \"doctype\": \"Assessment Result\",\n \"is_query_report\": true,\n \"label\": \"Course wise Assessment Report\",\n \"name\": \"Course wise Assessment Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Assessment Result\"\n ],\n \"doctype\": \"Assessment Result\",\n \"is_query_report\": true,\n \"label\": \"Final Assessment Grades\",\n \"name\": \"Final Assessment Grades\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Assessment Plan\"\n ],\n \"doctype\": \"Assessment Plan\",\n \"is_query_report\": true,\n \"label\": \"Assessment Plan Status\",\n \"name\": \"Assessment Plan Status\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Student Report Generation Tool\",\n \"name\": \"Student Report Generation Tool\",\n \"type\": \"doctype\"\n }\n]" + }, + { + "hidden": 0, + "label": "Reports", + "links": "[\n {\n \"dependencies\": [\n \"Fees\"\n ],\n \"doctype\": \"Fees\",\n \"is_query_report\": true,\n \"label\": \"Student Fee Collection\",\n \"name\": \"Student Fee Collection\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Student Attendance\"\n ],\n \"doctype\": \"Student Attendance\",\n \"is_query_report\": true,\n \"label\": \"Student Monthly Attendance Sheet\",\n \"name\": \"Student Monthly Attendance Sheet\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Student Attendance\"\n ],\n \"doctype\": \"Student Attendance\",\n \"is_query_report\": true,\n \"label\": \"Absent Student Report\",\n \"name\": \"Absent Student Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Program Enrollment\"\n ],\n \"doctype\": \"Program Enrollment\",\n \"is_query_report\": true,\n \"label\": \"Student and Guardian Contact Details\",\n \"name\": \"Student and Guardian Contact Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Student Attendance\"\n ],\n \"doctype\": \"Student Attendance\",\n \"is_query_report\": true,\n \"label\": \"Student Batch-Wise Attendance\",\n \"name\": \"Student Batch-Wise Attendance\",\n \"type\": \"report\"\n }\n]" } ], "category": "Domains", @@ -77,7 +82,7 @@ "idx": 0, "is_standard": 1, "label": "Education", - "modified": "2020-04-01 11:28:51.011309", + "modified": "2020-05-22 01:09:13.058482", "modified_by": "Administrator", "module": "Education", "name": "Education", From 1fe8956347c8757b982238a86862f3c5e7b58651 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 22 May 2020 10:48:35 +0530 Subject: [PATCH 0048/1264] fix: Added Inactive serial no status (#21848) --- erpnext/patches.txt | 2 +- erpnext/patches/v12_0/set_serial_no_status.py | 4 +++- erpnext/stock/doctype/serial_no/serial_no.json | 4 ++-- erpnext/stock/doctype/serial_no/serial_no.py | 2 ++ erpnext/stock/doctype/serial_no/serial_no_list.js | 2 ++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4fd668c80b..eacede6a92 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -684,7 +684,7 @@ execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation -erpnext.patches.v12_0.set_serial_no_status +erpnext.patches.v12_0.set_serial_no_status #2020-05-21 erpnext.patches.v12_0.update_price_list_currency_in_bom execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py index abba37d48e..3b5f5ef340 100644 --- a/erpnext/patches/v12_0/set_serial_no_status.py +++ b/erpnext/patches/v12_0/set_serial_no_status.py @@ -5,7 +5,7 @@ from frappe.utils import getdate, nowdate def execute(): frappe.reload_doc('stock', 'doctype', 'serial_no') - serial_no_list = frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date from `tabSerial No` + serial_no_list = frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date, warehouse from `tabSerial No` where (status is NULL OR status='')""", as_dict = 1) if len(serial_no_list) > 20000: frappe.db.auto_commit_on_many_writes = True @@ -15,6 +15,8 @@ def execute(): status = "Delivered" elif serial_no.get("warranty_expiry_date") and getdate(serial_no.get("warranty_expiry_date")) <= getdate(nowdate()): status = "Expired" + elif not serial_no.get("warehouse"): + status = "Inactive" else: status = "Active" diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index 731a730279..d9f8b62754 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -420,14 +420,14 @@ "fieldtype": "Select", "in_standard_filter": 1, "label": "Status", - "options": "\nActive\nDelivered\nExpired", + "options": "\nActive\nInactive\nDelivered\nExpired", "read_only": 1 } ], "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2020-04-08 13:29:58.517772", + "modified": "2020-05-21 19:29:58.517772", "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 914eea379a..f3514c7385 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -42,6 +42,8 @@ class SerialNo(StockController): self.status = "Delivered" elif self.warranty_expiry_date and getdate(self.warranty_expiry_date) <= getdate(nowdate()): self.status = "Expired" + elif not self.warehouse: + self.status = "Inactive" else: self.status = "Active" diff --git a/erpnext/stock/doctype/serial_no/serial_no_list.js b/erpnext/stock/doctype/serial_no/serial_no_list.js index 651f790583..7526d1d8a5 100644 --- a/erpnext/stock/doctype/serial_no/serial_no_list.js +++ b/erpnext/stock/doctype/serial_no/serial_no_list.js @@ -5,6 +5,8 @@ frappe.listview_settings['Serial No'] = { return [__("Delivered"), "green", "delivery_document_type,is,set"]; } else if (doc.warranty_expiry_date && frappe.datetime.get_diff(doc.warranty_expiry_date, frappe.datetime.nowdate()) <= 0) { return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set"]; + } else if (!doc.warehouse) { + return [__("Inactive"), "grey", "warehouse,is,not set"]; } else { return [__("Active"), "green", "delivery_document_type,is,not set"]; } From 131ca435e7c74fbe55dee55a8b5375688a52f5a1 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Fri, 22 May 2020 10:50:13 +0530 Subject: [PATCH 0049/1264] fix: set customer and supplier details using sql (#21846) * fix: set customer and supplier details using sql instead of slowing down the query with get_doc and save() we can just use sql to update the required values for customer and supplier Signed-off-by: Chinmay D. Pai * chore: remove extra quote Co-authored-by: Himanshu * fix: update sql query to include tabPrice List Signed-off-by: Chinmay D. Pai Co-authored-by: Himanshu --- ...er_supplier_based_on_type_of_item_price.py | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py index 60aec05466..b8efb210a0 100644 --- a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py +++ b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py @@ -1,15 +1,29 @@ from __future__ import unicode_literals import frappe + def execute(): - invalid_selling_item_price = frappe.db.sql( - """SELECT name FROM `tabItem Price` WHERE selling = 1 and buying = 0 and (supplier IS NOT NULL or supplier = '')""" - ) - invalid_buying_item_price = frappe.db.sql( - """SELECT name FROM `tabItem Price` WHERE selling = 0 and buying = 1 and (customer IS NOT NULL or customer = '')""" - ) - docs_to_modify = invalid_buying_item_price + invalid_selling_item_price - for d in docs_to_modify: - # saving the doc will auto reset invalid customer/supplier field - doc = frappe.get_doc("Item Price", d[0]) - doc.save() \ No newline at end of file + """ + set proper customer and supplier details for item price + based on selling and buying values + """ + + # update for selling + frappe.db.sql( + """UPDATE `tabItem Price` ip, `tabPrice List` pl + SET ip.`reference` = ip.`customer`, ip.`supplier` = NULL + WHERE ip.`selling` = 1 + AND ip.`buying` = 0 + AND (ip.`supplier` IS NOT NULL OR ip.`supplier` = '') + AND ip.`price_list` = pl.`name` + AND pl.`enabled` = 1""") + + # update for buying + frappe.db.sql( + """UPDATE `tabItem Price` ip, `tabPrice List` pl + SET ip.`reference` = ip.`supplier`, ip.`customer` = NULL + WHERE ip.`selling` = 0 + AND ip.`buying` = 1 + AND (ip.`customer` IS NOT NULL OR ip.`customer` = '') + AND ip.`price_list` = pl.`name` + AND pl.`enabled` = 1""") From f42408c00369468c0774b20c15ac444b789c50ce Mon Sep 17 00:00:00 2001 From: Michelle Alva <50285544+michellealva@users.noreply.github.com> Date: Fri, 22 May 2020 10:51:19 +0530 Subject: [PATCH 0050/1264] fix: expense account error message in DN (#21851) Changed error message if expense account not set for item in Delivery Note. Earlier: Expense or Difference account is mandatory for Item IT - 6 as it impacts overall stock value After fix: Expense Account not set for Item IT - 6. Please set an Expense Account for the item in the Items table --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 86de80815d..90d293088b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -226,7 +226,7 @@ class StockController(AccountsController): def check_expense_account(self, item): if not item.get("expense_account"): - frappe.throw(_("Expense or Difference account is mandatory for Item {0} as it impacts overall stock value").format(item.item_code)) + frappe.throw(_("Expense Account not set for Item {0}. Please set an Expense Account for the item in the Items table").format(item.item_code)) else: is_expense_account = frappe.db.get_value("Account", From bd24a074597970e7bee9e02ffac6b84f022444ab Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 22 May 2020 12:28:45 +0530 Subject: [PATCH 0051/1264] fix: Item tax template not getting mapped from source to traget doc (#21862) --- erpnext/public/js/controllers/transaction.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 28c2102aef..637d3b3267 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1652,8 +1652,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(!r.exc) { $.each(me.frm.doc.items || [], function(i, item) { if(item.item_code && r.message.hasOwnProperty(item.item_code)) { - item.item_tax_template = r.message[item.item_code].item_tax_template; - item.item_tax_rate = r.message[item.item_code].item_tax_rate; + if (!item.item_tax_template) { + item.item_tax_template = r.message[item.item_code].item_tax_template; + item.item_tax_rate = r.message[item.item_code].item_tax_rate; + } me.add_taxes_from_item_tax_template(item.item_tax_rate); } else { item.item_tax_template = ""; From 32635ef193472880222c782dc613a5d684e37421 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 22 May 2020 13:13:17 +0530 Subject: [PATCH 0052/1264] fix: Remove duplicate leave ledger entry (#21871) * fix: Remove duplicate leave ledger entry * fix: Remove duplicate leave ledger entry --- .../remove_duplicate_leave_ledger_entries.py | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py index 6353304d7a..24286dcebf 100644 --- a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py @@ -6,6 +6,7 @@ import frappe def execute(): """Delete duplicate leave ledger entries of type allocation created.""" + frappe.reload_doc('hr', 'doctype', 'leave_ledger_entry') if not frappe.db.a_row_exists("Leave Ledger Entry"): return @@ -14,31 +15,32 @@ def execute(): def get_duplicate_records(): """Fetch all but one duplicate records from the list of expired leave allocation.""" - return frappe.db.sql_list(""" - WITH duplicate_records AS - (SELECT - name, transaction_name, is_carry_forward, - ROW_NUMBER() over(partition by transaction_name order by creation)as row - FROM `tabLeave Ledger Entry` l - WHERE (EXISTS - (SELECT name - FROM `tabLeave Ledger Entry` - WHERE - transaction_name = l.transaction_name - AND transaction_type = 'Leave Allocation' - AND name <> l.name - AND employee = l.employee - AND docstatus = 1 - AND leave_type = l.leave_type - AND is_carry_forward=l.is_carry_forward - AND to_date = l.to_date - AND from_date = l.from_date - AND is_expired = 1 - ))) - SELECT name FROM duplicate_records WHERE row > 1 + return frappe.db.sql(""" + SELECT name, employee, transaction_name, leave_type, is_carry_forward, from_date, to_date + FROM `tabLeave Ledger Entry` + WHERE + transaction_type = 'Leave Allocation' + AND docstatus = 1 + AND is_expired = 1 + GROUP BY + employee, transaction_name, leave_type, is_carry_forward, from_date, to_date + HAVING + count(name) > 1 + ORDER BY + creation """) def delete_duplicate_ledger_entries(duplicate_records_list): """Delete duplicate leave ledger entries.""" if not duplicate_records_list: return - frappe.db.sql('''DELETE FROM `tabLeave Ledger Entry` WHERE name in %s''', ((tuple(duplicate_records_list)), )) \ No newline at end of file + for d in duplicate_records_list: + frappe.db.sql(''' + DELETE FROM `tabLeave Ledger Entry` + WHERE name != %s + AND employee = %s + AND transaction_name = %s + AND leave_type = %s + AND is_carry_forward = %s + AND from_date = %s + AND to_date = %s + ''', tuple(d)) \ No newline at end of file From febf6b260e3cdc7a7fb30a06b1e4d669b8dc78e4 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Fri, 22 May 2020 15:52:15 +0530 Subject: [PATCH 0053/1264] fix(patch): rerun remove_duplicate_leave_ledger_entries (#21878) --- erpnext/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index eacede6a92..cb82901697 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -678,7 +678,7 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.retain_permission_rules_for_video_doctype -erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries +erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22 erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) From 804c8709e35d14691afb0ac876af91b4a972e899 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 22 May 2020 20:39:50 +0530 Subject: [PATCH 0054/1264] chore: added change log (#21869) * chore: added change log * fix: change log --- erpnext/change_log/v13/v13_0_0_beta_2.md | 68 ++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0_beta_2.md diff --git a/erpnext/change_log/v13/v13_0_0_beta_2.md b/erpnext/change_log/v13/v13_0_0_beta_2.md new file mode 100644 index 0000000000..05c52c9c28 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0_beta_2.md @@ -0,0 +1,68 @@ +### Version 13.0.0 Beta 2 Release Notes + +#### Accounting +- Onboarding and Dashboard ([#21677](https://github.com/frappe/erpnext/pull/21677)) +- Immutable Ledger ([#18740](https://github.com/frappe/erpnext/pull/18740)) +- Process Deferred Accounting document ([#19658](https://github.com/frappe/erpnext/pull/19658)) +- Journal Entry Template ([#21404](https://github.com/frappe/erpnext/pull/21404)) + + +#### Buying +- Onboarding and Dashboard ([#21611](https://github.com/frappe/erpnext/pull/21611)) +- New Reports + - Requested Items To Order ([#21611](https://github.com/frappe/erpnext/pull/21611)) + - Purchase Order Analysis ([#21611](https://github.com/frappe/erpnext/pull/21611)) + - Refactored Quoted Item Comparison report ([#21273](https://github.com/frappe/erpnext/pull/21273)) + +#### Stock +- Onboarding and Dashboard ([#21727](https://github.com/frappe/erpnext/pull/21727)) +- Invoice from Purchase Receipt with duplicate items which has been partially returned ([#20724](https://github.com/frappe/erpnext/pull/20724)) +- Report Enhancements ([#21727](https://github.com/frappe/erpnext/pull/21727)) + - Item Shortage Report + - Stock Ageing + - Purchase Receipt Trends + - Delivery Note Trends + +#### Manufacturing +- Onboarding and Dashboard ([#21430](https://github.com/frappe/erpnext/pull/21430)) +- Production forecasting using exponential smoothing method ([#21724](https://github.com/frappe/erpnext/pull/21724)) +- BOM Template ([#21262](https://github.com/frappe/erpnext/pull/21262)) +- Run MRP at parent level in the production plan and make material transfer based upon materials availability ([#21545](https://github.com/frappe/erpnext/pull/21545)) +- Downtime Entry ([#21430](https://github.com/frappe/erpnext/pull/21430)) +- New Reports + - Production Planning Report ([#21763](https://github.com/frappe/erpnext/pull/21763)) + - BOM Operations Time ([#21763](https://github.com/frappe/erpnext/pull/21763)) + - Work Order Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Job card Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Downtime Analysis ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Quality Inspection ([#21430](https://github.com/frappe/erpnext/pull/21430)) + + +#### HR +- Onboarding and Dashboard ([#21705](https://github.com/frappe/erpnext/pull/21705)) +- Recurring Additional Salary and reference fields ([#20936](https://github.com/frappe/erpnext/pull/20936)) +- Payroll based on employee cost center ([#21609](https://github.com/frappe/erpnext/pull/21609)) +- Payroll based on attendance ([#21258](https://github.com/frappe/erpnext/pull/21258)) +- Monthly attendance sheet report enhancements, group by Department, Designation, Employee Grade and Branch ([#21331](https://github.com/frappe/erpnext/pull/21331)) +- Upload Attendance template now have pre-filled holiday status ([#20947](https://github.com/frappe/erpnext/pull/20947)) +- New and enhanced reports + - Employee Analytics report ([#21705](https://github.com/frappe/erpnext/pull/21705)) + - Employee Leave Balance ([#20754](https://github.com/frappe/erpnext/pull/20754)) + - Employee Leave Balance Summary ([#20754](https://github.com/frappe/erpnext/pull/20754)) + +#### Healthcare +- Onboarding and Dashboard ([#21774](https://github.com/frappe/erpnext/pull/21774)) +- Multi company support in Healthcare ([#21290](https://github.com/frappe/erpnext/pull/21290)) + +#### CRM +- Onboarding and Dashboard ([#21733](https://github.com/frappe/erpnext/pull/21733)) + +#### Selling +- Territory tree in Customer Acquisition and Loyalty report ([#21668](https://github.com/frappe/erpnext/pull/21668)) + +#### Project +- Onboarding and Dashboard ([#21587](https://github.com/frappe/erpnext/pull/21587)) +- Project Summary Report ([#21587](https://github.com/frappe/erpnext/pull/21587)) + +#### Integrations +- Woocommerce Integration ([#13217](https://github.com/frappe/erpnext/pull/13217)) \ No newline at end of file From a7f39a98799f76c02a9bbf7fac19486545ffa08c Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Sun, 24 May 2020 00:54:04 +0530 Subject: [PATCH 0055/1264] fix: set default posting_date value to None using mutable python defaults, and especially function calls, inside function definitions causes bugs that can be really hard to debug sometimes. please refrain from using such defaults. instead, using None is almost always a sane default. the values can then be manipulated inside the function instead. Signed-off-by: Chinmay D. Pai (cherry picked from commit 0d147b011e5a9685de81564170f0668070d75af4) --- erpnext/accounts/deferred_revenue.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index b57e6783ce..448011016e 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -199,10 +199,13 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): if item.get(enable_check): _book_deferred_revenue_or_expense(item) -def process_deferred_accounting(posting_date=today()): +def process_deferred_accounting(posting_date=None): ''' Converts deferred income/expense into income/expense Executed via background jobs on every month end ''' + if not posting_date: + posting_date = today() + if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')): return From 5f57f5948206a55280ec140674ce6108fbcd30d6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 24 May 2020 21:55:47 +0530 Subject: [PATCH 0056/1264] fix: Company query for number cards (cherry picked from commit 9f963a2ac730e38dc480a8ecdc523f1af168d956) --- erpnext/healthcare/dashboard_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py index 59da71a0ec..967117d22c 100644 --- a/erpnext/healthcare/dashboard_fixtures.py +++ b/erpnext/healthcare/dashboard_fixtures.py @@ -19,7 +19,7 @@ def get_company(): else: company = frappe.get_list("Company", limit=1) if company: - return company.name + return company[0].name return None def get_dashboards(): From fa62b72174910391517090fe720afe84a44c6e80 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 25 May 2020 11:00:18 +0000 Subject: [PATCH 0057/1264] refactor: open link in new tab (#21909) --- erpnext/buying/doctype/buying_settings/buying_settings.js | 2 +- erpnext/stock/doctype/stock_settings/stock_settings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js index a27950a941..01b40cd26f 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.js +++ b/erpnext/buying/doctype/buying_settings/buying_settings.js @@ -11,7 +11,7 @@ frappe.tour['Buying Settings'] = [ { fieldname: "supp_master_name", title: "Supplier Naming By", - description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a set ") + "Naming Series" + __(" choose the 'Naming Series' option."), + description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a set ") + "Naming Series" + __(" choose the 'Naming Series' option."), }, { fieldname: "buying_price_list", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js index 6f9757274d..877d0c3bbf 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.js +++ b/erpnext/stock/doctype/stock_settings/stock_settings.js @@ -36,7 +36,7 @@ frappe.tour['Stock Settings'] = [ { fieldname: "valuation_method", title: __("Valuation Method"), - description: __("Choose between FIFO and Moving Average Valuation Methods. Click ") + "here" + __(" to know more about them.") + description: __("Choose between FIFO and Moving Average Valuation Methods. Click ") + "here" + __(" to know more about them.") }, { fieldname: "show_barcode_field", From 4771362d39978a8f754714dd00df0867a521bbd7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 25 May 2020 19:18:01 +0530 Subject: [PATCH 0058/1264] fix(healthcare): patient vitals undefined (#21906) (#21918) (cherry picked from commit 4dd6b9986fbf5320a80288b871d32ded4c4e39f1) Co-authored-by: Rucha Mahabal --- erpnext/healthcare/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index f092578003..9abaa0784a 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -512,10 +512,10 @@ def get_children(doctype, parent, company, is_root=False): def get_patient_vitals(patient, from_date=None, to_date=None): if not patient: return - vitals = frappe.db.get_all('Vital Signs', { + vitals = frappe.db.get_all('Vital Signs', filters={ 'docstatus': 1, 'patient': patient - }, order_by='signs_date, signs_time') + }, order_by='signs_date, signs_time', fields=['*']) if len(vitals): return vitals From dba67dc4a5b972e4376658597149cac62418b40f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 25 May 2020 19:18:11 +0530 Subject: [PATCH 0059/1264] fix(Healthcare): unhide company field in Sample Collection, add field in Rehab DocTypes (#21907) (#21917) * fix: unhide company field in Sample Collection * fix: add and set company field in rehab doctypes (cherry picked from commit 316d136acabba37e58c5987f22570bc1ebf9b64b) Co-authored-by: Rucha Mahabal --- .../doctype/patient_assessment/patient_assessment.json | 10 +++++++++- .../doctype/sample_collection/sample_collection.json | 8 +++----- .../healthcare/doctype/therapy_plan/therapy_plan.json | 10 +++++++++- erpnext/patches.txt | 2 +- .../v13_0/set_company_field_in_healthcare_doctypes.py | 4 ++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json index 3952a8153f..15c94344e9 100644 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json @@ -11,6 +11,7 @@ "patient", "assessment_template", "column_break_4", + "company", "healthcare_practitioner", "assessment_datetime", "assessment_description", @@ -127,11 +128,18 @@ "fieldname": "assessment_description", "fieldtype": "Small Text", "label": "Assessment Description" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" } ], "is_submittable": 1, "links": [], - "modified": "2020-04-21 13:23:09.815007", + "modified": "2020-05-25 14:38:38.302399", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Assessment", diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json index c352287faf..016cfbc3ae 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json @@ -85,11 +85,9 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, + "in_standard_filter": 1, "label": "Company", - "options": "Company", - "print_hide": 1, - "report_hide": 1 + "options": "Company" }, { "fieldname": "section_break_6", @@ -167,7 +165,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-04-04 19:17:02.707203", + "modified": "2020-05-25 14:36:46.990469", "modified_by": "Administrator", "module": "Healthcare", "name": "Sample Collection", diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json index ca78b6618e..9edfeb2faa 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json @@ -10,6 +10,7 @@ "patient", "patient_name", "column_break_4", + "company", "status", "start_date", "section_break_3", @@ -98,10 +99,17 @@ "label": "Status", "options": "Not Started\nIn Progress\nCompleted\nCancelled", "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" } ], "links": [], - "modified": "2020-04-21 13:13:43.956014", + "modified": "2020-05-25 14:38:53.649315", "modified_by": "Administrator", "module": "Healthcare", "name": "Therapy Plan", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cb82901697..481cd35d49 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -688,7 +688,7 @@ erpnext.patches.v12_0.set_serial_no_status #2020-05-21 erpnext.patches.v12_0.update_price_list_currency_in_bom execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo -erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes +erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25 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) diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py index 9d0dae4553..a7d4c665a1 100644 --- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py +++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py @@ -3,8 +3,8 @@ import frappe def execute(): company = frappe.db.get_single_value('Global Defaults', 'default_company') - doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Patient Appointment', 'Patient Encounter', 'Vital Signs'] + doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection' 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment'] for entry in doctypes: if frappe.db.exists('DocType', entry): - frappe.reload_doc("Healthcare", "doctype", entry) + frappe.reload_doc('Healthcare', 'doctype', entry) frappe.db.sql("update `tab{dt}` set company = '{company}' where ifnull(company, '') = ''".format(dt=entry, company=company)) From 3f09b1fade2279c353cb274f874927f54e5468c8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Sat, 2 May 2020 22:31:30 +0530 Subject: [PATCH 0060/1264] chore: Rename change log --- erpnext/change_log/v13/{v13_0_0_beta_1.md => v13_0_0_beta-1.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename erpnext/change_log/v13/{v13_0_0_beta_1.md => v13_0_0_beta-1.md} (100%) diff --git a/erpnext/change_log/v13/v13_0_0_beta_1.md b/erpnext/change_log/v13/v13_0_0_beta-1.md similarity index 100% rename from erpnext/change_log/v13/v13_0_0_beta_1.md rename to erpnext/change_log/v13/v13_0_0_beta-1.md From 08ed9fc0643a1f7f7fe5a38c234fe91f73cec591 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Sat, 2 May 2020 19:25:49 +0200 Subject: [PATCH 0061/1264] Update v13_0_0_beta-1.md --- erpnext/change_log/v13/v13_0_0_beta-1.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/change_log/v13/v13_0_0_beta-1.md b/erpnext/change_log/v13/v13_0_0_beta-1.md index f84dc24560..5bd13dd823 100644 --- a/erpnext/change_log/v13/v13_0_0_beta-1.md +++ b/erpnext/change_log/v13/v13_0_0_beta-1.md @@ -30,6 +30,14 @@ - [Item-wise Sales Register](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) - [Territory-wise Sales](https://github.com/frappe/erpnext/pull/20428) +## Regional + +- Germany + + - [Update report DATEV Export to version 7.0](https://github.com/frappe/erpnext/pull/20582) and [allow to filter by voucher type](https://github.com/frappe/erpnext/pull/21060). + +- [Use any available Address Template](https://github.com/frappe/erpnext/pull/19862), not just your country's. + ## Other Changes - [Report Summary in Financial Statement](https://github.com/frappe/erpnext/pull/20876) - [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) From 65fa85ed89109ef58e3909b6cbde026139be6a79 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 26 May 2020 10:34:20 +0530 Subject: [PATCH 0062/1264] Rename v13_0_0_beta_2.md to v13_0_0_beta-2.md --- erpnext/change_log/v13/{v13_0_0_beta_2.md => v13_0_0_beta-2.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename erpnext/change_log/v13/{v13_0_0_beta_2.md => v13_0_0_beta-2.md} (99%) diff --git a/erpnext/change_log/v13/v13_0_0_beta_2.md b/erpnext/change_log/v13/v13_0_0_beta-2.md similarity index 99% rename from erpnext/change_log/v13/v13_0_0_beta_2.md rename to erpnext/change_log/v13/v13_0_0_beta-2.md index 05c52c9c28..fb54483d5a 100644 --- a/erpnext/change_log/v13/v13_0_0_beta_2.md +++ b/erpnext/change_log/v13/v13_0_0_beta-2.md @@ -65,4 +65,4 @@ - Project Summary Report ([#21587](https://github.com/frappe/erpnext/pull/21587)) #### Integrations -- Woocommerce Integration ([#13217](https://github.com/frappe/erpnext/pull/13217)) \ No newline at end of file +- Woocommerce Integration ([#13217](https://github.com/frappe/erpnext/pull/13217)) From ccb9ae49aaf98c95f6c0dbfeb85be8da2602ea52 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 26 May 2020 10:58:19 +0530 Subject: [PATCH 0063/1264] fix: Rename change log to fix Invalid version string --- erpnext/change_log/v13/{v13_0_0_beta-1.md => v13_0_0-beta_1.md} | 0 erpnext/change_log/v13/{v13_0_0_beta-2.md => v13_0_0-beta_2.md} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename erpnext/change_log/v13/{v13_0_0_beta-1.md => v13_0_0-beta_1.md} (100%) rename erpnext/change_log/v13/{v13_0_0_beta-2.md => v13_0_0-beta_2.md} (99%) diff --git a/erpnext/change_log/v13/v13_0_0_beta-1.md b/erpnext/change_log/v13/v13_0_0-beta_1.md similarity index 100% rename from erpnext/change_log/v13/v13_0_0_beta-1.md rename to erpnext/change_log/v13/v13_0_0-beta_1.md diff --git a/erpnext/change_log/v13/v13_0_0_beta-2.md b/erpnext/change_log/v13/v13_0_0-beta_2.md similarity index 99% rename from erpnext/change_log/v13/v13_0_0_beta-2.md rename to erpnext/change_log/v13/v13_0_0-beta_2.md index fb54483d5a..05c52c9c28 100644 --- a/erpnext/change_log/v13/v13_0_0_beta-2.md +++ b/erpnext/change_log/v13/v13_0_0-beta_2.md @@ -65,4 +65,4 @@ - Project Summary Report ([#21587](https://github.com/frappe/erpnext/pull/21587)) #### Integrations -- Woocommerce Integration ([#13217](https://github.com/frappe/erpnext/pull/13217)) +- Woocommerce Integration ([#13217](https://github.com/frappe/erpnext/pull/13217)) \ No newline at end of file From 0662a2194c3ef33a83280349026de56b3f1f071f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 26 May 2020 12:19:13 +0000 Subject: [PATCH 0064/1264] fix: title for onboarding step (#21930) --- .../introduction_to_stock_entry.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json index 447611fe47..009a44f6e4 100644 --- a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json +++ b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 18:55:41.457289", + "modified": "2020-05-26 15:55:41.457289", "modified_by": "Administrator", "name": "Introduction to Stock Entry", "owner": "Administrator", "show_full_form": 0, - "title": "Introduction to the multi-purpose stock transaction", + "title": "Introduction to Stock Entry", "validate_action": 1, "video_url": "https://www.youtube.com/watch?v=Njt107hlY3I" } \ No newline at end of file From 1edca82280aff9fb3ecad10a2206b4e709e49838 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 26 May 2020 18:01:20 +0530 Subject: [PATCH 0065/1264] fix(Healthcare): set company in healthcare service unit setup (#21929) (#21934) (cherry picked from commit c52bbd79bf02be46248c5daa100d80168c59f151) Co-authored-by: Rucha Mahabal --- erpnext/healthcare/setup.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py index 2087f49f32..06840801d3 100644 --- a/erpnext/healthcare/setup.py +++ b/erpnext/healthcare/setup.py @@ -195,10 +195,21 @@ def create_sensitivity(): def add_healthcare_service_unit_tree_root(): record = [ - { - "doctype": "Healthcare Service Unit", - "healthcare_service_unit_name": "All Healthcare Service Units", - "is_group": 1 - } + { + "doctype": "Healthcare Service Unit", + "healthcare_service_unit_name": "All Healthcare Service Units", + "is_group": 1, + "company": get_company() + } ] insert_record(record) + +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 From 8513d516e39a73d48e1bb6b9f5624d941252bec1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 26 May 2020 12:52:09 +0000 Subject: [PATCH 0066/1264] fix: status label in project summary report (#21935) --- erpnext/projects/report/project_summary/project_summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/report/project_summary/project_summary.js b/erpnext/projects/report/project_summary/project_summary.js index 15367acd7d..c12a83ee9e 100644 --- a/erpnext/projects/report/project_summary/project_summary.js +++ b/erpnext/projects/report/project_summary/project_summary.js @@ -16,7 +16,7 @@ frappe.query_reports["Project Summary"] = { "fieldname": "status", "label": __("Status"), "fieldtype": "Select", - "options": "Open\nComplete\nCancelled", + "options": "Open\nCompleted\nCancelled", "default": "Open" } ] From 41ef0b849ef92a63ab68ee2367497d1283a5b081 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 26 May 2020 20:29:08 +0530 Subject: [PATCH 0067/1264] feat: added tour to manufacturing settings --- .../manufacturing_settings.js | 28 +++++++++++++++++++ .../explore_manufacturing_settings.json | 4 +-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js index ac144e24b1..668e981d18 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js @@ -3,3 +3,31 @@ frappe.ui.form.on('Manufacturing Settings', { }); + +frappe.tour["Manufacturing Settings"] = [ + { + fieldname: "material_consumption", + title: __("Allow Multiple Material Consumption"), + description: __("If ticked, multiple materials can be used for a single Work Order. This is useful if one or more time consuming products are being manufactured.") + }, + { + fieldname: "backflush_raw_materials_based_on", + title: __("Backflush Raw Materials"), + description: __("The Stock Entry of type 'Manufacture' is known as backflush. Raw materials being consumed to manufacture finished goods is known as backflushing.

When creating Manufacture Entry, raw-material items are backflushed based on BOM of production item. If you want raw-material items to be backflushed based on Material Transfer entry made against that Work Order instead, then you can set it under this field.") + }, + { + fieldname: "default_wip_warehouse", + title: __("Work In Progress Warehouse"), + description: __("This Warehouse will be auto-updated in the Work In Progress Warehouse field of Work Orders.") + }, + { + fieldname: "default_fg_warehouse", + title: __("Finished Goods Warehouse"), + description: __("This Warehouse will be auto-updated in the Target Warehouse field of Work Order.") + }, + { + fieldname: "update_bom_costs_automatically", + title: __("Update BOM Cost Automatically"), + description: __("If ticked, the BOM cost will be automatically updated based on Valuation Rate / Price List Rate / last purchase rate of raw materials.") + } +]; \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json index 582aba40d6..7ef202ee4e 100644 --- a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json +++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json @@ -1,5 +1,5 @@ { - "action": "Update Settings", + "action": "Show Form Tour", "creation": "2020-05-19 11:55:11.378374", "docstatus": 0, "doctype": "Onboarding Step", @@ -8,7 +8,7 @@ "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-19 12:12:28.145366", + "modified": "2020-05-26 20:28:03.558199", "modified_by": "Administrator", "name": "Explore Manufacturing Settings", "owner": "Administrator", From 4e9ae53a13ad796ea8b2e7aa143cbca9c631469a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 26 May 2020 23:07:11 +0530 Subject: [PATCH 0068/1264] fix: manufacturing dashboard and work order summary chart --- erpnext/manufacturing/dashboard_fixtures.py | 42 ++++++++++--------- .../downtime_entry/downtime_entry.json | 11 ++++- .../downtime_analysis/downtime_analysis.py | 11 +++-- .../work_order_summary/work_order_summary.py | 11 ++--- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py index ef61f230ac..4a17fd07fb 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -3,7 +3,7 @@ import frappe, erpnext, json from frappe import _ -from frappe.utils import nowdate, get_date_str +from frappe.utils import nowdate, get_first_day, get_last_day, add_months from erpnext.accounts.utils import get_fiscal_year def get_data(): @@ -28,10 +28,10 @@ def get_dashboards(): { "chart": "Job Card Analysis", "width": "Full" } ], "cards": [ - { "card": "Total Work Order" }, - { "card": "Completed Work Order" }, + { "card": "Monthly Total Work Order" }, + { "card": "Monthly Completed Work Order" }, { "card": "Ongoing Job Card" }, - { "card": "Total Quality Inspection"} + { "card": "Monthly Quality Inspection"} ] }] @@ -180,38 +180,37 @@ def get_charts(): }] def get_number_cards(): - fiscal_year = get_fiscal_year(date=nowdate()) - year_start_date = get_date_str(fiscal_year[1]) - year_end_date = get_date_str(fiscal_year[2]) + start_date = add_months(nowdate(), -1) + end_date = nowdate() return [{ "doctype": "Number Card", "document_type": "Work Order", - "name": "Total Work Order", + "name": "Monthly Total Work Order", "filters_json": json.dumps([ ['Work Order', 'docstatus', '=', 1], - ['Work Order', 'creation', 'between', [year_start_date, year_end_date]] + ['Work Order', 'creation', 'between', [start_date, end_date]] ]), "function": "Count", "is_public": 1, - "label": _("Total Work Order"), + "label": _("Monthly Total Work Order"), "show_percentage_stats": 1, - "stats_time_interval": "Monthly" + "stats_time_interval": "Weekly" }, { "doctype": "Number Card", "document_type": "Work Order", - "name": "Completed Work Order", + "name": "Monthly Completed Work Order", "filters_json": json.dumps([ ['Work Order', 'status', '=', 'Completed'], ['Work Order', 'docstatus', '=', 1], - ['Work Order', 'creation', 'between', [year_start_date, year_end_date]] + ['Work Order', 'creation', 'between', [start_date, end_date]] ]), "function": "Count", "is_public": 1, - "label": _("Completed Work Order"), + "label": _("Monthly Completed Work Order"), "show_percentage_stats": 1, - "stats_time_interval": "Monthly" + "stats_time_interval": "Weekly" }, { "doctype": "Number Card", @@ -225,16 +224,19 @@ def get_number_cards(): "is_public": 1, "label": _("Ongoing Job Card"), "show_percentage_stats": 1, - "stats_time_interval": "Monthly" + "stats_time_interval": "Weekly" }, { "doctype": "Number Card", "document_type": "Quality Inspection", - "name": "Total Quality Inspection", - "filters_json": json.dumps([['Quality Inspection', 'docstatus', '=', 1]]), + "name": "Monthly Quality Inspection", + "filters_json": json.dumps([ + ['Quality Inspection', 'docstatus', '=', 1], + ['Quality Inspection', 'creation', 'between', [start_date, end_date]] + ]), "function": "Count", "is_public": 1, - "label": _("Total Quality Inspection"), + "label": _("Monthly Quality Inspection"), "show_percentage_stats": 1, - "stats_time_interval": "Monthly" + "stats_time_interval": "Weekly" }] \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json index 9acb4f0513..b301a9ec05 100644 --- a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json @@ -1,11 +1,13 @@ { "actions": [], "allow_import": 1, + "autoname": "naming_series:", "creation": "2020-04-18 04:50:46.187638", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "naming_series", "workstation", "operator", "column_break_4", @@ -78,10 +80,17 @@ "fieldname": "remarks", "fieldtype": "Text", "label": "Remarks" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "DT-", + "reqd": 1 } ], "links": [], - "modified": "2020-05-19 12:59:37.358483", + "modified": "2020-05-26 22:14:54.479831", "modified_by": "Administrator", "module": "Manufacturing", "name": "Downtime Entry", diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py index 2b2be4faa3..093309a005 100644 --- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py @@ -24,7 +24,12 @@ def get_data(filters): if filters.get("workstation"): query_filters["workstation"] = filters.get("workstation") - return frappe.get_all("Downtime Entry", fields= fields, filters=query_filters) + data = frappe.get_all("Downtime Entry", fields= fields, filters=query_filters) or [] + for d in data: + if d.downtime: + d.downtime = d.downtime / 60 + + return data def get_chart_data(data, columns): labels = sorted(list(set([d.workstation for d in data]))) @@ -44,7 +49,7 @@ def get_chart_data(data, columns): "data": { "labels": labels, "datasets": [ - {"name": "Dataset 1", "values": datasets} + {"name": "Machine Downtime", "values": datasets} ] }, "type": "bar" @@ -88,7 +93,7 @@ def get_columns(filters): "width": 160 }, { - "label": _("Downtime (In Mins)"), + "label": _("Downtime (In Hours)"), "fieldname": "downtime", "fieldtype": "Float", "width": 150 diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index bc09ed4335..fb047b230c 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -56,7 +56,7 @@ def get_chart_data(data, filters): return get_chart_based_on_qty(data, filters) def get_chart_based_on_status(data): - labels = ["Not Started", "In Process", "Stopped", "Completed"] + labels = ["Completed", "In Process", "Stopped", "Not Started"] status_wise_data = { "Not Started": 0, @@ -66,13 +66,10 @@ def get_chart_based_on_status(data): } for d in data: - if d.status == "In Process" and d.produced_qty: - status_wise_data["Completed"] += d.produced_qty + status_wise_data[d.status] += 1 - status_wise_data[d.status] += d.qty - - values = [status_wise_data["Not Started"], status_wise_data["In Process"], - status_wise_data["Stopped"], status_wise_data["Completed"]] + values = [status_wise_data["Completed"], status_wise_data["In Process"], + status_wise_data["Stopped"], status_wise_data["Not Started"]] chart = { "data": { From 56893ed37334c8e634ec0214519c23fe7f935b49 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2020 06:23:06 +0000 Subject: [PATCH 0069/1264] refactor: project summary report (#21953) * feat: added more filters * feat: show only first 30 projects in chart * fix: status completed * refactor: allow empty option in status filter --- .../report/project_summary/project_summary.js | 21 ++++++++++++++++++- .../report/project_summary/project_summary.py | 17 ++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/report/project_summary/project_summary.js b/erpnext/projects/report/project_summary/project_summary.js index c12a83ee9e..414b7b206a 100644 --- a/erpnext/projects/report/project_summary/project_summary.js +++ b/erpnext/projects/report/project_summary/project_summary.js @@ -12,12 +12,31 @@ frappe.query_reports["Project Summary"] = { "default": frappe.defaults.get_user_default("Company"), "reqd": 1 }, + { + "fieldname": "is_active", + "label": __("Is Active"), + "fieldtype": "Select", + "options": "\nYes\nNo", + "default": "Yes", + }, { "fieldname": "status", "label": __("Status"), "fieldtype": "Select", - "options": "Open\nCompleted\nCancelled", + "options": "\nOpen\nCompleted\nCancelled", "default": "Open" + }, + { + "fieldname": "project_type", + "label": __("Project Type"), + "fieldtype": "Link", + "options": "Project Type" + }, + { + "fieldname": "priority", + "label": __("Priority"), + "fieldtype": "Select", + "options": "\nLow\nMedium\nHigh" } ] }; diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index a20d7f25a3..ea7f1ab2e7 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -9,7 +9,7 @@ def execute(filters=None): columns = get_columns() data = [] - data = frappe.db.get_all("Project", filters=filters, fields=["name", 'status', "percent_complete", "expected_start_date", "expected_end_date"], order_by="expected_end_date") + data = frappe.db.get_all("Project", filters=filters, fields=["name", 'status', "percent_complete", "expected_start_date", "expected_end_date", "project_type"], order_by="expected_end_date") for project in data: project["total_tasks"] = frappe.db.count("Task", filters={"project": project.name}) @@ -30,6 +30,13 @@ def get_columns(): "options": "Project", "width": 200 }, + { + "fieldname": "project_type", + "label": _("Type"), + "fieldtype": "Link", + "options": "Project Type", + "width": 120 + }, { "fieldname": "status", "label": _("Status"), @@ -88,19 +95,19 @@ def get_chart_data(data): return { "data": { - 'labels': labels, + 'labels': labels[:30], 'datasets': [ { "name": "Overdue", - "values": overdue + "values": overdue[:30] }, { "name": "Completed", - "values": completed + "values": completed[:30] }, { "name": "Total Tasks", - "values": total + "values": total[:30] }, ] }, From 2cdd572f6b760f94c7e3ea5543188067f03ddb3c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 May 2020 12:44:02 +0530 Subject: [PATCH 0070/1264] fix: addtional salary date validation (#21952) (#21955) (cherry picked from commit ef0026c06f224a486978164325461abb81e32aac) Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> --- erpnext/hr/doctype/additional_salary/additional_salary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py index bab6fb545f..e369ba7cef 100644 --- a/erpnext/hr/doctype/additional_salary/additional_salary.py +++ b/erpnext/hr/doctype/additional_salary/additional_salary.py @@ -37,7 +37,7 @@ class AdditionalSalary(Document): frappe.throw(_("Payroll date can not be less than employee's joining date.")) elif getdate(self.from_date) < getdate(date_of_joining): frappe.throw(_("From date can not be less than employee's joining date.")) - elif getdate(self.to_date) > getdate(relieving_date): + elif relieving_date and getdate(self.to_date) > getdate(relieving_date): frappe.throw(_("To date can not be greater than employee's relieving date.")) def get_amount(self, sal_start_date, sal_end_date): From 5c9469b1ee49a65320f60b1fb809ffe7aa0554f2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 May 2020 12:52:52 +0530 Subject: [PATCH 0071/1264] fix: titles and order of Healthcare Onboarding steps (#21948) (#21956) * fix(Healthcare): title and order of onboarding steps * refactor: healthcare settings tour (cherry picked from commit a18c896a5641fb74b7cb82bf6808a0b7dcdcfb6d) Co-authored-by: Rucha Mahabal --- .../doctype/healthcare_settings/healthcare_settings.js | 10 +++++----- .../module_onboarding/healthcare/healthcare.json | 10 +++++----- .../create_healthcare_practitioner.json} | 8 ++++---- .../explore_clinical_procedure_templates.json | 2 +- .../explore_healthcare_settings.json | 2 +- .../introduction_to_healthcare_practitioner.json} | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) rename erpnext/healthcare/onboarding_step/{create_practitioner/create_practitioner.json => create_healthcare_practitioner/create_healthcare_practitioner.json} (64%) rename erpnext/healthcare/onboarding_step/{setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json => introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json} (68%) diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js index c266ba8647..cf2276fc07 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js @@ -57,19 +57,19 @@ frappe.tour['Healthcare Settings'] = [ description: __('Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.') }, { - fieldname: 'healthcare_service_items', + fieldname: 'inpatient_visit_charge_item', title: __('Healthcare Service Items'), - description: __('Set up the Healthcare Service Items for billing. Click ') + "here" + __(' to know more') + description: __('You can create a service item for Inpatient Visit Charge and set it here. Similarly, you can set up other Healthcare Service Items for billing in this section. Click ') + "here" + __(' to know more') }, { - fieldname: 'sb_in_ac', + fieldname: 'income_account', title: __('Set up default Accounts for the Healthcare Facility'), description: __('If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.') }, { - fieldname: 'out_patient_sms_alerts', + fieldname: 'send_registration_msg', title: __('Out Patient SMS alerts'), - description: __('You can set up Out Patient SMS alerts here. Click ') + "here" + __(' to know more') + description: __('If you want to send SMS alert on Patient Registration, you can enable this option. Similary, you can set up Out Patient SMS alerts for other functionalities in this section. Click ') + "here" + __(' to know more') } ]; diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json index db35149f87..3e50726060 100644 --- a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json +++ b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json @@ -10,7 +10,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare", "idx": 0, "is_complete": 0, - "modified": "2020-05-19 12:52:09.757729", + "modified": "2020-05-26 23:16:37.603361", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", @@ -19,14 +19,14 @@ { "step": "Create Patient" }, - { - "step": "Create Practitioner" - }, { "step": "Create Practitioner Schedule" }, { - "step": "Setup Schedule and Employee for Healthcare Practitioner" + "step": "Introduction to Healthcare Practitioner" + }, + { + "step": "Create Healthcare Practitioner" }, { "step": "Explore Healthcare Settings" diff --git a/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json similarity index 64% rename from erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json rename to erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json index 614b201a58..c45a347080 100644 --- a/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json +++ b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json @@ -1,6 +1,6 @@ { "action": "Create Entry", - "creation": "2020-05-19 10:39:55.728057", + "creation": "2020-05-19 10:39:55.728058", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, @@ -8,12 +8,12 @@ "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:27:39.851375", + "modified": "2020-05-26 23:16:31.965521", "modified_by": "Administrator", - "name": "Create Practitioner", + "name": "Create Healthcare Practitioner", "owner": "Administrator", "reference_document": "Healthcare Practitioner", "show_full_form": 1, - "title": "Create Practitioner", + "title": "Create Healthcare Practitioner", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json index f0c0f612e1..697b761e52 100644 --- a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json +++ b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json @@ -8,7 +8,7 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 11:46:35.085270", + "modified": "2020-05-26 23:10:24.504030", "modified_by": "Administrator", "name": "Explore Clinical Procedure Templates", "owner": "Administrator", diff --git a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json index 2bdab69faa..b2d5aef431 100644 --- a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json +++ b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json @@ -8,7 +8,7 @@ "is_mandatory": 1, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-19 12:26:48.682673", + "modified": "2020-05-26 23:10:24.507648", "modified_by": "Administrator", "name": "Explore Healthcare Settings", "owner": "Administrator", diff --git a/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json similarity index 68% rename from erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json rename to erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json index c5af177e34..fa4c9036d7 100644 --- a/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json +++ b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json @@ -9,12 +9,12 @@ "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:26:42.492734", + "modified": "2020-05-26 22:07:07.482530", "modified_by": "Administrator", - "name": "Setup Schedule and Employee for Healthcare Practitioner", + "name": "Introduction to Healthcare Practitioner", "owner": "Administrator", "reference_document": "Healthcare Practitioner", "show_full_form": 0, - "title": "Setup Schedule and Employee for Healthcare Practitioner", + "title": "Introduction to Healthcare Practitioner", "validate_action": 0 } \ No newline at end of file From 6bb1eb26ffe314661f715af1d1be1bd84118aa63 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 27 May 2020 14:47:46 +0530 Subject: [PATCH 0072/1264] fix: filters for the manufacturing reports --- .../job_card_summary/job_card_summary.js | 29 ++++++++++++++++--- .../job_card_summary/job_card_summary.py | 29 +++++++++++++++---- .../work_order_summary/work_order_summary.js | 29 ++++++++++++++++--- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js index b7e307183f..33953b1265 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js @@ -13,17 +13,38 @@ frappe.query_reports["Job Card Summary"] = { reqd: 1 }, { - label: __("From Date"), + fieldname: "fiscal_year", + label: __("Fiscal Year"), + fieldtype: "Link", + options: "Fiscal Year", + default: frappe.defaults.get_user_default("fiscal_year"), + reqd: 1, + on_change: function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + from_date: fy.year_start_date, + to_date: fy.year_end_date + }); + }); + } + }, + { + label: __("From Posting Date"), fieldname:"from_date", fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + default: frappe.defaults.get_user_default("year_start_date"), reqd: 1 }, { - label: __("To Date"), + label: __("To Posting Datetime"), fieldname:"to_date", fieldtype: "Date", - default: frappe.datetime.get_today(), + default: frappe.defaults.get_user_default("year_end_date"), reqd: 1, }, { diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index ae1e4f3046..953d8201a7 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -15,9 +15,12 @@ def execute(filters=None): return columns, data, None, chart_data def get_data(filters): - query_filters = {"docstatus": ("<", 2)} + query_filters = { + "docstatus": ("=", 1), + "posting_date": ("between", [filters.from_date, filters.to_date]) + } - fields = ["name", "status", "work_order", "production_item", "item_name", + fields = ["name", "status", "work_order", "production_item", "item_name", "posting_date", "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"] for field in ["work_order", "workstation", "operation", "company"]: @@ -30,12 +33,19 @@ def get_data(filters): if not data: return [] job_cards = [d.name for d in data] + + job_card_time_filter = { + "docstatus": 1, + "parent": ("in", job_cards), + } + job_card_time_details = {} for job_card_data in frappe.get_all("Job Card Time Log", fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"], - filters={"docstatus": ("<", 2), "parent": ("in", job_cards)}, group_by="parent"): + filters=job_card_time_filter, group_by="parent", debug=1): job_card_time_details[job_card_data.parent] = job_card_data + res = [] for d in data: if d.status == "Material Transferred": d.status = "Open" @@ -43,8 +53,9 @@ def get_data(filters): if job_card_time_details.get(d.name): d.from_time = job_card_time_details.get(d.name).from_time d.to_time = job_card_time_details.get(d.name).to_time + res.append(d) - return data + return res def get_chart_data(job_card_details, filters): labels, periodic_data = prepare_chart_data(job_card_details, filters) @@ -86,10 +97,10 @@ def prepare_chart_data(job_card_details, filters): labels.append(period) for d in job_card_details: - if getdate(d.from_time) >= from_date and getdate(d.to_time) <= end_date: + if getdate(d.posting_date) > from_date and getdate(d.posting_date) <= end_date: status = "Completed" if d.status == "Completed" else "Pending" - if periodic_data.get(status) and periodic_data.get(status).get(period): + if periodic_data.get(status).get(period): periodic_data[status][period] += 1 else: periodic_data[status][period] = 1 @@ -105,6 +116,12 @@ def get_columns(filters): "options": "Job Card", "width": 100 }, + { + "label": _("Posting Date"), + "fieldname": "posting_date", + "fieldtype": "Date", + "width": 100 + }, ] if not filters.get("status"): diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index ec9fe35d63..22928657b9 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -13,17 +13,38 @@ frappe.query_reports["Work Order Summary"] = { reqd: 1 }, { - label: __("From Date"), + fieldname: "fiscal_year", + label: __("Fiscal Year"), + fieldtype: "Link", + options: "Fiscal Year", + default: frappe.defaults.get_user_default("fiscal_year"), + reqd: 1, + on_change: function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + from_date: fy.year_start_date, + to_date: fy.year_end_date + }); + }); + } + }, + { + label: __("From Posting Date"), fieldname:"from_date", fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + default: frappe.defaults.get_user_default("year_start_date"), reqd: 1 }, { - label: __("To Date"), + label: __("To Posting Datetime"), fieldname:"to_date", fieldtype: "Date", - default: frappe.datetime.get_today(), + default: frappe.defaults.get_user_default("year_end_date"), reqd: 1, }, { From 310309e81603385823a2378219cc675976a450ed Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 27 May 2020 12:57:16 +0530 Subject: [PATCH 0073/1264] fix: label changed in production plan (cherry picked from commit 07388495f36c389830277d7a21124593f91d8cb9) --- .../manufacturing/doctype/production_plan/production_plan.js | 4 ++-- .../report/bom_operations_time/bom_operations_time.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 64c952b67b..1a64bc5e24 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -201,9 +201,9 @@ frappe.ui.form.on('Production Plan', { title: title, fields: [ { - "fieldtype": "Table MultiSelect", "label": __("Source Warehouses"), + "fieldtype": "Table MultiSelect", "label": __("Source Warehouses (Optional)"), "fieldname": "warehouses", "options": "Production Plan Material Request Warehouse", - "description": "System will pickup the materials from the selected warehouses", + "description": __("System will pickup the materials from the selected warehouses. If not specified, system will create material request for purchase."), get_query: function () { return { filters: { diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index 1279011b22..e7d92658f7 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -103,7 +103,7 @@ def get_columns(filters): "fieldtype": "Int", "width": 140 }, { - "label": _("Subassembly BOM Count"), + "label": _("Sub-assembly BOM Count"), "fieldname": "used_as_subassembly_items", "fieldtype": "Int", "width": 180 From 7634250e780fc803e04182e463b2ae85601ff0e5 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 27 May 2020 20:28:47 +0530 Subject: [PATCH 0074/1264] fix: Grammar fixes (#21971) --- erpnext/buying/dashboard_fixtures.py | 6 +++--- erpnext/buying/desk_page/buying/buying.json | 8 ++++---- erpnext/stock/desk_page/stock/stock.json | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py index 0e2f78f347..186bfb23af 100644 --- a/erpnext/buying/dashboard_fixtures.py +++ b/erpnext/buying/dashboard_fixtures.py @@ -41,7 +41,7 @@ def get_dashboards(): { "chart": "Top Suppliers", "width": "Full"} ], "cards": [ - { "card": "This Year Purchases"}, + { "card": "Annual Purchase"}, { "card": "Purchase Orders to Receive"}, { "card": "Purchase Orders to Bill"}, { "card": "Active Suppliers"} @@ -142,7 +142,7 @@ def get_charts(): def get_number_cards(): return [ { - "name": "This Year Purchases", + "name": "Annual Purchase", "aggregate_function_based_on": "base_net_total", "doctype": "Number Card", "document_type": "Purchase Order", @@ -155,7 +155,7 @@ def get_number_cards(): ]), "function": "Sum", "is_public": 1, - "label": _("This Year Purchases"), + "label": _("Annual Purchase"), "owner": "Administrator", "show_percentage_stats": 1, "stats_time_interval": "Monthly" diff --git a/erpnext/buying/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json index ee18545fd4..88f0a2b421 100644 --- a/erpnext/buying/desk_page/buying/buying.json +++ b/erpnext/buying/desk_page/buying/buying.json @@ -55,7 +55,7 @@ "idx": 0, "is_standard": 1, "label": "Buying", - "modified": "2020-05-19 19:44:36.260982", + "modified": "2020-05-27 19:15:05.067756", "modified_by": "Administrator", "module": "Buying", "name": "Buying", @@ -66,7 +66,7 @@ "shortcuts": [ { "color": "#cef6d1", - "format": "{} available", + "format": "{} Available", "label": "Item", "link_to": "Item", "stats_filter": "{\n \"disabled\": 0\n}", @@ -82,7 +82,7 @@ }, { "color": "#ffe8cd", - "format": "{} to Receive", + "format": "{} To Receive", "label": "Purchase Order", "link_to": "Purchase Order", "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Receive\", \"To Receive and Bill\"]]\n}", @@ -99,7 +99,7 @@ "type": "Report" }, { - "label": "Buying Dashboard", + "label": "Dashboard", "link_to": "Buying", "type": "Dashboard" } diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json index 4506664c1e..23401fd761 100644 --- a/erpnext/stock/desk_page/stock/stock.json +++ b/erpnext/stock/desk_page/stock/stock.json @@ -58,7 +58,7 @@ "idx": 0, "is_standard": 1, "label": "Stock", - "modified": "2020-05-19 17:36:08.185652", + "modified": "2020-05-27 19:14:51.210671", "modified_by": "Administrator", "module": "Stock", "name": "Stock", @@ -69,7 +69,7 @@ "shortcuts": [ { "color": "#cef6d1", - "format": "{} available", + "format": "{} Available", "label": "Item", "link_to": "Item", "stats_filter": "{\n \"disabled\" : 0\n}", @@ -90,7 +90,7 @@ }, { "color": "#ffe8cd", - "format": "{} to Bill", + "format": "{} To Bill", "label": "Purchase Receipt", "link_to": "Purchase Receipt", "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}", @@ -98,7 +98,7 @@ }, { "color": "#ffe8cd", - "format": "{} to Bill", + "format": "{} To Bill", "label": "Delivery Note", "link_to": "Delivery Note", "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}", @@ -115,7 +115,7 @@ "type": "Report" }, { - "label": "Stock Dashboard", + "label": "Dashboard", "link_to": "Stock", "type": "Dashboard" } From 97ca1e7e71d460aade7e55dc1a749a41b8dd9182 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 May 2020 20:31:43 +0530 Subject: [PATCH 0075/1264] onbarding steps fix (#21959) (#21975) (cherry picked from commit a77032f926fde8244854715f6506e1f081fb3b95) Co-authored-by: Anupam Kumar --- erpnext/crm/module_onboarding/crm/crm.json | 2 +- .../create_and_send_quotation.json | 6 +++--- erpnext/crm/onboarding_step/create_lead/create_lead.json | 6 +++--- .../create_opportunity/create_opportunity.json | 2 +- .../introduction_to_crm/introduction_to_crm.json | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json index 694763f7c7..9b3d91ecee 100644 --- a/erpnext/crm/module_onboarding/crm/crm.json +++ b/erpnext/crm/module_onboarding/crm/crm.json @@ -16,7 +16,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM", "idx": 0, "is_complete": 0, - "modified": "2020-05-20 12:53:47.029412", + "modified": "2020-05-27 11:33:09.941263", "modified_by": "Administrator", "module": "CRM", "name": "CRM", diff --git a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json index a6edfd7e53..9201d77cf8 100644 --- a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json +++ b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 17:30:07.887411", + "modified": "2020-05-27 11:30:28.237263", "modified_by": "Administrator", "name": "Create and Send Quotation", "owner": "Administrator", "reference_document": "Quotation", - "show_full_form": 0, + "show_full_form": 1, "title": "Create and Send Quotation", - "validate_action": 0 + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/crm/onboarding_step/create_lead/create_lead.json b/erpnext/crm/onboarding_step/create_lead/create_lead.json index 47a45d70a8..6ff0bd61f1 100644 --- a/erpnext/crm/onboarding_step/create_lead/create_lead.json +++ b/erpnext/crm/onboarding_step/create_lead/create_lead.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 17:28:36.441387", + "modified": "2020-05-27 11:30:59.493720", "modified_by": "Administrator", "name": "Create Lead", "owner": "Administrator", "reference_document": "Lead", - "show_full_form": 0, + "show_full_form": 1, "title": "Create Lead", - "validate_action": 0 + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json index 231cf17bb5..9f996d9e2b 100644 --- a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json +++ b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json @@ -15,5 +15,5 @@ "reference_document": "Opportunity", "show_full_form": 0, "title": "Create Opportunity", - "validate_action": 0 + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json index 552ade0fdd..545a756a59 100644 --- a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json +++ b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 17:28:16.448676", + "modified": "2020-05-27 11:28:07.452857", "modified_by": "Administrator", "name": "Introduction to CRM", "owner": "Administrator", "show_full_form": 0, "title": "Introduction to CRM", - "validate_action": 0, + "validate_action": 1, "video_url": "https://www.youtube.com/watch?v=o9XCSZHJfpA" } \ No newline at end of file From 4ea24687fc443d96047a063fa1934e43065d6a1c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 May 2020 20:32:11 +0530 Subject: [PATCH 0076/1264] fix: Module Onboarding for HR (#21963) (#21976) (cherry picked from commit d6d84a3c1561195810c6e1da9cdd16ad7ff62b18) Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> --- .../create_holiday_list/create_holiday_list.json | 4 ++-- .../onboarding_step/create_leave_type/create_leave_type.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json index 25cb9febd0..208e394560 100644 --- a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json +++ b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json @@ -1,6 +1,6 @@ { "action": "Create Entry", - "creation": "2020-05-14 11:47:34.700174", + "creation": "2020-05-27 11:47:34.700174", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, @@ -13,7 +13,7 @@ "name": "Create Holiday list", "owner": "Administrator", "reference_document": "Holiday List", - "show_full_form": 0, + "show_full_form": 1, "title": "Create Holiday list", "validate_action": 0 } \ No newline at end of file diff --git a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json index e8b97c2e5b..8cbfc5c81f 100644 --- a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json +++ b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json @@ -1,6 +1,6 @@ { "action": "Create Entry", - "creation": "2020-05-20 11:17:31.119312", + "creation": "2020-05-27 11:17:31.119312", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, @@ -13,7 +13,7 @@ "name": "Create Leave Type", "owner": "Administrator", "reference_document": "Leave Type", - "show_full_form": 0, + "show_full_form": 1, "title": "Create Leave Type", "validate_action": 0 } \ No newline at end of file From 402ed1000446f92a5a6169f492bcdd7316004a13 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2020 15:01:30 +0000 Subject: [PATCH 0077/1264] fix: compare start and end time to prevent negative diff (#21974) * fix: compare start and end time to prevent negative diff * feat: parse date when comparing --- erpnext/manufacturing/doctype/job_card/job_card.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e43b98aee1..d97714a23e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -102,8 +102,11 @@ class JobCard(Document): workstation_doc = frappe.get_cached_doc("Workstation", self.workstation) if (not workstation_doc.working_hours or cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))): - row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time, - row.planned_start_time) + if getdate(row.planned_end_time) < getdate(row.planned_start_time): + row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins) + row.remaining_time_in_mins = 0.0 + else: + row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time, row.planned_start_time) self.update_time_logs(row) return From 48b7a1a20f683d6cd69749e67a8eb79f0d7535e9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2020 20:36:28 +0530 Subject: [PATCH 0078/1264] fix: use get_datetime instead of getdate --- erpnext/manufacturing/doctype/job_card/job_card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index d97714a23e..c29d4ba3d5 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -102,7 +102,7 @@ class JobCard(Document): workstation_doc = frappe.get_cached_doc("Workstation", self.workstation) if (not workstation_doc.working_hours or cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))): - if getdate(row.planned_end_time) < getdate(row.planned_start_time): + if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time): row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins) row.remaining_time_in_mins = 0.0 else: From 01dda8a000f5785b19bfdcfdd2902b58980d1fc6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 May 2020 20:50:20 +0530 Subject: [PATCH 0079/1264] fix: Filtering issues in opneing invoice creation tool (#21969) (#21979) (cherry picked from commit 0030b95595e2a74b0167a5e791d62371af4bb24a) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../opening_invoice_creation_tool.js | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index 4d8da37efe..699eb08e17 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -11,21 +11,9 @@ frappe.ui.form.on('Opening Invoice Creation Tool', { }; }); - frm.set_query('cost_center', 'invoices', function(doc, cdt, cdn) { - return { - filters: { - 'company': doc.company - } - }; - }); - - frm.set_query('cost_center', function(doc) { - return { - filters: { - 'company': doc.company - } - }; - }); + if (frm.doc.company) { + frm.trigger('setup_company_filters'); + } }, refresh: function(frm) { @@ -51,19 +39,50 @@ frappe.ui.form.on('Opening Invoice Creation Tool', { }); }, - company: function(frm) { - frappe.call({ - method: 'erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool.get_temporary_opening_account', - args: { - company: frm.doc.company - }, - callback: (r) => { - if (r.message) { - frm.doc.__onload.temporary_opening_account = r.message; - frm.trigger('update_invoice_table'); + setup_company_filters: function(frm) { + frm.set_query('cost_center', 'invoices', function(doc, cdt, cdn) { + return { + filters: { + 'company': doc.company + } + }; + }); + + frm.set_query('cost_center', function(doc) { + return { + filters: { + 'company': doc.company + } + }; + }); + + frm.set_query('temporary_opening_account', 'invoices', function(doc, cdt, cdn) { + return { + filters: { + 'company': doc.company } } - }) + }); + }, + + company: function(frm) { + if (frm.doc.company) { + + frm.trigger('setup_company_filters'); + + frappe.call({ + method: 'erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool.get_temporary_opening_account', + args: { + company: frm.doc.company + }, + callback: (r) => { + if (r.message) { + frm.doc.__onload.temporary_opening_account = r.message; + frm.trigger('update_invoice_table'); + } + } + }) + } }, invoice_type: function(frm) { From 8644a7f1fc047dcfa800bbef22f0c84c6ea8e8fb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 May 2020 20:50:28 +0530 Subject: [PATCH 0080/1264] fix: Do not allow backdated stock transactions in previous fiscal year (#21967) (#21980) (cherry picked from commit 873542bc7f57f6627d6a906f133ea179785a3795) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/utilities/transaction_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index ea96503dff..024aa6f31d 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -166,7 +166,7 @@ class TransactionBase(StatusUpdater): last_transaction_time = frappe.db.sql(""" select MAX(timestamp(posting_date, posting_time)) as posting_time from `tabStock Ledger Entry` - where docstatus = 1 and fiscal_year = %s""", (fiscal_year))[0][0] + where docstatus = 1""")[0][0] cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00") From 8f1aed73fdd3d93938eef161a42c63b28aeb5152 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 27 May 2020 20:57:02 +0530 Subject: [PATCH 0081/1264] fix: Buying Module fixes (#21966) * fix: Buying Module fixes * fix: Delete Report files as well * fix: Add Purchase Order Analysis to Stock & Accounting --- .../desk_page/accounting/accounting.json | 5 +- .../__init__.py | 0 .../purchase_order_items_to_be_billed.js | 8 --- .../purchase_order_items_to_be_billed.json | 33 ------------ .../purchase_order_items_to_be_billed.py | 26 --------- .../module_onboarding/buying/buying.json | 4 +- .../requested_items_to_be_ordered/__init__.py | 0 .../requested_items_to_be_ordered.json | 31 ----------- .../requested_items_to_order.js | 14 ++++- .../requested_items_to_order.py | 54 +++++++++++++------ erpnext/patches.txt | 1 + .../v13_0/delete_old_purchase_reports.py | 15 ++++++ erpnext/stock/desk_page/stock/stock.json | 4 +- .../__init__.py | 0 .../purchase_order_items_to_be_received.json | 34 ------------ .../__init__.py | 0 ..._order_items_to_be_received_or_billed.json | 34 ------------ 17 files changed, 74 insertions(+), 189 deletions(-) delete mode 100644 erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py delete mode 100644 erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.js delete mode 100644 erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.json delete mode 100644 erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py delete mode 100644 erpnext/buying/report/requested_items_to_be_ordered/__init__.py delete mode 100644 erpnext/buying/report/requested_items_to_be_ordered/requested_items_to_be_ordered.json create mode 100644 erpnext/patches/v13_0/delete_old_purchase_reports.py delete mode 100644 erpnext/stock/report/purchase_order_items_to_be_received/__init__.py delete mode 100644 erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json delete mode 100644 erpnext/stock/report/purchase_order_items_to_be_received_or_billed/__init__.py delete mode 100644 erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 576d10c024..42fb9f4f37 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -18,7 +18,7 @@ { "hidden": 0, "label": "Accounts Payable", - "links": "[\n {\n \"description\": \"Bills raised by Suppliers.\",\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bank/Cash transactions against party or for internal transfer\",\n \"label\": \"Payment Entry\",\n \"name\": \"Payment Entry\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable\",\n \"name\": \"Accounts Payable\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable Summary\",\n \"name\": \"Accounts Payable Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Register\",\n \"name\": \"Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase Register\",\n \"name\": \"Item-wise Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Items To Be Billed\",\n \"name\": \"Purchase Order Items To Be Billed\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Received Items To Be Billed\",\n \"name\": \"Received Items To Be Billed\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"description\": \"Bills raised by Suppliers.\",\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bank/Cash transactions against party or for internal transfer\",\n \"label\": \"Payment Entry\",\n \"name\": \"Payment Entry\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable\",\n \"name\": \"Accounts Payable\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable Summary\",\n \"name\": \"Accounts Payable Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Register\",\n \"name\": \"Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase Register\",\n \"name\": \"Item-wise Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Received Items To Be Billed\",\n \"name\": \"Received Items To Be Billed\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -94,10 +94,11 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, + "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-05-18 17:27:26.882340", + "modified": "2020-05-27 20:34:50.949772", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py b/erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.js b/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.js deleted file mode 100644 index 24c9592c24..0000000000 --- a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.query_reports["Purchase Order Items To Be Billed"] = { - "filters": [ - - ] -} diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.json b/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.json deleted file mode 100644 index 3645ec02e1..0000000000 --- a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "add_total_row": 1, - "apply_user_permissions": 1, - "creation": "2013-05-28 15:54:16", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2017-02-24 20:00:24.302988", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Purchase Order Items To Be Billed", - "owner": "Administrator", - "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.base_amount as \"Amount:Currency:100\",\n\t(`tabPurchase Order Item`.billed_amt * ifnull(`tabPurchase Order`.conversion_rate, 1)) as \"Billed Amount:Currency:100\", \n\t(`tabPurchase Order Item`.base_amount - (`tabPurchase Order Item`.billed_amt * ifnull(`tabPurchase Order`.conversion_rate, 1))) as \"Amount to Bill:Currency:100\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n\t`tabPurchase Order`.company as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status != \"Closed\"\n and `tabPurchase Order Item`.amount > 0\n\tand (`tabPurchase Order Item`.billed_amt * ifnull(`tabPurchase Order`.conversion_rate, 1)) < `tabPurchase Order Item`.base_amount\norder by `tabPurchase Order`.transaction_date asc", - "ref_doctype": "Purchase Invoice", - "report_name": "Purchase Order Items To Be Billed", - "report_type": "Script Report", - "roles": [ - { - "role": "Accounts User" - }, - { - "role": "Purchase User" - }, - { - "role": "Auditor" - }, - { - "role": "Accounts Manager" - } - ] -} \ No newline at end of file diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py b/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py deleted file mode 100644 index 99d0a36813..0000000000 --- a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 _ -from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data - -def execute(filters=None): - columns = get_column() - args = get_args() - data = get_ordered_to_be_billed_data(args) - return columns, data - -def get_column(): - return [ - _("Purchase Order") + ":Link/Purchase Order:120", _("Status") + "::120", _("Date") + ":Date:100", - _("Suplier") + ":Link/Supplier:120", _("Suplier Name") + "::120", - _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", - _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100", - _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", - ] - -def get_args(): - return {'doctype': 'Purchase Order', 'party': 'supplier', - 'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'} diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json index 8c798b3473..8fe2f388b0 100644 --- a/erpnext/buying/module_onboarding/buying/buying.json +++ b/erpnext/buying/module_onboarding/buying/buying.json @@ -19,7 +19,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying", "idx": 0, "is_complete": 0, - "modified": "2020-05-19 20:03:55.776080", + "modified": "2020-05-27 17:17:52.075947", "modified_by": "Administrator", "module": "Buying", "name": "Buying", @@ -49,6 +49,6 @@ ], "subtitle": "Products, Purchases, Analysis and more.", "success_message": "The Buying Module is all set up!", - "title": "Let's Setup the Buying Module.", + "title": "Let's Set Up the Buying Module.", "user_can_dismiss": 1 } \ No newline at end of file diff --git a/erpnext/buying/report/requested_items_to_be_ordered/__init__.py b/erpnext/buying/report/requested_items_to_be_ordered/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/buying/report/requested_items_to_be_ordered/requested_items_to_be_ordered.json b/erpnext/buying/report/requested_items_to_be_ordered/requested_items_to_be_ordered.json deleted file mode 100644 index bb112698d3..0000000000 --- a/erpnext/buying/report/requested_items_to_be_ordered/requested_items_to_be_ordered.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "add_total_row": 1, - "creation": "2013-05-13 16:10:02", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2019-04-18 19:02:03.099422", - "modified_by": "Administrator", - "module": "Buying", - "name": "Requested Items To Be Ordered", - "owner": "Administrator", - "prepared_report": 0, - "query": "select \n mr.name as \"Material Request:Link/Material Request:120\",\n\tmr.transaction_date as \"Date:Date:100\",\n\tmr_item.item_code as \"Item Code:Link/Item:120\",\n\tsum(ifnull(mr_item.stock_qty, 0)) as \"Qty:Float:100\",\n\tifnull(mr_item.stock_uom, '') as \"UOM:Link/UOM:100\",\n\tsum(ifnull(mr_item.ordered_qty, 0)) as \"Ordered Qty:Float:100\", \n\t(sum(mr_item.stock_qty) - sum(ifnull(mr_item.ordered_qty, 0))) as \"Qty to Order:Float:100\",\n\tmr_item.item_name as \"Item Name::150\",\n\tmr_item.description as \"Description::200\",\n\tmr.company as \"Company:Link/Company:\"\nfrom\n\t`tabMaterial Request` mr, `tabMaterial Request Item` mr_item\nwhere\n\tmr_item.parent = mr.name\n\tand mr.material_request_type = \"Purchase\"\n\tand mr.docstatus = 1\n\tand mr.status != \"Stopped\"\ngroup by mr.name, mr_item.item_code\nhaving\n\tsum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0))\norder by mr.transaction_date asc", - "ref_doctype": "Purchase Order", - "report_name": "Requested Items To Be Ordered", - "report_type": "Query Report", - "roles": [ - { - "role": "Stock User" - }, - { - "role": "Purchase Manager" - }, - { - "role": "Purchase User" - } - ] - } \ No newline at end of file diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js index 21adb13547..9555e8252a 100644 --- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js +++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js @@ -35,7 +35,7 @@ frappe.query_reports["Requested Items to Order"] = { "fieldtype": "Link", "width": "80", "options": "Material Request", - "get_query": () =>{ + "get_query": () => { return { filters: { "docstatus": 1, @@ -45,6 +45,18 @@ frappe.query_reports["Requested Items to Order"] = { } } }, + { + "fieldname": "item_code", + "label": __("Item"), + "fieldtype": "Link", + "width": "80", + "options": "Item", + "get_query": () => { + return { + query: "erpnext.controllers.queries.item_query" + } + } + }, { "fieldname": "group_by_mr", "label": __("Group by Material Request"), 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 a021d3c1ca..cca01b104a 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 @@ -44,6 +44,9 @@ def get_conditions(filters): if filters.get("material_request"): conditions += " and mr.name = '{0}'".format(filters.get("material_request")) + if filters.get("item_code"): + conditions += " and mr_item.item_code = '{0}'".format(filters.get("item_code")) + return conditions def get_data(filters, conditions): @@ -74,25 +77,41 @@ def get_data(filters, conditions): return data +def update_qty_columns(row_to_update, data_row): + fields = ["qty", "ordered_qty", "qty_to_order"] + for field in fields: + row_to_update[field] += flt(data_row[field]) + def prepare_data(data, filters): """Prepare consolidated Report data and Chart data""" - material_request_map = {} + material_request_map, item_qty_map = {}, {} for row in data: - if not row["material_request"] in material_request_map: - # create an entry with mr as key - row_copy = copy.deepcopy(row) - material_request_map[row["material_request"]] = row_copy + # item wise map for charts + if not row["item_code"] in item_qty_map: + item_qty_map[row["item_code"]] = { + "qty" : row["qty"], + "ordered_qty" : row["ordered_qty"], + "qty_to_order" : row["qty_to_order"] + } else: - mr_row = material_request_map[row["material_request"]] - mr_row["required_date"] = min(getdate(mr_row["required_date"]), getdate(row["required_date"])) + item_entry = item_qty_map[row["item_code"]] + update_qty_columns(item_entry, row) - #sum numeric rows - fields = ["qty", "ordered_qty", "qty_to_order"] - for field in fields: - mr_row[field] = flt(mr_row[field]) + flt(row[field]) + if filters.get("group_by_mr"): + # consolidated material request map for group by filter + if not row["material_request"] in material_request_map: + # create an entry with mr as key + row_copy = copy.deepcopy(row) + material_request_map[row["material_request"]] = row_copy + else: + mr_row = material_request_map[row["material_request"]] + mr_row["required_date"] = min(getdate(mr_row["required_date"]), getdate(row["required_date"])) - chart_data = prepare_chart_data(material_request_map) + #sum numeric columns + update_qty_columns(mr_row, row) + + chart_data = prepare_chart_data(item_qty_map) if filters.get("group_by_mr"): data =[] @@ -102,12 +121,15 @@ def prepare_data(data, filters): return data, chart_data -def prepare_chart_data(data): +def prepare_chart_data(item_data): labels, qty_to_order, ordered_qty = [], [], [] - for row in data: - mr_row = data[row] - labels.append(mr_row["material_request"]) + if len(item_data) > 30: + item_data = dict(list(item_data.items())[:30]) + + for row in item_data: + mr_row = item_data[row] + labels.append(row) qty_to_order.append(mr_row["qty_to_order"]) ordered_qty.append(mr_row["ordered_qty"]) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 481cd35d49..1d2bb12d8c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -692,3 +692,4 @@ erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25 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.v13_0.delete_old_purchase_reports \ No newline at end of file diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py new file mode 100644 index 0000000000..8271d2e6dc --- /dev/null +++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py @@ -0,0 +1,15 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe + +def execute(): + reports_to_delete = ["Requested Items To Be Ordered", + "Purchase Order Items To Be Received or Billed","Purchase Order Items To Be Received", + "Purchase Order Items To Be Billed"] + + for report in reports_to_delete: + if frappe.db.exists("Report", report): + frappe.delete_doc("Report", report) \ No newline at end of file diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json index 23401fd761..9404292c04 100644 --- a/erpnext/stock/desk_page/stock/stock.json +++ b/erpnext/stock/desk_page/stock/stock.json @@ -33,7 +33,7 @@ { "hidden": 0, "label": "Key Reports", - "links": "[\n {\n \"dependencies\": [\n \"Item Price\"\n ],\n \"doctype\": \"Item Price\",\n \"is_query_report\": false,\n \"label\": \"Item-wise Price List Rate\",\n \"name\": \"Item-wise Price List Rate\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Entry\"\n ],\n \"doctype\": \"Stock Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Analytics\",\n \"name\": \"Stock Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Delivery Note Trends\",\n \"name\": \"Delivery Note Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Receipt\"\n ],\n \"doctype\": \"Purchase Receipt\",\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Ordered Items To Be Delivered\",\n \"name\": \"Ordered Items To Be Delivered\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Receipt\"\n ],\n \"doctype\": \"Purchase Receipt\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Items To Be Received\",\n \"name\": \"Purchase Order Items To Be Received\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Bin\"\n ],\n \"doctype\": \"Bin\",\n \"is_query_report\": true,\n \"label\": \"Item Shortage Report\",\n \"name\": \"Item Shortage Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Batch\"\n ],\n \"doctype\": \"Batch\",\n \"is_query_report\": true,\n \"label\": \"Batch-Wise Balance History\",\n \"name\": \"Batch-Wise Balance History\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Item Price\"\n ],\n \"doctype\": \"Item Price\",\n \"is_query_report\": false,\n \"label\": \"Item-wise Price List Rate\",\n \"name\": \"Item-wise Price List Rate\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Entry\"\n ],\n \"doctype\": \"Stock Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Analytics\",\n \"name\": \"Stock Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Delivery Note Trends\",\n \"name\": \"Delivery Note Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Receipt\"\n ],\n \"doctype\": \"Purchase Receipt\",\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Ordered Items To Be Delivered\",\n \"name\": \"Ordered Items To Be Delivered\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Bin\"\n ],\n \"doctype\": \"Bin\",\n \"is_query_report\": true,\n \"label\": \"Item Shortage Report\",\n \"name\": \"Item Shortage Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Batch\"\n ],\n \"doctype\": \"Batch\",\n \"is_query_report\": true,\n \"label\": \"Batch-Wise Balance History\",\n \"name\": \"Batch-Wise Balance History\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -58,7 +58,7 @@ "idx": 0, "is_standard": 1, "label": "Stock", - "modified": "2020-05-27 19:14:51.210671", + "modified": "2020-05-27 20:38:25.255323", "modified_by": "Administrator", "module": "Stock", "name": "Stock", diff --git a/erpnext/stock/report/purchase_order_items_to_be_received/__init__.py b/erpnext/stock/report/purchase_order_items_to_be_received/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json b/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json deleted file mode 100644 index dfaa9ed6cc..0000000000 --- a/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "add_total_row": 1, - "creation": "2013-02-22 18:01:55", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2019-04-01 22:12:05.573343", - "modified_by": "Administrator", - "module": "Stock", - "name": "Purchase Order Items To Be Received", - "owner": "Administrator", - "prepared_report": 0, - "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`status` as \"Status:Data:120\",\n\t`tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order Item`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.qty as \"Qty:Float:100\",\n\t`tabPurchase Order Item`.received_qty as \"Received Qty:Float:100\", \n\t(`tabPurchase Order Item`.qty - ifnull(`tabPurchase Order Item`.received_qty, 0)) as \"Qty to Receive:Float:100\",\n `tabPurchase Order Item`.warehouse as \"Warehouse:Link/Warehouse:150\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n `tabPurchase Order Item`.brand as \"Brand::100\",\n\t`tabPurchase Order`.`company` as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status not in (\"Stopped\", \"Closed\")\n\tand ifnull(`tabPurchase Order Item`.received_qty, 0) < ifnull(`tabPurchase Order Item`.qty, 0)\norder by `tabPurchase Order`.transaction_date asc", - "ref_doctype": "Purchase Receipt", - "report_name": "Purchase Order Items To Be Received", - "report_type": "Query Report", - "roles": [ - { - "role": "Stock Manager" - }, - { - "role": "Stock User" - }, - { - "role": "Purchase User" - }, - { - "role": "Accounts User" - } - ] -} \ No newline at end of file diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/__init__.py b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json deleted file mode 100644 index 48c0f423fd..0000000000 --- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "add_total_row": 0, - "creation": "2019-09-16 14:10:33.102865", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2019-09-21 15:19:55.710578", - "modified_by": "Administrator", - "module": "Stock", - "name": "Purchase Order Items To Be Received or Billed", - "owner": "Administrator", - "prepared_report": 0, - "query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`", - "ref_doctype": "Purchase Order", - "report_name": "Purchase Order Items To Be Received or Billed", - "report_type": "Query Report", - "roles": [ - { - "role": "Purchase Manager" - }, - { - "role": "Purchase User" - }, - { - "role": "Stock User" - }, - { - "role": "Stock Manager" - } - ] -} \ No newline at end of file From c63b68949785a076a6d4d3bd6dcea0f5cc9eecda Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 27 May 2020 20:29:10 +0530 Subject: [PATCH 0082/1264] fix: Total for ageing column 121-Above (#21972) (cherry picked from commit 152377f84d7de8b8a0d20d970660ee107d5eca26) --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a0a1b9783a..c6f852a66d 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -534,7 +534,7 @@ class ReceivablePayableReport(object): def get_ageing_data(self, entry_date, row): # [0-30, 30-60, 60-90, 90-120, 120-above] - row.range1 = row.range2 = row.range3 = row.range4 = range5 = 0.0 + row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0 if not (self.age_as_on and entry_date): return From 962e9bcd71da73f1f0c77a400402c71ef49f9f11 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 27 May 2020 21:53:29 +0530 Subject: [PATCH 0083/1264] fix: raw material warehouse in Production Planning Report (#21983) --- .../production_planning_report/production_planning_report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index b5e6c6fc85..5ac3923187 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -220,6 +220,9 @@ class ProductionPlanReport(object): if item_details: warehouses = [item_details["default_warehouse"]] + if self.filters.raw_material_warehouse: + warehouses = get_child_warehouses(self.filters.raw_material_warehouse) + d.remaining_qty = d.required_qty self.pick_materials_from_warehouses(d, data, warehouses) From 5255b714efbc78b26d8b02225a91d8ccc8b7a483 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 28 May 2020 09:55:24 +0530 Subject: [PATCH 0084/1264] refactor: showing the order's data for past period --- .../exponential_smoothing_forecasting.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 1b49df6af8..cac8067729 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -21,8 +21,6 @@ class ExponentialSmoothingForecast(object): if value.get(period.key) and not forecast_data: value[forecast_key] = flt(value.get("avg", 0)) or flt(value.get(period.key)) - # will be use to forecaset next period - forecast_data.append([value.get(period.key), value.get(forecast_key)]) elif forecast_data: previous_period_data = forecast_data[-1] value[forecast_key] = (previous_period_data[1] + @@ -31,6 +29,10 @@ class ExponentialSmoothingForecast(object): ) ) + if value.get(forecast_key): + # will be use to forecaset next period + forecast_data.append([value.get(period.key), value.get(forecast_key)]) + class ForecastingReport(ExponentialSmoothingForecast): def __init__(self, filters=None): self.filters = frappe._dict(filters or {}) @@ -78,7 +80,9 @@ class ForecastingReport(ExponentialSmoothingForecast): list_of_period_value = [value.get(p.key, 0) for p in self.period_list] if list_of_period_value: - value["avg"] = sum(list_of_period_value) / len(list_of_period_value) + total_qty = [1 for d in list_of_period_value if d] + if total_qty: + value["avg"] = flt(sum(list_of_period_value)) / flt(sum(total_qty)) def get_data_for_forecast(self): cond = "" @@ -152,15 +156,19 @@ class ForecastingReport(ExponentialSmoothingForecast): "width": 130 }] - width = 150 if self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"] else 100 + width = 180 if self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"] else 100 for period in self.period_list: if (self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"] or period.from_date >= getdate(self.filters.from_date)): - forecast_key = 'forecast_' + period.key + forecast_key = period.key + label = _(period.label) + if period.from_date >= getdate(self.filters.from_date): + forecast_key = 'forecast_' + period.key + label = _(period.label) + " " + _("(Forecast)") columns.append({ - "label": _(period.label), + "label": label, "fieldname": forecast_key, "fieldtype": self.fieldtype, "width": width, From b8ffaa7f07b9986d2970d0880fc5d743e43078c7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 28 May 2020 10:10:22 +0530 Subject: [PATCH 0085/1264] refactor: display draft job card as Open job card --- .../job_card_summary/job_card_summary.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index 953d8201a7..b1bff3500c 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -16,7 +16,7 @@ def execute(filters=None): def get_data(filters): query_filters = { - "docstatus": ("=", 1), + "docstatus": ("<", 2), "posting_date": ("between", [filters.from_date, filters.to_date]) } @@ -35,7 +35,7 @@ def get_data(filters): job_cards = [d.name for d in data] job_card_time_filter = { - "docstatus": 1, + "docstatus": ("<", 2), "parent": ("in", job_cards), } @@ -47,27 +47,28 @@ def get_data(filters): res = [] for d in data: - if d.status == "Material Transferred": + if d.status != "Completed": d.status = "Open" if job_card_time_details.get(d.name): d.from_time = job_card_time_details.get(d.name).from_time d.to_time = job_card_time_details.get(d.name).to_time - res.append(d) + + res.append(d) return res def get_chart_data(job_card_details, filters): labels, periodic_data = prepare_chart_data(job_card_details, filters) - pending, completed = [], [] + open_job_cards, completed = [], [] datasets = [] for d in labels: - pending.append(periodic_data.get("Pending").get(d)) + open_job_cards.append(periodic_data.get("Open").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": "Pending", "values": pending}) + datasets.append({"name": "Open", "values": open_job_cards}) datasets.append({"name": "Completed", "values": completed}) chart = { @@ -84,7 +85,7 @@ def prepare_chart_data(job_card_details, filters): labels = [] periodic_data = { - "Pending": {}, + "Open": {}, "Completed": {} } @@ -98,7 +99,7 @@ def prepare_chart_data(job_card_details, filters): for d in job_card_details: if getdate(d.posting_date) > from_date and getdate(d.posting_date) <= end_date: - status = "Completed" if d.status == "Completed" else "Pending" + status = "Completed" if d.status == "Completed" else "Open" if periodic_data.get(status).get(period): periodic_data[status][period] += 1 From 2f45fad47d5742234e60d4b90f846860d716fb6f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 May 2020 14:43:38 +0530 Subject: [PATCH 0086/1264] CRM dashboard fixes (#21989) (#22003) (cherry picked from commit f10771029a88f987795eb46e5081cb109b1a8d6f) Co-authored-by: Anupam Kumar --- erpnext/crm/dashboard_fixtures.py | 24 ++++++++++++++++++------ erpnext/crm/desk_page/crm/crm.json | 4 ++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/crm/dashboard_fixtures.py b/erpnext/crm/dashboard_fixtures.py index 16904b3429..0535cbbcc9 100644 --- a/erpnext/crm/dashboard_fixtures.py +++ b/erpnext/crm/dashboard_fixtures.py @@ -21,8 +21,8 @@ def get_dashboards(): { "chart": "Opportunity Trends", "width": "Full"}, { "chart": "Won Opportunities", "width": "Full" }, { "chart": "Territory Wise Opportunity Count", "width": "Half"}, - { "chart": "Territory Wise Sales", "width": "Half"}, { "chart": "Opportunities via Campaigns", "width": "Half" }, + { "chart": "Territory Wise Sales", "width": "Full"}, { "chart": "Lead Source", "width": "Half"} ], "cards": [ @@ -59,7 +59,7 @@ def get_charts(): 'is_public': 1, 'timeseries': 1, "owner": "Administrator", - "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]), + "filters_json": json.dumps([]), "type": "Bar" }, { @@ -90,7 +90,11 @@ def get_charts(): 'timeseries': 1, "owner": "Administrator", "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]), - "type": "Pie" + "type": "Pie", + "custom_options": json.dumps({ + "truncateLegends": 1, + "maxSlices": 8 + }) }, { "name": "Won Opportunities", @@ -123,7 +127,11 @@ def get_charts(): ["Opportunity", "company", "=", company, False] ]), "owner": "Administrator", - "type": "Donut" + "type": "Donut", + "custom_options": json.dumps({ + "truncateLegends": 1, + "maxSlices": 8 + }) }, { "name": "Territory Wise Sales", @@ -140,7 +148,7 @@ def get_charts(): ["Opportunity", "company", "=", company, False], ["Opportunity", "status", "=", "Converted", False] ]), - "type": "Donut" + "type": "Bar" }, { "name": "Lead Source", @@ -152,7 +160,11 @@ def get_charts(): "document_type": "Lead", 'is_public': 1, "owner": "Administrator", - "type": "Pie" + "type": "Pie", + "custom_options": json.dumps({ + "truncateLegends": 1, + "maxSlices": 8 + }) }] def get_number_cards(): diff --git a/erpnext/crm/desk_page/crm/crm.json b/erpnext/crm/desk_page/crm/crm.json index 2fc4582917..013fabef89 100644 --- a/erpnext/crm/desk_page/crm/crm.json +++ b/erpnext/crm/desk_page/crm/crm.json @@ -42,7 +42,7 @@ "idx": 0, "is_standard": 1, "label": "CRM", - "modified": "2020-05-20 12:11:36.250491", + "modified": "2020-05-28 13:29:28.253749", "modified_by": "Administrator", "module": "CRM", "name": "CRM", @@ -76,7 +76,7 @@ "type": "Report" }, { - "label": "CRM Dashboard", + "label": "Dashboard", "link_to": "CRM", "type": "Dashboard" } From 4afda7601574a93e9232d06341556dbcf0f142ab Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 15:06:38 +0530 Subject: [PATCH 0087/1264] fix: set fiscal year for Profit and Loss chart --- erpnext/accounts/dashboard_fixtures.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py index 1eed5a0f9c..421c86dba0 100644 --- a/erpnext/accounts/dashboard_fixtures.py +++ b/erpnext/accounts/dashboard_fixtures.py @@ -60,9 +60,9 @@ def get_charts(): "report_name": "Profit and Loss Statement", "filters_json": json.dumps({ "company": company.name, - "filter_based_on": "Date Range", - "period_start_date": get_date_str(fiscal_year[1]), - "period_end_date": get_date_str(fiscal_year[2]), + "filter_based_on": "Fiscal Year", + "from_fiscal_year": fiscal_year[0], + "to_fiscal_year": fiscal_year[0], "periodicity": "Monthly", "include_default_book_entries": 1 }), From fa0b73ce2594d6ceb3cf9cddefb3e328da147733 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 09:50:22 +0000 Subject: [PATCH 0088/1264] refactor: clean up desk pages (#22004) (#22007) * refactor: clean up desk pages * refactor: update label for CRM --- erpnext/buying/desk_page/buying/buying.json | 8 +- erpnext/crm/desk_page/crm/crm.json | 4 +- erpnext/crm/module_onboarding/crm/crm.json | 4 +- erpnext/hr/desk_page/hr/hr.json | 19 ++-- .../loan_management.json => loan/loan.json} | 6 +- .../manufacturing/manufacturing.json | 6 +- .../projects/desk_page/projects/projects.json | 6 +- .../selling/desk_page/selling/selling.json | 94 +++++++++++++++++++ .../support/desk_page/support/support.json | 25 ++--- 9 files changed, 141 insertions(+), 31 deletions(-) rename erpnext/loan_management/desk_page/{loan_management/loan_management.json => loan/loan.json} (96%) create mode 100644 erpnext/selling/desk_page/selling/selling.json diff --git a/erpnext/buying/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json index 88f0a2b421..c4cdce1615 100644 --- a/erpnext/buying/desk_page/buying/buying.json +++ b/erpnext/buying/desk_page/buying/buying.json @@ -36,7 +36,7 @@ "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" } ], - "cards_label": "Masters & Reports ", + "cards_label": "", "category": "Modules", "charts": [ { @@ -44,7 +44,7 @@ "label": "Purchase Order Trends" } ], - "charts_label": "Buying Dashboard", + "charts_label": "", "creation": "2020-01-28 11:50:26.195467", "developer_mode_only": 0, "disable_user_customization": 0, @@ -55,7 +55,7 @@ "idx": 0, "is_standard": 1, "label": "Buying", - "modified": "2020-05-27 19:15:05.067756", + "modified": "2020-05-28 13:32:49.960574", "modified_by": "Administrator", "module": "Buying", "name": "Buying", @@ -104,5 +104,5 @@ "type": "Dashboard" } ], - "shortcuts_label": "Quick Access" + "shortcuts_label": "" } \ No newline at end of file diff --git a/erpnext/crm/desk_page/crm/crm.json b/erpnext/crm/desk_page/crm/crm.json index 013fabef89..eb69dc06b6 100644 --- a/erpnext/crm/desk_page/crm/crm.json +++ b/erpnext/crm/desk_page/crm/crm.json @@ -42,7 +42,7 @@ "idx": 0, "is_standard": 1, "label": "CRM", - "modified": "2020-05-28 13:29:28.253749", + "modified": "2020-05-28 13:33:52.906750", "modified_by": "Administrator", "module": "CRM", "name": "CRM", @@ -52,6 +52,7 @@ "pin_to_top": 0, "shortcuts": [ { + "color": "#ffe8cd", "format": "{} Open", "label": "Lead", "link_to": "Lead", @@ -59,6 +60,7 @@ "type": "DocType" }, { + "color": "#cef6d1", "format": "{} Assigned", "label": "Opportunity", "link_to": "Opportunity", diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json index 9b3d91ecee..c43fcaef8c 100644 --- a/erpnext/crm/module_onboarding/crm/crm.json +++ b/erpnext/crm/module_onboarding/crm/crm.json @@ -16,7 +16,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM", "idx": 0, "is_complete": 0, - "modified": "2020-05-27 11:33:09.941263", + "modified": "2020-05-28 13:59:33.693420", "modified_by": "Administrator", "module": "CRM", "name": "CRM", @@ -37,6 +37,6 @@ ], "subtitle": "Lead, Opportunity, Customer and more", "success_message": "CRM Module is all setup!", - "title": "Let's Setup Your CRM", + "title": "Let's Set Up Your CRM", "user_can_dismiss": 1 } \ No newline at end of file diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json index 2d0e8857ca..89abca00aa 100644 --- a/erpnext/hr/desk_page/hr/hr.json +++ b/erpnext/hr/desk_page/hr/hr.json @@ -93,7 +93,7 @@ "idx": 0, "is_standard": 1, "label": "HR", - "modified": "2020-05-20 11:20:54.255557", + "modified": "2020-05-28 13:36:07.710600", "modified_by": "Administrator", "module": "HR", "name": "HR", @@ -103,6 +103,7 @@ "pin_to_top": 0, "shortcuts": [ { + "color": "#cef6d1", "format": "{} Active", "label": "Employee", "link_to": "Employee", @@ -110,17 +111,19 @@ "type": "DocType" }, { - "label": "Attendance", - "link_to": "Attendance", - "stats_filter": "", - "type": "DocType" - }, - { + "color": "#ffe8cd", + "format": "{} Open", "label": "Leave Application", "link_to": "Leave Application", "stats_filter": "{\"status\":\"Open\"}", "type": "DocType" }, + { + "label": "Attendance", + "link_to": "Attendance", + "stats_filter": "", + "type": "DocType" + }, { "label": "Salary Structure", "link_to": "Payroll Entry", @@ -133,7 +136,7 @@ }, { "format": "{} Open", - "label": "HR Dashboard", + "label": "Dashboard", "link_to": "Human Resource", "stats_filter": "{\n \"status\": \"Open\"\n}", "type": "Dashboard" diff --git a/erpnext/loan_management/desk_page/loan_management/loan_management.json b/erpnext/loan_management/desk_page/loan/loan.json similarity index 96% rename from erpnext/loan_management/desk_page/loan_management/loan_management.json rename to erpnext/loan_management/desk_page/loan/loan.json index d2a17630c9..d79860a352 100644 --- a/erpnext/loan_management/desk_page/loan_management/loan_management.json +++ b/erpnext/loan_management/desk_page/loan/loan.json @@ -34,10 +34,11 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, + "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Loan", - "modified": "2020-05-22 11:28:51.380509", + "modified": "2020-05-28 13:37:42.017709", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", @@ -46,8 +47,11 @@ "pin_to_top": 0, "shortcuts": [ { + "color": "#ffe8cd", + "format": "{} Open", "label": "Loan Application", "link_to": "Loan Application", + "stats_filter": "{ \"status\": \"Open\" }", "type": "DocType" }, { diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json index f2e07bfe20..763f533a94 100644 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json @@ -47,7 +47,7 @@ "idx": 0, "is_standard": 1, "label": "Manufacturing", - "modified": "2020-05-20 11:50:20.029056", + "modified": "2020-05-28 13:54:02.048419", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -58,6 +58,7 @@ "restrict_to_domain": "Manufacturing", "shortcuts": [ { + "color": "#cef6d1", "format": "{} Active", "label": "Item", "link_to": "Item", @@ -66,6 +67,7 @@ "type": "DocType" }, { + "color": "#cef6d1", "format": "{} Active", "label": "BOM", "link_to": "BOM", @@ -74,6 +76,7 @@ "type": "DocType" }, { + "color": "#ffe8cd", "format": "{} Open", "label": "Work Order", "link_to": "Work Order", @@ -82,6 +85,7 @@ "type": "DocType" }, { + "color": "#ffe8cd", "format": "{} Open", "label": "Production Plan", "link_to": "Production Plan", diff --git a/erpnext/projects/desk_page/projects/projects.json b/erpnext/projects/desk_page/projects/projects.json index fdbe13b2fb..d91fe5304a 100644 --- a/erpnext/projects/desk_page/projects/projects.json +++ b/erpnext/projects/desk_page/projects/projects.json @@ -33,7 +33,7 @@ "idx": 0, "is_standard": 1, "label": "Projects", - "modified": "2020-05-19 21:09:52.031828", + "modified": "2020-05-28 13:38:19.934937", "modified_by": "Administrator", "module": "Projects", "name": "Projects", @@ -42,7 +42,7 @@ "pin_to_top": 0, "shortcuts": [ { - "color": "#4d4da8", + "color": "#cef6d1", "format": "{} Assigned", "label": "Task", "link_to": "Task", @@ -50,7 +50,7 @@ "type": "DocType" }, { - "color": "#4d4da8", + "color": "#ffe8cd", "format": "{} Open", "label": "Project", "link_to": "Project", diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json new file mode 100644 index 0000000000..c32a7c8481 --- /dev/null +++ b/erpnext/selling/desk_page/selling/selling.json @@ -0,0 +1,94 @@ +{ + "cards": [ + { + "hidden": 0, + "label": "Items and Pricing", + "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Price List\"\n ],\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for adding shipping costs.\",\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Define coupon codes.\",\n \"label\": \"Coupon Code\",\n \"name\": \"Coupon Code\",\n \"type\": \"doctype\"\n }\n]" + }, + { + "hidden": 0, + "label": "Settings", + "links": "[\n {\n \"description\": \"Default settings for selling transactions.\",\n \"label\": \"Selling Settings\",\n \"name\": \"Selling Settings\",\n \"settings\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for selling transactions.\",\n \"label\": \"Sales Taxes and Charges Template\",\n \"name\": \"Sales Taxes and Charges Template\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Track Leads by Lead Source.\",\n \"label\": \"Lead Source\",\n \"name\": \"Lead Source\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n }\n]" + }, + { + "hidden": 0, + "label": "Other Reports", + "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Customer Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"route_options\": {\n \"party_type\": \"Customer\"\n },\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Available Stock for Packing Items\",\n \"name\": \"Available Stock for Packing Items\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Pending SO Items For Purchase Request\",\n \"name\": \"Pending SO Items For Purchase Request\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customers Without Any Sales Transactions\",\n \"name\": \"Customers Without Any Sales Transactions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n }\n]" + }, + { + "hidden": 0, + "label": "Sales", + "links": "[\n {\n \"description\": \"Customer Database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Quotes to Leads or Customers.\",\n \"label\": \"Quotation\",\n \"name\": \"Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Confirmed orders from Customers.\",\n \"label\": \"Sales Order\",\n \"name\": \"Sales Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Invoices for Costumers.\",\n \"label\": \"Sales Invoice\",\n \"name\": \"Sales Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Blanket Orders from Costumers.\",\n \"label\": \"Blanket Order\",\n \"name\": \"Blanket Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Manage Sales Partners.\",\n \"label\": \"Sales Partner\",\n \"name\": \"Sales Partner\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"type\": \"doctype\"\n }\n]" + }, + { + "hidden": 0, + "label": "Key Reports", + "links": "[\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Analytics\",\n \"name\": \"Sales Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"icon\": \"fa fa-bar-chart\",\n \"is_query_report\": true,\n \"label\": \"Customer Acquisition and Loyalty\",\n \"name\": \"Customer Acquisition and Loyalty\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Ordered Items To Be Delivered\",\n \"name\": \"Ordered Items To Be Delivered\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Person-wise Transaction Summary\",\n \"name\": \"Sales Person-wise Transaction Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales History\",\n \"name\": \"Item-wise Sales History\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quotation\"\n ],\n \"doctype\": \"Quotation\",\n \"is_query_report\": true,\n \"label\": \"Quotation Trends\",\n \"name\": \"Quotation Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Trends\",\n \"name\": \"Sales Order Trends\",\n \"type\": \"report\"\n }\n]" + } + ], + "category": "Modules", + "charts": [ + { + "chart_name": "Income", + "label": "Income" + } + ], + "creation": "2020-01-28 11:49:12.092882", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Desk Page", + "extends_another_page": 0, + "hide_custom": 1, + "idx": 0, + "is_standard": 1, + "label": "Selling", + "modified": "2020-05-28 13:46:08.314240", + "modified_by": "Administrator", + "module": "Selling", + "name": "Selling", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "#ffe8cd", + "format": "{} Draft", + "label": "Sales Invoice", + "link_to": "Sales Invoice", + "stats_filter": "{ \"status\": \"Draft\" }", + "type": "DocType" + }, + { + "color": "#ffe8cd", + "format": "{} To Deliver", + "label": "Sales Order", + "link_to": "Sales Order", + "stats_filter": "{\"Status\": \"To Deliver and Bill\"}", + "type": "DocType" + }, + { + "color": "#cef6d1", + "format": "{} Open", + "label": "Quotation", + "link_to": "Quotation", + "stats_filter": "{ \"Status\": \"Open\" }", + "type": "DocType" + }, + { + "label": "Delivery Note", + "link_to": "Delivery Note", + "type": "DocType" + }, + { + "label": "Accounts Receivable", + "link_to": "Accounts Receivable", + "type": "Report" + }, + { + "label": "Sales Register", + "link_to": "Sales Register", + "type": "Report" + } + ] +} \ No newline at end of file diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json index 596987f46a..a3fe72d051 100644 --- a/erpnext/support/desk_page/support/support.json +++ b/erpnext/support/desk_page/support/support.json @@ -2,8 +2,8 @@ "cards": [ { "hidden": 0, - "label": "Service Level Agreement", - "links": "[\n {\n \"description\": \"Service Level.\",\n \"label\": \"Service Level\",\n \"name\": \"Service Level\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]" + "label": "Issues", + "links": "[\n {\n \"description\": \"Support queries from customers.\",\n \"label\": \"Issue\",\n \"name\": \"Issue\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Issue Type.\",\n \"label\": \"Issue Type\",\n \"name\": \"Issue Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Issue Priority.\",\n \"label\": \"Issue Priority\",\n \"name\": \"Issue Priority\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -12,8 +12,8 @@ }, { "hidden": 0, - "label": "Issues", - "links": "[\n {\n \"description\": \"Support queries from customers.\",\n \"label\": \"Issue\",\n \"name\": \"Issue\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Issue Type.\",\n \"label\": \"Issue Type\",\n \"name\": \"Issue Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Issue Priority.\",\n \"label\": \"Issue Priority\",\n \"name\": \"Issue Priority\",\n \"type\": \"doctype\"\n }\n]" + "label": "Service Level Agreement", + "links": "[\n {\n \"description\": \"Service Level.\",\n \"label\": \"Service Level\",\n \"name\": \"Service Level\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -39,11 +39,11 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, - "icon": "", + "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Support", - "modified": "2020-04-01 11:28:51.120583", + "modified": "2020-05-28 13:51:23.869954", "modified_by": "Administrator", "module": "Support", "name": "Support", @@ -52,19 +52,22 @@ "pin_to_top": 0, "shortcuts": [ { + "color": "#ffc4c4", + "format": "{} Assigned", "label": "Issue", "link_to": "Issue", - "type": "DocType" - }, - { - "label": "Service Level", - "link_to": "Service Level", + "stats_filter": "{\n \"_assign\": [\"like\", '%' + frappe.session.user + '%'],\n \"status\": \"Open\"\n}", "type": "DocType" }, { "label": "Maintenance Visit", "link_to": "Maintenance Visit", "type": "DocType" + }, + { + "label": "Service Level", + "link_to": "Service Level", + "type": "DocType" } ] } \ No newline at end of file From 354baf0c5e45c970eba51ffa5d71a5d149f84912 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 May 2020 18:31:50 +0530 Subject: [PATCH 0089/1264] fix: Delete Auto Email Reports (#22010) --- erpnext/patches/v13_0/delete_old_purchase_reports.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py index 8271d2e6dc..8bdc07ee5b 100644 --- a/erpnext/patches/v13_0/delete_old_purchase_reports.py +++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py @@ -12,4 +12,12 @@ def execute(): for report in reports_to_delete: if frappe.db.exists("Report", report): - frappe.delete_doc("Report", report) \ No newline at end of file + delete_auto_email_reports(report) + + frappe.delete_doc("Report", report) + +def delete_auto_email_reports(report): + """ Check for one or multiple Auto Email Reports and delete """ + auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) + for auto_email_report in auto_email_reports: + frappe.delete_doc("Auto Email Report", auto_email_report[0]) \ No newline at end of file From a0f061abd903a05638faa3c14dab90f787a66506 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Thu, 28 May 2020 18:32:34 +0530 Subject: [PATCH 0090/1264] fix(rename_bank_reconcilliation): do not delete doc after renaming (#22014) --- erpnext/patches/v12_0/rename_bank_reconciliation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/patches/v12_0/rename_bank_reconciliation.py b/erpnext/patches/v12_0/rename_bank_reconciliation.py index eda47a95e0..2efa854fba 100644 --- a/erpnext/patches/v12_0/rename_bank_reconciliation.py +++ b/erpnext/patches/v12_0/rename_bank_reconciliation.py @@ -8,9 +8,6 @@ def execute(): if frappe.db.table_exists("Bank Reconciliation"): frappe.rename_doc('DocType', 'Bank Reconciliation', 'Bank Clearance', force=True) frappe.reload_doc('Accounts', 'doctype', 'Bank Clearance') - + frappe.rename_doc('DocType', 'Bank Reconciliation Detail', 'Bank Clearance Detail', force=True) frappe.reload_doc('Accounts', 'doctype', 'Bank Clearance Detail') - - frappe.delete_doc("DocType", "Bank Reconciliation") - frappe.delete_doc("DocType", "Bank Reconciliation Detail") From 11bd731d164bba2746506a4195fdeefca84e42e2 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Thu, 28 May 2020 18:50:13 +0530 Subject: [PATCH 0091/1264] fix(set_purchase_receipt_delivery_note_detail): commit after every 100 sql updates (#22018) --- .../set_purchase_receipt_delivery_note_detail.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py index 6f843cdabd..52c9a2d7b3 100644 --- a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py +++ b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py @@ -64,11 +64,13 @@ def execute(): return_document_map = make_return_document_map(doctype, return_document_map) + count = 0 + #iterate through original documents and its return documents for docname in return_document_map: - doc_items = frappe.get_doc(doctype, docname).get("items") + doc_items = frappe.get_cached_doc(doctype, docname).get("items") for return_doc in return_document_map[docname]: - return_doc_items = frappe.get_doc(doctype, return_doc).get("items") + return_doc_items = frappe.get_cached_doc(doctype, return_doc).get("items") #iterate through return document items and original document items for mapping for return_item in return_doc_items: @@ -80,9 +82,11 @@ def execute(): else: continue + # commit after every 100 sql updates + count += 1 + if count%100 == 0: + frappe.db.commit() + set_document_detail_in_return_document("Purchase Receipt") set_document_detail_in_return_document("Delivery Note") frappe.db.commit() - - - From 1a1447c2a12fa460ee1968f73e0c7c2913860820 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 28 May 2020 19:03:37 +0530 Subject: [PATCH 0092/1264] fix: empty filters in Healthcare Charts (cherry picked from commit a3dd75e77f6139f47dc142212550768d78197fee) --- erpnext/healthcare/dashboard_fixtures.py | 8 ++++---- erpnext/healthcare/desk_page/healthcare/healthcare.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py index 967117d22c..94668a16d9 100644 --- a/erpnext/healthcare/dashboard_fixtures.py +++ b/erpnext/healthcare/dashboard_fixtures.py @@ -71,7 +71,7 @@ def get_charts(): "chart_name": _("Department wise Patient Appointments"), "chart_type": "Custom", "source": "Department wise Patient Appointments", - "filters_json": json.dumps({}), + "filters_json": json.dumps([]), 'is_public': 1, "owner": "Administrator", "type": "Bar", @@ -159,7 +159,7 @@ def get_charts(): "document_type": "Patient Encounter Symptom", "group_by_type": "Count", "group_by_based_on": "complaint", - "filters_json": json.dumps({}), + "filters_json": json.dumps([]), 'is_public': 1, "owner": "Administrator", "type": "Percentage", @@ -173,7 +173,7 @@ def get_charts(): "document_type": "Patient Encounter Diagnosis", "group_by_type": "Count", "group_by_based_on": "diagnosis", - "filters_json": json.dumps({}), + "filters_json": json.dumps([]), 'is_public': 1, "owner": "Administrator", "type": "Percentage", @@ -229,7 +229,7 @@ def get_number_cards(): }, { "name": "Appointments to Bill", - "label": _("Appointments to Bill"), + "label": _("Appointments To Bill"), "function": "Count", "doctype": "Number Card", "document_type": "Patient Appointment", diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json index 60b53137cd..334b65563b 100644 --- a/erpnext/healthcare/desk_page/healthcare/healthcare.json +++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json @@ -64,7 +64,7 @@ "idx": 0, "is_standard": 1, "label": "Healthcare", - "modified": "2020-05-19 20:57:22.797267", + "modified": "2020-05-28 19:02:28.824995", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", @@ -109,7 +109,7 @@ "type": "Page" }, { - "label": "Healthcare Dashboard", + "label": "Dashboard", "link_to": "Healthcare", "type": "Dashboard" } From f42a37ea43c6ccbf9070ffad8d9eb30ffb6570c0 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Thu, 28 May 2020 20:41:41 +0550 Subject: [PATCH 0093/1264] bumped to version 13.0.0-beta.2 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 43c3935220..530a6e4880 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.1' +__version__ = '13.0.0-beta.2' def get_default_company(user=None): '''Get default company for user''' From 25add7c9bfe115847f0849cf4b94119a1b2b4948 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 May 2020 09:34:14 +0530 Subject: [PATCH 0094/1264] crm onboarding typos (#22008) (#22025) (cherry picked from commit 8385fed04293e16cf2017af8435ff48899707f13) Co-authored-by: Anupam Kumar --- erpnext/crm/module_onboarding/crm/crm.json | 8 ++++---- .../create_and_send_quotation.json | 2 +- erpnext/crm/onboarding_step/create_lead/create_lead.json | 2 +- .../introduction_to_crm/introduction_to_crm.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json index c43fcaef8c..44d672a7b5 100644 --- a/erpnext/crm/module_onboarding/crm/crm.json +++ b/erpnext/crm/module_onboarding/crm/crm.json @@ -16,7 +16,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM", "idx": 0, "is_complete": 0, - "modified": "2020-05-28 13:59:33.693420", + "modified": "2020-05-28 21:07:41.278784", "modified_by": "Administrator", "module": "CRM", "name": "CRM", @@ -35,8 +35,8 @@ "step": "Create and Send Quotation" } ], - "subtitle": "Lead, Opportunity, Customer and more", - "success_message": "CRM Module is all setup!", - "title": "Let's Set Up Your CRM", + "subtitle": "Lead, Opportunity, Customer and more.", + "success_message": "CRM Module is all Set Up!", + "title": "Let's Set Up Your CRM.", "user_can_dismiss": 1 } \ No newline at end of file diff --git a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json index 9201d77cf8..78f7e4de9c 100644 --- a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json +++ b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json @@ -8,7 +8,7 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-27 11:30:28.237263", + "modified": "2020-05-28 21:07:11.461172", "modified_by": "Administrator", "name": "Create and Send Quotation", "owner": "Administrator", diff --git a/erpnext/crm/onboarding_step/create_lead/create_lead.json b/erpnext/crm/onboarding_step/create_lead/create_lead.json index 6ff0bd61f1..c45e8b036c 100644 --- a/erpnext/crm/onboarding_step/create_lead/create_lead.json +++ b/erpnext/crm/onboarding_step/create_lead/create_lead.json @@ -8,7 +8,7 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-27 11:30:59.493720", + "modified": "2020-05-28 21:07:01.373403", "modified_by": "Administrator", "name": "Create Lead", "owner": "Administrator", diff --git a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json index 545a756a59..fa26921ae2 100644 --- a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json +++ b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json @@ -8,7 +8,7 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-27 11:28:07.452857", + "modified": "2020-05-14 17:28:16.448676", "modified_by": "Administrator", "name": "Introduction to CRM", "owner": "Administrator", From 9b36a9be2680041f3812a168db9b46e6ecf4725a Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Fri, 29 May 2020 12:44:09 +0530 Subject: [PATCH 0095/1264] fix: check if swift_number exists in bank account Signed-off-by: Chinmay D. Pai (cherry picked from commit d6587fa1d5dc115cf2ea09946800aba9878f9200) --- .../patches/v12_0/move_bank_account_swift_number_to_bank.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3c9758eb84..1ddbae6cd2 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 @@ -4,7 +4,7 @@ import frappe def execute(): frappe.reload_doc('accounts', 'doctype', 'bank', force=1) - if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account'): + 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 @@ -12,4 +12,4 @@ def execute(): """) frappe.reload_doc('accounts', 'doctype', 'bank_account') - frappe.reload_doc('accounts', 'doctype', 'payment_request') \ No newline at end of file + frappe.reload_doc('accounts', 'doctype', 'payment_request') From bfa93ea76fe8222d65ae2299d634b6b3a2ab46f3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 May 2020 21:31:28 +0530 Subject: [PATCH 0096/1264] fix: Migrate standard_tax_exemption_amount if field exists (#22036) (#22047) (cherry picked from commit 2186c223b9513d72b8d935410aca305d732761d9) Co-authored-by: Nabin Hait --- ..._slabs_from_payroll_period_to_income_tax_slab.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py index ec94cd01d1..5ade8ca0f4 100644 --- a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py +++ b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py @@ -14,15 +14,21 @@ def execute(): frappe.reload_doc("hr", "doctype", doctype) + standard_tax_exemption_amount_exists = frappe.db.has_column("Payroll Period", "standard_tax_exemption_amount") + + select_fields = "name, start_date, end_date" + if standard_tax_exemption_amount_exists: + select_fields = "name, start_date, end_date, standard_tax_exemption_amount" + for company in frappe.get_all("Company"): payroll_periods = frappe.db.sql(""" SELECT - name, start_date, end_date, standard_tax_exemption_amount + {0} FROM `tabPayroll Period` WHERE company=%s ORDER BY start_date DESC - """, company.name, as_dict = 1) + """.format(select_fields), company.name, as_dict = 1) for i, period in enumerate(payroll_periods): income_tax_slab = frappe.new_doc("Income Tax Slab") @@ -36,7 +42,8 @@ def execute(): income_tax_slab.effective_from = period.start_date income_tax_slab.company = company.name income_tax_slab.allow_tax_exemption = 1 - income_tax_slab.standard_tax_exemption_amount = period.standard_tax_exemption_amount + if standard_tax_exemption_amount_exists: + income_tax_slab.standard_tax_exemption_amount = period.standard_tax_exemption_amount income_tax_slab.flags.ignore_mandatory = True income_tax_slab.submit() From d9f7b4df8f951e063916cc33321e9b9bf4640c1c Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Sat, 30 May 2020 15:00:56 +0530 Subject: [PATCH 0097/1264] fix(ewb): remove checksum validation for TRANSIN (cherry picked from commit ca46bedfcb874ac5e03d44a00b996d31db336d85) --- erpnext/regional/india/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 732780a0a3..3085a310c4 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -615,8 +615,9 @@ def get_transport_details(data, doc): data.transDocDate = frappe.utils.formatdate(doc.lr_date, 'dd/mm/yyyy') if doc.gst_transporter_id: - validate_gstin_check_digit(doc.gst_transporter_id, label='GST Transporter ID') - data.transporterId = doc.gst_transporter_id + if doc.gst_transporter_id[0:2] != "88": + validate_gstin_check_digit(doc.gst_transporter_id, label='GST Transporter ID') + data.transporterId = doc.gst_transporter_id return data From 8f17438723cf78bdf20b8311561f8713ada45e0a Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Sat, 30 May 2020 23:21:47 +0530 Subject: [PATCH 0098/1264] chore: Delete redundant change-log file Not sure how this still exists even after renaming the file - https://github.com/frappe/erpnext/pull/21923 - https://github.com/frappe/erpnext/commit/ccb9ae49aaf98c95f6c0dbfeb85be8da2602ea52#diff-05325be11cd93bcdb3c8cebcb60b2c35 --- erpnext/change_log/v13/v13_0_0_beta-1.md | 52 ------------------------ 1 file changed, 52 deletions(-) delete mode 100644 erpnext/change_log/v13/v13_0_0_beta-1.md diff --git a/erpnext/change_log/v13/v13_0_0_beta-1.md b/erpnext/change_log/v13/v13_0_0_beta-1.md deleted file mode 100644 index 5bd13dd823..0000000000 --- a/erpnext/change_log/v13/v13_0_0_beta-1.md +++ /dev/null @@ -1,52 +0,0 @@ -# Version 13.0.0 Beta 1 Release Notes - -## Accounting -- [Loan Management and Accounting](https://docs.erpnext.com/docs/user/manual/en/loan-management) -- [Accounting Dimensions in Budget Variance Report](https://github.com/frappe/erpnext/pull/19973) -- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile) -- [Custom Fields in POS](https://github.com/frappe/erpnext/pull/19876) -- [HSN Code Wise Item Tax](https://github.com/frappe/erpnext/pull/19478) -- Auto State-wise Taxation for GST India - - The Accounts entered in CGST and SGST accounts in GST Settings will be automatically skipped for Interstate Transaction and the Accounts in IGST Account will be skipped in Intrastate transaction. - -## Stock -- [Fetch Items from BOM in Stock Entry](https://github.com/frappe/erpnext/pull/19498) -- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note) - -## HR -- [Work From Home in Attendance](https://github.com/frappe/erpnext/pull/20464) -- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062) - -## Healthcare -- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare) -- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type) - -## CRM -- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post) -- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order) -- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280) - -## New Reports -- [Item-wise Sales Register](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) -- [Territory-wise Sales](https://github.com/frappe/erpnext/pull/20428) - -## Regional - -- Germany - - - [Update report DATEV Export to version 7.0](https://github.com/frappe/erpnext/pull/20582) and [allow to filter by voucher type](https://github.com/frappe/erpnext/pull/21060). - -- [Use any available Address Template](https://github.com/frappe/erpnext/pull/19862), not just your country's. - -## Other Changes -- [Report Summary in Financial Statement](https://github.com/frappe/erpnext/pull/20876) -- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) -- [Allow Purchase Invoice Creation Without Purchase Receipt Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) -- [Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) -- Add / Delete Items in submitted Sales / Purchase Order -- Provision to edit Item Details from Marketplace -- Nested Set filtering for Accounting Dimension -- UX changes and better validation message in all Modules -- Scan Barcode in Purchase Receipt -- Disable Rounded Totals Checkbox for Salary Slips in HR Settings - From 1dd5efcfc439e66ed715e2ae290ea14aa9f7fd4e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 1 Jun 2020 11:30:34 +0530 Subject: [PATCH 0099/1264] fix: Method in hooks for proccesing deferred revenue --- erpnext/hooks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e6f6c8e47a..394655195a 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -318,8 +318,7 @@ scheduler_events = { "erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans" ], "monthly_long": [ - "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income", - "erpnext.accounts.deferred_revenue.convert_deferred_expense_to_expense", + "erpnext.accounts.deferred_revenue.process_deferred_accounting", "erpnext.hr.utils.allocate_earned_leaves", "erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans" ] From 2e55609a84974855daec7a067c71fe0b4bf9d289 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 1 Jun 2020 12:03:31 +0530 Subject: [PATCH 0100/1264] Revert "fix: Method in hooks for processing deferred revenue" --- erpnext/hooks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9d7cdc2a3b..ab161aa9f5 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -320,7 +320,8 @@ scheduler_events = { "erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans" ], "monthly_long": [ - "erpnext.accounts.deferred_revenue.process_deferred_accounting", + "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income", + "erpnext.accounts.deferred_revenue.convert_deferred_expense_to_expense", "erpnext.hr.utils.allocate_earned_leaves", "erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans" ] From 3064e361e11983d73cc0c7626294029f16cb882f Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Sat, 30 May 2020 23:21:47 +0530 Subject: [PATCH 0101/1264] chore: Delete redundant change-log file Not sure how this still exists even after renaming the file - https://github.com/frappe/erpnext/pull/21923 - https://github.com/frappe/erpnext/commit/ccb9ae49aaf98c95f6c0dbfeb85be8da2602ea52#diff-05325be11cd93bcdb3c8cebcb60b2c35 --- erpnext/change_log/v13/v13_0_0_beta-1.md | 52 ------------------------ 1 file changed, 52 deletions(-) delete mode 100644 erpnext/change_log/v13/v13_0_0_beta-1.md diff --git a/erpnext/change_log/v13/v13_0_0_beta-1.md b/erpnext/change_log/v13/v13_0_0_beta-1.md deleted file mode 100644 index 5bd13dd823..0000000000 --- a/erpnext/change_log/v13/v13_0_0_beta-1.md +++ /dev/null @@ -1,52 +0,0 @@ -# Version 13.0.0 Beta 1 Release Notes - -## Accounting -- [Loan Management and Accounting](https://docs.erpnext.com/docs/user/manual/en/loan-management) -- [Accounting Dimensions in Budget Variance Report](https://github.com/frappe/erpnext/pull/19973) -- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile) -- [Custom Fields in POS](https://github.com/frappe/erpnext/pull/19876) -- [HSN Code Wise Item Tax](https://github.com/frappe/erpnext/pull/19478) -- Auto State-wise Taxation for GST India - - The Accounts entered in CGST and SGST accounts in GST Settings will be automatically skipped for Interstate Transaction and the Accounts in IGST Account will be skipped in Intrastate transaction. - -## Stock -- [Fetch Items from BOM in Stock Entry](https://github.com/frappe/erpnext/pull/19498) -- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note) - -## HR -- [Work From Home in Attendance](https://github.com/frappe/erpnext/pull/20464) -- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062) - -## Healthcare -- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare) -- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type) - -## CRM -- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post) -- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order) -- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280) - -## New Reports -- [Item-wise Sales Register](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) -- [Territory-wise Sales](https://github.com/frappe/erpnext/pull/20428) - -## Regional - -- Germany - - - [Update report DATEV Export to version 7.0](https://github.com/frappe/erpnext/pull/20582) and [allow to filter by voucher type](https://github.com/frappe/erpnext/pull/21060). - -- [Use any available Address Template](https://github.com/frappe/erpnext/pull/19862), not just your country's. - -## Other Changes -- [Report Summary in Financial Statement](https://github.com/frappe/erpnext/pull/20876) -- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) -- [Allow Purchase Invoice Creation Without Purchase Receipt Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) -- [Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) -- Add / Delete Items in submitted Sales / Purchase Order -- Provision to edit Item Details from Marketplace -- Nested Set filtering for Accounting Dimension -- UX changes and better validation message in all Modules -- Scan Barcode in Purchase Receipt -- Disable Rounded Totals Checkbox for Salary Slips in HR Settings - From cdd92d756d52b2c8c3250e1dad6dc9436138b2be Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 7 Jun 2020 00:01:20 +0530 Subject: [PATCH 0102/1264] fix: Cancelled entries in tds payable monthly report --- .../accounts/report/tds_payable_monthly/tds_payable_monthly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 4ac0f65611..a9fb237a04 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -111,7 +111,7 @@ def get_gle_map(filters): # {"purchase_invoice": list of dict of all gle created for this invoice} gle_map = {} gle = frappe.db.get_all('GL Entry',\ - {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]]}, + {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0}, ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"]) for d in gle: From 3fed098f16097e6071d1403e45811852ec694243 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 7 Jun 2020 21:18:47 +0530 Subject: [PATCH 0103/1264] fix: Precision in loan amount calculation --- .../loan_management/loan_management.json | 6 +-- .../loan_interest_accrual.py | 30 +++++++------ .../doctype/loan_repayment/loan_repayment.py | 45 ++++++++++++------- .../doctype/loan_type/loan_type.json | 3 +- .../loan_security_status.py | 13 +++--- 5 files changed, 54 insertions(+), 43 deletions(-) diff --git a/erpnext/loan_management/desk_page/loan_management/loan_management.json b/erpnext/loan_management/desk_page/loan_management/loan_management.json index 691d2c1e0c..fe113d4700 100644 --- a/erpnext/loan_management/desk_page/loan_management/loan_management.json +++ b/erpnext/loan_management/desk_page/loan_management/loan_management.json @@ -23,7 +23,7 @@ { "hidden": 0, "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Loan Repayment\"\n ],\n \"doctype\": \"Loan Repayment\",\n \"incomplete_dependencies\": [\n \"Loan Repayment\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"doctype\": \"Loan Security Pledge\",\n \"incomplete_dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]" } ], "category": "Modules", @@ -36,8 +36,8 @@ "extends_another_page": 0, "idx": 0, "is_standard": 1, - "label": "Loan Management", - "modified": "2020-04-01 11:28:51.380509", + "label": "Loan", + "modified": "2020-06-07 19:42:14.947902", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Management", diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 094b9c698c..659173557b 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -176,21 +176,23 @@ def get_term_loans(date, term_loan=None, loan_type=None): return term_loans def make_loan_interest_accrual_entry(args): - loan_interest_accrual = frappe.new_doc("Loan Interest Accrual") - loan_interest_accrual.loan = args.loan - loan_interest_accrual.applicant_type = args.applicant_type - loan_interest_accrual.applicant = args.applicant - loan_interest_accrual.interest_income_account = args.interest_income_account - loan_interest_accrual.loan_account = args.loan_account - loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, 2) - loan_interest_accrual.interest_amount = flt(args.interest_amount, 2) - loan_interest_accrual.posting_date = args.posting_date or nowdate() - loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest - loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name - loan_interest_accrual.payable_principal_amount = args.payable_principal + precision = cint(frappe.db.get_default("currency_precision")) or 2 - loan_interest_accrual.save() - loan_interest_accrual.submit() + loan_interest_accrual = frappe.new_doc("Loan Interest Accrual") + loan_interest_accrual.loan = args.loan + loan_interest_accrual.applicant_type = args.applicant_type + loan_interest_accrual.applicant = args.applicant + loan_interest_accrual.interest_income_account = args.interest_income_account + loan_interest_accrual.loan_account = args.loan_account + loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision) + loan_interest_accrual.interest_amount = flt(args.interest_amount, precision) + loan_interest_accrual.posting_date = args.posting_date or nowdate() + loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest + loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name + loan_interest_accrual.payable_principal_amount = args.payable_principal + + loan_interest_accrual.save() + loan_interest_accrual.submit() def get_no_of_days_for_interest_accural(loan, posting_date): diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 87e8a15ab4..66456f9e94 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, erpnext import json from frappe import _ -from frappe.utils import flt, getdate +from frappe.utils import flt, getdate, cint from six import iteritems from frappe.model.document import Document from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime @@ -29,8 +29,11 @@ class LoanRepayment(AccountsController): def on_cancel(self): self.mark_as_unpaid() self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ['GL Entry'] def set_missing_values(self, amounts): + precision = cint(frappe.db.get_default("currency_precision")) or 2 + if not self.posting_date: self.posting_date = get_datetime() @@ -38,24 +41,26 @@ class LoanRepayment(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) if not self.interest_payable: - self.interest_payable = flt(amounts['interest_amount'], 2) + self.interest_payable = flt(amounts['interest_amount'], precision) if not self.penalty_amount: - self.penalty_amount = flt(amounts['penalty_amount'], 2) + self.penalty_amount = flt(amounts['penalty_amount'], precision) if not self.pending_principal_amount: - self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2) + self.pending_principal_amount = flt(amounts['pending_principal_amount'], precision) if not self.payable_principal_amount and self.is_term_loan: - self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2) + self.payable_principal_amount = flt(amounts['payable_principal_amount'], precision) if not self.payable_amount: - self.payable_amount = flt(amounts['payable_amount'], 2) + self.payable_amount = flt(amounts['payable_amount'], precision) if amounts.get('due_date'): self.due_date = amounts.get('due_date') def validate_amount(self): + precision = cint(frappe.db.get_default("currency_precision")) or 2 + if not self.amount_paid: frappe.throw(_("Amount paid cannot be zero")) @@ -63,11 +68,13 @@ class LoanRepayment(AccountsController): msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount) frappe.throw(msg) - if self.payment_type == "Loan Closure" and flt(self.amount_paid, 2) < flt(self.payable_amount, 2): + if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision): msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount) frappe.throw(msg) def update_paid_amount(self): + precision = cint(frappe.db.get_default("currency_precision")) or 2 + loan = frappe.get_doc("Loan", self.against_loan) for payment in self.repayment_details: @@ -75,10 +82,13 @@ class LoanRepayment(AccountsController): SET paid_principal_amount = `paid_principal_amount` + %s, paid_interest_amount = `paid_interest_amount` + %s WHERE name = %s""", - (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual)) + (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual)) - if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2): - frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") + if flt(loan.total_principal_paid + self.principal_amount_paid, precision) >= flt(loan.total_payment, precision): + if loan.is_secured_loan: + frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") + else: + frappe.db.set_value("Loan", self.against_loan, "status", "Closed") frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s WHERE name = %s """, (loan.total_amount_paid + self.amount_paid, @@ -249,6 +259,7 @@ def get_accrued_interest_entries(against_loan): # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable def get_amounts(amounts, against_loan, posting_date, payment_type): + precision = cint(frappe.db.get_default("currency_precision")) or 2 against_loan_doc = frappe.get_doc("Loan", against_loan) loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type) @@ -277,8 +288,8 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): payable_principal_amount += entry.payable_principal_amount pending_accrual_entries.setdefault(entry.name, { - 'interest_amount': flt(entry.interest_amount), - 'payable_principal_amount': flt(entry.payable_principal_amount) + 'interest_amount': flt(entry.interest_amount, precision), + 'payable_principal_amount': flt(entry.payable_principal_amount, precision) }) final_due_date = due_date @@ -291,11 +302,11 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365 total_pending_interest += (pending_days * per_day_interest) - amounts["pending_principal_amount"] = pending_principal_amount - amounts["payable_principal_amount"] = payable_principal_amount - amounts["interest_amount"] = total_pending_interest - amounts["penalty_amount"] = penalty_amount - amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount + amounts["pending_principal_amount"] = flt(pending_principal_amount, precision) + amounts["payable_principal_amount"] = flt(payable_principal_amount, precision) + amounts["interest_amount"] = flt(total_pending_interest, precision) + amounts["penalty_amount"] = flt(penalty_amount, precision) + amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision) amounts["pending_accrual_entries"] = pending_accrual_entries if final_due_date: diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index 51c5cb98a6..1dd3710cd2 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -41,6 +41,7 @@ "options": "Company:company:default_currency" }, { + "default": "0", "fieldname": "rate_of_interest", "fieldtype": "Percent", "label": "Rate of Interest (%) Yearly", @@ -143,7 +144,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-04-15 00:24:43.259963", + "modified": "2020-06-07 18:55:59.346292", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type", diff --git a/erpnext/loan_management/report/loan_security_status/loan_security_status.py b/erpnext/loan_management/report/loan_security_status/loan_security_status.py index ea6a2ee645..1951855475 100644 --- a/erpnext/loan_management/report/loan_security_status/loan_security_status.py +++ b/erpnext/loan_management/report/loan_security_status/loan_security_status.py @@ -76,7 +76,8 @@ def get_columns(filters): "fieldtype": "Link", "fieldname": "currency", "options": "Currency", - "width": 50 + "width": 50, + "hidden": 1 } ] @@ -84,17 +85,13 @@ def get_columns(filters): def get_data(filters): - loan_security_price_map = frappe._dict(frappe.get_all("Loan Security", - fields=["name", "loan_security_price"], as_list=1 - )) - data = [] conditions = get_conditions(filters) loan_security_pledges = frappe.db.sql(""" SELECT p.name, p.applicant, p.loan, p.status, p.pledge_time, - c.loan_security, c.qty + c.loan_security, c.qty, c.loan_security_price, c.amount FROM `tabLoan Security Pledge` p, `tabPledge` c WHERE @@ -115,8 +112,8 @@ def get_data(filters): row["pledge_time"] = pledge.pledge_time row["loan_security"] = pledge.loan_security row["qty"] = pledge.qty - row["loan_security_price"] = loan_security_price_map.get(pledge.loan_security) - row["loan_security_value"] = row["loan_security_price"] * pledge.qty + row["loan_security_price"] = pledge.loan_security_price + row["loan_security_value"] = pledge.amount row["currency"] = default_currency data.append(row) From df93589032f7da80d0142329d691aa79133fe6ea Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 7 Jun 2020 21:23:55 +0530 Subject: [PATCH 0104/1264] fix: Ignore GL Entry on cancel --- .../doctype/loan_disbursement/loan_disbursement.py | 1 + .../doctype/loan_interest_accrual/loan_interest_accrual.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index c9e36a84dd..d44088bee7 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -27,6 +27,7 @@ class LoanDisbursement(AccountsController): def on_cancel(self): self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ['GL Entry'] def set_missing_values(self): if not self.disbursement_date: diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 659173557b..e6ceb55185 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -31,6 +31,7 @@ class LoanInterestAccrual(AccountsController): self.update_is_accrued() self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ['GL Entry'] def update_is_accrued(self): frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0) From c1bc819f18a0ebd210edc9973a255a8d99a16226 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 9 Jun 2020 17:41:27 +0530 Subject: [PATCH 0105/1264] Revert "fix: docfield of sales_order is not fetching route options for new doc (#21123)" This reverts commit 5c54adec28c7fda2e290a1bd2bfd1ab7f3c751d0. (cherry picked from commit e5fe00cf58bf1aa0edbb07bfbc70dc66c60023f2) --- erpnext/projects/doctype/project/project.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 5862963496..3570a0f2be 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -18,7 +18,7 @@ frappe.ui.form.on("Project", { }; }, onload: function (frm) { - var so = frm.get_docfield("Project", "sales_order"); + var so = frappe.meta.get_docfield("Project", "sales_order"); so.get_route_options_for_new_doc = function (field) { if (frm.is_new()) return; return { @@ -135,4 +135,4 @@ function open_form(frm, doctype, child_doctype, parentfield) { frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); -} +} \ No newline at end of file From 9e3d6755d7e3b4c1b9a290ce13193d5d927ebbcb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 10 Jun 2020 19:57:49 +0530 Subject: [PATCH 0106/1264] fix: Party validation for inter-warehouse transaction (cherry picked from commit 7963e2b708db5c0240b228846f17d999373f59b3) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 05b85dabd4..a81fdacf47 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1450,11 +1450,17 @@ def get_inter_company_details(doc, doctype): parties = frappe.db.get_all("Supplier", fields=["name"], filters={"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company}) company = frappe.get_cached_value("Customer", doc.customer, "represents_company") + if not parties: + frappe.throw(_('No Supplier found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company))) + party = get_internal_party(parties, "Supplier", doc) else: parties = frappe.db.get_all("Customer", fields=["name"], filters={"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company}) company = frappe.get_cached_value("Supplier", doc.supplier, "represents_company") + if not parties: + frappe.throw(_('No Customer found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company))) + party = get_internal_party(parties, "Customer", doc) return { From 5a176a78b724ad99d1d27974b4ef0567352fe9be Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2020 13:01:22 +0530 Subject: [PATCH 0107/1264] feat(Attendance): Add In and Out time to attendance (#21547) (#22212) * feat(Attendance): Add In and Out time to attendance Co-authored-by: Karthikeyan S * fix:add depends in attendance IN time and OUT time Co-authored-by: Karthikeyan S (cherry picked from commit 90957b7f747b89e85037aeb713043a4e4437e96d) Co-authored-by: Vignesh S --- erpnext/hr/doctype/attendance/attendance.json | 35 ++++++++++++++++--- .../employee_checkin/employee_checkin.py | 6 ++-- erpnext/hr/doctype/shift_type/shift_type.py | 13 +++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json index 906f6f77f2..a656a7ea5f 100644 --- a/erpnext/hr/doctype/attendance/attendance.json +++ b/erpnext/hr/doctype/attendance/attendance.json @@ -19,11 +19,15 @@ "attendance_date", "company", "department", - "shift", "attendance_request", - "amended_from", + "details_section", + "shift", + "in_time", + "out_time", + "column_break_18", "late_entry", - "early_exit" + "early_exit", + "amended_from" ], "fields": [ { @@ -172,13 +176,36 @@ "fieldname": "early_exit", "fieldtype": "Check", "label": "Early Exit" + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "depends_on": "shift", + "fieldname": "in_time", + "fieldtype": "Datetime", + "label": "In Time", + "read_only": 1 + }, + { + "depends_on": "shift", + "fieldname": "out_time", + "fieldtype": "Datetime", + "label": "Out Time", + "read_only": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" } ], "icon": "fa fa-ok", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2020-04-11 11:40:14.319496", + "modified": "2020-05-29 13:51:37.177231", "modified_by": "Administrator", "module": "HR", "name": "Attendance", diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index 86705121ac..15fbd4e015 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N return doc -def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None): +def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, in_time=None, out_time=None, shift=None): """Creates an attendance and links the attendance to the Employee Checkin. Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown. @@ -100,7 +100,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki 'company': employee_doc.company, 'shift': shift, 'late_entry': late_entry, - 'early_exit': early_exit + 'early_exit': early_exit, + 'in_time': in_time, + 'out_time': out_time } attendance = frappe.get_doc(doc_dict).insert() attendance.submit() diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index d56080eecd..19735648aa 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -28,13 +28,14 @@ class ShiftType(Document): logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time") for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])): single_shift_logs = list(group) - attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs) - mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name) + attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs) + mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name) for employee in self.get_assigned_employee(self.process_attendance_after, True): self.mark_absent_for_dates_with_no_attendance(employee) def get_attendance(self, logs): - """Return attendance_status, working_hours for a set of logs belonging to a single shift. + """Return attendance_status, working_hours, late_entry, early_exit, in_time, out_time + for a set of logs belonging to a single shift. Assumtion: 1. These logs belongs to an single shift, single employee and is not in a holiday date. 2. Logs are in chronological order @@ -48,10 +49,10 @@ class ShiftType(Document): early_exit = True if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent: - return 'Absent', total_working_hours, late_entry, early_exit + return 'Absent', total_working_hours, late_entry, early_exit, in_time, out_time if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day: - return 'Half Day', total_working_hours, late_entry, early_exit - return 'Present', total_working_hours, late_entry, early_exit + return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time + return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time def mark_absent_for_dates_with_no_attendance(self, employee): """Marks Absents for the given employee on working days in this shift which have no attendance marked. From 103645835bdb91a0d9fa4a5ba4babab7bbad9b03 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 14 Jun 2020 13:27:19 +0530 Subject: [PATCH 0108/1264] fix: Handle cancelled entries in financial statements --- erpnext/accounts/report/financial_statements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 4a35a66865..5ec78ac72a 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -391,6 +391,7 @@ def set_gl_entries_by_account( where company=%(company)s {additional_conditions} and posting_date <= %(to_date)s + and is_cancelled = 0 order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec if filters and filters.get('presentation_currency'): From 6e2ca779c5f66f82486e8e52ec908b7e20283f87 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 12 Jun 2020 18:33:42 +0530 Subject: [PATCH 0109/1264] feat: Accounting entries for service item in Purchase receipt (cherry picked from commit c258ac82c58f5937a0f5fcd564ef624f5ec6227a) --- erpnext/accounts/doctype/account/account.json | 82 +++- .../purchase_invoice/purchase_invoice.py | 12 + erpnext/setup/doctype/company/company.js | 5 +- erpnext/setup/doctype/company/company.json | 373 +++++++++++++----- .../purchase_receipt/purchase_receipt.py | 27 ++ .../stock_settings/stock_settings.json | 131 ++++-- 6 files changed, 489 insertions(+), 141 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index af252e6191..d2659d429b 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -34,11 +34,15 @@ { "fieldname": "properties", "fieldtype": "Section Break", - "oldfieldtype": "Section Break" + "oldfieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break0", "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -49,7 +53,9 @@ "no_copy": 1, "oldfieldname": "account_name", "oldfieldtype": "Data", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "account_number", @@ -57,13 +63,17 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Account Number", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "is_group", "fieldtype": "Check", - "label": "Is Group" + "label": "Is Group", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", @@ -75,7 +85,9 @@ "options": "Company", "read_only": 1, "remember_last_selected_value": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "root_type", @@ -83,7 +95,9 @@ "in_standard_filter": 1, "label": "Root Type", "options": "\nAsset\nLiability\nIncome\nExpense\nEquity", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "report_type", @@ -91,24 +105,32 @@ "in_standard_filter": 1, "label": "Report Type", "options": "\nBalance Sheet\nProfit and Loss", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_group==0", "fieldname": "account_currency", "fieldtype": "Link", "label": "Currency", - "options": "Currency" + "options": "Currency", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "inter_company_account", "fieldtype": "Check", - "label": "Inter Company Account" + "label": "Inter Company Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -120,7 +142,9 @@ "oldfieldtype": "Link", "options": "Account", "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "description": "Setting Account Type helps in selecting this Account in transactions.", @@ -130,7 +154,9 @@ "label": "Account Type", "oldfieldname": "account_type", "oldfieldtype": "Select", - "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nTax\nTemporary" + "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary", + "show_days": 1, + "show_seconds": 1 }, { "description": "Rate at which this tax is applied", @@ -138,7 +164,9 @@ "fieldtype": "Float", "label": "Rate", "oldfieldname": "tax_rate", - "oldfieldtype": "Currency" + "oldfieldtype": "Currency", + "show_days": 1, + "show_seconds": 1 }, { "description": "If the account is frozen, entries are allowed to restricted users.", @@ -147,13 +175,17 @@ "label": "Frozen", "oldfieldname": "freeze_account", "oldfieldtype": "Select", - "options": "No\nYes" + "options": "No\nYes", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "balance_must_be", "fieldtype": "Select", "label": "Balance must be", - "options": "\nDebit\nCredit" + "options": "\nDebit\nCredit", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "lft", @@ -162,7 +194,9 @@ "label": "Lft", "print_hide": 1, "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rgt", @@ -171,7 +205,9 @@ "label": "Rgt", "print_hide": 1, "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "old_parent", @@ -179,27 +215,33 @@ "hidden": 1, "label": "Old Parent", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)", "fieldname": "include_in_gross", "fieldtype": "Check", - "label": "Include in gross" + "label": "Include in gross", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disable" + "label": "Disable", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-money", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 17:57:52.063233", + "modified": "2020-06-11 15:15:54.338622", "modified_by": "Administrator", "module": "Accounts", "name": "Account", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 870718c22d..1e548c00b7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -587,6 +587,18 @@ class PurchaseInvoice(BuyingController): else: amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) + auto_accounting_for_non_stock_items = cint(frappe.db.get_single_value('Stock Settings', 'enable_perpetual_inventory_for_non_stock_items')) + service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") + + if item.purchase_receipt and auto_accounting_for_non_stock_items: + # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt + expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0, + 'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail, + 'account':service_received_but_not_billed_account}, ['name']) + + if expense_booked_in_pr: + expense_account = service_received_but_not_billed_account + gl_entries.append(self.get_gl_dict({ "account": expense_account, "against": self.supplier, diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 875904fe6f..7ae5385a23 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -264,7 +264,10 @@ erpnext.company.setup_queries = function(frm) { ["expenses_included_in_valuation", {"root_type": "Expense", "account_type": "Expenses Included in Valuation"}], ["stock_received_but_not_billed", - {"root_type": "Liability", "account_type": "Stock Received But Not Billed"}] + {"root_type": "Liability", "account_type": "Stock Received But Not Billed"}], + ["service_received_but_not_billed", + {"root_type": "Liability", "account_type": "Service Received But Not Billed"}], + ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 020a93ff6a..0b91b70034 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -71,6 +71,7 @@ "stock_adjustment_account", "column_break_32", "stock_received_but_not_billed", + "service_received_but_not_billed", "expenses_included_in_valuation", "fixed_asset_depreciation_settings", "accumulated_depreciation_account", @@ -106,7 +107,9 @@ { "fieldname": "details", "fieldtype": "Section Break", - "oldfieldtype": "Section Break" + "oldfieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company_name", @@ -115,6 +118,8 @@ "oldfieldname": "company_name", "oldfieldtype": "Data", "reqd": 1, + "show_days": 1, + "show_seconds": 1, "unique": 1 }, { @@ -123,36 +128,48 @@ "label": "Abbr", "oldfieldname": "abbr", "oldfieldtype": "Data", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")", "fieldname": "change_abbr", "fieldtype": "Button", - "label": "Change Abbreviation" + "label": "Change Abbreviation", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, "default": "0", "fieldname": "is_group", "fieldtype": "Check", - "label": "Is Group" + "label": "Is Group", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_finance_book", "fieldtype": "Link", "label": "Default Finance Book", - "options": "Finance Book" + "options": "Finance Book", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cb0", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "domain", "fieldtype": "Link", "label": "Domain", - "options": "Domain" + "options": "Domain", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "parent_company", @@ -160,24 +177,32 @@ "ignore_user_permissions": 1, "in_list_view": 1, "label": "Parent Company", - "options": "Company" + "options": "Company", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company_logo", "fieldtype": "Attach Image", "hidden": 1, - "label": "Company Logo" + "label": "Company Logo", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company_description", "fieldtype": "Text Editor", - "label": "Company Description" + "label": "Company Description", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "sales_settings", "fieldtype": "Section Break", - "label": "Sales Settings" + "label": "Sales Settings", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sales_monthly_history", @@ -185,7 +210,9 @@ "hidden": 1, "label": "Sales Monthly History", "no_copy": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "transactions_annual_history", @@ -193,17 +220,23 @@ "hidden": 1, "label": "Transactions Annual History", "no_copy": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "monthly_sales_target", "fieldtype": "Currency", "label": "Monthly Sales Target", - "options": "default_currency" + "options": "default_currency", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_goals", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_monthly_sales", @@ -211,12 +244,16 @@ "label": "Total Monthly Sales", "no_copy": 1, "options": "default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "charts_section", "fieldtype": "Section Break", - "label": "Default Values" + "label": "Default Values", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_currency", @@ -224,34 +261,46 @@ "ignore_user_permissions": 1, "label": "Default Currency", "options": "Currency", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_letter_head", "fieldtype": "Link", "label": "Default Letter Head", - "options": "Letter Head" + "options": "Letter Head", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_holiday_list", "fieldtype": "Link", "label": "Default Holiday List", - "options": "Holiday List" + "options": "Holiday List", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "standard_working_hours", "fieldtype": "Float", - "label": "Standard Working Hours" + "label": "Standard Working Hours", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_warehouse_for_sales_return", "fieldtype": "Link", "label": "Default warehouse for Sales Return", - "options": "Warehouse" + "options": "Warehouse", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_10", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "country", @@ -259,20 +308,26 @@ "in_list_view": 1, "label": "Country", "options": "Country", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "create_chart_of_accounts_based_on", "fieldtype": "Select", "label": "Create Chart Of Accounts Based On", - "options": "\nStandard Template\nExisting Company" + "options": "\nStandard Template\nExisting Company", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", "fieldname": "chart_of_accounts", "fieldtype": "Select", "label": "Chart Of Accounts Template", - "no_copy": 1 + "no_copy": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", @@ -281,23 +336,31 @@ "ignore_user_permissions": 1, "label": "Existing Company ", "no_copy": 1, - "options": "Company" + "options": "Company", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_id", "fieldtype": "Data", - "label": "Tax ID" + "label": "Tax ID", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "date_of_establishment", "fieldtype": "Date", - "label": "Date of Establishment" + "label": "Date of Establishment", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_settings", "fieldtype": "Section Break", "label": "Accounts Settings", - "oldfieldtype": "Section Break" + "oldfieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -308,7 +371,9 @@ "no_copy": 1, "oldfieldname": "default_bank_account", "oldfieldtype": "Link", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -317,7 +382,9 @@ "ignore_user_permissions": 1, "label": "Default Cash Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -328,54 +395,72 @@ "no_copy": 1, "oldfieldname": "receivables_group", "oldfieldtype": "Link", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "round_off_account", "fieldtype": "Link", "label": "Round Off Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "round_off_cost_center", "fieldtype": "Link", "label": "Round Off Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "write_off_account", "fieldtype": "Link", "label": "Write Off Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "discount_allowed_account", "fieldtype": "Link", "label": "Discount Allowed Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "discount_received_account", "fieldtype": "Link", "label": "Discount Received Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "exchange_gain_loss_account", "fieldtype": "Link", "label": "Exchange Gain / Loss Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "unrealized_exchange_gain_loss_account", "fieldtype": "Link", "label": "Unrealized Exchange Gain/Loss Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break0", "fieldtype": "Column Break", "oldfieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -383,7 +468,9 @@ "depends_on": "eval:doc.parent_company", "fieldname": "allow_account_creation_against_child_company", "fieldtype": "Check", - "label": "Allow Account Creation Against Child Company" + "label": "Allow Account Creation Against Child Company", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -394,14 +481,18 @@ "no_copy": 1, "oldfieldname": "payables_group", "oldfieldtype": "Link", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_employee_advance_account", "fieldtype": "Link", "label": "Default Employee Advance Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -410,7 +501,9 @@ "ignore_user_permissions": 1, "label": "Default Cost of Goods Sold Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -419,7 +512,9 @@ "ignore_user_permissions": 1, "label": "Default Income Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -428,7 +523,9 @@ "ignore_user_permissions": 1, "label": "Default Deferred Revenue Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -437,7 +534,9 @@ "ignore_user_permissions": 1, "label": "Default Deferred Expense Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -446,7 +545,9 @@ "ignore_user_permissions": 1, "label": "Default Payroll Payable Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -455,11 +556,15 @@ "ignore_user_permissions": 1, "label": "Default Expense Claim Payable Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_22", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -468,11 +573,15 @@ "ignore_user_permissions": 1, "label": "Default Cost Center", "no_copy": 1, - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_26", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -481,31 +590,41 @@ "label": "Credit Limit", "oldfieldname": "credit_limit", "oldfieldtype": "Currency", - "options": "default_currency" + "options": "default_currency", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_terms", "fieldtype": "Link", "label": "Default Payment Terms Template", - "options": "Payment Terms Template" + "options": "Payment Terms Template", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", "fieldname": "auto_accounting_for_stock_settings", "fieldtype": "Section Break", - "label": "Stock Settings" + "label": "Stock Settings", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "fieldname": "enable_perpetual_inventory", "fieldtype": "Check", - "label": "Enable Perpetual Inventory" + "label": "Enable Perpetual Inventory", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_inventory_account", "fieldtype": "Link", "label": "Default Inventory Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "stock_adjustment_account", @@ -513,11 +632,15 @@ "ignore_user_permissions": 1, "label": "Stock Adjustment Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_32", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "stock_received_but_not_billed", @@ -525,7 +648,9 @@ "ignore_user_permissions": 1, "label": "Stock Received But Not Billed", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "expenses_included_in_valuation", @@ -533,108 +658,144 @@ "ignore_user_permissions": 1, "label": "Expenses Included In Valuation", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "fixed_asset_depreciation_settings", "fieldtype": "Section Break", - "label": "Fixed Asset Depreciation Settings" + "label": "Fixed Asset Depreciation Settings", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "accumulated_depreciation_account", "fieldtype": "Link", "label": "Accumulated Depreciation Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "depreciation_expense_account", "fieldtype": "Link", "label": "Depreciation Expense Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "series_for_depreciation_entry", "fieldtype": "Data", - "label": "Series for Asset Depreciation Entry (Journal Entry)" + "label": "Series for Asset Depreciation Entry (Journal Entry)", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "expenses_included_in_asset_valuation", "fieldtype": "Link", "label": "Expenses Included In Asset Valuation", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_40", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "disposal_account", "fieldtype": "Link", "label": "Gain/Loss Account on Asset Disposal", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "depreciation_cost_center", "fieldtype": "Link", "label": "Asset Depreciation Cost Center", "no_copy": 1, - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "capital_work_in_progress_account", "fieldtype": "Link", "label": "Capital Work In Progress Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "asset_received_but_not_billed", "fieldtype": "Link", "label": "Asset Received But Not Billed", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "budget_detail", "fieldtype": "Section Break", - "label": "Budget Detail" + "label": "Budget Detail", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "exception_budget_approver_role", "fieldtype": "Link", "label": "Exception Budget Approver Role", - "options": "Role" + "options": "Role", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "description": "For reference only.", "fieldname": "company_info", "fieldtype": "Section Break", - "label": "Company Info" + "label": "Company Info", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "date_of_incorporation", "fieldtype": "Date", - "label": "Date of Incorporation" + "label": "Date of Incorporation", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "address_html", - "fieldtype": "HTML" + "fieldtype": "HTML", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { "depends_on": "eval:doc.date_of_incorporation", "fieldname": "date_of_commencement", "fieldtype": "Date", - "label": "Date of Commencement" + "label": "Date of Commencement", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "phone_no", @@ -642,7 +803,9 @@ "label": "Phone No", "oldfieldname": "phone_no", "oldfieldtype": "Data", - "options": "Phone" + "options": "Phone", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "fax", @@ -650,7 +813,9 @@ "label": "Fax", "oldfieldname": "fax", "oldfieldtype": "Data", - "options": "Phone" + "options": "Phone", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "email", @@ -658,19 +823,25 @@ "label": "Email", "oldfieldname": "email", "oldfieldtype": "Data", - "options": "Email" + "options": "Email", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "website", "fieldtype": "Data", "label": "Website", "oldfieldname": "website", - "oldfieldtype": "Data" + "oldfieldtype": "Data", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "registration_info", "fieldtype": "Section Break", "oldfieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -679,12 +850,16 @@ "fieldtype": "Code", "label": "Registration Details", "oldfieldname": "registration_details", - "oldfieldtype": "Code" + "oldfieldtype": "Code", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "delete_company_transactions", "fieldtype": "Button", - "label": "Delete Company Transactions" + "label": "Delete Company Transactions", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "lft", @@ -693,7 +868,9 @@ "label": "Lft", "print_hide": 1, "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rgt", @@ -702,7 +879,9 @@ "label": "Rgt", "print_hide": 1, "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "old_parent", @@ -710,19 +889,35 @@ "hidden": 1, "label": "old_parent", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_selling_terms", "fieldtype": "Link", "label": "Default Selling Terms", - "options": "Terms and Conditions" + "options": "Terms and Conditions", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_buying_terms", "fieldtype": "Link", "label": "Default Buying Terms", - "options": "Terms and Conditions" + "options": "Terms and Conditions", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "service_received_but_not_billed", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Service Received But Not Billed", + "no_copy": 1, + "options": "Account", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-building", @@ -730,7 +925,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2020-03-21 18:09:53.534211", + "modified": "2020-06-11 15:32:59.147735", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e6ab8d634d..94a6edd2c1 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -211,6 +211,8 @@ class PurchaseReceipt(BuyingController): stock_rbnb = self.get_company_default("stock_received_but_not_billed") landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") + auto_accounting_for_non_stock_items = cint(frappe.db.get_single_value('Stock Settings', 'enable_perpetual_inventory_for_non_stock_items')) gl_entries = [] warehouse_with_no_account = [] @@ -301,6 +303,31 @@ class PurchaseReceipt(BuyingController): elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(d.warehouse) + elif d.item_code not in stock_items and flt(d.qty) and auto_accounting_for_non_stock_items: + + credit_currency = get_account_currency(service_received_but_not_billed_account) + + gl_entries.append(self.get_gl_dict({ + "account": service_received_but_not_billed_account, + "against": d.expense_account, + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Service"), + "project": d.project, + "credit": d.amount, + "voucher_detail_no": d.name + }, credit_currency, item=d)) + + debit_currency = get_account_currency(d.expense_account) + + gl_entries.append(self.get_gl_dict({ + "account": d.expense_account, + "against": service_received_but_not_billed_account, + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Service"), + "project": d.project, + "debit": d.amount, + "voucher_detail_no": d.name + }, debit_currency, item=d)) self.get_asset_gl_entry(gl_entries) # Cost center-wise amount breakup for other charges included for valuation diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index c9a3527e91..a7ed620dca 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -16,6 +16,7 @@ "action_if_quality_inspection_is_not_submitted", "show_barcode_field", "clean_description_html", + "enable_perpetual_inventory_for_non_stock_items", "section_break_7", "auto_insert_price_list_rate_if_missing", "allow_negative_stock", @@ -43,180 +44,248 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Item Naming By", - "options": "Item Code\nNaming Series" + "options": "Item Code\nNaming Series", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "item_group", "fieldtype": "Link", "in_list_view": 1, "label": "Default Item Group", - "options": "Item Group" + "options": "Item Group", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "stock_uom", "fieldtype": "Link", "in_list_view": 1, "label": "Default Stock UOM", - "options": "UOM" + "options": "UOM", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "default_warehouse", "fieldtype": "Link", "label": "Default Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sample_retention_warehouse", "fieldtype": "Link", "label": "Sample Retention Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_4", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "valuation_method", "fieldtype": "Select", "label": "Default Valuation Method", - "options": "FIFO\nMoving Average" + "options": "FIFO\nMoving Average", + "show_days": 1, + "show_seconds": 1 }, { "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", "fieldname": "over_delivery_receipt_allowance", "fieldtype": "Float", - "label": "Over Delivery/Receipt Allowance (%)" + "label": "Over Delivery/Receipt Allowance (%)", + "show_days": 1, + "show_seconds": 1 }, { "default": "Stop", "fieldname": "action_if_quality_inspection_is_not_submitted", "fieldtype": "Select", "label": "Action if Quality inspection is not submitted", - "options": "Stop\nWarn" + "options": "Stop\nWarn", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "fieldname": "show_barcode_field", "fieldtype": "Check", - "label": "Show Barcode Field" + "label": "Show Barcode Field", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "fieldname": "clean_description_html", "fieldtype": "Check", - "label": "Convert Item Description to Clean HTML" + "label": "Convert Item Description to Clean HTML", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_7", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "auto_insert_price_list_rate_if_missing", "fieldtype": "Check", - "label": "Auto insert Price List rate if missing" + "label": "Auto insert Price List rate if missing", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "allow_negative_stock", "fieldtype": "Check", - "label": "Allow Negative Stock" + "label": "Allow Negative Stock", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_10", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "fieldname": "automatically_set_serial_nos_based_on_fifo", "fieldtype": "Check", - "label": "Automatically Set Serial Nos based on FIFO" + "label": "Automatically Set Serial Nos based on FIFO", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "fieldname": "set_qty_in_transactions_based_on_serial_no_input", "fieldtype": "Check", - "label": "Set Qty in Transactions based on Serial No Input" + "label": "Set Qty in Transactions based on Serial No Input", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "auto_material_request", "fieldtype": "Section Break", - "label": "Auto Material Request" + "label": "Auto Material Request", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "auto_indent", "fieldtype": "Check", - "label": "Raise Material Request when stock reaches re-order level" + "label": "Raise Material Request when stock reaches re-order level", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "reorder_email_notify", "fieldtype": "Check", - "label": "Notify by Email on creation of automatic Material Request" + "label": "Notify by Email on creation of automatic Material Request", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "freeze_stock_entries", "fieldtype": "Section Break", - "label": "Freeze Stock Entries" + "label": "Freeze Stock Entries", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "stock_frozen_upto", "fieldtype": "Date", - "label": "Stock Frozen Upto" + "label": "Stock Frozen Upto", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "stock_frozen_upto_days", "fieldtype": "Int", - "label": "Freeze Stocks Older Than [Days]" + "label": "Freeze Stocks Older Than [Days]", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "stock_auth_role", "fieldtype": "Link", "label": "Role Allowed to edit frozen stock", - "options": "Role" + "options": "Role", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "batch_id_sb", "fieldtype": "Section Break", - "label": "Batch Identification" + "label": "Batch Identification", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "use_naming_series", "fieldtype": "Check", - "label": "Use Naming Series" + "label": "Use Naming Series", + "show_days": 1, + "show_seconds": 1 }, { "default": "BATCH-", "depends_on": "eval:doc.use_naming_series==1", "fieldname": "naming_series_prefix", "fieldtype": "Data", - "label": "Naming Series Prefix" + "label": "Naming Series Prefix", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "inter_warehouse_transfer_settings_section", "fieldtype": "Section Break", - "label": "Inter Warehouse Transfer Settings" + "label": "Inter Warehouse Transfer Settings", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "allow_from_dn", "fieldtype": "Check", - "label": "Allow Material Transfer From Delivery Note and Sales Invoice" + "label": "Allow Material Transfer From Delivery Note and Sales Invoice", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "allow_from_pr", "fieldtype": "Check", - "label": "Allow Material Transfer From Purchase Receipt and Purchase Invoice" + "label": "Allow Material Transfer From Purchase Receipt and Purchase Invoice", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "fieldname": "enable_perpetual_inventory_for_non_stock_items", + "fieldtype": "Check", + "label": "Enable Perpetual Inventory For Non Stock Items", + "show_days": 1, + "show_seconds": 1 } ], "icon": "icon-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2020-04-01 18:11:25.417678", + "modified": "2020-06-11 15:10:41.211638", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 12f1f8e1111e4a89c9f90da9ff3ae3cf05600720 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 20 Jun 2020 11:55:56 +0530 Subject: [PATCH 0110/1264] fix: Move check from Stock Settings to Company Master (cherry picked from commit 53d5d16abb52790d60c0ef050a9c7a6685fbc5df) --- .../purchase_invoice/purchase_invoice.py | 2 +- erpnext/setup/doctype/company/company.json | 373 +++++------------- .../purchase_receipt/purchase_receipt.py | 2 +- .../stock_settings/stock_settings.json | 131 ++---- 4 files changed, 130 insertions(+), 378 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 1e548c00b7..2f2f959f5d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -587,7 +587,7 @@ class PurchaseInvoice(BuyingController): else: amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) - auto_accounting_for_non_stock_items = cint(frappe.db.get_single_value('Stock Settings', 'enable_perpetual_inventory_for_non_stock_items')) + auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") if item.purchase_receipt and auto_accounting_for_non_stock_items: diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 0b91b70034..c25edc5505 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -67,6 +67,7 @@ "payment_terms", "auto_accounting_for_stock_settings", "enable_perpetual_inventory", + "enable_perpetual_inventory_for_non_stock_items", "default_inventory_account", "stock_adjustment_account", "column_break_32", @@ -107,9 +108,7 @@ { "fieldname": "details", "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Section Break" }, { "fieldname": "company_name", @@ -118,8 +117,6 @@ "oldfieldname": "company_name", "oldfieldtype": "Data", "reqd": 1, - "show_days": 1, - "show_seconds": 1, "unique": 1 }, { @@ -128,48 +125,36 @@ "label": "Abbr", "oldfieldname": "abbr", "oldfieldtype": "Data", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")", "fieldname": "change_abbr", "fieldtype": "Button", - "label": "Change Abbreviation", - "show_days": 1, - "show_seconds": 1 + "label": "Change Abbreviation" }, { "bold": 1, "default": "0", "fieldname": "is_group", "fieldtype": "Check", - "label": "Is Group", - "show_days": 1, - "show_seconds": 1 + "label": "Is Group" }, { "fieldname": "default_finance_book", "fieldtype": "Link", "label": "Default Finance Book", - "options": "Finance Book", - "show_days": 1, - "show_seconds": 1 + "options": "Finance Book" }, { "fieldname": "cb0", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "domain", "fieldtype": "Link", "label": "Domain", - "options": "Domain", - "show_days": 1, - "show_seconds": 1 + "options": "Domain" }, { "fieldname": "parent_company", @@ -177,32 +162,24 @@ "ignore_user_permissions": 1, "in_list_view": 1, "label": "Parent Company", - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" }, { "fieldname": "company_logo", "fieldtype": "Attach Image", "hidden": 1, - "label": "Company Logo", - "show_days": 1, - "show_seconds": 1 + "label": "Company Logo" }, { "fieldname": "company_description", "fieldtype": "Text Editor", - "label": "Company Description", - "show_days": 1, - "show_seconds": 1 + "label": "Company Description" }, { "collapsible": 1, "fieldname": "sales_settings", "fieldtype": "Section Break", - "label": "Sales Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Sales Settings" }, { "fieldname": "sales_monthly_history", @@ -210,9 +187,7 @@ "hidden": 1, "label": "Sales Monthly History", "no_copy": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "transactions_annual_history", @@ -220,23 +195,17 @@ "hidden": 1, "label": "Transactions Annual History", "no_copy": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "monthly_sales_target", "fieldtype": "Currency", "label": "Monthly Sales Target", - "options": "default_currency", - "show_days": 1, - "show_seconds": 1 + "options": "default_currency" }, { "fieldname": "column_break_goals", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "total_monthly_sales", @@ -244,16 +213,12 @@ "label": "Total Monthly Sales", "no_copy": 1, "options": "default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "charts_section", "fieldtype": "Section Break", - "label": "Default Values", - "show_days": 1, - "show_seconds": 1 + "label": "Default Values" }, { "fieldname": "default_currency", @@ -261,46 +226,34 @@ "ignore_user_permissions": 1, "label": "Default Currency", "options": "Currency", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "default_letter_head", "fieldtype": "Link", "label": "Default Letter Head", - "options": "Letter Head", - "show_days": 1, - "show_seconds": 1 + "options": "Letter Head" }, { "fieldname": "default_holiday_list", "fieldtype": "Link", "label": "Default Holiday List", - "options": "Holiday List", - "show_days": 1, - "show_seconds": 1 + "options": "Holiday List" }, { "fieldname": "standard_working_hours", "fieldtype": "Float", - "label": "Standard Working Hours", - "show_days": 1, - "show_seconds": 1 + "label": "Standard Working Hours" }, { "fieldname": "default_warehouse_for_sales_return", "fieldtype": "Link", "label": "Default warehouse for Sales Return", - "options": "Warehouse", - "show_days": 1, - "show_seconds": 1 + "options": "Warehouse" }, { "fieldname": "column_break_10", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "country", @@ -308,26 +261,20 @@ "in_list_view": 1, "label": "Country", "options": "Country", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "create_chart_of_accounts_based_on", "fieldtype": "Select", "label": "Create Chart Of Accounts Based On", - "options": "\nStandard Template\nExisting Company", - "show_days": 1, - "show_seconds": 1 + "options": "\nStandard Template\nExisting Company" }, { "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", "fieldname": "chart_of_accounts", "fieldtype": "Select", "label": "Chart Of Accounts Template", - "no_copy": 1, - "show_days": 1, - "show_seconds": 1 + "no_copy": 1 }, { "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", @@ -336,31 +283,23 @@ "ignore_user_permissions": 1, "label": "Existing Company ", "no_copy": 1, - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" }, { "fieldname": "tax_id", "fieldtype": "Data", - "label": "Tax ID", - "show_days": 1, - "show_seconds": 1 + "label": "Tax ID" }, { "fieldname": "date_of_establishment", "fieldtype": "Date", - "label": "Date of Establishment", - "show_days": 1, - "show_seconds": 1 + "label": "Date of Establishment" }, { "fieldname": "default_settings", "fieldtype": "Section Break", "label": "Accounts Settings", - "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Section Break" }, { "depends_on": "eval:!doc.__islocal", @@ -371,9 +310,7 @@ "no_copy": 1, "oldfieldname": "default_bank_account", "oldfieldtype": "Link", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -382,9 +319,7 @@ "ignore_user_permissions": 1, "label": "Default Cash Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -395,72 +330,54 @@ "no_copy": 1, "oldfieldname": "receivables_group", "oldfieldtype": "Link", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "round_off_account", "fieldtype": "Link", "label": "Round Off Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "round_off_cost_center", "fieldtype": "Link", "label": "Round Off Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "write_off_account", "fieldtype": "Link", "label": "Write Off Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "discount_allowed_account", "fieldtype": "Link", "label": "Discount Allowed Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "discount_received_account", "fieldtype": "Link", "label": "Discount Received Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "exchange_gain_loss_account", "fieldtype": "Link", "label": "Exchange Gain / Loss Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "unrealized_exchange_gain_loss_account", "fieldtype": "Link", "label": "Unrealized Exchange Gain/Loss Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "column_break0", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -468,9 +385,7 @@ "depends_on": "eval:doc.parent_company", "fieldname": "allow_account_creation_against_child_company", "fieldtype": "Check", - "label": "Allow Account Creation Against Child Company", - "show_days": 1, - "show_seconds": 1 + "label": "Allow Account Creation Against Child Company" }, { "depends_on": "eval:!doc.__islocal", @@ -481,18 +396,14 @@ "no_copy": 1, "oldfieldname": "payables_group", "oldfieldtype": "Link", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "default_employee_advance_account", "fieldtype": "Link", "label": "Default Employee Advance Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -501,9 +412,7 @@ "ignore_user_permissions": 1, "label": "Default Cost of Goods Sold Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -512,9 +421,7 @@ "ignore_user_permissions": 1, "label": "Default Income Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -523,9 +430,7 @@ "ignore_user_permissions": 1, "label": "Default Deferred Revenue Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -534,9 +439,7 @@ "ignore_user_permissions": 1, "label": "Default Deferred Expense Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -545,9 +448,7 @@ "ignore_user_permissions": 1, "label": "Default Payroll Payable Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:!doc.__islocal", @@ -556,15 +457,11 @@ "ignore_user_permissions": 1, "label": "Default Expense Claim Payable Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "section_break_22", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "depends_on": "eval:!doc.__islocal", @@ -573,15 +470,11 @@ "ignore_user_permissions": 1, "label": "Default Cost Center", "no_copy": 1, - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "column_break_26", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:!doc.__islocal", @@ -590,41 +483,31 @@ "label": "Credit Limit", "oldfieldname": "credit_limit", "oldfieldtype": "Currency", - "options": "default_currency", - "show_days": 1, - "show_seconds": 1 + "options": "default_currency" }, { "fieldname": "payment_terms", "fieldtype": "Link", "label": "Default Payment Terms Template", - "options": "Payment Terms Template", - "show_days": 1, - "show_seconds": 1 + "options": "Payment Terms Template" }, { "depends_on": "eval:!doc.__islocal", "fieldname": "auto_accounting_for_stock_settings", "fieldtype": "Section Break", - "label": "Stock Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Stock Settings" }, { "default": "1", "fieldname": "enable_perpetual_inventory", "fieldtype": "Check", - "label": "Enable Perpetual Inventory", - "show_days": 1, - "show_seconds": 1 + "label": "Enable Perpetual Inventory" }, { "fieldname": "default_inventory_account", "fieldtype": "Link", "label": "Default Inventory Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "stock_adjustment_account", @@ -632,15 +515,11 @@ "ignore_user_permissions": 1, "label": "Stock Adjustment Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "column_break_32", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "stock_received_but_not_billed", @@ -648,9 +527,7 @@ "ignore_user_permissions": 1, "label": "Stock Received But Not Billed", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "expenses_included_in_valuation", @@ -658,144 +535,108 @@ "ignore_user_permissions": 1, "label": "Expenses Included In Valuation", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "collapsible": 1, "fieldname": "fixed_asset_depreciation_settings", "fieldtype": "Section Break", - "label": "Fixed Asset Depreciation Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Fixed Asset Depreciation Settings" }, { "fieldname": "accumulated_depreciation_account", "fieldtype": "Link", "label": "Accumulated Depreciation Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "depreciation_expense_account", "fieldtype": "Link", "label": "Depreciation Expense Account", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "series_for_depreciation_entry", "fieldtype": "Data", - "label": "Series for Asset Depreciation Entry (Journal Entry)", - "show_days": 1, - "show_seconds": 1 + "label": "Series for Asset Depreciation Entry (Journal Entry)" }, { "fieldname": "expenses_included_in_asset_valuation", "fieldtype": "Link", "label": "Expenses Included In Asset Valuation", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "column_break_40", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "disposal_account", "fieldtype": "Link", "label": "Gain/Loss Account on Asset Disposal", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "depreciation_cost_center", "fieldtype": "Link", "label": "Asset Depreciation Cost Center", "no_copy": 1, - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "capital_work_in_progress_account", "fieldtype": "Link", "label": "Capital Work In Progress Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "asset_received_but_not_billed", "fieldtype": "Link", "label": "Asset Received But Not Billed", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "collapsible": 1, "fieldname": "budget_detail", "fieldtype": "Section Break", - "label": "Budget Detail", - "show_days": 1, - "show_seconds": 1 + "label": "Budget Detail" }, { "fieldname": "exception_budget_approver_role", "fieldtype": "Link", "label": "Exception Budget Approver Role", - "options": "Role", - "show_days": 1, - "show_seconds": 1 + "options": "Role" }, { "collapsible": 1, "description": "For reference only.", "fieldname": "company_info", "fieldtype": "Section Break", - "label": "Company Info", - "show_days": 1, - "show_seconds": 1 + "label": "Company Info" }, { "fieldname": "date_of_incorporation", "fieldtype": "Date", - "label": "Date of Incorporation", - "show_days": 1, - "show_seconds": 1 + "label": "Date of Incorporation" }, { "fieldname": "address_html", - "fieldtype": "HTML", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "HTML" }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { "depends_on": "eval:doc.date_of_incorporation", "fieldname": "date_of_commencement", "fieldtype": "Date", - "label": "Date of Commencement", - "show_days": 1, - "show_seconds": 1 + "label": "Date of Commencement" }, { "fieldname": "phone_no", @@ -803,9 +644,7 @@ "label": "Phone No", "oldfieldname": "phone_no", "oldfieldtype": "Data", - "options": "Phone", - "show_days": 1, - "show_seconds": 1 + "options": "Phone" }, { "fieldname": "fax", @@ -813,9 +652,7 @@ "label": "Fax", "oldfieldname": "fax", "oldfieldtype": "Data", - "options": "Phone", - "show_days": 1, - "show_seconds": 1 + "options": "Phone" }, { "fieldname": "email", @@ -823,25 +660,19 @@ "label": "Email", "oldfieldname": "email", "oldfieldtype": "Data", - "options": "Email", - "show_days": 1, - "show_seconds": 1 + "options": "Email" }, { "fieldname": "website", "fieldtype": "Data", "label": "Website", "oldfieldname": "website", - "oldfieldtype": "Data", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Data" }, { "fieldname": "registration_info", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -850,16 +681,12 @@ "fieldtype": "Code", "label": "Registration Details", "oldfieldname": "registration_details", - "oldfieldtype": "Code", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Code" }, { "fieldname": "delete_company_transactions", "fieldtype": "Button", - "label": "Delete Company Transactions", - "show_days": 1, - "show_seconds": 1 + "label": "Delete Company Transactions" }, { "fieldname": "lft", @@ -868,9 +695,7 @@ "label": "Lft", "print_hide": 1, "read_only": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "rgt", @@ -879,9 +704,7 @@ "label": "Rgt", "print_hide": 1, "read_only": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "old_parent", @@ -889,25 +712,19 @@ "hidden": 1, "label": "old_parent", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "default_selling_terms", "fieldtype": "Link", "label": "Default Selling Terms", - "options": "Terms and Conditions", - "show_days": 1, - "show_seconds": 1 + "options": "Terms and Conditions" }, { "fieldname": "default_buying_terms", "fieldtype": "Link", "label": "Default Buying Terms", - "options": "Terms and Conditions", - "show_days": 1, - "show_seconds": 1 + "options": "Terms and Conditions" }, { "fieldname": "service_received_but_not_billed", @@ -915,9 +732,13 @@ "ignore_user_permissions": 1, "label": "Service Received But Not Billed", "no_copy": 1, - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" + }, + { + "default": "0", + "fieldname": "enable_perpetual_inventory_for_non_stock_items", + "fieldtype": "Check", + "label": "Enable Perpetual Inventory For Non Stock Items" } ], "icon": "fa fa-building", @@ -925,7 +746,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2020-06-11 15:32:59.147735", + "modified": "2020-06-20 11:38:43.178970", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 94a6edd2c1..15068ec510 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -212,7 +212,7 @@ class PurchaseReceipt(BuyingController): landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") - auto_accounting_for_non_stock_items = cint(frappe.db.get_single_value('Stock Settings', 'enable_perpetual_inventory_for_non_stock_items')) + auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) gl_entries = [] warehouse_with_no_account = [] diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index a7ed620dca..9c5d3d8340 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -16,7 +16,6 @@ "action_if_quality_inspection_is_not_submitted", "show_barcode_field", "clean_description_html", - "enable_perpetual_inventory_for_non_stock_items", "section_break_7", "auto_insert_price_list_rate_if_missing", "allow_negative_stock", @@ -44,248 +43,180 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Item Naming By", - "options": "Item Code\nNaming Series", - "show_days": 1, - "show_seconds": 1 + "options": "Item Code\nNaming Series" }, { "fieldname": "item_group", "fieldtype": "Link", "in_list_view": 1, "label": "Default Item Group", - "options": "Item Group", - "show_days": 1, - "show_seconds": 1 + "options": "Item Group" }, { "fieldname": "stock_uom", "fieldtype": "Link", "in_list_view": 1, "label": "Default Stock UOM", - "options": "UOM", - "show_days": 1, - "show_seconds": 1 + "options": "UOM" }, { "fieldname": "default_warehouse", "fieldtype": "Link", "label": "Default Warehouse", - "options": "Warehouse", - "show_days": 1, - "show_seconds": 1 + "options": "Warehouse" }, { "fieldname": "sample_retention_warehouse", "fieldtype": "Link", "label": "Sample Retention Warehouse", - "options": "Warehouse", - "show_days": 1, - "show_seconds": 1 + "options": "Warehouse" }, { "fieldname": "column_break_4", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "valuation_method", "fieldtype": "Select", "label": "Default Valuation Method", - "options": "FIFO\nMoving Average", - "show_days": 1, - "show_seconds": 1 + "options": "FIFO\nMoving Average" }, { "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", "fieldname": "over_delivery_receipt_allowance", "fieldtype": "Float", - "label": "Over Delivery/Receipt Allowance (%)", - "show_days": 1, - "show_seconds": 1 + "label": "Over Delivery/Receipt Allowance (%)" }, { "default": "Stop", "fieldname": "action_if_quality_inspection_is_not_submitted", "fieldtype": "Select", "label": "Action if Quality inspection is not submitted", - "options": "Stop\nWarn", - "show_days": 1, - "show_seconds": 1 + "options": "Stop\nWarn" }, { "default": "1", "fieldname": "show_barcode_field", "fieldtype": "Check", - "label": "Show Barcode Field", - "show_days": 1, - "show_seconds": 1 + "label": "Show Barcode Field" }, { "default": "1", "fieldname": "clean_description_html", "fieldtype": "Check", - "label": "Convert Item Description to Clean HTML", - "show_days": 1, - "show_seconds": 1 + "label": "Convert Item Description to Clean HTML" }, { "fieldname": "section_break_7", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "default": "0", "fieldname": "auto_insert_price_list_rate_if_missing", "fieldtype": "Check", - "label": "Auto insert Price List rate if missing", - "show_days": 1, - "show_seconds": 1 + "label": "Auto insert Price List rate if missing" }, { "default": "0", "fieldname": "allow_negative_stock", "fieldtype": "Check", - "label": "Allow Negative Stock", - "show_days": 1, - "show_seconds": 1 + "label": "Allow Negative Stock" }, { "fieldname": "column_break_10", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "1", "fieldname": "automatically_set_serial_nos_based_on_fifo", "fieldtype": "Check", - "label": "Automatically Set Serial Nos based on FIFO", - "show_days": 1, - "show_seconds": 1 + "label": "Automatically Set Serial Nos based on FIFO" }, { "default": "1", "fieldname": "set_qty_in_transactions_based_on_serial_no_input", "fieldtype": "Check", - "label": "Set Qty in Transactions based on Serial No Input", - "show_days": 1, - "show_seconds": 1 + "label": "Set Qty in Transactions based on Serial No Input" }, { "fieldname": "auto_material_request", "fieldtype": "Section Break", - "label": "Auto Material Request", - "show_days": 1, - "show_seconds": 1 + "label": "Auto Material Request" }, { "default": "0", "fieldname": "auto_indent", "fieldtype": "Check", - "label": "Raise Material Request when stock reaches re-order level", - "show_days": 1, - "show_seconds": 1 + "label": "Raise Material Request when stock reaches re-order level" }, { "default": "0", "fieldname": "reorder_email_notify", "fieldtype": "Check", - "label": "Notify by Email on creation of automatic Material Request", - "show_days": 1, - "show_seconds": 1 + "label": "Notify by Email on creation of automatic Material Request" }, { "fieldname": "freeze_stock_entries", "fieldtype": "Section Break", - "label": "Freeze Stock Entries", - "show_days": 1, - "show_seconds": 1 + "label": "Freeze Stock Entries" }, { "fieldname": "stock_frozen_upto", "fieldtype": "Date", - "label": "Stock Frozen Upto", - "show_days": 1, - "show_seconds": 1 + "label": "Stock Frozen Upto" }, { "fieldname": "stock_frozen_upto_days", "fieldtype": "Int", - "label": "Freeze Stocks Older Than [Days]", - "show_days": 1, - "show_seconds": 1 + "label": "Freeze Stocks Older Than [Days]" }, { "fieldname": "stock_auth_role", "fieldtype": "Link", "label": "Role Allowed to edit frozen stock", - "options": "Role", - "show_days": 1, - "show_seconds": 1 + "options": "Role" }, { "fieldname": "batch_id_sb", "fieldtype": "Section Break", - "label": "Batch Identification", - "show_days": 1, - "show_seconds": 1 + "label": "Batch Identification" }, { "default": "0", "fieldname": "use_naming_series", "fieldtype": "Check", - "label": "Use Naming Series", - "show_days": 1, - "show_seconds": 1 + "label": "Use Naming Series" }, { "default": "BATCH-", "depends_on": "eval:doc.use_naming_series==1", "fieldname": "naming_series_prefix", "fieldtype": "Data", - "label": "Naming Series Prefix", - "show_days": 1, - "show_seconds": 1 + "label": "Naming Series Prefix" }, { "fieldname": "inter_warehouse_transfer_settings_section", "fieldtype": "Section Break", - "label": "Inter Warehouse Transfer Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Inter Warehouse Transfer Settings" }, { "default": "0", "fieldname": "allow_from_dn", "fieldtype": "Check", - "label": "Allow Material Transfer From Delivery Note and Sales Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Allow Material Transfer From Delivery Note and Sales Invoice" }, { "default": "0", "fieldname": "allow_from_pr", "fieldtype": "Check", - "label": "Allow Material Transfer From Purchase Receipt and Purchase Invoice", - "show_days": 1, - "show_seconds": 1 - }, - { - "default": "0", - "fieldname": "enable_perpetual_inventory_for_non_stock_items", - "fieldtype": "Check", - "label": "Enable Perpetual Inventory For Non Stock Items", - "show_days": 1, - "show_seconds": 1 + "label": "Allow Material Transfer From Purchase Receipt and Purchase Invoice" } ], "icon": "icon-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2020-06-11 15:10:41.211638", + "modified": "2020-06-20 11:39:15.344112", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 51ce1e1d4e60b1e2b48fd86712f34654cb922ed4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Jun 2020 09:40:15 +0530 Subject: [PATCH 0111/1264] fix: Test Cases (cherry picked from commit ded3ab1cd778389ab348bcb5628379f129a8d02d) --- .../purchase_invoice/purchase_invoice.py | 18 ++++++++++-------- erpnext/setup/doctype/company/company.py | 7 +++++++ .../purchase_receipt/purchase_receipt.py | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2f2f959f5d..3cd57d403a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -588,16 +588,18 @@ class PurchaseInvoice(BuyingController): amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) - service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") - if item.purchase_receipt and auto_accounting_for_non_stock_items: - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0, - 'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail, - 'account':service_received_but_not_billed_account}, ['name']) + if auto_accounting_for_non_stock_items: + service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") - if expense_booked_in_pr: - expense_account = service_received_but_not_billed_account + if item.purchase_receipt: + # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt + expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0, + 'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail, + 'account':service_received_but_not_billed_account}, ['name']) + + if expense_booked_in_pr: + expense_account = service_received_but_not_billed_account gl_entries.append(self.get_gl_dict({ "account": expense_account, diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 8bcaa28707..47b41a97ad 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -46,6 +46,7 @@ class Company(NestedSet): self.validate_currency() self.validate_coa_input() self.validate_perpetual_inventory() + self.validate_perpetual_inventory_for_non_stock_items() self.check_country_change() self.set_chart_of_accounts() self.validate_parent_company() @@ -182,6 +183,12 @@ class Company(NestedSet): frappe.msgprint(_("Set default inventory account for perpetual inventory"), alert=True, indicator='orange') + def validate_perpetual_inventory_for_non_stock_items(self): + if not self.get("__islocal"): + if cint(self.enable_perpetual_inventory_for_non_stock_items) == 1 and not self.service_received_but_not_billed: + frappe.throw(_("Set default {0} account for perpetual inventory for non stock items").format( + frappe.bold('Service Received But Not Billed'))) + def check_country_change(self): frappe.flags.country_change = False diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 15068ec510..d0ba001d7e 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -211,7 +211,6 @@ class PurchaseReceipt(BuyingController): stock_rbnb = self.get_company_default("stock_received_but_not_billed") landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) gl_entries = [] @@ -305,6 +304,7 @@ class PurchaseReceipt(BuyingController): warehouse_with_no_account.append(d.warehouse) elif d.item_code not in stock_items and flt(d.qty) and auto_accounting_for_non_stock_items: + service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") credit_currency = get_account_currency(service_received_but_not_billed_account) gl_entries.append(self.get_gl_dict({ From faa0c5624e8d7f53a53aa0c0fcf45a54f7310074 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 24 Jun 2020 17:49:50 +0530 Subject: [PATCH 0112/1264] fix: Update journal entry account timestamp --- .../doctype/journal_entry_account/journal_entry_account.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 26c84a6398..7bdc5a89b5 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -273,7 +273,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-25 01:47:49.060128", + "modified": "2020-06-26 14:06:54.833738", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From 4c726b25eea7c56bde2c6df24ac1b94ee735224d Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 25 Jun 2020 15:37:04 +0530 Subject: [PATCH 0113/1264] fix: Add view ledger button for cancelled docs (#22433) * fix: Add view ledger button for cancelled docs * fix: Add moddified to date in Stock Ledger --- .../invoice_discounting/invoice_discounting.js | 7 ++++--- .../accounts/doctype/journal_entry/journal_entry.js | 7 ++++--- .../accounts/doctype/payment_entry/payment_entry.js | 11 ++++++----- .../period_closing_voucher/period_closing_voucher.js | 5 +++-- erpnext/education/doctype/fees/fees.js | 7 ++++--- erpnext/hr/doctype/expense_claim/expense_claim.js | 7 +++++-- erpnext/loan_management/loan_common.js | 7 +++++-- erpnext/public/js/controllers/stock_controller.js | 10 ++++++---- 8 files changed, 37 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js index 0fab8b70f4..db4f7c423f 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js @@ -188,14 +188,15 @@ frappe.ui.form.on('Invoice Discounting', { }, show_general_ledger: (frm) => { - if(frm.doc.docstatus===1) { + if(frm.doc.docstatus > 0) { cur_frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { voucher_no: frm.doc.name, from_date: frm.doc.posting_date, - to_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), company: frm.doc.company, - group_by: "Group by Voucher (Consolidated)" + group_by: "Group by Voucher (Consolidated)", + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 5685f839fe..a09face791 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -13,15 +13,16 @@ frappe.ui.form.on("Journal Entry", { refresh: function(frm) { erpnext.toggle_naming_series(); - if(frm.doc.docstatus==1) { + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, - "to_date": frm.doc.posting_date, + "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'), "company": frm.doc.company, "finance_book": frm.doc.finance_book, - "group_by_voucher": 0 + "group_by": '', + "show_cancelled_entries": frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __('View')); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index a378a51cdf..42c9fdeba4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -172,8 +172,8 @@ frappe.ui.form.on('Payment Entry', { frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency); frm.toggle_display("base_received_amount", ( - frm.doc.paid_to_account_currency != company_currency - && frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency + frm.doc.paid_to_account_currency != company_currency + && frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency && frm.doc.base_paid_amount != frm.doc.base_received_amount )); @@ -234,14 +234,15 @@ frappe.ui.form.on('Payment Entry', { }, show_general_ledger: function(frm) { - if(frm.doc.docstatus==1) { + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, - "to_date": frm.doc.posting_date, + "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'), "company": frm.doc.company, - group_by: "" + "group_by": "", + "show_cancelled_entries": frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, "fa fa-table"); diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js index 87e02fef1b..e923d4ed5e 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js @@ -25,9 +25,10 @@ frappe.ui.form.on('Period Closing Voucher', { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, - "to_date": frm.doc.posting_date, + "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'), "company": frm.doc.company, - group_by_voucher: 0 + "group_by": "", + "show_cancelled_entries": frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, "fa fa-table"); diff --git a/erpnext/education/doctype/fees/fees.js b/erpnext/education/doctype/fees/fees.js index 17ef44954b..867866fbf1 100644 --- a/erpnext/education/doctype/fees/fees.js +++ b/erpnext/education/doctype/fees/fees.js @@ -55,14 +55,15 @@ frappe.ui.form.on("Fees", { frm.set_df_property('posting_date', 'read_only', 1); frm.set_df_property('posting_time', 'read_only', 1); } - if(frm.doc.docstatus===1) { + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { voucher_no: frm.doc.name, from_date: frm.doc.posting_date, - to_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), company: frm.doc.company, - group_by_voucher: false + group_by: '', + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 6bb9af9826..fa63ec2834 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -213,12 +213,15 @@ frappe.ui.form.on("Expense Claim", { refresh: function(frm) { frm.trigger("toggle_fields"); - if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") { + if(frm.doc.docstatus > 0 && frm.doc.approval_status !== "Rejected") { frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { voucher_no: frm.doc.name, company: frm.doc.company, - group_by_voucher: false + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + group_by: '', + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); diff --git a/erpnext/loan_management/loan_common.js b/erpnext/loan_management/loan_common.js index 3a47a88cbe..d9dd415296 100644 --- a/erpnext/loan_management/loan_common.js +++ b/erpnext/loan_management/loan_common.js @@ -9,12 +9,15 @@ frappe.ui.form.on(cur_frm.doctype, { } if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual'].includes(frm.doc.doctype) - && frm.doc.docstatus == 1) { + && frm.doc.docstatus > 0) { frm.add_custom_button(__("Accounting Ledger"), function() { frappe.route_options = { voucher_no: frm.doc.name, - company: frm.doc.company + company: frm.doc.company, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index 2ce49e766b..87b21b78de 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -55,8 +55,9 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({ frappe.route_options = { voucher_no: me.frm.doc.name, from_date: me.frm.doc.posting_date, - to_date: me.frm.doc.posting_date, - company: me.frm.doc.company + to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'), + company: me.frm.doc.company, + show_cancelled_entries: me.frm.doc.docstatus === 2 }; frappe.set_route("query-report", "Stock Ledger"); }, __("View")); @@ -71,9 +72,10 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({ frappe.route_options = { voucher_no: me.frm.doc.name, from_date: me.frm.doc.posting_date, - to_date: me.frm.doc.posting_date, + to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'), company: me.frm.doc.company, - group_by: "Group by Voucher (Consolidated)" + group_by: "Group by Voucher (Consolidated)", + show_cancelled_entries: me.frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); From fce39f706da3a297fbf8d5344f2b837404572622 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2020 16:21:56 +0530 Subject: [PATCH 0114/1264] fix: Send email to credit controllers to increase credit limit (#22473) (cherry picked from commit 0ff5af222a5842e93f4b9e1bdbc31011abbe105d) Co-authored-by: Nabin Hait --- erpnext/selling/doctype/customer/customer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 682dfede72..d70c64fce4 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -388,8 +388,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager") # form a list of emails and names to show to the user - credit_controller_users_list = [user for user in credit_controller_users if frappe.db.exists("Employee", {"prefered_email": user})] - credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users_list] + credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users] if not credit_controller_users: frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer))) @@ -409,7 +408,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, 'customer': customer, 'customer_outstanding': customer_outstanding, 'credit_limit': credit_limit, - 'credit_controller_users_list': credit_controller_users_list + 'credit_controller_users_list': credit_controller_users } } ) From 28c1e8ef771d40b101e258d1a63436ac55533b1f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 19 Jun 2020 16:12:32 +0530 Subject: [PATCH 0115/1264] fix: handle hold time for custom statuses --- erpnext/support/doctype/issue/issue.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 883e603fd3..976aa8ed5b 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -79,7 +79,7 @@ class Issue(Document): def handle_hold_time(self, status): if self.service_level_agreement: - # set response and resolution variance as None as the issue is on Hold for status as Replied + # set response and resolution variance as None as the issue is on Hold pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"], filters={"parent": self.service_level_agreement}) hold_statuses = [entry.status for entry in pause_sla_on] @@ -93,15 +93,14 @@ class Issue(Document): update_values['resolution_by'] = None update_values['resolution_by_variance'] = 0 - # calculate hold time when status is changed from Replied to any other status + # calculate hold time when status is changed from any hold status to any non-hold status if self.status not in hold_statuses and status in hold_statuses: hold_time = self.total_hold_time if self.total_hold_time else 0 now_time = frappe.flags.current_time or now_datetime() update_values['total_hold_time'] = hold_time + time_diff_in_seconds(now_time, self.on_hold_since) - # re-calculate SLA variables after issue changes from Replied to Open - # add hold time to SLA variables - if self.status == "Open" and status in hold_statuses: + # re-calculate SLA variables after issue changes from any hold status to any non-hold status + # add hold time to SLA variables start_date_time = get_datetime(self.service_level_agreement_creation) priority = get_priority(self) now_time = frappe.flags.current_time or now_datetime() @@ -109,13 +108,15 @@ class Issue(Document): if not self.first_responded_on: response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time) - update_values['response_by'] = add_to_date(response_by, seconds=round(hold_time)) - response_by_variance = round(time_diff_in_hours(self.response_by, now_time)) + response_by = add_to_date(response_by, seconds=round(hold_time)) + response_by_variance = round(time_diff_in_hours(response_by, now_time)) + update_values['response_by'] = response_by update_values['response_by_variance'] = response_by_variance + (hold_time // 3600) resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time) - update_values['resolution_by'] = add_to_date(resolution_by, seconds=round(hold_time)) - resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_time)) + resolution_by = add_to_date(resolution_by, seconds=round(hold_time)) + resolution_by_variance = round(time_diff_in_hours(resolution_by, now_time)) + update_values['resolution_by'] = resolution_by update_values['resolution_by_variance'] = resolution_by_variance + (hold_time // 3600) update_values['on_hold_since'] = None From 5dd4bcb229fedccdd66211c8a4cd185f9baf1761 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 19 Jun 2020 17:27:25 +0530 Subject: [PATCH 0116/1264] fix: set total hold time only if hold configurations are enabled --- erpnext/support/doctype/issue/issue.py | 66 ++++++++++++++------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 976aa8ed5b..87168e151e 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -85,42 +85,46 @@ class Issue(Document): hold_statuses = [entry.status for entry in pause_sla_on] update_values = {} - if self.status in hold_statuses and status not in hold_statuses: - update_values['on_hold_since'] = frappe.flags.current_time or now_datetime() - if not self.first_responded_on: - update_values['response_by'] = None - update_values['response_by_variance'] = 0 - update_values['resolution_by'] = None - update_values['resolution_by_variance'] = 0 + if hold_statuses: + if self.status in hold_statuses and status not in hold_statuses: + update_values['on_hold_since'] = frappe.flags.current_time or now_datetime() + if not self.first_responded_on: + update_values['response_by'] = None + update_values['response_by_variance'] = 0 + update_values['resolution_by'] = None + update_values['resolution_by_variance'] = 0 - # calculate hold time when status is changed from any hold status to any non-hold status - if self.status not in hold_statuses and status in hold_statuses: - hold_time = self.total_hold_time if self.total_hold_time else 0 - now_time = frappe.flags.current_time or now_datetime() - update_values['total_hold_time'] = hold_time + time_diff_in_seconds(now_time, self.on_hold_since) + # calculate hold time when status is changed from any hold status to any non-hold status + if self.status not in hold_statuses and status in hold_statuses: + hold_time = self.total_hold_time if self.total_hold_time else 0 + now_time = frappe.flags.current_time or now_datetime() + last_hold_time = 0 + if self.on_hold_since: + # last_hold_time will be added to the sla variables + last_hold_time = time_diff_in_seconds(now_time, self.on_hold_since) + update_values['total_hold_time'] = hold_time + last_hold_time - # re-calculate SLA variables after issue changes from any hold status to any non-hold status - # add hold time to SLA variables - start_date_time = get_datetime(self.service_level_agreement_creation) - priority = get_priority(self) - now_time = frappe.flags.current_time or now_datetime() - hold_time = time_diff_in_seconds(now_time, self.on_hold_since) + # re-calculate SLA variables after issue changes from any hold status to any non-hold status + # add hold time to SLA variables + start_date_time = get_datetime(self.service_level_agreement_creation) + priority = get_priority(self) + now_time = frappe.flags.current_time or now_datetime() - if not self.first_responded_on: - response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time) - response_by = add_to_date(response_by, seconds=round(hold_time)) - response_by_variance = round(time_diff_in_hours(response_by, now_time)) - update_values['response_by'] = response_by - update_values['response_by_variance'] = response_by_variance + (hold_time // 3600) + if not self.first_responded_on: + response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time) + response_by = add_to_date(response_by, seconds=round(last_hold_time)) + response_by_variance = round(time_diff_in_hours(response_by, now_time)) + update_values['response_by'] = response_by + update_values['response_by_variance'] = response_by_variance + (last_hold_time // 3600) - resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time) - resolution_by = add_to_date(resolution_by, seconds=round(hold_time)) - resolution_by_variance = round(time_diff_in_hours(resolution_by, now_time)) - update_values['resolution_by'] = resolution_by - update_values['resolution_by_variance'] = resolution_by_variance + (hold_time // 3600) - update_values['on_hold_since'] = None + resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time) + resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time)) + resolution_by_variance = round(time_diff_in_hours(resolution_by, now_time)) + update_values['resolution_by'] = resolution_by + update_values['resolution_by_variance'] = resolution_by_variance + (last_hold_time // 3600) + update_values['on_hold_since'] = None - self.db_set(update_values) + self.db_set(update_values) def update_agreement_status(self): if self.service_level_agreement and self.agreement_fulfilled == "Ongoing": From cf53305abb53a0cc1359687adb3f13e90217392e Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 18:36:57 +0200 Subject: [PATCH 0117/1264] fix(report view): explicitly set column width for (cherry picked from commit 7a7add5001f65c74237c0e8d04daaa50be9a7866) --- .../regional/report/datev/datev_constants.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py index a059ed365a..e063703005 100644 --- a/erpnext/regional/report/datev/datev_constants.py +++ b/erpnext/regional/report/datev/datev_constants.py @@ -465,60 +465,71 @@ QUERY_REPORT_COLUMNS = [ "label": "Umsatz (ohne Soll/Haben-Kz)", "fieldname": "Umsatz (ohne Soll/Haben-Kz)", "fieldtype": "Currency", + "width": 100 }, { "label": "Soll/Haben-Kennzeichen", "fieldname": "Soll/Haben-Kennzeichen", "fieldtype": "Data", + "width": 100 }, { "label": "Konto", "fieldname": "Konto", "fieldtype": "Data", + "width": 100 }, { "label": "Gegenkonto (ohne BU-Schlüssel)", "fieldname": "Gegenkonto (ohne BU-Schlüssel)", "fieldtype": "Data", + "width": 100 }, { "label": "Belegdatum", "fieldname": "Belegdatum", "fieldtype": "Date", + "width": 100 }, { "label": "Belegfeld 1", "fieldname": "Belegfeld 1", "fieldtype": "Data", + "width": 150 }, { "label": "Buchungstext", "fieldname": "Buchungstext", "fieldtype": "Text", + "width": 300 }, { "label": "Beleginfo - Art 1", "fieldname": "Beleginfo - Art 1", "fieldtype": "Link", - "options": "DocType" + "options": "DocType", + "width": 100 }, { "label": "Beleginfo - Inhalt 1", "fieldname": "Beleginfo - Inhalt 1", "fieldtype": "Dynamic Link", - "options": "Beleginfo - Art 1" + "options": "Beleginfo - Art 1", + "width": 150 }, { "label": "Beleginfo - Art 2", "fieldname": "Beleginfo - Art 2", "fieldtype": "Link", - "options": "DocType" + "options": "DocType", + "width": 100 }, { "label": "Beleginfo - Inhalt 2", "fieldname": "Beleginfo - Inhalt 2", "fieldtype": "Dynamic Link", - "options": "Beleginfo - Art 2" + "options": "Beleginfo - Art 2", + "width": 150 } ] From 3efe0d0e839e6d66cf0f387765126bd9e9e158d9 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 18:50:02 +0200 Subject: [PATCH 0118/1264] refactor: query meta data only once (cherry picked from commit 55c048f56c1b2ed91e467edd4700cf8da448c514) --- erpnext/regional/report/datev/datev.py | 35 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index a8e40cc493..02296a9356 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -8,17 +8,18 @@ Provide a report and downloadable CSV according to the German DATEV format. all required columns. Used to import the data into the DATEV Software. """ from __future__ import unicode_literals + import datetime import json -import zlib import zipfile import six +import frappe +import pandas as pd + +from frappe import _ from csv import QUOTE_NONNUMERIC from six import BytesIO from six import string_types -import frappe -from frappe import _ -import pandas as pd from .datev_constants import DataCategory from .datev_constants import Transactions from .datev_constants import DebtorsCreditors @@ -287,9 +288,7 @@ def get_datev_csv(data, filters, csv_class): def get_header(filters, csv_class): - coa = frappe.get_value("Company", filters.get("company"), "chart_of_accounts") - description = filters.get("voucher_type", csv_class.FORMAT_NAME) - coa_used = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "") + description = filters.get('voucher_type', csv_class.FORMAT_NAME) header = [ # DATEV format @@ -316,13 +315,13 @@ def get_header(filters, csv_class): # J = Imported by -- stays empty '', # K = Tax consultant number (Beraternummer) - frappe.get_value("DATEV Settings", filters.get("company"), "consultant_number"), + filters.get('consultant_number', '0000000'), # L = Tax client number (Mandantennummer) - frappe.get_value("DATEV Settings", filters.get("company"), "client_number"), + filters.get('client_number', '00000'), # M = Start of the fiscal year (Wirtschaftsjahresbeginn) frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"), # N = Length of account numbers (Sachkontenlänge) - '4', + '%d' % filters.get('acc_len', 4), # O = Transaction batch start date (YYYYMMDD) frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd"), # P = Transaction batch end date (YYYYMMDD) @@ -348,7 +347,7 @@ def get_header(filters, csv_class): # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1" '0', # V = Default currency, for example, "EUR" - '"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"), + '"%s"' % filters.get('default_currency', 'EUR'), # reserviert '', # Derivatskennzeichen @@ -358,7 +357,7 @@ def get_header(filters, csv_class): # reserviert '', # SKR - '"%s"' % coa_used, + '"%s"' % filters.get('skr', '04'), # Branchen-Lösungs-ID '', # reserviert @@ -389,6 +388,18 @@ def download_datev_csv(filters=None): validate(filters) + # set chart of accounts used + coa = frappe.get_value('Company', filters.get('company'), 'chart_of_accounts') + filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '') + + # set account number length + account_numbers = frappe.get_list('Account', fields=['account_number'], filters={'is_group': 0, 'account_number': ('!=', '')}) + filters['acc_len'] = max([len(a.account_number) for a in account_numbers]) + + filters['consultant_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'consultant_number') + filters['client_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'client_number') + filters['default_currency'] = frappe.get_value('Company', filters.get('company'), 'default_currency') + # This is where my zip will be written zip_buffer = BytesIO() # This is my zip file From 6adb617ede8094a8617c571f333597a969a0f143 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 19:15:03 +0200 Subject: [PATCH 0119/1264] fix: truncate account names to max length (cherry picked from commit 2976831560ddf9e16aae6b500f0e4f1b621e60d2) --- erpnext/regional/report/datev/datev.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 02296a9356..51ddfc780d 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -227,9 +227,18 @@ def get_suppliers(filters): def get_account_names(filters): - return frappe.get_list("Account", - fields=["account_number as Konto", "name as Kontenbeschriftung"], - filters={"company": filters.get("company"), "is_group": "0"}) + return frappe.db.sql(""" + SELECT + + account_number as 'Konto', + LEFT(account_name, 40) as 'Kontenbeschriftung', + 'de-DE' as 'Sprach-ID' + + FROM `tabAccount` + WHERE company = %(company)s + AND is_group = 0 + AND account_number != '' + """, filters, as_dict=1) def get_datev_csv(data, filters, csv_class): From 8f7d21485325e623dddfae22c3156afe8b808ab4 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 19:15:49 +0200 Subject: [PATCH 0120/1264] fix: customer and supplier data (cherry picked from commit 53445aa25adc664d5eac55ba9e16a1eb4365ea38) --- erpnext/regional/report/datev/datev.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 51ddfc780d..d7036c55af 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -131,8 +131,10 @@ def get_customers(filters): SELECT acc.account_number as 'Konto', - cus.customer_name as 'Name (Adressatentyp Unternehmen)', - case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp', + CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)', + CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', + CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', + CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', @@ -141,8 +143,7 @@ def get_customers(filters): con.email_id as 'E-Mail', coalesce(con.mobile_no, con.phone) as 'Telefon', cus.website as 'Internet', - cus.tax_id as 'Steuernummer', - ccl.credit_limit as 'Kreditlimit (Debitor)' + cus.tax_id as 'Steuernummer' FROM `tabParty Account` par @@ -161,10 +162,6 @@ def get_customers(filters): left join `tabContact` con on con.name = cus.customer_primary_contact - left join `tabCustomer Credit Limit` ccl - on ccl.parent = cus.name - and ccl.company = par.company - WHERE par.company = %(company)s AND par.parenttype = 'Customer'""", filters, as_dict=1) @@ -180,8 +177,10 @@ def get_suppliers(filters): SELECT acc.account_number as 'Konto', - sup.supplier_name as 'Name (Adressatentyp Unternehmen)', - case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp', + CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)', + CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', + CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', + CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', From 7df5de94687e1f747771287cd4278e9f130f7ee7 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 19:23:54 +0200 Subject: [PATCH 0121/1264] fix: hide transaction-specific for master data (cherry picked from commit 30d194d8a7eab77d056999aac61d00666518c005) --- erpnext/regional/report/datev/datev.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index d7036c55af..7fec94e740 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -331,11 +331,11 @@ def get_header(filters, csv_class): # N = Length of account numbers (Sachkontenlänge) '%d' % filters.get('acc_len', 4), # O = Transaction batch start date (YYYYMMDD) - frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd"), + frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # P = Transaction batch end date (YYYYMMDD) - frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"), + frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # Q = Description (for example, "Sales Invoice") Max. 30 chars - '"{}"'.format(_(description)), + '"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # R = Diktatkürzel '', # S = Buchungstyp @@ -350,12 +350,12 @@ def get_header(filters, csv_class): # 40 = Kalkulatorik # 11 = Reserviert # 12 = Reserviert - '0', + '0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # U = Festschreibung # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1" '0', # V = Default currency, for example, "EUR" - '"%s"' % filters.get('default_currency', 'EUR'), + '"%s"' % filters.get('default_currency', 'EUR') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # reserviert '', # Derivatskennzeichen From 47a3524a7d66a9d9a9c9aa1c1f2c853e9d2c813c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 18:24:25 +0530 Subject: [PATCH 0122/1264] fix: Stock Onboarding typo and reorder (#22499) (#22503) (cherry picked from commit 8732b8caf618037b402ebcbe3097402e15c65494) Co-authored-by: Marica --- .../stock/doctype/stock_settings/stock_settings.js | 2 +- erpnext/stock/module_onboarding/stock/stock.json | 11 +++++------ .../create_a_supplier/create_a_supplier.json | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js index d5049ac6ed..48624e0f25 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.js +++ b/erpnext/stock/doctype/stock_settings/stock_settings.js @@ -20,7 +20,7 @@ frappe.tour['Stock Settings'] = [ { fieldname: "item_naming_by", title: __("Item Naming By"), - description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a") + "Naming Series" + __(" choose the 'Naming Series' option."), + description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a ") + "Naming Series" + __(" choose the 'Naming Series' option."), }, { fieldname: "default_warehouse", diff --git a/erpnext/stock/module_onboarding/stock/stock.json b/erpnext/stock/module_onboarding/stock/stock.json index de24575a14..10f05d4520 100644 --- a/erpnext/stock/module_onboarding/stock/stock.json +++ b/erpnext/stock/module_onboarding/stock/stock.json @@ -19,7 +19,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/stock", "idx": 0, "is_complete": 0, - "modified": "2020-05-19 19:03:23.602423", + "modified": "2020-06-29 16:41:09.440378", "modified_by": "Administrator", "module": "Stock", "name": "Stock", @@ -31,15 +31,15 @@ { "step": "Create a Product" }, + { + "step": "Create a Supplier" + }, { "step": "Introduction to Stock Entry" }, { "step": "Create a Stock Entry" }, - { - "step": "Create a Supplier" - }, { "step": "Create a Purchase Receipt" }, @@ -49,6 +49,5 @@ ], "subtitle": "Inventory, Warehouses, Analysis and more.", "success_message": "The Stock Module is all set up!", - "title": "Let's Setup the Stock Module.", - "user_can_dismiss": 1 + "title": "Let's Set Up the Stock Module." } \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json index 7a64224bd4..4e753f4d84 100644 --- a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json +++ b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json @@ -8,7 +8,7 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 22:09:10.043554", + "modified": "2020-06-29 16:36:53.948242", "modified_by": "Administrator", "name": "Create a Supplier", "owner": "Administrator", From c312b3af13bc5cc1837c963aa516009b076711f3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 19:10:57 +0530 Subject: [PATCH 0123/1264] fix: job offer validation fix (#22504) (#22505) (cherry picked from commit 343651fc393055ed07e6ac6b56203248e0818058) Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> --- erpnext/hr/doctype/job_offer/job_offer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index 9a2c4c64eb..f9ee44a4de 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -32,7 +32,8 @@ class JobOffer(Document): return frappe.get_all("Job Offer", filters={ "offer_date": ['between', (from_date, to_date)], "designation": self.designation, - "company": self.company + "company": self.company, + "docstatus": 1 }, fields=['name']) def update_job_applicant(status, job_applicant): From 9cba6bddde879418cd67e96d26d9235ae8972f78 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 20:07:57 +0530 Subject: [PATCH 0124/1264] fixes: Payroll module pre relese test fixes (#22500) (#22506) * fix: salary payment based on payments * fix: bank remittance report * fix: Payroll period onboarding * fix: provident-fund-deductions report * fix: Considered unmarked days * fix: onboarding paYroll * feat: quick entry in payroll entry (cherry picked from commit c2523e845340addec214ea36e42992ba89d9b210) Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> --- .../payroll/doctype/payroll_period/payroll_period.json | 3 ++- erpnext/payroll/doctype/salary_slip/salary_slip.py | 8 +++----- erpnext/payroll/module_onboarding/payroll/payroll.json | 2 +- .../create_payroll_period/create_payroll_period.json | 2 +- .../payroll_settings/payroll_settings.json | 10 +++++----- .../payroll/report/bank_remittance/bank_remittance.py | 5 ++++- .../salary_payments_based_on_payment_mode.py | 2 ++ .../provident_fund_deductions.py | 2 +- 8 files changed, 19 insertions(+), 15 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period.json b/erpnext/payroll/doctype/payroll_period/payroll_period.json index c919b4fe13..0e0948475c 100644 --- a/erpnext/payroll/doctype/payroll_period/payroll_period.json +++ b/erpnext/payroll/doctype/payroll_period/payroll_period.json @@ -53,7 +53,7 @@ } ], "links": [], - "modified": "2020-06-22 20:12:32.684189", + "modified": "2020-06-29 17:17:12.689089", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Period", @@ -96,6 +96,7 @@ "write": 1 } ], + "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 2da19b0397..1e2983e421 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -207,7 +207,7 @@ class SalarySlip(TransactionBase): frappe.throw(_("There are more holidays than working days this month.")) if not payroll_based_on: - frappe.throw(_("Please set Payroll based on in HR settings")) + frappe.throw(_("Please set Payroll based on in Payroll settings")) if payroll_based_on == "Attendance": actual_lwp, absent = self.calculate_lwp_and_absent_days_based_on_attendance(holidays) @@ -244,15 +244,13 @@ class SalarySlip(TransactionBase): for holiday in holidays: if not frappe.db.exists("Attendance", {"employee": self.employee, "attendance_date": holiday, "docstatus": 1 }): self.payment_days += 1 - - else: self.payment_days = 0 def get_unmarked_days(self): marked_days = frappe.get_all("Attendance", filters = { - "attendance_date": ["between", ['2020-05-1',"2020-05-30"]], - "employee": 'HR-EMP-00003', + "attendance_date": ["between", [self.start_date, self.end_date]], + "employee": self.employee, "docstatus": 1 }, fields = ["COUNT(*) as marked_days"])[0].marked_days diff --git a/erpnext/payroll/module_onboarding/payroll/payroll.json b/erpnext/payroll/module_onboarding/payroll/payroll.json index a4ea53640e..7ed786faee 100644 --- a/erpnext/payroll/module_onboarding/payroll/payroll.json +++ b/erpnext/payroll/module_onboarding/payroll/payroll.json @@ -13,7 +13,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/human-resources/payroll-entry", "idx": 0, "is_complete": 0, - "modified": "2020-06-04 16:35:30.650792", + "modified": "2020-06-29 17:00:25.113341", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll", diff --git a/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json b/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json index 4bae67546c..b1a7cc2734 100644 --- a/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json +++ b/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json @@ -8,7 +8,7 @@ "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-06-01 11:53:54.553947", + "modified": "2020-06-29 11:53:54.553947", "modified_by": "Administrator", "name": "Create Payroll Period", "owner": "Administrator", diff --git a/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json b/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json index 946b8c8707..a7cf7bf988 100644 --- a/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json @@ -1,19 +1,19 @@ { - "action": "Go to Page", + "action": "Update Settings", "creation": "2020-06-04 16:34:29.664917", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_mandatory": 0, - "is_single": 0, + "is_single": 1, "is_skipped": 0, - "modified": "2020-06-04 16:34:29.664917", + "modified": "2020-06-29 16:34:29.664917", "modified_by": "Administrator", "name": "Payroll Settings", "owner": "Administrator", - "path": "#Form/Payroll Settings", + "reference_document": "Payroll Settings", "show_full_form": 0, "title": "Payroll Settings", - "validate_action": 1 + "validate_action": 0 } \ No newline at end of file diff --git a/erpnext/payroll/report/bank_remittance/bank_remittance.py b/erpnext/payroll/report/bank_remittance/bank_remittance.py index a35d8e550e..4b052bf5c4 100644 --- a/erpnext/payroll/report/bank_remittance/bank_remittance.py +++ b/erpnext/payroll/report/bank_remittance/bank_remittance.py @@ -152,6 +152,9 @@ def set_company_account(payment_accounts, payroll_entries): company_accounts_map[acc.account] = acc for entry in payroll_entries: - entry["company_account"] = company_accounts_map[entry.payment_account]['bank_account_no'] + company_account = '' + if entry.payment_account in company_accounts_map: + company_account = company_accounts_map[entry.payment_account]['bank_account_no'] + entry["company_account"] = company_account return payroll_entries diff --git a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py index 7f0c2e2e00..a0dab63654 100644 --- a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py +++ b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py @@ -100,6 +100,8 @@ def get_data(filters, mode_of_payments): total_row = get_total_based_on_mode_of_payment(data, mode_of_payments) total_deductions = gross_pay - total_row.get("total") + report_summary = [] + if data: data.append(total_row) data.append({}) diff --git a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py index 9f58957fed..86f2b5b05e 100644 --- a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py +++ b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py @@ -97,7 +97,7 @@ def prepare_data(entry,component_type_dict): "employee": d.employee, "employee_name": d.employee_name, "pf_account": employee_account_dict.get(d.employee), - "component_type": d.amount + component_type: d.amount }) return data_list From 0efda4e457f017689ca0fe22423c09f049648aad Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 30 Jun 2020 08:20:35 +0530 Subject: [PATCH 0125/1264] fix: Added Missing Trends Reports to Buying/Selling Desk (#22509) * fix: Added Missing Trends Reports to Buying/Selling Desk * fix: Move Reports to Other Reports --- erpnext/buying/desk_page/buying/buying.json | 4 ++-- erpnext/selling/desk_page/selling/selling.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json index bddb9573ad..565d39c3c8 100644 --- a/erpnext/buying/desk_page/buying/buying.json +++ b/erpnext/buying/desk_page/buying/buying.json @@ -33,7 +33,7 @@ { "hidden": 0, "label": "Other Reports", - "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -60,7 +60,7 @@ "idx": 0, "is_standard": 1, "label": "Buying", - "modified": "2020-05-28 13:32:49.960574", + "modified": "2020-06-29 19:30:24.983050", "modified_by": "Administrator", "module": "Buying", "name": "Buying", diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json index 60b15326e8..225238233a 100644 --- a/erpnext/selling/desk_page/selling/selling.json +++ b/erpnext/selling/desk_page/selling/selling.json @@ -23,7 +23,7 @@ { "hidden": 0, "label": "Other Reports", - "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Customer Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"route_options\": {\n \"party_type\": \"Customer\"\n },\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Available Stock for Packing Items\",\n \"name\": \"Available Stock for Packing Items\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Pending SO Items For Purchase Request\",\n \"name\": \"Pending SO Items For Purchase Request\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customers Without Any Sales Transactions\",\n \"name\": \"Customers Without Any Sales Transactions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Customer Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"route_options\": {\n \"party_type\": \"Customer\"\n },\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Available Stock for Packing Items\",\n \"name\": \"Available Stock for Packing Items\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Pending SO Items For Purchase Request\",\n \"name\": \"Pending SO Items For Purchase Request\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Delivery Note Trends\",\n \"name\": \"Delivery Note Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customers Without Any Sales Transactions\",\n \"name\": \"Customers Without Any Sales Transactions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n }\n]" } ], "category": "Modules", @@ -44,7 +44,7 @@ "idx": 0, "is_standard": 1, "label": "Selling", - "modified": "2020-06-19 13:23:24.861706", + "modified": "2020-06-29 19:26:35.139097", "modified_by": "Administrator", "module": "Selling", "name": "Selling", From 7ca8022b8f3b53eb1f05ff3811b9a0010140616f Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 30 Jun 2020 08:20:58 +0530 Subject: [PATCH 0126/1264] fix: Set Root as Parent if no parent in new tree view node (#22508) --- erpnext/setup/doctype/customer_group/customer_group.py | 5 ++++- erpnext/setup/doctype/sales_person/sales_person.py | 5 ++++- erpnext/setup/doctype/territory/territory.py | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py index f62613ea1b..68e1ccb635 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.py +++ b/erpnext/setup/doctype/customer_group/customer_group.py @@ -6,9 +6,12 @@ import frappe from frappe import _ -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_root_of class CustomerGroup(NestedSet): nsm_parent_field = 'parent_customer_group' + def validate(self): + if not self.parent_customer_group: + self.parent_customer_group = get_root_of("Customer Group") def on_update(self): self.validate_name_with_customer() diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py index 3379534cf8..19c2e5b954 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.py +++ b/erpnext/setup/doctype/sales_person/sales_person.py @@ -5,13 +5,16 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_root_of from erpnext import get_default_currency class SalesPerson(NestedSet): nsm_parent_field = 'parent_sales_person' def validate(self): + if not self.parent_sales_person: + self.parent_sales_person = get_root_of("Sales Person") + for d in self.get('targets') or []: if not flt(d.target_qty) and not flt(d.target_amount): frappe.throw(_("Either target qty or target amount is mandatory.")) diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py index 808b5386ab..05e8f666cf 100644 --- a/erpnext/setup/doctype/territory/territory.py +++ b/erpnext/setup/doctype/territory/territory.py @@ -6,12 +6,14 @@ import frappe from frappe.utils import flt from frappe import _ -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_root_of class Territory(NestedSet): nsm_parent_field = 'parent_territory' def validate(self): + if not self.parent_territory: + self.parent_territory = get_root_of("Territory") for d in self.get('targets') or []: if not flt(d.target_qty) and not flt(d.target_amount): From e0388a169af5ff98ef994733fdd743b49d05e251 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2020 08:57:26 +0530 Subject: [PATCH 0127/1264] fix: manufacturing dashboard grammar (#22510) (#22516) * fix: manufacturing dashboard grammar * Update manufacturing.json (cherry picked from commit 7056cd3782161f0bfb1ba5c0519e87d40c09ce8d) Co-authored-by: rohitwaghchaure --- erpnext/manufacturing/dashboard_fixtures.py | 8 ++++---- .../module_onboarding/manufacturing/manufacturing.json | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py index 64e4bc6ed0..0e9a21c026 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -192,7 +192,7 @@ def get_number_cards(): ]), "function": "Count", "is_public": 1, - "label": _("Monthly Total Work Order"), + "label": _("Monthly Total Work Orders"), "show_percentage_stats": 1, "stats_time_interval": "Weekly" }, @@ -207,7 +207,7 @@ def get_number_cards(): ]), "function": "Count", "is_public": 1, - "label": _("Monthly Completed Work Order"), + "label": _("Monthly Completed Work Orders"), "show_percentage_stats": 1, "stats_time_interval": "Weekly" }, @@ -221,7 +221,7 @@ def get_number_cards(): ]), "function": "Count", "is_public": 1, - "label": _("Ongoing Job Card"), + "label": _("Ongoing Job Cards"), "show_percentage_stats": 1, "stats_time_interval": "Weekly" }, @@ -235,7 +235,7 @@ def get_number_cards(): ]), "function": "Count", "is_public": 1, - "label": _("Monthly Quality Inspection"), + "label": _("Monthly Quality Inspections"), "show_percentage_stats": 1, "stats_time_interval": "Weekly" }] \ No newline at end of file diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json index 952d1f0e07..a36b63a1d9 100644 --- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json @@ -19,7 +19,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", "idx": 0, "is_complete": 0, - "modified": "2020-05-19 12:51:42.744570", + "modified": "2020-06-29 20:25:36.899106", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -52,6 +52,5 @@ ], "subtitle": "Products, Raw Materials, BOM, Work Order and more.", "success_message": "Manufacturing module is all setup!", - "title": "Let's Setup Manufacturing Module", - "user_can_dismiss": 1 -} \ No newline at end of file + "title": "Let's Set Up the Manufacturing Module" +} From 7db00dde2d2a34872af4f38810bb9a40629cde6a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:33:18 +0530 Subject: [PATCH 0128/1264] fix: Dashboard label in Projects and Assets module (#22517) (#22518) (cherry picked from commit fdbd10f1939a47511d2288dbaf4aad138f81eec8) Co-authored-by: Nabin Hait --- erpnext/assets/desk_page/assets/assets.json | 2 +- .../desk_page/manufacturing/manufacturing.json | 14 +++++++------- erpnext/projects/desk_page/projects/projects.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/assets/desk_page/assets/assets.json b/erpnext/assets/desk_page/assets/assets.json index 94939fdd2a..449a5facb0 100644 --- a/erpnext/assets/desk_page/assets/assets.json +++ b/erpnext/assets/desk_page/assets/assets.json @@ -58,7 +58,7 @@ "type": "Report" }, { - "label": "Assets Dashboard", + "label": "Dashboard", "link_to": "Asset", "type": "Dashboard" } diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json index 763f533a94..8d11294164 100644 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json @@ -93,12 +93,6 @@ "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}", "type": "DocType" }, - { - "label": "Dashboard", - "link_to": "Manufacturing", - "restrict_to_domain": "Manufacturing", - "type": "Dashboard" - }, { "label": "Forecasting", "link_to": "Exponential Smoothing Forecasting", @@ -119,6 +113,12 @@ "label": "Production Planning Report", "link_to": "Production Planning Report", "type": "Report" - } + }, + { + "label": "Dashboard", + "link_to": "Manufacturing", + "restrict_to_domain": "Manufacturing", + "type": "Dashboard" + } ] } \ No newline at end of file diff --git a/erpnext/projects/desk_page/projects/projects.json b/erpnext/projects/desk_page/projects/projects.json index d91fe5304a..e24cf3081c 100644 --- a/erpnext/projects/desk_page/projects/projects.json +++ b/erpnext/projects/desk_page/projects/projects.json @@ -68,7 +68,7 @@ "type": "Report" }, { - "label": "Project Dashboard", + "label": "Dashboard", "link_to": "Project", "type": "Dashboard" } From 7813cabfd39f68bc8fa463fc139b97c76adcca2d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 30 Jun 2020 11:35:30 +0530 Subject: [PATCH 0129/1264] chore: Added Change Log --- erpnext/change_log/v13/v13_0_0-beta_3.md | 66 ++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0-beta_3.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_3.md b/erpnext/change_log/v13/v13_0_0-beta_3.md new file mode 100644 index 0000000000..09ea90087d --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0-beta_3.md @@ -0,0 +1,66 @@ +### Version 13.0.0 Beta 3 Release Notes + +#### Features and Enhancements +- Dedicated Payroll module with Onboarding and Dashboard ([#21990](https://github.com/frappe/erpnext/pull/21990)) +- New Payroll Reports + - Income Tax Deductions + - Professional Tax Deductions + - Provident Fund Deductions + - Total Salary Payments Based on Payment Mode + - Salary Payments via ECS +- Distributed Cost Center ([#21531](https://github.com/frappe/erpnext/pull/21531)) +- More controlled deferred revenue booking ([#21671](https://github.com/frappe/erpnext/pull/21671)) + - Book deferred accounting via Journal Entry, provision to keep in draft + - Provision to book deferred revenue/expense on a monthly basis rather than by days +- Selling Desk, Dashboard and Onboarding ([#22055](https://github.com/frappe/erpnext/pull/22055)) +- Help Articles on support portal ([#22194](https://github.com/frappe/erpnext/pull/22194)) +- Issue Metrics and SLA Enhancements ([#21617](https://github.com/frappe/erpnext/pull/21617)) +- Multi UOM support in Request for Quotation ([#22249](https://github.com/frappe/erpnext/pull/22249)) +- The ability for a contract to be authorized internally using a signature field ([#22095](https://github.com/frappe/erpnext/pull/22095)) +- Gross Profit In Quotation ([#21795](https://github.com/frappe/erpnext/pull/21795)) +- Notify credit controller users for credit limit extension via Email ([#22213](https://github.com/frappe/erpnext/pull/22213)) +- Added In and Out time in attendance ([#21547](https://github.com/frappe/erpnext/pull/21547)) +- Renamed Loan Management to Loan on Desk Page ([#21877](https://github.com/frappe/erpnext/pull/21877)) +- Document tour to manufacturing settings ([#21962](https://github.com/frappe/erpnext/pull/21962)) +- Added Expense Approver field in Employee master ([#22244](https://github.com/frappe/erpnext/pull/22244)) + + +#### Fixes +- Bill all hours by default on Timesheet ([#22155](https://github.com/frappe/erpnext/pull/22155)) +- Unable to cancel employee advance ([#22374](https://github.com/frappe/erpnext/pull/22374)) +- Status error in purchase invoice ([#22351](https://github.com/frappe/erpnext/pull/22351)) +- Item-wise sales and purchase register export ([#22184](https://github.com/frappe/erpnext/pull/22184)) +- Billing address in for Purchase documents ([#22233](https://github.com/frappe/erpnext/pull/22233)) +- Handle canceled entries in financial statements ([#22231](https://github.com/frappe/erpnext/pull/22231)) +- Default period start date and period end date for financial statements ([#22011](https://github.com/frappe/erpnext/pull/22011)) +- Update Packed Items via Update Items in Sales Order ([#22392](https://github.com/frappe/erpnext/pull/22392)) +- Hide delete company transactions button if not system manager ([#21839](https://github.com/frappe/erpnext/pull/21839)) +- Skipping total row for tree-view reports ([#22350](https://github.com/frappe/erpnext/pull/22350)) +- Cancelled entries in tds payable monthly report ([#22131](https://github.com/frappe/erpnext/pull/22131)) +- Inter-company Invoice currency for multicurrency transactions ([#21984](https://github.com/frappe/erpnext/pull/21984)) +- Filter batches based on item and warehouse in Pick List (develop) ([#21780](https://github.com/frappe/erpnext/pull/21780)) +- Set cost center in Expense Claim child based on parent (if missing) ([#22175](https://github.com/frappe/erpnext/pull/22175)) +- Item wise backdated stock entry posting for immutable ledger ([#22366](https://github.com/frappe/erpnext/pull/22366)) +- Shopping cart UI fixes ([#22137](https://github.com/frappe/erpnext/pull/22137)) +- Filter Leave Type based on allocation for a particular employee ([#22050](https://github.com/frappe/erpnext/pull/22050)) +- Party validation for inter-warehouse transaction ([#22186](https://github.com/frappe/erpnext/pull/22186)) +- Manufacturing dashboard and work order summary chart ([#21946](https://github.com/frappe/erpnext/pull/21946)) +- IP Admission and Discharge, Minor fixes ([#21817](https://github.com/frappe/erpnext/pull/21817)) +- Validation of Purchase Order against Material Request missing ([#22192](https://github.com/frappe/erpnext/pull/22192)) +- Staffing Plan validation ([#22379](https://github.com/frappe/erpnext/pull/22379)) +- Do not allow backdated stock transactions in previous fiscal year ([#21967](https://github.com/frappe/erpnext/pull/21967)) +- Employee Advance Return not working ([#21812](https://github.com/frappe/erpnext/pull/21812)) +- Added card for reports on education desk ([#21853](https://github.com/frappe/erpnext/pull/21853)) +- Refactored project summary report ([#21943](https://github.com/frappe/erpnext/pull/21943)) +- Revenue and Customer Count only in date range in Customer Acquitition Report ([#22210](https://github.com/frappe/erpnext/pull/22210)) +- Alternative item not working for subcontract ([#22386](https://github.com/frappe/erpnext/pull/22386)) +- Unable to create batched Item ([#22393](https://github.com/frappe/erpnext/pull/22393)) +- Filters for the manufacturing reports ([#21960](https://github.com/frappe/erpnext/pull/21960)) +- Raw material warehouse in Production Planning Report ([#21982](https://github.com/frappe/erpnext/pull/21982)) +- Allowed LWP leave types to select in Leave Application even if there is no allocation against them ([#22197](https://github.com/frappe/erpnext/pull/22197)) +- Report not working on parameter Grade ([#21951](https://github.com/frappe/erpnext/pull/21951)) +- Allow to enter Relieving date if employee status is Left ([#22242](https://github.com/frappe/erpnext/pull/22242)) +- Resetting lost reason in opportunity and quotation ([#22378](https://github.com/frappe/erpnext/pull/22378)) +- Filtering issues in opening invoice creation tool ([#21969](https://github.com/frappe/erpnext/pull/21969)) +- Set default reference Id for "On Previous Row Amount" and "On Previous Row Total" ([#22346](https://github.com/frappe/erpnext/pull/22346)) +- UX date range field separated in from and to date fields. ([#21765](https://github.com/frappe/erpnext/pull/21765)) From dca65de1b600562007a7d64ba06724033f0400cb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2020 11:45:40 +0530 Subject: [PATCH 0130/1264] fix: Salary deductions report fixes (bp #22397) (#22520) * feat: added year filters (cherry picked from commit d0d9b53361c57d362e7e975f16b48ccbfdc021a8) * Fix: if there is no component (cherry picked from commit f9ca29cebd0a0963ec58b8971283d7e0d2868f2b) * fix: test_tax_for_payroll_period (cherry picked from commit ece9508eb5a02962a994fbaad0e989e323d3f56c) Co-authored-by: Anurag Mishra --- .../doctype/salary_slip/test_salary_slip.py | 1 + .../income_tax_deductions.py | 12 ++++++--- .../salary_payments_via_ecs.py | 8 +++--- .../salary_slip_deductions_report_filters.js | 25 ++++++++++++++++--- .../professional_tax_deductions.py | 5 +++- .../provident_fund_deductions.py | 25 +++++++++++++++---- 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index f42b9ad11d..be9a2d3728 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -271,6 +271,7 @@ class TestSalarySlip(unittest.TestCase): # as per assigned salary structure 40500 in monthly salary so 236000*5/100/12 frappe.db.sql("""delete from `tabPayroll Period`""") frappe.db.sql("""delete from `tabSalary Component`""") + frappe.db.sql("""delete from `tabAdditional Salary`""") payroll_period = create_payroll_period() diff --git a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py index 3bad5879bb..8a79416edb 100644 --- a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py +++ b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py @@ -6,8 +6,8 @@ import frappe, erpnext from frappe import _ def execute(filters=None): - columns = get_columns(filters) data = get_data(filters) + columns = get_columns(filters) if len(data) else [] return columns, data @@ -78,8 +78,11 @@ def get_conditions(filters): if filters.get("company"): conditions.append("sal.company = '%s' " % (filters["company"]) ) - if filters.get("period"): - conditions.append("month(sal.start_date) = '%s' " % (filters["period"])) + if filters.get("month"): + conditions.append("month(sal.start_date) = '%s' " % (filters["month"])) + + if filters.get("year"): + conditions.append("year(start_date) = '%s' " % (filters["year"])) return " and ".join(conditions) @@ -96,6 +99,9 @@ def get_data(filters): component_types = [comp_type[0] for comp_type in component_types] + if not len(component_types): + return [] + conditions = get_conditions(filters) entry = frappe.db.sql(""" select sal.employee, sal.employee_name, sal.posting_date, ded.salary_component, ded.amount,sal.gross_pay diff --git a/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py b/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py index 073bd91300..d09745c37b 100644 --- a/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py +++ b/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py @@ -84,9 +84,11 @@ def get_conditions(filters): if filters.get("company"): conditions.append("company = '%s' " % (filters["company"]) ) - if filters.get("period"): - conditions.append("month(start_date) = '%s' " % (filters["period"])) - conditions.append("year(start_date) = '%s' " % (frappe.utils.getdate().year)) + if filters.get("month"): + conditions.append("month(start_date) = '%s' " % (filters["month"])) + + if filters.get("year"): + conditions.append("year(start_date) = '%s' " % (filters["year"])) return " and ".join(conditions) diff --git a/erpnext/public/js/salary_slip_deductions_report_filters.js b/erpnext/public/js/salary_slip_deductions_report_filters.js index 242037991a..2b30e65075 100644 --- a/erpnext/public/js/salary_slip_deductions_report_filters.js +++ b/erpnext/public/js/salary_slip_deductions_report_filters.js @@ -11,8 +11,8 @@ erpnext.salary_slip_deductions_report_filters = { default: frappe.defaults.get_user_default("Company"), }, { - fieldname: "period", - label: __("Period"), + fieldname: "month", + label: __("Month"), fieldtype: "Select", reqd: 1 , options: [ @@ -31,6 +31,12 @@ erpnext.salary_slip_deductions_report_filters = { ], default: frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1 }, + { + fieldname:"year", + label: __("Year"), + fieldtype: "Select", + reqd: 1 + }, { fieldname: "department", label: __("Department"), @@ -43,5 +49,18 @@ erpnext.salary_slip_deductions_report_filters = { fieldtype: "Link", options: "Branch", } - ] + ], + + "onload": function() { + return frappe.call({ + method: "erpnext.regional.report.provident_fund_deductions.provident_fund_deductions.get_years", + callback: function(r) { + var year_filter = frappe.query_report.get_filter('year'); + year_filter.df.options = r.message; + year_filter.df.default = r.message.split("\n")[0]; + year_filter.refresh(); + year_filter.set_input(year_filter.df.default); + } + }); + } } \ No newline at end of file diff --git a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py index 900fe963b4..acde68a942 100644 --- a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py +++ b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py @@ -7,8 +7,8 @@ from frappe import _ from erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import get_conditions def execute(filters=None): - columns = get_columns(filters) data = get_data(filters) + columns = get_columns(filters) if len(data) else [] return columns, data @@ -45,6 +45,9 @@ def get_data(filters): component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component` where component_type = 'Professional Tax' """)) + if not len(component_type_dict): + return [] + conditions = get_conditions(filters) entry = frappe.db.sql(""" select sal.employee, sal.employee_name, ded.salary_component, ded.amount diff --git a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py index 86f2b5b05e..597072c53a 100644 --- a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py +++ b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py @@ -3,11 +3,12 @@ from __future__ import unicode_literals import frappe +from frappe.utils import getdate from frappe import _ def execute(filters=None): - columns = get_columns(filters) data = get_data(filters) + columns = get_columns(filters) if len(data) else [] return columns, data @@ -71,10 +72,13 @@ def get_conditions(filters): conditions.append("sal.branch = '%s' " % (filters["branch"]) ) if filters.get("company"): - conditions.append("sal.company = '%s' " % (filters["company"]) ) + conditions.append("sal.company = '%s' " % (filters["company"])) - if filters.get("period"): - conditions.append("month(sal.start_date) = '%s' " % (filters["period"])) + if filters.get("month"): + conditions.append("month(sal.start_date) = '%s' " % (filters["month"])) + + if filters.get("year"): + conditions.append("year(start_date) = '%s' " % (filters["year"])) if filters.get("mode_of_payment"): conditions.append("sal.mode_of_payment = '%s' " % (filters["mode_of_payment"])) @@ -114,6 +118,9 @@ def get_data(filters): component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component` where component_type in ('Provident Fund', 'Additional Provident Fund', 'Provident Fund Loan')""")) + if not len(component_type_dict): + return [] + entry = frappe.db.sql(""" select sal.name, sal.employee, sal.employee_name, ded.salary_component, ded.amount from `tabSalary Slip` sal, `tabSalary Detail` ded where sal.name = ded.parent @@ -150,4 +157,12 @@ def get_data(filters): data.append(employee) - return data \ No newline at end of file + return data + +@frappe.whitelist() +def get_years(): + year_list = frappe.db.sql_list("""select distinct YEAR(end_date) from `tabSalary Slip` ORDER BY YEAR(end_date) DESC""") + if not year_list: + year_list = [getdate().year] + + return "\n".join(str(year) for year in year_list) \ No newline at end of file From f278425c01134acbf1f9cdfd3bf6a6a796c9d97d Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 30 Jun 2020 19:06:02 +0550 Subject: [PATCH 0131/1264] bumped to version 13.0.0-beta.3 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 530a6e4880..18294f3604 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.2' +__version__ = '13.0.0-beta.3' def get_default_company(user=None): '''Get default company for user''' From 762f6297f6746a164c566047703cefdb97e3ef3f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Jul 2020 18:05:13 +0530 Subject: [PATCH 0132/1264] fix(payment-request): do not set guest as administrator (#22804) --- erpnext/accounts/doctype/payment_request/payment_request.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 287e00f70f..e93ec951fb 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -140,9 +140,6 @@ class PaymentRequest(Document): }) def set_as_paid(self): - if frappe.session.user == "Guest": - frappe.set_user("Administrator") - payment_entry = self.create_payment_entry() self.make_invoice() @@ -254,7 +251,7 @@ class PaymentRequest(Document): if status in ["Authorized", "Completed"]: redirect_to = None - self.run_method("set_as_paid") + self.set_as_paid() # if shopping cart enabled and in session if (shopping_cart_settings.enabled and hasattr(frappe.local, "session") From abc3227480b772189f65bfc4bc17123ab37f74a8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 24 Jul 2020 21:02:15 +0530 Subject: [PATCH 0133/1264] chore: Added change log --- erpnext/change_log/v13/v13_0_0-beta_4.md | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0-beta_4.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_4.md b/erpnext/change_log/v13/v13_0_0-beta_4.md new file mode 100644 index 0000000000..b835cec211 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0-beta_4.md @@ -0,0 +1,72 @@ +### Version 13.0.0 Beta 4 Release Notes + +#### Features and Enhancements +- New and refreshed POS ([#20789](https://github.com/frappe/erpnext/pull/20789)) +- Introduced Dunning ([#22559](https://github.com/frappe/erpnext/pull/22559)) +- Taxjar Integration ([#21047](https://github.com/frappe/erpnext/pull/21047)) +- Patient Progress Page ([#22474](https://github.com/frappe/erpnext/pull/22474)) +- Provision to make RFQ against Opportunity ([#22765](https://github.com/frappe/erpnext/pull/22765)) +- Added form dashboards and refactored custom buttons in Education module ([#22727](https://github.com/frappe/erpnext/pull/22727)) +- Student Attendance and Leave Enhancements ([#22623](https://github.com/frappe/erpnext/pull/22623)) +- Recruitment analytics ([#21732](https://github.com/frappe/erpnext/pull/21732)) +- Add medical coding fields to Healthcare DocTypes ([#22501](https://github.com/frappe/erpnext/pull/22501)) +- Autofill Supplier pop-up when only 1 Supplier in RFQ ([#22512](https://github.com/frappe/erpnext/pull/22512)) +- Accounting entries for service item in Purchase receipt ([#22223](https://github.com/frappe/erpnext/pull/22223)) +- Enhancement in subscription ([#22263](https://github.com/frappe/erpnext/pull/22263)) +- Laboratory Module Enhancements ([#22416](https://github.com/frappe/erpnext/pull/22416)) +- Added columns to get complete analysis for material request ([#22607](https://github.com/frappe/erpnext/pull/22607)) +- Added all companies option in employee tree to view employee across all companies ([#22573](https://github.com/frappe/erpnext/pull/22573)) +- Email Group Option In Email Campaign ([#22731](https://github.com/frappe/erpnext/pull/22731)) +- Added range for age in stock ageing ([#22622](https://github.com/frappe/erpnext/pull/22622)) +- Refactored shopping cart ([#22617](https://github.com/frappe/erpnext/pull/22617)) + +#### Fixes: +- Enable show_configure_button when shopping cart is enabled ([#22468](https://github.com/frappe/erpnext/pull/22468)) +- Setup status indicators for Job Offer and Job Applicant (develop) ([#22445](https://github.com/frappe/erpnext/pull/22445)) +- Item-wise sales history report ([#22783](https://github.com/frappe/erpnext/pull/22783)) +- Setting filter for project in kanban board ([#22717](https://github.com/frappe/erpnext/pull/22717)) +- Dashboard For Timesheet ([#22750](https://github.com/frappe/erpnext/pull/22750)) +- Handle custom statuses for the pause SLA configuration ([#22349](https://github.com/frappe/erpnext/pull/22349)) +- Quality Feedback and Template ([#22571](https://github.com/frappe/erpnext/pull/22571)) +- Unable to change link from new lead to existing customer ([#22787](https://github.com/frappe/erpnext/pull/22787)) +- Job applicant fixes ([#22448](https://github.com/frappe/erpnext/pull/22448)) +- Move Issue List actions under 'Actions' dropdown (ux) ([#22710](https://github.com/frappe/erpnext/pull/22710)) +- Cost center should only show option of selected company ([#22598](https://github.com/frappe/erpnext/pull/22598)) +- Serial No Rename does not affect Stock Ledger Entry ([#22746](https://github.com/frappe/erpnext/pull/22746)) +- Descriptions not copied while creating Fees from Fee Structure ([#22792](https://github.com/frappe/erpnext/pull/22792)) +- Company filter for cost_center and expense_account in all sales and purchase transactions ([#22478](https://github.com/frappe/erpnext/pull/22478)) +- Arrangements of filters for reports accounts payable & receivable ([#22636](https://github.com/frappe/erpnext/pull/22636)) +- Update the project after task deletion so that the % completed shows correct value ([#22591](https://github.com/frappe/erpnext/pull/22591)) +- Block Invalid Serial No updates in Maintenance Schedule ([#22665](https://github.com/frappe/erpnext/pull/22665)) +- Fetch item price in sales invoice based on it's validity ([#22563](https://github.com/frappe/erpnext/pull/22563)) +- Add view ledger button for cancelled docs ([#22432](https://github.com/frappe/erpnext/pull/22432)) +- Allow creating SLA documents even if SLA tracking is not enabled ([#22608](https://github.com/frappe/erpnext/pull/22608)) +- Quotation list view blank if quotation_to field not set as a standard filter ([#22672](https://github.com/frappe/erpnext/pull/22672)) +- Salary deductions report fixes ([#22397](https://github.com/frappe/erpnext/pull/22397)) +22727)) +- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22631](https://github.com/frappe/erpnext/pull/22631)) +- Moved parent warehouse to top section also added a section break ([#22708](https://github.com/frappe/erpnext/pull/22708)) +- Skip Progress and Completed by fields on Task Duplication ([#22565](https://github.com/frappe/erpnext/pull/22565)) +- Incorrect stock after merging the items ([#22526](https://github.com/frappe/erpnext/pull/22526)) +- Letter head not found in opening invoice creation tool ([#22488](https://github.com/frappe/erpnext/pull/22488)) +- Cannot cancel asset and asset movement ([#22441](https://github.com/frappe/erpnext/pull/22441)) +- Fetch project-related info in Timesheet ([#22423](https://github.com/frappe/erpnext/pull/22423)) +- Currency symbol not showing as per company currency in stock balance report ([#22724](https://github.com/frappe/erpnext/pull/22724)) +- Add default cost center in payment reconciliation JV ([#22614](https://github.com/frappe/erpnext/pull/22614)) +- Stock Reconciliation Invalid Quantity for Batched Item ([#22726](https://github.com/frappe/erpnext/pull/22726)) +- Project link not set in accounts other than profit and loss accounts ([#22051](https://github.com/frappe/erpnext/pull/22051)) +- Buying price for non stock item in gross profit report ([#22616](https://github.com/frappe/erpnext/pull/22616)) +- Heatmap in Vehicle ([#22743](https://github.com/frappe/erpnext/pull/22743)) +- Added Project Field in Purchase Receipt for Stock Ledger Tagging ([#22666](https://github.com/frappe/erpnext/pull/22666)) +- Multi currency payment reconciliation ([#22738](https://github.com/frappe/erpnext/pull/22738)) +- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440)) +- Reset homepage to home after unchecking products page ([#22736](https://github.com/frappe/erpnext/pull/22736)) +- Add project filter in parent task field ([#22655](https://github.com/frappe/erpnext/pull/22655)) +- Generic Message in previous doc validation for buying and selling ([#22546](https://github.com/frappe/erpnext/pull/22546)) +- Cess amount in GSTR 3B report ([#22701](https://github.com/frappe/erpnext/pull/22701)) +- Expense claim outstanding while making payment entry ([#22735](https://github.com/frappe/erpnext/pull/22735)) +- Take parent cost center for child if no cost center at child in expense claim ([#22496](https://github.com/frappe/erpnext/pull/22496)) +- Consider company fiscal year for getting balance ([#22577](https://github.com/frappe/erpnext/pull/22577)) +- Pick List empty table and Serial-Batch items handling ([#22426](https://github.com/frappe/erpnext/pull/22426)) +- Show total row in print format of financial statement ([#22693](https://github.com/frappe/erpnext/pull/22693)) +- Set Root as Parent if no parent in new tree view node ([#22497](https://github.com/frappe/erpnext/pull/22497)) \ No newline at end of file From fa28e600b5c32066cb12ae6c4641abd5218341a9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 28 Jul 2020 08:49:21 +0530 Subject: [PATCH 0134/1264] fix: Unable to submit backdated stock transactions for different items (#22822) * fix: Unable to submit backdated stock transactions for different items * fix: Test cases * fix: Test Cases * fix: Test Cases * fix: Test for stock account JV * fix: Journal Entry Test * fix: Delete unwanted code --- .../doctype/coupon_code/test_coupon_code.py | 27 ++++++------ .../journal_entry/test_journal_entry.py | 42 +++++++++++++++---- erpnext/accounts/general_ledger.py | 5 ++- erpnext/stock/doctype/item/item.py | 4 +- .../doctype/stock_entry/test_stock_entry.py | 26 +++++++++--- 5 files changed, 75 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py index 990b896fde..3a0d4162ae 100644 --- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py @@ -26,22 +26,22 @@ def test_create_test_data(): "item_group": "_Test Item Group", "item_name": "_Test Tesla Car", "apply_warehouse_wise_reorder_level": 0, - "warehouse":"_Test Warehouse - _TC", + "warehouse":"Stores - TCP1", "gst_hsn_code": "999800", "valuation_rate": 5000, "standard_rate":5000, "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", + "company": "_Test Company with perpetual inventory", + "default_warehouse": "Stores - TCP1", "default_price_list":"_Test Price List", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" + "expense_account": "Cost of Goods Sold - TCP1", + "buying_cost_center": "Main - TCP1", + "selling_cost_center": "Main - TCP1", + "income_account": "Sales - TCP1" }], "show_in_website": 1, "route":"-test-tesla-car", - "website_warehouse": "_Test Warehouse - _TC" + "website_warehouse": "Stores - TCP1" }) item.insert() # create test item price @@ -63,12 +63,12 @@ def test_create_test_data(): "items": [{ "item_code": "_Test Tesla Car" }], - "warehouse":"_Test Warehouse - _TC", + "warehouse":"Stores - TCP1", "coupon_code_based":1, "selling": 1, "rate_or_discount": "Discount Percentage", "discount_percentage": 30, - "company": "_Test Company", + "company": "_Test Company with perpetual inventory", "currency":"INR", "for_price_list":"_Test Price List" }) @@ -112,7 +112,10 @@ class TestCouponCode(unittest.TestCase): self.assertEqual(coupon_code.get("used"),0) def test_2_sales_order_with_coupon_code(self): - so = make_sales_order(customer="_Test Customer",selling_price_list="_Test Price List",item_code="_Test Tesla Car", rate=5000,qty=1, do_not_submit=True) + so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', + customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1, + do_not_submit=True) + so = frappe.get_doc('Sales Order', so.name) # check item price before coupon code is applied self.assertEqual(so.items[0].rate, 5000) @@ -120,7 +123,7 @@ class TestCouponCode(unittest.TestCase): so.sales_partner='_Test Coupon Partner' so.save() # check item price after coupon code is applied - self.assertEqual(so.items[0].rate, 3500) + self.assertEqual(so.items[0].rate, 3500) so.submit() def test_3_check_coupon_code_used_after_so(self): diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 23ad1eef14..479d4b64bb 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -6,6 +6,7 @@ import unittest, frappe from frappe.utils import flt, nowdate from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.exceptions import InvalidAccountCurrency +from erpnext.accounts.general_ledger import StockAccountInvalidTransaction class TestJournalEntry(unittest.TestCase): def test_journal_entry_with_against_jv(self): @@ -81,19 +82,46 @@ class TestJournalEntry(unittest.TestCase): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory set_perpetual_inventory() - jv = frappe.copy_doc(test_records[0]) + jv = frappe.copy_doc({ + "cheque_date": nowdate(), + "cheque_no": "33", + "company": "_Test Company with perpetual inventory", + "doctype": "Journal Entry", + "accounts": [ + { + "account": "Debtors - TCP1", + "party_type": "Customer", + "party": "_Test Customer", + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "Main - TCP1" + }, + { + "account": "_Test Bank - TCP1", + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "Main - TCP1" + } + ], + "naming_series": "_T-Journal Entry-", + "posting_date": nowdate(), + "user_remark": "test", + "voucher_type": "Bank Entry" + }) + jv.get("accounts")[0].update({ - "account": get_inventory_account('_Test Company'), - "company": "_Test Company", + "account": get_inventory_account('_Test Company with perpetual inventory'), + "company": "_Test Company with perpetual inventory", "party_type": None, "party": None }) - jv.insert() - - from erpnext.accounts.general_ledger import StockAccountInvalidTransaction self.assertRaises(StockAccountInvalidTransaction, jv.submit) - + jv.cancel() set_perpetual_inventory(0) def test_multi_currency(self): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a245d63f52..cf3deb828f 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -158,8 +158,10 @@ def validate_account_for_perpetual_inventory(gl_map): if account not in aii_accounts: continue + # Always use current date to get stock and account balance as there can future entries for + # other items account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, - gl_map[0].posting_date, gl_map[0].company) + getdate(), gl_map[0].company) if gl_map[0].voucher_type=="Journal Entry": # In case of Journal Entry, there are no corresponding SL entries, @@ -169,7 +171,6 @@ def validate_account_for_perpetual_inventory(gl_map): frappe.throw(_("Account: {0} can only be updated via Stock Transactions") .format(account), StockAccountInvalidTransaction) - # This has been comment for a temporary, will add this code again on release of immutable ledger elif account_bal != stock_bal: precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index d5f479ff82..991ec4743d 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, get_link_to_form) + now_datetime, random_string, strip, get_link_to_form, nowtime) from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ get_slideshow @@ -194,7 +194,7 @@ class Item(WebsiteGenerator): if default_warehouse: stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse, qty=self.opening_stock, - rate=self.valuation_rate, company=default.company) + rate=self.valuation_rate, company=default.company, posting_date=getdate(), posting_time=nowtime()) stock_entry.add_comment("Comment", _("Opening Stock")) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 0fbc63101e..8e25804511 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -413,7 +413,7 @@ class TestStockEntry(unittest.TestCase): def test_serial_item_error(self): se, serial_nos = self.test_serial_by_series() if not frappe.db.exists('Serial No', 'ABCD'): - make_serialized_item("_Test Serialized Item", "ABCD\nEFGH") + make_serialized_item(item_code="_Test Serialized Item", serial_no="ABCD\nEFGH") se = frappe.copy_doc(test_records[0]) se.purpose = "Material Transfer" @@ -823,15 +823,29 @@ class TestStockEntry(unittest.TestCase): ]) ) -def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): +def make_serialized_item(**args): + args = frappe._dict(args) se = frappe.copy_doc(test_records[0]) - se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series" - se.get("items")[0].serial_no = serial_no + + if args.company: + se.company = args.company + + se.get("items")[0].item_code = args.item_code or "_Test Serialized Item With Series" + + if args.serial_no: + se.get("items")[0].serial_no = args.serial_no + + if args.cost_center: + se.get("items")[0].cost_center = args.cost_center + + if args.expense_account: + se.get("items")[0].expense_account = args.expense_account + se.get("items")[0].qty = 2 se.get("items")[0].transfer_qty = 2 - if target_warehouse: - se.get("items")[0].t_warehouse = target_warehouse + if args.target_warehouse: + se.get("items")[0].t_warehouse = args.target_warehouse se.set_stock_entry_type() se.insert() From b7602d29f2f52a5bfb6a7ac4628eb5c113fa5466 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 28 Jul 2020 09:13:42 +0530 Subject: [PATCH 0135/1264] fix: POS patch fix (#22827) --- erpnext/patches/v12_0/rename_pos_closing_doctype.py | 4 ++-- erpnext/patches/v13_0/replace_pos_payment_mode_table.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v12_0/rename_pos_closing_doctype.py b/erpnext/patches/v12_0/rename_pos_closing_doctype.py index 8ca92ef65c..0577f81234 100644 --- a/erpnext/patches/v12_0/rename_pos_closing_doctype.py +++ b/erpnext/patches/v12_0/rename_pos_closing_doctype.py @@ -12,11 +12,11 @@ def execute(): 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.rename_doc('DocType', 'POS Closing Voucher Details', 'POS Closing Entry Detail', 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') + frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Detail') if frappe.db.exists("DocType", "POS Closing Voucher"): frappe.delete_doc("DocType", "POS Closing Voucher") diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py index 4a621b6a51..1ca211bf1b 100644 --- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py +++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("Selling", "doctype", "POS Payment Method") + frappe.reload_doc("accounts", "doctype", "POS Payment Method") pos_profiles = frappe.get_all("POS Profile") for pos_profile in pos_profiles: From 60cdef7d5751954c3ed44b8d681023a73edebb1e Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Jul 2020 14:51:38 +0530 Subject: [PATCH 0136/1264] fix: add range filters to oldest items chart --- erpnext/stock/dashboard_chart/oldest_items/oldest_items.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json index 6da3b28baf..9c10a5346b 100644 --- a/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json +++ b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json @@ -6,11 +6,11 @@ "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}", + "filters_json": "{\"range1\":30,\"range2\":60,\"range3\":90,\"show_warehouse_wise_stock\":0}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 13:04:36.271198", + "modified": "2020-07-29 14:50:26.846482", "modified_by": "Administrator", "module": "Stock", "name": "Oldest Items", From 38d5c2e04fa94b9b00dd84ff3d6256dccbf16e38 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 30 Jul 2020 20:52:20 +0530 Subject: [PATCH 0137/1264] fix: Ignore cancelled gl entries to get account balance --- erpnext/accounts/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 824b2f2efb..51ac7cfbfa 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -122,7 +122,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company cost_center = frappe.form_dict.get("cost_center") - cond = [] + cond = ["is_cancelled=0"] if date: cond.append("posting_date <= %s" % frappe.db.escape(cstr(date))) else: @@ -206,7 +206,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company return flt(bal) def get_count_on(account, fieldname, date): - cond = [] + cond = ["is_cancelled=0"] if date: cond.append("posting_date <= %s" % frappe.db.escape(cstr(date))) else: From 804426023f7bd6d0b1a9a865bcc94aa3c4c464bc Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 3 Aug 2020 08:57:49 +0530 Subject: [PATCH 0138/1264] fix: minor payroll_dashboard fixes (#22832) * fix: minor payroll_dashboard fixes * Update erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js Co-authored-by: Rucha Mahabal Co-authored-by: Nabin Hait Co-authored-by: Rucha Mahabal --- .../job_application_status/job_application_status.json | 6 +++--- .../monthly_attendance_sheet.js | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) 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 index bfcfa96752..42a830970e 100644 --- a/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json +++ b/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json @@ -7,14 +7,14 @@ "doctype": "Dashboard Chart", "document_type": "Job Applicant", "dynamic_filters_json": "", - "filters_json": "[[\"Job Applicant\",\"creation\",\"Previous\",\"1 month\"]]", + "filters_json": "[[\"Job Applicant\",\"creation\",\"Timespan\",\"last month\",false]]", "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", + "last_synced_on": "2020-07-28 16:19:12.109979", + "modified": "2020-07-28 16:19:45.279490", "modified_by": "Administrator", "module": "HR", "name": "Job Application Status", 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 4b9b928640..42f7cdb50f 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js @@ -35,7 +35,15 @@ frappe.query_reports["Monthly Attendance Sheet"] = { "fieldname":"employee", "label": __("Employee"), "fieldtype": "Link", - "options": "Employee" + "options": "Employee", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company + } + }; + } }, { "fieldname":"company", From 826158c79235684addbcbfaad5247471a43dc1ee Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 3 Aug 2020 09:34:55 +0530 Subject: [PATCH 0139/1264] fix: Update modified timestamp in accounts settings (#22874) --- .../accounts/doctype/accounts_settings/accounts_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 8ca8b71ef8..b2e8b090c7 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -225,7 +225,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2020-06-22 20:13:26.043092", + "modified": "2020-08-03 20:13:26.043092", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 1a64d1a4ab299477c15f9200c79be2ee64aa3b72 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 3 Aug 2020 10:43:56 +0530 Subject: [PATCH 0140/1264] Reset home page based on product settings (#22876) --- erpnext/portal/doctype/products_settings/products_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/portal/doctype/products_settings/products_settings.py b/erpnext/portal/doctype/products_settings/products_settings.py index b984aeb67d..ae7dc68020 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.py +++ b/erpnext/portal/doctype/products_settings/products_settings.py @@ -11,9 +11,9 @@ from frappe.model.document import Document class ProductsSettings(Document): def validate(self): if self.home_page_is_products: - frappe.db.set_value("Website Settings", "home_page", "products") + frappe.db.set_value("Website Settings", None, "home_page", "products") elif frappe.db.get_single_value("Website Settings", "home_page") == 'products': - frappe.db.set_value("Website Settings", "home_page", "home") + frappe.db.set_value("Website Settings", None, "home_page", "home") self.validate_field_filters() self.validate_attribute_filters() From 4e0989d0cb35563c3658a4aa742286ddd6490b67 Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 3 Aug 2020 14:56:20 +0530 Subject: [PATCH 0141/1264] fix: Misleading description in Warehouse (#22878) --- erpnext/stock/doctype/warehouse/warehouse.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index 3b49c4ca52..c0c9fbc92d 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.json @@ -45,7 +45,6 @@ "oldfieldtype": "Section Break" }, { - "description": "If blank, parent Warehouse Account or company default will be considered", "fieldname": "warehouse_name", "fieldtype": "Data", "label": "Warehouse Name", @@ -236,7 +235,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-07-16 15:43:50.653256", + "modified": "2020-08-03 11:19:35.943330", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse", From b44ee186cd8be03034c6badecbed0de074649248 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Mon, 3 Aug 2020 15:18:35 +0530 Subject: [PATCH 0142/1264] fix: check if row['delay'] exists (#22892) --- .../selling/report/sales_order_analysis/sales_order_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 7e8e6e9e8b..f5feb95f1a 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -96,7 +96,7 @@ def prepare_data(data, filters): # prepare data for report view row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"]) - row["delay"] = 0 if row["delay"] < 0 else row["delay"] + row["delay"] = 0 if row["delay"] and row["delay"] < 0 else row["delay"] if filters.get("group_by_so"): so_name = row["sales_order"] From 25ee4328d35eb6dcf1c756ad78c6e0b213e0ccbe Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 3 Aug 2020 19:39:55 +0530 Subject: [PATCH 0143/1264] card company issue (#22900) --- .../shopping_cart_settings.js | 6 ++++++ .../shopping_cart_settings.json | 13 +++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) 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 14500ba6b3..21fa4c3065 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 @@ -12,5 +12,11 @@ frappe.ui.form.on("Shopping Cart Settings", { if (frm.doc.enabled === 1) { frm.set_value('enable_variants', 1); } + else { + frm.set_value('company', ''); + frm.set_value('price_list', ''); + frm.set_value('default_customer_group', ''); + frm.set_value('quotation_series', ''); + } } }); 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 c574afa68c..271895f2a4 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 @@ -95,15 +95,16 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Company", + "mandatory_depends_on": "eval: doc.enabled === 1", "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1 + "remember_last_selected_value": 1 }, { "description": "Prices will not be shown if Price List is not set", "fieldname": "price_list", "fieldtype": "Link", "label": "Price List", + "mandatory_depends_on": "eval: doc.enabled === 1", "options": "Price List" }, { @@ -115,14 +116,14 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Default Customer Group", - "options": "Customer Group", - "reqd": 1 + "mandatory_depends_on": "eval: doc.enabled === 1", + "options": "Customer Group" }, { "fieldname": "quotation_series", "fieldtype": "Select", "label": "Quotation Series", - "reqd": 1 + "mandatory_depends_on": "eval: doc.enabled === 1" }, { "collapsible": 1, @@ -171,7 +172,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2020-07-17 17:53:22.667228", + "modified": "2020-08-03 19:32:27.958221", "modified_by": "Administrator", "module": "Shopping Cart", "name": "Shopping Cart Settings", From cb665dc4d1f5f1df12c716303d548312192682df Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 3 Aug 2020 19:43:17 +0530 Subject: [PATCH 0144/1264] fix: Assessment Result child table not visible when accessed via dashboard (#22881) --- .../assessment_result/assessment_result.js | 11 +- .../assessment_result/assessment_result.json | 648 ++---------------- .../assessment_result_detail.json | 242 ++----- 3 files changed, 111 insertions(+), 790 deletions(-) diff --git a/erpnext/education/doctype/assessment_result/assessment_result.js b/erpnext/education/doctype/assessment_result/assessment_result.js index 12fdd91c25..63d1aee0cb 100644 --- a/erpnext/education/doctype/assessment_result/assessment_result.js +++ b/erpnext/education/doctype/assessment_result/assessment_result.js @@ -6,10 +6,11 @@ frappe.ui.form.on('Assessment Result', { if (!frm.doc.__islocal) { frm.trigger('setup_chart'); } + frm.set_df_property('details', 'read_only', 1); }, onload: function(frm) { - frm.set_query('assessment_plan', function(){ + frm.set_query('assessment_plan', function() { return { filters: { docstatus: 1 @@ -27,14 +28,14 @@ frappe.ui.form.on('Assessment Result', { }, callback: function(r) { if (r.message) { - frm.doc.details = []; + frappe.model.clear_table(frm.doc, 'details'); $.each(r.message, function(i, d) { - var row = frappe.model.add_child(frm.doc, 'Assessment Result Detail', 'details'); + var row = frm.add_child('details'); row.assessment_criteria = d.assessment_criteria; row.maximum_score = d.maximum_score; }); + frm.refresh_field('details'); } - refresh_field('details'); } }); } @@ -80,7 +81,7 @@ 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) { + if (!d.maximum_score || !frm.doc.grading_scale) { d.score = ''; frappe.throw(__('Please fill in all the details to generate Assessment Result.')); } diff --git a/erpnext/education/doctype/assessment_result/assessment_result.json b/erpnext/education/doctype/assessment_result/assessment_result.json index 212d47cff0..7a893aabb8 100644 --- a/erpnext/education/doctype/assessment_result/assessment_result.json +++ b/erpnext/education/doctype/assessment_result/assessment_result.json @@ -1,724 +1,182 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, - "allow_rename": 0, "autoname": "EDU-RES-.YYYY.-.#####", - "beta": 0, "creation": "2015-11-13 17:18:06.468332", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "assessment_plan", + "program", + "course", + "academic_year", + "academic_term", + "column_break_3", + "student", + "student_name", + "student_group", + "assessment_group", + "grading_scale", + "section_break_5", + "details", + "section_break_8", + "maximum_score", + "column_break_11", + "total_score", + "grade", + "section_break_13", + "comment", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "assessment_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": "Assessment Plan", - "length": 0, - "no_copy": 0, "options": "Assessment 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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.program", "fieldname": "program", "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": "Program", - "length": 0, - "no_copy": 0, - "options": "Program", - "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 + "options": "Program" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.course", "fieldname": "course", "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": "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Course" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.academic_year", "fieldname": "academic_year", "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": "Academic Year", - "length": 0, - "no_copy": 0, - "options": "Academic Year", - "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 + "options": "Academic Year" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.academic_term", "fieldname": "academic_term", "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": "Academic Term", - "length": 0, - "no_copy": 0, - "options": "Academic Term", - "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 + "options": "Academic Term" }, { - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "student", "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": "Student", - "length": 0, - "no_copy": 0, "options": "Student", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "student.title", "fieldname": "student_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, - "in_standard_filter": 0, "label": "Student 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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.student_group", "fieldname": "student_group", "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": "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Student Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.assessment_group", "fieldname": "assessment_group", "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": "Assessment Group", - "length": 0, - "no_copy": 0, - "options": "Assessment 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Assessment Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.grading_scale", "fieldname": "grading_scale", "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": "Grading Scale", - "length": 0, - "no_copy": 0, "options": "Grading Scale", - "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 + "read_only": 1 }, { - "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, - "label": "Result", - "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 + "label": "Result" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "fieldname": "details", "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": "Details", - "length": 0, - "no_copy": 0, "options": "Assessment Result Detail", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "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, - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.maximum_assessment_score", "fieldname": "maximum_score", "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": "Maximum Score", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assessment_plan.maximum_assessment_score", "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_score", "fieldtype": "Float", - "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": "Total Score", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "grade", "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": "Grade", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "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": "Summary", - "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 + "label": "Summary" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "comment", "fieldtype": "Small Text", - "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": "Comment", - "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 + "label": "Comment" }, { - "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": "Assessment Result", - "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 + "read_only": 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": "2018-08-30 02:10:36.813413", + "links": [], + "modified": "2020-08-03 11:47:54.119486", "modified_by": "Administrator", "module": "Education", "name": "Assessment Result", - "name_case": "", "owner": "Administrator", "permissions": [ { @@ -728,28 +186,18 @@ "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": 1, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "restrict_to_domain": "Education", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "student_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "title_field": "student_name" } \ No newline at end of file diff --git a/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json b/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json index 85d943beaa..450f41cbbb 100644 --- a/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json +++ b/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json @@ -1,194 +1,66 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2016-12-14 17:44:35.583123", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2016-12-14 17:44:35.583123", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_criteria", + "maximum_score", + "column_break_2", + "score", + "grade" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 4, - "fieldname": "assessment_criteria", - "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": "Assessment Criteria", - "length": 0, - "no_copy": 0, - "options": "Assessment Criteria", - "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, - "unique": 0 - }, + "columns": 4, + "fieldname": "assessment_criteria", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Criteria", + "options": "Assessment Criteria", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "maximum_score", - "fieldtype": "Float", - "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": "Maximum Score", - "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, - "unique": 0 - }, + "columns": 2, + "fieldname": "maximum_score", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Maximum Score", + "read_only": 1 + }, { - "allow_bulk_edit": 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, - "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, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "score", - "fieldtype": "Float", - "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": "Score", - "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 - }, + "columns": 2, + "fieldname": "score", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Score", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "grade", - "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": "Grade", - "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, - "unique": 0 + "columns": 2, + "fieldname": "grade", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Grade", + "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": "2017-11-10 19:11:14.362410", - "modified_by": "Administrator", - "module": "Education", - "name": "Assessment Result Detail", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-07-31 13:27:17.699022", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Result Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From 5e548b43875b85ebe92c14dd2c292c34bffa0332 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 3 Aug 2020 20:39:15 +0530 Subject: [PATCH 0145/1264] Dunning cleanup beta (#22899) * fix: Dunning cleanup * fix: Added dashboard for Dunning --- erpnext/accounts/doctype/dunning/dunning.js | 19 ++++++++++++++++--- erpnext/accounts/doctype/dunning/dunning.json | 8 ++++---- erpnext/accounts/doctype/dunning/dunning.py | 16 +++++++++------- .../doctype/dunning/dunning_dashboard.py | 17 +++++++++++++++++ .../sales_invoice/sales_invoice_dashboard.py | 2 +- 5 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 erpnext/accounts/doctype/dunning/dunning_dashboard.py diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index c563368894..9909c6c2ab 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -44,6 +44,19 @@ frappe.ui.form.on("Dunning", { ); frm.page.set_inner_btn_group_as_primary(__("Create")); } + + if(frm.doc.docstatus > 0) { + frm.add_custom_button(__('Ledger'), function() { + frappe.route_options = { + "voucher_no": frm.doc.name, + "from_date": frm.doc.posting_date, + "to_date": frm.doc.posting_date, + "company": frm.doc.company, + "show_cancelled_entries": frm.doc.docstatus === 2 + }; + frappe.set_route("query-report", "General Ledger"); + }, __('View')); + } }, overdue_days: function (frm) { frappe.db.get_value( @@ -125,9 +138,9 @@ frappe.ui.form.on("Dunning", { }, 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; + const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount')); + const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount')); + const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total')); frm.set_value("interest_amount", interest_amount); frm.set_value("dunning_amount", dunning_amount); frm.set_value("grand_total", grand_total); diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index b3eddf5f22..d55bfd1ac4 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -29,10 +29,10 @@ "company_address_display", "section_break_6", "dunning_type", - "interest_amount", + "dunning_fee", "column_break_8", "rate_of_interest", - "dunning_fee", + "interest_amount", "section_break_12", "dunning_amount", "grand_total", @@ -215,7 +215,7 @@ }, { "default": "0", - "fetch_from": "dunning_type.interest_rate", + "fetch_from": "dunning_type.rate_of_interest", "fetch_if_empty": 1, "fieldname": "rate_of_interest", "fieldtype": "Float", @@ -315,7 +315,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-07-21 18:20:23.512151", + "modified": "2020-08-03 18:55:43.683053", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning", diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 0be6a480c9..3e372affa1 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import json from six import string_types -from frappe.utils import getdate, get_datetime, rounded, flt +from frappe.utils import getdate, get_datetime, rounded, flt, cint 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 @@ -27,11 +27,11 @@ class Dunning(AccountsController): 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') + self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount')) if self.dunning_amount != amounts.get('dunning_amount'): - self.dunning_amount = amounts.get('dunning_amount') + self.dunning_amount = flt(amounts.get('dunning_amount'), self.precision('dunning_amount')) if self.grand_total != amounts.get('grand_total'): - self.grand_total = amounts.get('grand_total') + self.grand_total = flt(amounts.get('grand_total'), self.precision('grand_total')) def on_submit(self): self.make_gl_entries() @@ -47,10 +47,13 @@ class Dunning(AccountsController): 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, @@ -91,9 +94,8 @@ def resolve_dunning(doc, state): 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) + interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100 + interest_amount = (interest_per_year * cint(overdue_days)) / 365 grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee) dunning_amount = flt(interest_amount) + flt(dunning_fee) return { diff --git a/erpnext/accounts/doctype/dunning/dunning_dashboard.py b/erpnext/accounts/doctype/dunning/dunning_dashboard.py new file mode 100644 index 0000000000..19a73ddfa4 --- /dev/null +++ b/erpnext/accounts/doctype/dunning/dunning_dashboard.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'dunning', + 'non_standard_fieldnames': { + 'Journal Entry': 'reference_name', + 'Payment Entry': 'reference_name' + }, + 'transactions': [ + { + 'label': _('Payment'), + 'items': ['Payment Entry', 'Journal Entry'] + } + ] + } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index 4a8fcc03fd..f1069282ed 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -18,7 +18,7 @@ def get_data(): 'transactions': [ { 'label': _('Payment'), - 'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting'] + 'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting', 'Dunning'] }, { 'label': _('Reference'), From a1d3537f2a151d364803decee9ab693fa7cd7b69 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 3 Aug 2020 20:42:00 +0530 Subject: [PATCH 0146/1264] fix: Bank Clearance of POS purchase invoice (#22883) --- .../doctype/bank_clearance/bank_clearance.py | 21 +++++++++++++++---- .../purchase_invoice/purchase_invoice.json | 8 ++++--- .../sales_invoice_payment.json | 3 ++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 6fec3ab368..76d82e7339 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -60,12 +60,12 @@ class BankClearance(Document): """.format(condition=condition), {"account": self.account, "from":self.from_date, "to": self.to_date, "bank_account": self.bank_account}, as_dict=1) - pos_entries = [] + pos_sales_invoices, pos_purchase_invoices = [], [] if self.include_pos_transactions: - pos_entries = frappe.db.sql(""" + pos_sales_invoices = frappe.db.sql(""" select "Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit, - si.posting_date, si.debit_to as against_account, sip.clearance_date, + si.posting_date, si.customer as against_account, sip.clearance_date, account.account_currency, 0 as credit from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account where @@ -75,7 +75,20 @@ class BankClearance(Document): si.posting_date ASC, si.name DESC """, {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1) - entries = sorted(list(payment_entries)+list(journal_entries+list(pos_entries)), + pos_purchase_invoices = frappe.db.sql(""" + select + "Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit, + pi.posting_date, pi.supplier as against_account, pi.clearance_date, + account.account_currency, 0 as debit + from `tabPurchase Invoice` pi, `tabAccount` account + where + pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account + and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s + order by + pi.posting_date ASC, pi.name DESC + """, {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) + + entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)), key=lambda k: k['posting_date'] or getdate(nowdate())) self.set('payment_entries', []) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index df77dc8417..2e91c8ef19 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -969,8 +969,10 @@ { "fieldname": "clearance_date", "fieldtype": "Date", - "hidden": 1, - "label": "Clearance Date" + "label": "Clearance Date", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 }, { "fieldname": "col_br_payments", @@ -1332,7 +1334,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2020-07-24 09:46:40.405463", + "modified": "2020-08-03 12:46:01.411074", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", 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 2f9d381c92..5ab46b7fd5 100644 --- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json +++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json @@ -64,6 +64,7 @@ "fieldname": "clearance_date", "fieldtype": "Date", "label": "Clearance Date", + "no_copy": 1, "print_hide": 1, "read_only": 1 }, @@ -78,7 +79,7 @@ ], "istable": 1, "links": [], - "modified": "2020-05-05 16:51:20.091441", + "modified": "2020-08-03 12:45:39.986598", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Payment", From 6823e8ed034665459b074d5cb9cf052cb7947610 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 4 Aug 2020 14:04:15 +0530 Subject: [PATCH 0147/1264] fix: Remove Duplicate Banket order method --- erpnext/controllers/queries.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 5c59fe0e1d..b402204072 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -395,20 +395,6 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "parent_account"], limit_start=start, limit_page_length=page_len, as_list=True) -def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date - from `tabBlanket Order` bo, `tabBlanket Order Item` boi - where - boi.parent = bo.name - and boi.item_code = {item_code} - and bo.blanket_order_type = '{blanket_order_type}' - and bo.company = {company} - and bo.docstatus = 1""" - .format(item_code = frappe.db.escape(filters.get("item")), - blanket_order_type = filters.get("blanket_order_type"), - company = frappe.db.escape(filters.get("company")) - )) - @frappe.whitelist() def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date From 8d055142608a403e205fe81dd7da961c97930d25 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 5 Aug 2020 20:07:30 +0530 Subject: [PATCH 0148/1264] refactor: Format and sanitise user inputs to search queries. (#22922) * refactor: Sanitize whitelisted method inputs Co-authored-by: Prssanna Desai Co-authored-by: Shivam Mishra * refactor: Format and sanitize tax_account_query inputs Co-authored-by: Nabin Hait Co-authored-by: Prssanna Desai Co-authored-by: Shivam Mishra * refactor: Validate and sanitize search inputs via decorator Co-authored-by: Nabin Hait Co-authored-by: Prssanna Desai Co-authored-by: Shivam Mishra * style: Minor formatting fix * refactor: Validate and sanitize search inputs using decorator * fix: Typo * fix: Remove unwanted import statement * refactor: Repalce validate_and_sanitize_search_inputs() with validate_and_sanitize_search_inputs Co-authored-by: Prssanna Desai Co-authored-by: Shivam Mishra Co-authored-by: Prssanna Desai Co-authored-by: Shivam Mishra Co-authored-by: Nabin Hait --- erpnext/accounts/doctype/account/account.py | 2 + .../doctype/journal_entry/journal_entry.py | 32 +++++++-- .../doctype/payment_order/payment_order.py | 2 + .../pos_closing_entry/pos_closing_entry.py | 13 ++-- .../doctype/pos_profile/pos_profile.py | 1 + .../doctype/pricing_rule/pricing_rule.py | 12 ++-- .../bank_reconciliation.py | 3 + .../asset_maintenance/asset_maintenance.py | 1 + .../asset_maintenance_log.py | 1 + .../request_for_quotation.py | 1 + erpnext/controllers/queries.py | 71 ++++++++++++++----- .../program_enrollment/program_enrollment.py | 2 + .../doctype/student_group/student_group.py | 1 + .../healthcare_practitioner.py | 1 + .../inpatient_record/inpatient_record.py | 1 + .../department_approver.py | 1 + erpnext/manufacturing/doctype/bom/bom.py | 1 + .../doctype/work_order/work_order.py | 1 + .../bom_variance_report.py | 5 +- .../production_planning_report.py | 3 - .../employee_benefit_application.py | 1 + .../doctype/payroll_entry/payroll_entry.py | 1 + erpnext/projects/doctype/project/project.py | 1 + erpnext/projects/doctype/task/task.py | 1 + .../projects/doctype/timesheet/timesheet.py | 1 + erpnext/projects/utils.py | 1 + erpnext/selling/doctype/customer/customer.py | 2 + .../doctype/product_bundle/product_bundle.py | 1 + .../doctype/sales_order/sales_order.py | 1 + .../page/point_of_sale/point_of_sale.py | 13 ++-- .../setup/doctype/party_type/party_type.py | 1 + .../item_alternative/item_alternative.py | 1 + .../material_request/material_request.py | 2 + .../doctype/packing_slip/packing_slip.py | 1 + .../quality_inspection/quality_inspection.py | 2 + 35 files changed, 138 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index c6de6410eb..164f120067 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -244,6 +244,8 @@ class Account(NestedSet): super(Account, self).on_trash(True) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_parent_account(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select name from tabAccount where is_group = 1 and docstatus != 2 and company = %s diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index cfdae936a4..dda17082a2 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -841,13 +841,33 @@ def get_opening_accounts(company): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_against_jv(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark - from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail - where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s - and (jv_detail.reference_type is null or jv_detail.reference_type = '') - and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(searchfield), - (filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len)) + if not frappe.db.has_column('Journal Entry', searchfield): + return [] + + return frappe.db.sql(""" + SELECT jv.name, jv.posting_date, jv.user_remark + FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail + WHERE jv_detail.parent = jv.name + AND jv_detail.account = %(account)s + AND IFNULL(jv_detail.party, '') = %(party)s + AND ( + jv_detail.reference_type IS NULL + OR jv_detail.reference_type = '' + ) + AND jv.docstatus = 1 + AND jv.`{0}` LIKE %(txt)s + ORDER BY jv.name DESC + LIMIT %(offset)s, %(limit)s + """.format(searchfield), dict( + account=filters.get("account"), + party=cstr(filters.get("party")), + txt="%{0}%".format(txt), + offset=start, + limit=page_len + ) + ) @frappe.whitelist() diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py index 4702e58cef..e5880aa67a 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.py +++ b/erpnext/accounts/doctype/payment_order/payment_order.py @@ -27,6 +27,7 @@ class PaymentOrder(Document): frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_mop_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference` where parent = %(parent)s and mode_of_payment like %(txt)s @@ -38,6 +39,7 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters): }) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_supplier_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" select supplier from `tabPayment Order Reference` where parent = %(parent)s and supplier like %(txt)s and diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 8eb0a222a4..9899219bdc 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -24,7 +24,7 @@ class POSClosingEntry(Document): 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")) @@ -41,6 +41,7 @@ class POSClosingEntry(Document): {"data": self, "currency": currency}) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs 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] @@ -48,12 +49,12 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() def get_pos_invoices(start, end, user): data = frappe.db.sql(""" - select + select name, timestamp(posting_date, posting_time) as "timestamp" - from + from `tabPOS Invoice` - where - owner = %s and docstatus = 1 and + where + owner = %s and docstatus = 1 and (consolidated_invoice is NULL or consolidated_invoice = '') """, (user), as_dict=1) @@ -101,7 +102,7 @@ def make_closing_entry_from_opening(opening_entry): 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); + existing_tax[0].amount += flt(t.tax_amount); else: taxes.append(frappe._dict({ 'account_head': t.account_head, diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 8655b4bf3a..789b4c3bd9 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -105,6 +105,7 @@ def get_series(): return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s" @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): user = frappe.session['user'] company = filters.get('company') or frappe.defaults.get_user_default('company') diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index d90ae28e5a..cff7d5ba22 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -433,14 +433,14 @@ def make_pricing_rule(doctype, docname): return doc @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_item_uoms(doctype, txt, searchfield, start, page_len, filters): items = [filters.get('value')] if filters.get('apply_on') != 'Item Code': field = frappe.scrub(filters.get('apply_on')) + items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get('value')})] - items = frappe.db.sql_list("""select name - from `tabItem` where {0} = %s""".format(field), filters.get('value')) - - return frappe.get_all('UOM Conversion Detail', - filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))}, - fields = ["distinct uom"], as_list=1) + return frappe.get_all('UOM Conversion Detail', filters={ + 'parent': ('in', items), + 'uom': ("like", "{0}%".format(txt)) + }, fields = ["distinct uom"], as_list=1) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 7df090bf62..ce6baa6846 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -290,6 +290,7 @@ def get_matching_transactions_payments(description_matching): return [] @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def payment_entry_query(doctype, txt, searchfield, start, page_len, filters): account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") if not account: @@ -319,6 +320,7 @@ def payment_entry_query(doctype, txt, searchfield, start, page_len, filters): ) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") @@ -355,6 +357,7 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): ) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" SELECT diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index 1869a29c8d..60c528bcc4 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -106,6 +106,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task): maintenance_log.save() @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_team_members(doctype, txt, searchfield, start, page_len, filters): return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") }) diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py index f169f01616..148357f392 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py @@ -41,6 +41,7 @@ class AssetMaintenanceLog(Document): asset_maintenance_doc.save() @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters): asset_maintenance_tasks = frappe.db.get_values('Asset Maintenance Task', {'parent':filters.get("asset_maintenance")}, 'maintenance_task') return asset_maintenance_tasks diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 4b852300e5..b54a585b97 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -207,6 +207,7 @@ def get_list_context(context=None): return list_context @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select `tabContact`.name from `tabContact`, `tabDynamic Link` where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index b402204072..babc5bdd79 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -12,6 +12,7 @@ from frappe.utils import unique # searches for active employees @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] fields = get_fields("Employee", ["name", "employee_name"]) @@ -42,6 +43,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): # searches for leads which are not converted @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def lead_query(doctype, txt, searchfield, start, page_len, filters): fields = get_fields("Lead", ["name", "lead_name", "company_name"]) @@ -72,6 +74,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): # searches for customer @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def customer_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] cust_master_name = frappe.defaults.get_user_default("cust_master_name") @@ -110,8 +113,10 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): # searches for supplier @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def supplier_query(doctype, txt, searchfield, start, page_len, filters): supp_master_name = frappe.defaults.get_user_default("supp_master_name") + if supp_master_name == "Supplier Name": fields = ["name", "supplier_group"] else: @@ -142,32 +147,49 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def tax_account_query(doctype, txt, searchfield, start, page_len, filters): company_currency = erpnext.get_company_currency(filters.get('company')) - tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount - where tabAccount.docstatus!=2 - and account_type in (%s) - and is_group = 0 - and company = %s - and account_currency = %s - and `%s` LIKE %s - order by idx desc, name - limit %s, %s""" % - (", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"), - tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt, - start, page_len])) + def get_accounts(with_account_type_filter): + account_type_condition = '' + if with_account_type_filter: + account_type_condition = "AND account_type in %(account_types)s" + + accounts = frappe.db.sql(""" + SELECT name, parent_account + FROM `tabAccount` + WHERE `tabAccount`.docstatus!=2 + {account_type_condition} + AND is_group = 0 + AND company = %(company)s + AND account_currency = %(currency)s + AND `{searchfield}` LIKE %(txt)s + ORDER BY idx DESC, name + LIMIT %(offset)s, %(limit)s + """.format(account_type_condition=account_type_condition, searchfield=searchfield), + dict( + account_types=filters.get("account_type"), + company=filters.get("company"), + currency=company_currency, + txt="%{}%".format(txt), + offset=start, + limit=page_len + ) + ) + + return accounts + + tax_accounts = get_accounts(True) + if not tax_accounts: - tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount - where tabAccount.docstatus!=2 and is_group = 0 - and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec - % ("%s", "%s", searchfield, "%s", "%s", "%s"), - (filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len)) + tax_accounts = get_accounts(False) return tax_accounts @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] @@ -215,7 +237,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals idx desc, name, item_name limit %(start)s, %(page_len)s """.format( - key=searchfield, columns=columns, scond=searchfields, fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), @@ -231,6 +252,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def bom(doctype, txt, searchfield, start, page_len, filters): conditions = [] fields = get_fields("BOM", ["name", "item"]) @@ -258,6 +280,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = '' if filters.get('customer'): @@ -285,6 +308,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): fields = get_fields("Delivery Note", ["name", "customer", "posting_date"]) @@ -315,6 +339,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_batch_no(doctype, txt, searchfield, start, page_len, filters): cond = "" if filters.get("posting_date"): @@ -373,6 +398,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_account_list(doctype, txt, searchfield, start, page_len, filters): filter_list = [] @@ -396,6 +422,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): limit_start=start, limit_page_length=page_len, as_list=True) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date from `tabBlanket Order` bo, `tabBlanket Order Item` boi @@ -412,6 +439,7 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_income_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond @@ -438,6 +466,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_expense_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond @@ -462,6 +491,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def warehouse_query(doctype, txt, searchfield, start, page_len, filters): # Should be used when item code is passed in filters. conditions, bin_conditions = [], [] @@ -505,6 +535,7 @@ def get_doctype_wise_filters(filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters): query = """select batch_id from `tabBatch` where disabled = 0 @@ -518,6 +549,7 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters): item_filters = [ ['manufacturer', 'like', '%' + txt + '%'], @@ -536,6 +568,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): query = """ select pr.name @@ -550,6 +583,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): query = """ select pi.name @@ -564,6 +598,7 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_tax_template(doctype, txt, searchfield, start, page_len, filters): item_doc = frappe.get_cached_doc('Item', filters.get('item_code')) diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index 7536172891..3e27670d05 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -97,6 +97,7 @@ class ProgramEnrollment(Document): return quiz_progress @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_program_courses(doctype, txt, searchfield, start, page_len, filters): if filters.get('program'): return frappe.db.sql("""select course, course_name from `tabProgram Course` @@ -115,6 +116,7 @@ def get_program_courses(doctype, txt, searchfield, start, page_len, filters): }) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_students(doctype, txt, searchfield, start, page_len, filters): if not filters.get("academic_term"): filters["academic_term"] = frappe.defaults.get_defaults().academic_term diff --git a/erpnext/education/doctype/student_group/student_group.py b/erpnext/education/doctype/student_group/student_group.py index 8b61c899bc..0260b80864 100644 --- a/erpnext/education/doctype/student_group/student_group.py +++ b/erpnext/education/doctype/student_group/student_group.py @@ -108,6 +108,7 @@ def get_program_enrollment(academic_year, academic_term=None, program=None, batc @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def fetch_students(doctype, txt, searchfield, start, page_len, filters): if filters.get("group_based_on") != "Activity": enrolled_students = get_program_enrollment(filters.get('academic_year'), filters.get('academic_term'), diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py index 3dc7c1ec39..5da5a0657c 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py @@ -71,6 +71,7 @@ def validate_service_item(item, msg): frappe.throw(_(msg)) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None): fields = ['name', 'practitioner_name', 'mobile_phone'] diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index cf63b65f4d..b4a4e295f0 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -217,6 +217,7 @@ def patient_leave_service_unit(inpatient_record, check_out, leave_from): inpatient_record.save(ignore_permissions = True) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_leave_from(doctype, txt, searchfield, start, page_len, filters): docname = filters['docname'] diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index d4c118f802..afd54b8346 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -11,6 +11,7 @@ class DepartmentApprover(Document): pass @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_approvers(doctype, txt, searchfield, start, page_len, filters): if not filters.get("employee"): diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 8062342cfc..c51f655a66 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -911,6 +911,7 @@ def get_bom_diff(bom1, bom2): return out @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters): meta = frappe.get_meta("Item", cached=True) searchfields = meta.get_search_fields() diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index f962a1157b..b7d968e974 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -632,6 +632,7 @@ class WorkOrder(Document): return bom @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_bom_operations(doctype, txt, searchfield, start, page_len, filters): if txt: filters['operation'] = ('like', '%%%s%%' % txt) diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py index e3e440ebc6..dc424b7605 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py @@ -30,7 +30,7 @@ def get_columns(filters): "width": 180 } ]) - + columns.extend([ { "label": _("Finished Good"), @@ -73,7 +73,7 @@ def get_columns(filters): ]) return columns - + def get_data(filters): cond = "1=1" @@ -95,6 +95,7 @@ def get_data(filters): return results @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_work_orders(doctype, txt, searchfield, start, page_len, filters): cond = "1=1" if filters.get('bom_no'): diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index 5ac3923187..ebc01c65af 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -369,6 +369,3 @@ class ProductionPlanReport(object): "fieldtype": "Float", "width": 140 }]) - -def document_query(doctype, txt, searchfield, start, page_len, filters): - pass \ No newline at end of file diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py index d7d00e6480..ef844fbd3b 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py @@ -223,6 +223,7 @@ def get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit): return benefit_amount @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_earning_components(doctype, txt, searchfield, start, page_len, filters): if len(filters) < 2: return {} diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index ad9b6d86c8..554484febb 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -540,6 +540,7 @@ def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progr frappe.msgprint(_("Could not submit some Salary Slips")) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" select name from `tabPayroll Entry` diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 6350f86abb..5bbd29c4c4 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -239,6 +239,7 @@ def get_list_context(context=None): } @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_users_for_project(doctype, txt, searchfield, start, page_len, filters): conditions = [] return frappe.db.sql("""select name, concat_ws(' ', first_name, middle_name, last_name) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index cf2fd26e57..fb84094ffe 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -193,6 +193,7 @@ def check_if_child_exists(name): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_project(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond return frappe.db.sql(""" select name from `tabProject` diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 7fe22bec4b..9e807f728e 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -214,6 +214,7 @@ def get_projectwise_timesheet_data(project, parent=None): and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_timesheet(doctype, txt, searchfield, start, page_len, filters): if not filters: filters = {} diff --git a/erpnext/projects/utils.py b/erpnext/projects/utils.py index d0d88ebdf0..c39f908e43 100644 --- a/erpnext/projects/utils.py +++ b/erpnext/projects/utils.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import frappe @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def query_task(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import build_match_conditions diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index e614acdb82..ca62488a8c 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -340,6 +340,7 @@ def get_loyalty_programs(doc): return lp_details @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None): from erpnext.controllers.queries import get_fields fields = ["name", "customer_name", "customer_group", "territory"] @@ -542,6 +543,7 @@ def make_address(args, is_primary_address=1): return address @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters): customer = filters.get('customer') return frappe.db.sql(""" diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index 0c85a1b53c..d3281f733f 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -29,6 +29,7 @@ class ProductBundle(Document): frappe.throw(_("Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save").format(item.idx, frappe.bold(item.item_code))) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index ffb66354fa..f88289871e 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -888,6 +888,7 @@ def make_purchase_order(source_name, for_supplier=None, selected_items=[], targe @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_supplier(doctype, txt, searchfield, start, page_len, filters): supp_master_name = frappe.defaults.get_user_default("supp_master_name") if supp_master_name == "Supplier Name": 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 f7b7ed8b89..9f8410f40b 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -160,6 +160,7 @@ def get_item_group_condition(pos_profile): return cond % tuple(item_groups) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_group_query(doctype, txt, searchfield, start, page_len, filters): item_groups = [] cond = "1=1" @@ -179,12 +180,12 @@ def item_group_query(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() def check_opening_entry(user): - open_vouchers = frappe.db.get_all("POS Opening Entry", - filters = { - "user": 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" ) @@ -229,7 +230,7 @@ def get_past_order_list(search_term, status, limit=20): invoice_list = frappe.db.get_all('POS Invoice', filters={ 'status': status }, fields=fields) - + return invoice_list @frappe.whitelist() @@ -244,7 +245,7 @@ def set_customer_info(fieldname, customer, value=""): 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': + 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/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py index b29c305ee7..96e60936a4 100644 --- a/erpnext/setup/doctype/party_type/party_type.py +++ b/erpnext/setup/doctype/party_type/party_type.py @@ -10,6 +10,7 @@ class PartyType(Document): pass @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_party_type(doctype, txt, searchfield, start, page_len, filters): cond = '' if filters and filters.get('account'): diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py index 522dfc67a9..190cb62e99 100644 --- a/erpnext/stock/doctype/item_alternative/item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/item_alternative.py @@ -43,6 +43,7 @@ class ItemAlternative(Document): frappe.throw(_("Already record exists for the item {0}").format(self.item_code)) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_alternative_items(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" (select alternative_item_code from `tabItem Alternative` where item_code = %(item_code)s and alternative_item_code like %(txt)s) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 25f1ed9505..335175f21d 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -370,6 +370,7 @@ def get_items_based_on_default_supplier(supplier): return supplier_items @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, page_len, filters): conditions = "" if txt: @@ -403,6 +404,7 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa return material_requests @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filters): doc = frappe.get_doc("Material Request", filters.get("doc")) item_list = [] diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py index 4f831d7a85..a7a29cca7f 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/packing_slip.py @@ -176,6 +176,7 @@ class PackingSlip(Document): self.update_item_details() @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_details(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond return frappe.db.sql("""select name, item_name, description from `tabItem` diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 568e742876..c3bb514184 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -59,6 +59,7 @@ class QualityInspection(Document): (quality_inspection, self.modified, self.reference_name, self.item_code)) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters): if filters.get("from"): from frappe.desk.reportview import get_match_cond @@ -88,6 +89,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt}) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters): return frappe.get_all('Quality Inspection', limit_start=start, From 5ad9c2a1a004c43101be217288347fbfce97981f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 6 Aug 2020 13:50:20 +0550 Subject: [PATCH 0149/1264] bumped to version 13.0.0-beta.4 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 18294f3604..9f658253ef 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.3' +__version__ = '13.0.0-beta.4' def get_default_company(user=None): '''Get default company for user''' From aa4f8cf4fcac0f7da62e9f50ccfd6b67f31ca705 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 6 Aug 2020 17:08:14 +0530 Subject: [PATCH 0150/1264] fix: handle product_info null --- erpnext/templates/generators/item/item_configure.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js index 163c955c56..284eb25218 100644 --- a/erpnext/templates/generators/item/item_configure.js +++ b/erpnext/templates/generators/item/item_configure.js @@ -194,9 +194,16 @@ class ItemConfigure { filtered_items[0] : ''; // Allow Add to Cart if adding out of stock items enabled in Shopping Cart else check stock. - const in_stock = product_info.allow_items_not_in_stock ? 1 : product_info.in_stock; - const add_to_cart = `${__('Add to cart')}`; - const product_action = in_stock ? add_to_cart : `${__('Not in Stock')}`; + var in_stock; + var add_to_cart; + var product_action; + if (product_info) { + in_stock = product_info.allow_items_not_in_stock ? 1 : product_info.in_stock; + add_to_cart = `${__('Add to cart')}`; + product_action = in_stock ? add_to_cart : `${__('Not in Stock')}`; + } else { + product_info = ''; + } const item_add_to_cart = one_item ? `
- +
Date: {{ frappe.utils.formatdate(doc.creation) }}
Date: {{ frappe.utils.format_date(doc.creation) }}
diff --git a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html index 1351517981..0ca940f8bd 100644 --- a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html +++ b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html @@ -17,7 +17,7 @@
- +
Date: {{ frappe.utils.formatdate(doc.creation) }}
Date: {{ frappe.utils.format_date(doc.creation) }}
diff --git a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html index f2e65d3334..283d505e3b 100644 --- a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html +++ b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html @@ -5,7 +5,7 @@ {{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }} {%- for label, value in ( - (_("Received On"), frappe.utils.formatdate(doc.voucher_date)), + (_("Received On"), frappe.utils.format_date(doc.voucher_date)), (_("Received From"), doc.pay_to_recd_from), (_("Amount"), "" + doc.get_formatted("total_amount") + "
" + (doc.total_amount_in_words or "") + "
"), (_("Remarks"), doc.remark) diff --git a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html index a7c3bce0b4..043ac254ed 100644 --- a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html +++ b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html @@ -8,7 +8,7 @@
- + @@ -17,7 +17,7 @@
Supplier Name: {{ doc.supplier }}
Due Date: {{ frappe.utils.formatdate(doc.due_date) }}
Due Date: {{ frappe.utils.format_date(doc.due_date) }}
Address: {{doc.address_display}}
Contact: {{doc.contact_display}}
Mobile no: {{doc.contact_mobile}}
- +
Voucher No: {{ doc.name }}
Date: {{ frappe.utils.formatdate(doc.creation) }}
Date: {{ frappe.utils.format_date(doc.creation) }}
diff --git a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html index ef4ada14a3..a53b593a72 100644 --- a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html +++ b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html @@ -8,7 +8,7 @@
- + @@ -17,7 +17,7 @@
Customer Name: {{ doc.customer }}
Due Date: {{ frappe.utils.formatdate(doc.due_date) }}
Due Date: {{ frappe.utils.format_date(doc.due_date) }}
Address: {{doc.address_display}}
Contact: {{doc.contact_display}}
Mobile no: {{doc.contact_mobile}}
- +
Voucher No: {{ doc.name }}
Date: {{ frappe.utils.formatdate(doc.creation) }}
Date: {{ frappe.utils.format_date(doc.creation) }}
diff --git a/erpnext/templates/pages/material_request_info.html b/erpnext/templates/pages/material_request_info.html index 9d189895b6..c7a78027b1 100644 --- a/erpnext/templates/pages/material_request_info.html +++ b/erpnext/templates/pages/material_request_info.html @@ -25,7 +25,7 @@
- {{ frappe.utils.formatdate(doc.transaction_date, 'medium') }} + {{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 01b5f6df6c..af7af11677 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -42,10 +42,10 @@
- {{ frappe.utils.formatdate(doc.transaction_date, 'medium') }} + {{ frappe.utils.format_date(doc.transaction_date, 'medium') }} {% if doc.valid_till %}

- {{ _("Valid Till") }}: {{ frappe.utils.formatdate(doc.valid_till, 'medium') }} + {{ _("Valid Till") }}: {{ frappe.utils.format_date(doc.valid_till, 'medium') }}

{% endif %}
From 886b680e1b050548aabeacf8aac4833ced60aa35 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Nov 2020 16:48:39 +0530 Subject: [PATCH 0200/1264] revert changes --- .../controllers/website_list_for_contact.py | 2 +- erpnext/crm/doctype/parameter/__init__.py | 0 erpnext/crm/doctype/parameter/parameter.js | 8 +++ erpnext/crm/doctype/parameter/parameter.json | 56 +++++++++++++++++++ erpnext/crm/doctype/parameter/parameter.py | 10 ++++ .../crm/doctype/parameter/test_parameter.py | 10 ++++ erpnext/shopping_cart/cart.py | 4 +- .../shopping_cart_settings.json | 9 +-- .../templates/includes/transaction_row.html | 6 +- erpnext/templates/pages/order.html | 31 +++++----- 10 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 erpnext/crm/doctype/parameter/__init__.py create mode 100644 erpnext/crm/doctype/parameter/parameter.js create mode 100644 erpnext/crm/doctype/parameter/parameter.json create mode 100644 erpnext/crm/doctype/parameter/parameter.py create mode 100644 erpnext/crm/doctype/parameter/test_parameter.py diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index 801c405732..ecf041efd1 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -25,7 +25,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p if not filters: filters = [] - if doctype in ['Supplier Quotation', 'Purchase Invoice', 'Quotation']: + if doctype in ['Supplier Quotation', 'Purchase Invoice']: filters.append((doctype, 'docstatus', '<', 2)) else: filters.append((doctype, 'docstatus', '=', 1)) diff --git a/erpnext/crm/doctype/parameter/__init__.py b/erpnext/crm/doctype/parameter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/parameter/parameter.js b/erpnext/crm/doctype/parameter/parameter.js new file mode 100644 index 0000000000..0a2b13be5e --- /dev/null +++ b/erpnext/crm/doctype/parameter/parameter.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('Parameter', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/crm/doctype/parameter/parameter.json b/erpnext/crm/doctype/parameter/parameter.json new file mode 100644 index 0000000000..7b2eb3edfc --- /dev/null +++ b/erpnext/crm/doctype/parameter/parameter.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "creation": "2020-11-02 16:04:08.280141", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parameter_name", + "value_based", + "uom" + ], + "fields": [ + { + "fieldname": "parameter_name", + "fieldtype": "Data", + "label": "Parameter Name" + }, + { + "default": "0", + "fieldname": "value_based", + "fieldtype": "Check", + "label": "Value Based" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-11-02 16:04:08.280141", + "modified_by": "Administrator", + "module": "CRM", + "name": "Parameter", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/parameter/parameter.py b/erpnext/crm/doctype/parameter/parameter.py new file mode 100644 index 0000000000..9943bc45b9 --- /dev/null +++ b/erpnext/crm/doctype/parameter/parameter.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 Parameter(Document): + pass diff --git a/erpnext/crm/doctype/parameter/test_parameter.py b/erpnext/crm/doctype/parameter/test_parameter.py new file mode 100644 index 0000000000..80bb8652c0 --- /dev/null +++ b/erpnext/crm/doctype/parameter/test_parameter.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 TestParameter(unittest.TestCase): + pass diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 0ccc0252c3..a7e8388be9 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -96,9 +96,7 @@ def place_order(): def request_for_quotation(): quotation = _get_cart_quotation() quotation.flags.ignore_permissions = True - quotation.save() - if not get_shopping_cart_settings().save_quotations_as_draft: - quotation.submit() + quotation.submit() return quotation.name @frappe.whitelist() 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 9d61e7d0ec..98a7eeda23 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 @@ -167,20 +167,13 @@ "fieldname": "enable_variants", "fieldtype": "Check", "label": "Enable Variants" - }, - { - "default": "0", - "depends_on": "eval: doc.enable_checkout == 0", - "fieldname": "save_quotations_as_draft", - "fieldtype": "Check", - "label": "Save Quotations as Draft" } ], "icon": "fa fa-shopping-cart", "idx": 1, "issingle": 1, "links": [], - "modified": "2020-09-24 16:28:07.192525", + "modified": "2020-08-02 18:21:43.873303", "modified_by": "Administrator", "module": "Shopping Cart", "name": "Shopping Cart Settings", diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index fd4835ed99..80a542f74b 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -14,11 +14,7 @@
- {% if doc.doctype == "Quotation" and not doc.docstatus %} - {{ _("Pending") }} - {% else %} - {{ doc.get_formatted("grand_total") }} - {% endif %} + {{ doc.get_formatted("grand_total") }}
Link diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index af7af11677..896954a287 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -12,21 +12,22 @@ {% endblock %} {% block header_actions %} - + + {% endblock %} {% block page_content %} From 14ef229b126a24b8ac9bd6c0dc74456513a2921c Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Nov 2020 16:50:15 +0530 Subject: [PATCH 0201/1264] Revert "Update item_add_to_cart.html" This reverts commit 5b3af82e75f60aeb9f9a9c8a4523ab8a5c1e273b. --- erpnext/templates/generators/item/item_add_to_cart.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index dbf15de1e4..c619963a91 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -11,7 +11,7 @@ ({{ product_info.price.formatted_price }} / {{ product_info.uom }}) {% else %} - {{ _("Unit of Measurement") }} : {{ product_info.uom }} + {{ _("UOM") }} : {{ product_info.uom }} {% endif %} {% if cart_settings.show_stock_availability %} From ec7002625f35dbd177a4bd36f8c9aa030ddfcdf9 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 6 Nov 2020 17:11:33 +0530 Subject: [PATCH 0202/1264] Delete __init__.py --- erpnext/crm/doctype/parameter/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/crm/doctype/parameter/__init__.py diff --git a/erpnext/crm/doctype/parameter/__init__.py b/erpnext/crm/doctype/parameter/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From b4bad63e2594e0dd9dc1bd1c20a3e2c6e2da59a5 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 6 Nov 2020 17:11:47 +0530 Subject: [PATCH 0203/1264] Delete parameter.js --- erpnext/crm/doctype/parameter/parameter.js | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 erpnext/crm/doctype/parameter/parameter.js diff --git a/erpnext/crm/doctype/parameter/parameter.js b/erpnext/crm/doctype/parameter/parameter.js deleted file mode 100644 index 0a2b13be5e..0000000000 --- a/erpnext/crm/doctype/parameter/parameter.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Parameter', { - // refresh: function(frm) { - - // } -}); From c7f8204997c1050fb7a2f588b002ea1eef6660e8 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 6 Nov 2020 17:12:21 +0530 Subject: [PATCH 0204/1264] Delete parameter.json --- erpnext/crm/doctype/parameter/parameter.json | 56 -------------------- 1 file changed, 56 deletions(-) delete mode 100644 erpnext/crm/doctype/parameter/parameter.json diff --git a/erpnext/crm/doctype/parameter/parameter.json b/erpnext/crm/doctype/parameter/parameter.json deleted file mode 100644 index 7b2eb3edfc..0000000000 --- a/erpnext/crm/doctype/parameter/parameter.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "actions": [], - "creation": "2020-11-02 16:04:08.280141", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "parameter_name", - "value_based", - "uom" - ], - "fields": [ - { - "fieldname": "parameter_name", - "fieldtype": "Data", - "label": "Parameter Name" - }, - { - "default": "0", - "fieldname": "value_based", - "fieldtype": "Check", - "label": "Value Based" - }, - { - "fieldname": "uom", - "fieldtype": "Link", - "label": "UOM", - "options": "UOM" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2020-11-02 16:04:08.280141", - "modified_by": "Administrator", - "module": "CRM", - "name": "Parameter", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file From a739554b7da312fd015818cbcd71816d1ad00cad Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 6 Nov 2020 17:12:44 +0530 Subject: [PATCH 0205/1264] Delete parameter.py --- erpnext/crm/doctype/parameter/parameter.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 erpnext/crm/doctype/parameter/parameter.py diff --git a/erpnext/crm/doctype/parameter/parameter.py b/erpnext/crm/doctype/parameter/parameter.py deleted file mode 100644 index 9943bc45b9..0000000000 --- a/erpnext/crm/doctype/parameter/parameter.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 Parameter(Document): - pass From bdb8a4023a8e50d5a77f8e9c30e03805c4c6ba0f Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 6 Nov 2020 17:13:02 +0530 Subject: [PATCH 0206/1264] Delete test_parameter.py --- erpnext/crm/doctype/parameter/test_parameter.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 erpnext/crm/doctype/parameter/test_parameter.py diff --git a/erpnext/crm/doctype/parameter/test_parameter.py b/erpnext/crm/doctype/parameter/test_parameter.py deleted file mode 100644 index 80bb8652c0..0000000000 --- a/erpnext/crm/doctype/parameter/test_parameter.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 TestParameter(unittest.TestCase): - pass From 109018120408e30df73bff41c23cae20712cd663 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 6 Nov 2020 18:19:26 +0530 Subject: [PATCH 0207/1264] fix: auto fetch sr nos with modifed conversion factor (#23855) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 8 ++++++-- erpnext/selling/page/point_of_sale/pos_controller.js | 2 ++ erpnext/selling/page/point_of_sale/pos_item_details.js | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 61263ac788..f763d302c7 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -131,15 +131,19 @@ class POSInvoice(SalesInvoice): msg = "" item_code = frappe.bold(d.item_code) + serial_nos = get_serial_nos(d.serial_no) if serialized and batched and (no_batch_selected or no_serial_selected): msg = (_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.') .format(d.idx, item_code)) - if serialized and no_serial_selected: + elif serialized and no_serial_selected: msg = (_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.') .format(d.idx, item_code)) - if batched and no_batch_selected: + elif batched and no_batch_selected: msg = (_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.') .format(d.idx, item_code)) + elif serialized and not no_serial_selected and len(serial_nos) != d.qty: + msg = (_("Row #{}: You must select {} serial numbers for item {}.").format(d.idx, frappe.bold(cint(d.qty)), item_code)) + if msg: error_msg.append(msg) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index f7d1fa4ded..970d840665 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -555,6 +555,8 @@ erpnext.PointOfSale.Controller = class { frappe.utils.play_sound("error"); return; } + if (!item_code) return; + item_selected_from_selector && (value = flt(value)) const args = { item_code, batch_no, [field]: value }; diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 9874d1b5f9..a4de9f165d 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -372,12 +372,13 @@ erpnext.PointOfSale.ItemDetails = class { this.$form_container.on('click', '.auto-fetch-btn', () => { this.batch_no_control && this.batch_no_control.set_value(''); let qty = this.qty_control.get_value(); + let conversion_factor = this.conversion_factor_control.get_value(); let expiry_date = this.item_row.has_batch_no ? this.events.get_frm().doc.posting_date : ""; let numbers = frappe.call({ method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", args: { - qty, + qty: qty * conversion_factor, item_code: this.current_item.item_code, warehouse: this.warehouse_control.get_value() || '', batch_nos: this.current_item.batch_no || '', From 8cbcd54f3b17e00eb045314ac6ab41ab4a9035c1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 6 Nov 2020 18:48:34 +0530 Subject: [PATCH 0208/1264] chore: change log for v13-beta-pre-release-5 (#23846) --- erpnext/change_log/v13/v13_0_0-beta_5.md | 89 ++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0-beta_5.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_5.md b/erpnext/change_log/v13/v13_0_0-beta_5.md new file mode 100644 index 0000000000..8374c775fe --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0-beta_5.md @@ -0,0 +1,89 @@ +### Version 13.0.0 Beta 5 Release Notes + +#### Features and Enhancements +- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614)) +- Process Statement Of Accounts ([#22901](https://github.com/frappe/erpnext/pull/22901)) +- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014)) +- Material Request and Stock Entry Enhancement ([#22671](https://github.com/frappe/erpnext/pull/22671)) +- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641)) +- Appraisal form Enhancements ([#23500](https://github.com/frappe/erpnext/pull/23500)) +- Crm reports cleanup ([#22844](https://github.com/frappe/erpnext/pull/22844)) +- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445)) +- Quoted Item Comparison Report Enhancements v2 ([#23127](https://github.com/frappe/erpnext/pull/23127)) +- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170)) +- Added address template for luxembourg ([#23621](https://github.com/frappe/erpnext/pull/23621)) +- Provision to draft quotation from portal ([#23416](https://github.com/frappe/erpnext/pull/23416)) +- M-pesa integration ([#23439](https://github.com/frappe/erpnext/pull/23439)) +- Education Desk, Dashboard, and Onboarding ([#22825](https://github.com/frappe/erpnext/pull/22825)) +- Added search to support page ([#22447](https://github.com/frappe/erpnext/pull/22447)) +- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675)) +- Added POS Register report ([#23313](https://github.com/frappe/erpnext/pull/23313)) +- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309)) +- Enhancement in Loan Topups ([#23049](https://github.com/frappe/erpnext/pull/23049)) +- Inpatient Medication Order and Entry ([#23473](https://github.com/frappe/erpnext/pull/23473)) +- Option to print UOM after quantity ([#23263](https://github.com/frappe/erpnext/pull/23263)) +- Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323)) +- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557)) +- Therapy Plan Template ([#23558](https://github.com/frappe/erpnext/pull/23558)) +- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867)) +- Laboratory Module ([#22853](https://github.com/frappe/erpnext/pull/22853)) +- Shift management ([#22262](https://github.com/frappe/erpnext/pull/22262)) + +#### Fixes +- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725)) +- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001)) +- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217)) +- Loan Security shortfall calculation fixes ([#22866](https://github.com/frappe/erpnext/pull/22866)) +- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669)) +- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256)) +- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333)) +- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580)) +- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541)) +- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755)) +- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302)) +- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165)) +- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772)) +- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880)) +- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407)) +- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072)) +- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718)) +- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288)) +- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) +- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548)) +- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145)) +- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918)) +- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597)) +- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245)) +- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361)) +- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704)) +- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161)) +- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369)) +- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195)) +- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419)) +- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953)) +- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110)) +- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059)) +- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007)) +- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066)) +- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814)) +- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076)) +- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434)) +- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009)) +- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958)) +- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483)) +- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539)) +- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151)) +- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476)) +- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911)) +- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950)) +- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943)) +- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282)) +- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714)) +- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205)) +- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623)) +- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264)) +- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676)) +- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988)) +- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436)) +- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357)) +- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515)) From e7ab61da9a69c146c74ec69572011947b14e4ae6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 6 Nov 2020 19:23:55 +0550 Subject: [PATCH 0209/1264] bumped to version 13.0.0-beta.5 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 9f658253ef..2e281d6824 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.4' +__version__ = '13.0.0-beta.5' def get_default_company(user=None): '''Get default company for user''' From 30c704a4cb755c51ad7f46acd32d6cccfa6b3dc4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 13 Nov 2020 22:17:07 +0530 Subject: [PATCH 0210/1264] fix: not able to save bom --- erpnext/manufacturing/doctype/bom/bom.py | 31 +++++++++--------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 2ab1b98707..8888a96768 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -76,6 +76,7 @@ class BOM(WebsiteGenerator): self.set_routing_operations() self.validate_operations() self.calculate_cost() + self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, save=False) def get_context(self, context): @@ -84,8 +85,6 @@ class BOM(WebsiteGenerator): def on_update(self): frappe.cache().hdel('bom_children', self.name) self.check_recursion() - self.update_stock_qty() - self.update_exploded_items() def on_submit(self): self.manage_default_bom() @@ -237,7 +236,8 @@ class BOM(WebsiteGenerator): self.calculate_cost() if save: self.db_update() - self.update_exploded_items() + + self.update_exploded_items(save=save) # update parent BOMs if self.total_cost != existing_bom_cost and update_parent: @@ -318,8 +318,6 @@ class BOM(WebsiteGenerator): m.uom = m.stock_uom m.qty = m.stock_qty - m.db_update() - def validate_uom_is_interger(self): from erpnext.utilities.transaction_base import validate_uom_is_integer validate_uom_is_integer(self, "uom", "qty", "BOM Item") @@ -372,15 +370,6 @@ class BOM(WebsiteGenerator): if raise_exception: frappe.throw(_("BOM recursion: {0} cannot be parent or child of {1}").format(self.name, self.name)) - def update_cost_and_exploded_items(self, bom_list=[]): - bom_list = self.traverse_tree(bom_list) - for bom in bom_list: - bom_obj = frappe.get_doc("BOM", bom) - bom_obj.check_recursion(bom_list=bom_list) - bom_obj.update_exploded_items() - - return bom_list - def traverse_tree(self, bom_list=None): def _get_children(bom_no): children = frappe.cache().hget('bom_children', bom_no) @@ -472,10 +461,10 @@ class BOM(WebsiteGenerator): d.rate = rate d.amount = (d.stock_qty or d.qty) * rate - def update_exploded_items(self): + def update_exploded_items(self, save=True): """ Update Flat BOM, following will be correct data""" self.get_exploded_items() - self.add_exploded_items() + self.add_exploded_items(save=save) def get_exploded_items(self): """ Get all raw materials including items from child bom""" @@ -544,11 +533,13 @@ class BOM(WebsiteGenerator): 'sourced_by_supplier': d.get('sourced_by_supplier', 0) })) - def add_exploded_items(self): + def add_exploded_items(self, save=True): "Add items to Flat BOM table" - frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name) self.set('exploded_items', []) + if save: + frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name) + for d in sorted(self.cur_exploded_items, key=itemgetter(0)): ch = self.append('exploded_items', {}) for i in self.cur_exploded_items[d].keys(): @@ -556,7 +547,9 @@ class BOM(WebsiteGenerator): ch.amount = flt(ch.stock_qty) * flt(ch.rate) ch.qty_consumed_per_unit = flt(ch.stock_qty) / flt(self.quantity) ch.docstatus = self.docstatus - ch.db_insert() + + if save: + ch.db_insert() def validate_bom_links(self): if not self.is_active: From b6ef59f8e95dc3947be7532201f9f7812b361236 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 18 Nov 2020 17:57:35 +0530 Subject: [PATCH 0211/1264] fix: incorrect delink serial no and batch --- erpnext/controllers/stock_controller.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 9f69bd610d..4aa1cad85e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -229,8 +229,10 @@ class StockController(AccountsController): def check_expense_account(self, item): if not item.get("expense_account"): - frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense Account in the Items table") - .format(item.idx, frappe.bold(item.item_code)), title=_("Expense Account Missing")) + msg = _("Please set an Expense Account in the Items table") + frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}") + .format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing")) + else: is_expense_account = frappe.db.get_value("Account", item.get("expense_account"), "report_type")=="Profit and Loss" @@ -245,15 +247,16 @@ class StockController(AccountsController): for d in self.items: if not d.batch_no: continue - serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': d.batch_no})] - if serial_nos: - frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None) - d.batch_no = None d.db_set("batch_no", None) for data in frappe.get_all("Batch", {'reference_name': self.name, 'reference_doctype': self.doctype}): + + serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': data.name})] + if serial_nos: + frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None) + frappe.delete_doc("Batch", data.name) def get_sl_entries(self, d, args): From 0b7180146201beda0a0bb0cff30faf0ef2157f31 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 20 Nov 2020 12:59:00 +0530 Subject: [PATCH 0212/1264] fix: incorrect delink serial no and batch --- erpnext/controllers/stock_controller.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4aa1cad85e..2d2fff8fd5 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -247,16 +247,17 @@ class StockController(AccountsController): for d in self.items: if not d.batch_no: continue + serial_nos = [sr.name for sr in frappe.get_all("Serial No", + {'batch_no': d.batch_no, 'status': 'Inactive'})] + + if serial_nos: + frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None) + d.batch_no = None d.db_set("batch_no", None) for data in frappe.get_all("Batch", {'reference_name': self.name, 'reference_doctype': self.doctype}): - - serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': data.name})] - if serial_nos: - frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None) - frappe.delete_doc("Batch", data.name) def get_sl_entries(self, d, args): From ecc50c8c24f322339bbdc3eae1e5062b85841382 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 20 Nov 2020 15:22:00 +0550 Subject: [PATCH 0213/1264] bumped to version 13.0.0-beta.6 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2e281d6824..14d3563262 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.5' +__version__ = '13.0.0-beta.6' def get_default_company(user=None): '''Get default company for user''' From 6273d3ada64546541f416ee14fe7d68d8ed09203 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 28 Oct 2020 11:02:02 +0530 Subject: [PATCH 0214/1264] fix: override field_map for job card gantt --- .../doctype/job_card/job_card_calendar.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js index cf07698ad6..f4877fdca0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js +++ b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js @@ -8,7 +8,17 @@ frappe.views.calendar["Job Card"] = { "allDay": "allDay", "progress": "progress" }, - gantt: true, + gantt: { + field_map: { + "start": "started_time", + "end": "started_time", + "id": "name", + "title": "subject", + "color": "color", + "allDay": "allDay", + "progress": "progress" + } + }, filters: [ { "fieldtype": "Link", From ad59726f20ef279c5149aa50c9347a3c74f406bf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Dec 2020 13:00:55 +0530 Subject: [PATCH 0215/1264] fix: Tax template update on customer address change --- erpnext/regional/india/taxes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 4d36cff1e6..3c70ca8e10 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -9,6 +9,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => { tax_category: function(frm) { frm.trigger('get_tax_template'); }, + customer_address: function(frm) { + frm.trigger('get_tax_template'); + }, get_tax_template: function(frm) { let party_details = { 'shipping_address': frm.doc.shipping_address || '', From e77e3aa36d8cd7865f6ea658b69b22cd8b58a485 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Dec 2020 18:46:59 +0530 Subject: [PATCH 0216/1264] fix: Typo in tax category doctype query --- erpnext/regional/india/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 4a547a2931..95ff291516 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -53,7 +53,7 @@ def validate_gstin_for_india(doc, method): .format(doc.gst_state_number)) def validate_tax_category(doc, method): - if doc.get('gst_state') and frappe.db.get_value('Tax category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): + if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): if doc.is_inter_state: frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state)) else: From bd4bdca6ada2a5a2c4d5987958cca4a674254252 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 21 Dec 2020 20:03:59 +0530 Subject: [PATCH 0217/1264] feat: Repost item costing (#24183) * Repost item valuation (#24031) * feat: Reposting logic for future finished/transferred item * feat: added fields to identify needs to recalculate rate while reposting * refactor: Set rate for outgoing and finished items * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Get outgoing rate for purchase return * refactor: Get incoming rate for sales return * test: Added tests for reposting valuation of transferred/finished/returned items * feat: added incoming rate field in DN, SI and Packed Item table * feat: get incoming rate for returned item * fix: no error while getting valuation rate in stock entry * fix: update stock ledger for DN and SI * feat: update item valuation rate in PR and PI based on supplied items cost * feat: SLE reposting logic for sales return and subcontracted item with test cases * feat: update qty in future sle * feat: repost future sle and gle via Repost Item Valuation * fix: Skip unwanted function calling while reposting * fix: repost sle for specific item and warehouse * test: Modified tests for backdated stock reco * fix: ignore cancelled sle in few methods * feat: role allowed to do backdated entry * feat: Show reposting status on stock valuation related reports * fix: minor fixes * fix: fixed sider issues * fix: serial no fix related to immutable ledger * fix: Test cases fixes related to perpetual inventory * fix: Test cases fixed * fix: Fixed reposting on cancel and test cases * feat: Restart reposting item valuation * refactor: Code cleanup using small functions and test case fixes * fix: minor fixes * fix: Raise on error while reposting item valuation * fix: minor fix * fix: Tests fixed * fix: skip some validation ig gle made from reposting * fix: test fixes * fix: debugging stock and account validation * fix: debugging stock and account validation * fix: debugging travis for stock and account sync validation * fix: debugging travis * fix: debugging travis * fix: debugging travis * fix: removed duplicate field from pos profile --- .../accounts/doctype/account/test_account.py | 2 +- .../doctype/coupon_code/test_coupon_code.py | 50 +- erpnext/accounts/doctype/gl_entry/gl_entry.py | 24 +- .../journal_entry/test_journal_entry.py | 68 +- .../loyalty_program/test_loyalty_program.py | 2 - .../doctype/pos_profile/pos_profile.json | 3 +- .../purchase_invoice/purchase_invoice.py | 19 +- .../purchase_invoice/test_purchase_invoice.py | 72 +-- .../doctype/sales_invoice/sales_invoice.py | 17 +- .../doctype/sales_invoice/test_records.json | 3 +- .../sales_invoice/test_sales_invoice.py | 128 ++-- .../sales_invoice_item.json | 30 +- erpnext/accounts/general_ledger.py | 38 +- erpnext/accounts/utils.py | 24 +- .../purchase_order_item.json | 2 +- erpnext/controllers/buying_controller.py | 129 ++-- .../controllers/sales_and_purchase_return.py | 42 ++ erpnext/controllers/selling_controller.py | 116 ++-- erpnext/controllers/stock_controller.py | 101 +-- .../doctype/work_order/test_work_order.py | 2 - .../sales_order_item/sales_order_item.json | 2 +- .../setup/doctype/company/test_records.json | 18 +- erpnext/stock/doctype/batch/test_batch.py | 5 - erpnext/stock/doctype/bin/bin.py | 14 +- .../doctype/delivery_note/delivery_note.py | 4 +- .../delivery_note/test_delivery_note.py | 7 +- .../delivery_note_item.json | 11 +- erpnext/stock/doctype/item/test_records.json | 10 + .../item_alternative/test_item_alternative.py | 2 - .../landed_cost_taxes_and_charges.json | 8 +- .../landed_cost_voucher.py | 18 +- .../test_landed_cost_voucher.py | 12 +- .../material_request/test_material_request.py | 3 - .../doctype/packed_item/packed_item.json | 18 +- .../purchase_receipt/purchase_receipt.py | 4 +- .../purchase_receipt/test_purchase_receipt.py | 188 +++--- .../purchase_receipt_item.json | 2 +- .../doctype/repost_item_valuation/__init__.py | 0 .../repost_item_valuation.js | 52 ++ .../repost_item_valuation.json | 215 +++++++ .../repost_item_valuation.py | 89 +++ .../test_repost_item_valuation.py | 10 + erpnext/stock/doctype/serial_no/serial_no.py | 33 +- .../stock/doctype/serial_no/test_serial_no.py | 3 - .../stock/doctype/stock_entry/stock_entry.js | 25 +- .../doctype/stock_entry/stock_entry.json | 3 +- .../stock/doctype/stock_entry/stock_entry.py | 303 +++++---- .../doctype/stock_entry/test_stock_entry.py | 83 +-- .../stock_entry_detail.json | 74 ++- .../stock_ledger_entry.json | 62 +- .../stock_ledger_entry/stock_ledger_entry.py | 45 +- .../test_stock_ledger_entry.py | 395 +++++++++++- .../stock_reconciliation.py | 4 +- .../test_stock_reconciliation.py | 47 +- .../stock_settings/stock_settings.json | 31 +- .../stock/doctype/warehouse/test_warehouse.py | 61 +- erpnext/stock/doctype/warehouse/warehouse.py | 1 - .../report/stock_analytics/stock_analytics.py | 2 + .../report/stock_balance/stock_balance.py | 3 +- .../stock/report/stock_ledger/stock_ledger.py | 3 +- .../stock_projected_qty.py | 3 +- ...rehouse_wise_item_balance_age_and_value.py | 2 + erpnext/stock/stock_balance.py | 18 +- erpnext/stock/stock_ledger.py | 597 +++++++++++++----- erpnext/stock/utils.py | 11 +- 65 files changed, 2337 insertions(+), 1036 deletions(-) create mode 100644 erpnext/stock/doctype/repost_item_valuation/__init__.py create mode 100644 erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js create mode 100644 erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json create mode 100644 erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py create mode 100644 erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 0605d89a7e..113bea0064 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -172,7 +172,7 @@ class TestAccount(unittest.TestCase): frappe.delete_doc("Account", doc) -def _make_test_records(verbose): +def _make_test_records(verbose=None): from frappe.test_runner import make_test_objects accounts = [ diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py index 340b9dd58a..622bd33e20 100644 --- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py @@ -28,22 +28,22 @@ def test_create_test_data(): "item_group": "_Test Item Group", "item_name": "_Test Tesla Car", "apply_warehouse_wise_reorder_level": 0, - "warehouse":"Stores - TCP1", + "warehouse":"Stores - _TC", "gst_hsn_code": "999800", "valuation_rate": 5000, "standard_rate":5000, "item_defaults": [{ - "company": "_Test Company with perpetual inventory", - "default_warehouse": "Stores - TCP1", + "company": "_Test Company", + "default_warehouse": "Stores - _TC", "default_price_list":"_Test Price List", - "expense_account": "Cost of Goods Sold - TCP1", - "buying_cost_center": "Main - TCP1", - "selling_cost_center": "Main - TCP1", - "income_account": "Sales - TCP1" + "expense_account": "Cost of Goods Sold - _TC", + "buying_cost_center": "Main - _TC", + "selling_cost_center": "Main - _TC", + "income_account": "Sales - _TC" }], "show_in_website": 1, "route":"-test-tesla-car", - "website_warehouse": "Stores - TCP1" + "website_warehouse": "Stores - _TC" }) item.insert() # create test item price @@ -65,12 +65,12 @@ def test_create_test_data(): "items": [{ "item_code": "_Test Tesla Car" }], - "warehouse":"Stores - TCP1", + "warehouse":"Stores - _TC", "coupon_code_based":1, "selling": 1, "rate_or_discount": "Discount Percentage", "discount_percentage": 30, - "company": "_Test Company with perpetual inventory", + "company": "_Test Company", "currency":"INR", "for_price_list":"_Test Price List" }) @@ -85,7 +85,7 @@ def test_create_test_data(): }) sales_partner.insert() # create test item coupon code - if not frappe.db.exists("Coupon Code","SAVE30"): + if not frappe.db.exists("Coupon Code", "SAVE30"): coupon_code = frappe.get_doc({ "doctype": "Coupon Code", "coupon_name":"SAVE30", @@ -102,35 +102,27 @@ class TestCouponCode(unittest.TestCase): test_create_test_data() def tearDown(self): - frappe.set_user("Administrator") + frappe.set_user("Administrator") - def test_1_check_coupon_code_used_before_so(self): - coupon_code = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"})) - # reset used coupon code count - coupon_code.used=0 - coupon_code.save() - # check no coupon code is used before sales order is made - self.assertEqual(coupon_code.get("used"),0) + def test_sales_order_with_coupon_code(self): + frappe.db.set_value("Coupon Code", "SAVE30", "used", 0) - def test_2_sales_order_with_coupon_code(self): - so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', - customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1, + so = make_sales_order(company='_Test Company', warehouse='Stores - _TC', + customer="_Test Customer", selling_price_list="_Test Price List", + item_code="_Test Tesla Car", rate=5000, qty=1, do_not_submit=True) - so = frappe.get_doc('Sales Order', so.name) - # check item price before coupon code is applied self.assertEqual(so.items[0].rate, 5000) + so.coupon_code='SAVE30' so.sales_partner='_Test Coupon Partner' so.save() + # check item price after coupon code is applied self.assertEqual(so.items[0].rate, 3500) + so.submit() - - def test_3_check_coupon_code_used_after_so(self): - doc = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"})) - # check no coupon code is used before sales order is made - self.assertEqual(doc.get("used"),1) + self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index def9ed6803..c441274908 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -30,20 +30,22 @@ class GLEntry(Document): self.pl_must_have_cost_center() self.validate_cost_center() - self.check_pl_account() - self.validate_party() - self.validate_currency() + if not self.flags.from_repost: + self.check_pl_account() + self.validate_party() + self.validate_currency() - def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'): - self.validate_account_details(adv_adj) - self.validate_dimensions_for_pl_and_bs() + def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): + if not from_repost: + self.validate_account_details(adv_adj) + self.validate_dimensions_for_pl_and_bs() validate_frozen_account(self.account, adv_adj) validate_balance_type(self.account, adv_adj) # Update outstanding amt on against voucher if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \ - and self.against_voucher and update_outstanding == 'Yes': + and self.against_voucher and update_outstanding == 'Yes' and not from_repost: update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher) @@ -106,8 +108,8 @@ class GLEntry(Document): from tabAccount where name=%s""", self.account, as_dict=1)[0] if ret.is_group==1: - frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in - transactions''').format(self.voucher_type, self.voucher_no, self.account)) + frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''') + .format(self.voucher_type, self.voucher_no, self.account)) if ret.docstatus==2: frappe.throw(_("{0} {1}: Account {2} is inactive") @@ -136,8 +138,8 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) if self.cost_center and _check_is_group(): - frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot - be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) + frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""") + .format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) def validate_party(self): validate_party_frozen_disabled(self.party_type, self.party) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 53c07583d8..1d2eacdb80 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -75,54 +75,40 @@ class TestJournalEntry(unittest.TestCase): elif test_voucher.doctype in ["Sales Order", "Purchase Order"]: # if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher + frappe.db.set_value("Accounts Settings", "Accounts Settings", + "unlink_advance_payment_on_cancelation_of_order", 0) submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name) self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel) def test_jv_against_stock_account(self): - from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory - set_perpetual_inventory() + company = "_Test Company with perpetual inventory" + stock_account = get_inventory_account(company) - jv = frappe.copy_doc({ - "cheque_date": nowdate(), - "cheque_no": "33", - "company": "_Test Company with perpetual inventory", - "doctype": "Journal Entry", - "accounts": [ - { - "account": "Debtors - TCP1", - "party_type": "Customer", - "party": "_Test Customer", - "credit_in_account_currency": 400.0, - "debit_in_account_currency": 0.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "Main - TCP1" - }, - { - "account": "_Test Bank - TCP1", - "credit_in_account_currency": 0.0, - "debit_in_account_currency": 400.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "Main - TCP1" - } - ], - "naming_series": "_T-Journal Entry-", - "posting_date": nowdate(), - "user_remark": "test", - "voucher_type": "Bank Entry" - }) - - jv.get("accounts")[0].update({ - "account": get_inventory_account('_Test Company with perpetual inventory'), - "company": "_Test Company with perpetual inventory", - "party_type": None, - "party": None + jv = frappe.new_doc("Journal Entry") + jv.company = company + jv.posting_date = nowdate() + jv.append("accounts", { + "account": stock_account, + "cost_center": "Main - TCP1", + "debit_in_account_currency": 100 }) + + jv.append("accounts", { + "account": "Stock Adjustment - TCP1", + "credit_in_account_currency": 100, + "cost_center": "Main - TCP1", + }) + jv.insert() - self.assertRaises(StockAccountInvalidTransaction, jv.submit) - jv.cancel() - set_perpetual_inventory(0) + from erpnext.accounts.utils import get_stock_and_account_balance + account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company) + + if account_bal == stock_bal: + self.assertRaises(StockAccountInvalidTransaction, jv.submit) + frappe.db.rollback() + else: + jv.submit() + jv.cancel() def test_multi_currency(self): jv = make_journal_entry("_Test Bank USD - _TC", diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index 5278d8b241..31994885aa 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -8,12 +8,10 @@ import unittest from frappe.utils import today, cint, flt, getdate from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points from erpnext.accounts.party import get_dashboard_info -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestLoyaltyProgram(unittest.TestCase): @classmethod def setUpClass(self): - set_perpetual_inventory(0) # create relevant item, customer, loyalty program, etc create_records() diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 830896b736..750ed82d15 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -14,7 +14,6 @@ "column_break_9", "update_stock", "ignore_pricing_rule", - "hide_unavailable_items", "warehouse", "campaign", "company_address", @@ -336,7 +335,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-10 13:59:28.877572", + "modified": "2020-12-20 13:59:28.877572", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d94d261c6b..b52678e8d3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -410,10 +410,13 @@ class PurchaseInvoice(BuyingController): # this sequence because outstanding may get -negative self.make_gl_entries() + if self.update_stock == 1: + self.repost_future_sle_and_gle() + self.update_project() update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) - def make_gl_entries(self, gl_entries=None): + def make_gl_entries(self, gl_entries=None, from_repost=False): if not gl_entries: gl_entries = self.get_gl_entries() @@ -421,7 +424,7 @@ class PurchaseInvoice(BuyingController): update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" if self.docstatus == 1: - make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False) + make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) elif self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) @@ -436,9 +439,11 @@ class PurchaseInvoice(BuyingController): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) if self.auto_accounting_for_stock: self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") + self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") else: self.stock_received_but_not_billed = None - self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + self.expenses_included_in_valuation = None + self.negative_expense_to_be_booked = 0.0 gl_entries = [] @@ -452,7 +457,7 @@ class PurchaseInvoice(BuyingController): self.make_internal_transfer_gl_entries(gl_entries) gl_entries = make_regional_gl_entries(gl_entries, self) - + gl_entries = merge_similar_entries(gl_entries) self.make_payment_gl_entries(gl_entries) @@ -994,11 +999,15 @@ class PurchaseInvoice(BuyingController): self.delete_auto_created_batches() self.make_gl_entries_on_cancel() + + if self.update_stock == 1: + self.repost_future_sle_and_gle() + self.update_project() frappe.db.set(self, 'status', 'Cancelled') unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) - self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') def update_project(self): project_list = [] diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index f2499d24b5..c0506ba97f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -9,8 +9,7 @@ import frappe.model from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from frappe.utils import cint, flt, today, nowdate, add_days, getdate import frappe.defaults -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ - test_records as pr_test_records, make_purchase_receipt, get_taxes +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt, get_taxes 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 @@ -33,13 +32,10 @@ class TestPurchaseInvoice(unittest.TestCase): def test_gl_entries_without_perpetual_inventory(self): frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC") - wrapper = frappe.copy_doc(test_records[0]) - set_perpetual_inventory(0, wrapper.company) - self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(wrapper.company))) - wrapper.insert() - wrapper.submit() - wrapper.load_from_db() - dl = wrapper + pi = frappe.copy_doc(test_records[0]) + self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(pi.company))) + pi.insert() + pi.submit() expected_gl_entries = { "_Test Payable - _TC": [0, 1512.0], @@ -54,12 +50,16 @@ class TestPurchaseInvoice(unittest.TestCase): "Round Off - _TC": [0, 0.3] } gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type = 'Purchase Invoice' and voucher_no = %s""", dl.name, as_dict=1) + where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name, as_dict=1) for d in gl_entries: self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account)) def test_gl_entries_with_perpetual_inventory(self): - pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10) + pi = make_purchase_invoice(company="_Test Company with perpetual inventory", + warehouse= "Stores - TCP1", cost_center = "Main - TCP1", + expense_account ="_Test Account Cost for Goods Sold - TCP1", + get_taxes_and_charges=True, qty=10) + self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1) self.check_gle_for_pi(pi.name) @@ -198,8 +198,6 @@ class TestPurchaseInvoice(unittest.TestCase): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,) - self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1) - pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True") for d in pi.items: @@ -247,17 +245,11 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertRaises(frappe.CannotChangeConstantError, pi.save) - def test_gl_entries_with_aia_for_non_stock_items(self): - pi = frappe.copy_doc(test_records[1]) - set_perpetual_inventory(1, pi.company) - self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1) - pi.get("items")[0].item_code = "_Test Non Stock Item" - pi.get("items")[0].expense_account = "_Test Account Cost for Goods Sold - _TC" - pi.get("taxes").pop(0) - pi.get("taxes").pop(1) - pi.insert() - pi.submit() - pi.load_from_db() + def test_gl_entries_for_non_stock_items_with_perpetual_inventory(self): + pi = make_purchase_invoice(item_code = "_Test Non Stock Item", + company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1", + cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1") + self.assertTrue(pi.status, "Unpaid") gl_entries = frappe.db.sql("""select account, debit, credit @@ -265,17 +257,15 @@ class TestPurchaseInvoice(unittest.TestCase): order by account asc""", pi.name, as_dict=1) self.assertTrue(gl_entries) - expected_values = sorted([ - ["_Test Payable - _TC", 0, 620], - ["_Test Account Cost for Goods Sold - _TC", 500.0, 0], - ["_Test Account VAT - _TC", 120.0, 0], - ]) + expected_values = [ + ["_Test Account Cost for Goods Sold - TCP1", 250.0, 0], + ["Creditors - TCP1", 0, 250] + ] for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[i][0], gle.account) self.assertEqual(expected_values[i][1], gle.debit) self.assertEqual(expected_values[i][2], gle.credit) - set_perpetual_inventory(0, pi.company) def test_purchase_invoice_calculation(self): pi = frappe.copy_doc(test_records[0]) @@ -457,12 +447,13 @@ class TestPurchaseInvoice(unittest.TestCase): pi.cancel() self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost) - def test_return_purchase_invoice(self): - set_perpetual_inventory() + def test_return_purchase_invoice_with_perpetual_inventory(self): + pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1", + cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1") - pi = make_purchase_invoice() - - return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2) + return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, + company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1", + cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1") # check gl entries for return @@ -473,19 +464,15 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertTrue(gl_entries) expected_values = { - "Creditors - _TC": [100.0, 0.0], - "Stock Received But Not Billed - _TC": [0.0, 100.0], + "Creditors - TCP1": [100.0, 0.0], + "Stock Received But Not Billed - TCP1": [0.0, 100.0], } for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) - set_perpetual_inventory(0) - def test_multi_currency_gle(self): - set_perpetual_inventory(0) - pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC", currency="USD", conversion_rate=50) @@ -640,10 +627,9 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(len(pi.get("supplied_items")), 2) rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")]) - self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) + self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2)) def test_rejected_serial_no(self): - set_perpetual_inventory(0) pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1, rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC") diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ca6f22cc30..50734c865c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -179,6 +179,9 @@ class SalesInvoice(SellingController): # this sequence because outstanding may get -ve self.make_gl_entries() + + if self.update_stock == 1: + self.repost_future_sle_and_gle() if not self.is_return: self.update_billing_status_for_zero_amount_refdoc("Delivery Note") @@ -258,6 +261,10 @@ class SalesInvoice(SellingController): self.update_stock_ledger() self.make_gl_entries_on_cancel() + + if self.update_stock == 1: + self.repost_future_sle_and_gle() + frappe.db.set(self, 'status', 'Cancelled') if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction": @@ -279,7 +286,7 @@ class SalesInvoice(SellingController): if "Healthcare" in active_domains: manage_invoice_submit_cancel(self, "on_cancel") - self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') def update_status_updater_args(self): if cint(self.update_stock): @@ -722,22 +729,20 @@ class SalesInvoice(SellingController): if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) - def make_gl_entries(self, gl_entries=None): - from erpnext.accounts.general_ledger import make_reverse_gl_entries + def make_gl_entries(self, gl_entries=None, from_repost=False): + from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) if not gl_entries: gl_entries = self.get_gl_entries() if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries - # if POS and amount is written off, updating outstanding amt after posting all gl entries update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or cint(self.redeem_loyalty_points)) else "Yes" if self.docstatus == 1: - make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False) + make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) elif self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index 11ebe6a573..ee6419db20 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -17,7 +17,8 @@ "description": "138-CMS Shoe", "doctype": "Sales Invoice Item", "income_account": "Sales - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "item_code": "138-CMS Shoe", "item_name": "138-CMS Shoe", "parentfield": "items", "qty": 1.0, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 22a4f33654..ceb7907989 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -10,7 +10,6 @@ from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from frappe.model.naming import make_autoname @@ -659,7 +658,6 @@ class TestSalesInvoice(unittest.TestCase): def test_sales_invoice_gl_entry_without_perpetual_inventory(self): si = frappe.copy_doc(test_records[1]) - set_perpetual_inventory(0, si.company) si.insert() si.submit() @@ -815,7 +813,6 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") def test_pos_si_without_payment(self): - set_perpetual_inventory() make_pos_profile() pos = copy.deepcopy(test_records[1]) @@ -829,9 +826,8 @@ class TestSalesInvoice(unittest.TestCase): self.assertRaises(frappe.ValidationError, si.submit) def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self): - set_perpetual_inventory() - - si = frappe.get_doc(test_records[1]) + si = create_sales_invoice(company="_Test Company with perpetual inventory", debit_to = "Debtors - TCP1", + income_account="Sales - TCP1", cost_center = "Main - TCP1", do_not_save=True) si.get("items")[0].item_code = None si.insert() si.submit() @@ -842,24 +838,16 @@ class TestSalesInvoice(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - [si.debit_to, 630.0, 0.0], - [test_records[1]["items"][0]["income_account"], 0.0, 500.0], - [test_records[1]["taxes"][0]["account_head"], 0.0, 80.0], - [test_records[1]["taxes"][1]["account_head"], 0.0, 50.0], + ["Debtors - TCP1", 100.0, 0.0], + ["Sales - TCP1", 0.0, 100.0] ]) for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account][0], gle.account) self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) - set_perpetual_inventory(0) - def test_sales_invoice_gl_entry_with_perpetual_inventory_non_stock_item(self): - set_perpetual_inventory() - si = frappe.get_doc(test_records[1]) - si.get("items")[0].item_code = "_Test Non Stock Item" - si.insert() - si.submit() + si = create_sales_invoice(item="_Test Non Stock Item") gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s @@ -867,17 +855,14 @@ class TestSalesInvoice(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - [si.debit_to, 630.0, 0.0], - [test_records[1]["items"][0]["income_account"], 0.0, 500.0], - [test_records[1]["taxes"][0]["account_head"], 0.0, 80.0], - [test_records[1]["taxes"][1]["account_head"], 0.0, 50.0], + [si.debit_to, 100.0, 0.0], + [test_records[1]["items"][0]["income_account"], 0.0, 100.0] ]) for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account][0], gle.account) self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) - set_perpetual_inventory(0) def _insert_purchase_receipt(self): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \ @@ -1106,7 +1091,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.grand_total, 859.43) def test_multi_currency_gle(self): - set_perpetual_inventory(0) si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", currency="USD", conversion_rate=50) @@ -1776,64 +1760,69 @@ class TestSalesInvoice(unittest.TestCase): si.submit() target_doc = make_inter_company_transaction("Sales Invoice", si.name) + target_doc.items[0].update({ + "expense_account": "Cost of Goods Sold - _TC1", + "cost_center": "Main - _TC1", + "warehouse": "Stores - _TC1" + }) target_doc.submit() self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.supplier, "_Test Internal Supplier") - def test_internal_transfer_gl_entry(self): - ## Create internal transfer account - account = create_account(account_name="Unrealized Profit", - parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory") + # def test_internal_transfer_gl_entry(self): + # ## Create internal transfer account + # account = create_account(account_name="Unrealized Profit", + # parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory") - frappe.db.set_value('Company', '_Test Company with perpetual inventory', - 'unrealized_profit_loss_account', account) + # frappe.db.set_value('Company', '_Test Company with perpetual inventory', + # 'unrealized_profit_loss_account', account) - customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory", - "_Test Company with perpetual inventory") + # customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory", + # "_Test Company with perpetual inventory") - create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory", - "_Test Company with perpetual inventory") + # create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory", + # "_Test Company with perpetual inventory") - si = create_sales_invoice( - company = "_Test Company with perpetual inventory", - customer = customer, - debit_to = "Debtors - TCP1", - warehouse = "Stores - TCP1", - income_account = "Sales - TCP1", - expense_account = "Cost of Goods Sold - TCP1", - cost_center = "Main - TCP1", - currency = "INR", - do_not_save = 1 - ) + # si = create_sales_invoice( + # company = "_Test Company with perpetual inventory", + # customer = customer, + # debit_to = "Debtors - TCP1", + # warehouse = "Stores - TCP1", + # income_account = "Sales - TCP1", + # expense_account = "Cost of Goods Sold - TCP1", + # cost_center = "Main - TCP1", + # currency = "INR", + # do_not_save = 1 + # ) - si.selling_price_list = "_Test Price List Rest of the World" - si.update_stock = 1 - si.items[0].target_warehouse = 'Work In Progress - TCP1' - add_taxes(si) - si.save() - si.submit() + # si.selling_price_list = "_Test Price List Rest of the World" + # si.update_stock = 1 + # si.items[0].target_warehouse = 'Work In Progress - TCP1' + # add_taxes(si) + # si.save() + # si.submit() - target_doc = make_inter_company_transaction("Sales Invoice", si.name) - target_doc.company = '_Test Company with perpetual inventory' - target_doc.items[0].warehouse = 'Finished Goods - TCP1' - add_taxes(target_doc) - target_doc.save() - target_doc.submit() + # target_doc = make_inter_company_transaction("Sales Invoice", si.name) + # target_doc.company = '_Test Company with perpetual inventory' + # target_doc.items[0].warehouse = 'Finished Goods - TCP1' + # add_taxes(target_doc) + # target_doc.save() + # target_doc.submit() - si_gl_entries = [ - ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()], - ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()] - ] + # si_gl_entries = [ + # ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()], + # ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()] + # ] - check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1)) + # check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1)) - pi_gl_entries = [ - ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()], - ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()] - ] + # pi_gl_entries = [ + # ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()], + # ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()] + # ] - check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) + # check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) def test_eway_bill_json(self): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): @@ -1991,14 +1980,19 @@ def create_sales_invoice(**args): si.append("items", { "item_code": args.item or args.item_code or "_Test Item", + "item_name": args.item_name or "_Test Item", + "description": args.description or "_Test Item", "gst_hsn_code": "999800", "warehouse": args.warehouse or "_Test Warehouse - _TC", "qty": args.qty or 1, + "uom": args.uom or "Nos", + "stock_uom": args.uom or "Nos", "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 + "serial_no": args.serial_no, + "conversion_factor": 1 }) if not args.do_not_save: 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 fb3dd6a92a..3695075798 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-06-04 11:02:19", "doctype": "DocType", @@ -51,6 +52,7 @@ "column_break_24", "base_net_rate", "base_net_amount", + "incoming_rate", "drop_ship", "delivered_by_supplier", "accounting", @@ -792,20 +794,28 @@ "options": "Project" }, { - "depends_on": "eval:parent.update_stock == 1", - "fieldname": "sales_invoice_item", - "fieldtype": "Data", - "ignore_user_permissions": 1, - "label": "Sales Invoice Item", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - } + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "sales_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Sales Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "incoming_rate", + "fieldtype": "Currency", + "label": "Incoming Rate", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-08-20 11:24:41.749986", + "modified": "2020-09-23 19:59:04.879322", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 9a091bf57b..c7f0c8781c 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -15,13 +15,13 @@ class ClosedAccountingPeriod(frappe.ValidationError): pass class StockAccountInvalidTransaction(frappe.ValidationError): pass class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass -def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'): +def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): if gl_map: if not cancel: validate_accounting_period(gl_map) gl_map = process_gl_map(gl_map, merge_entries) if gl_map and len(gl_map) > 1: - save_entries(gl_map, adv_adj, update_outstanding) + save_entries(gl_map, adv_adj, update_outstanding, from_repost) else: frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.")) else: @@ -119,8 +119,9 @@ def check_if_in_list(gle, gl_map, dimensions=None): if same_head: return e -def save_entries(gl_map, adv_adj, update_outstanding): - validate_cwip_accounts(gl_map) +def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): + if not from_repost: + validate_cwip_accounts(gl_map) round_off_debit_credit(gl_map) @@ -128,24 +129,24 @@ def save_entries(gl_map, adv_adj, update_outstanding): check_freezing_date(gl_map[0]["posting_date"], adv_adj) for entry in gl_map: - make_entry(entry, adv_adj, update_outstanding) + make_entry(entry, adv_adj, update_outstanding, from_repost) - # check against budget - validate_expense_against_budget(entry) - - validate_account_for_perpetual_inventory(gl_map) + if not from_repost: + validate_account_for_perpetual_inventory(gl_map) -def make_entry(args, adv_adj, update_outstanding): +def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle = frappe.new_doc("GL Entry") gle.update(args) gle.flags.ignore_permissions = 1 + gle.flags.from_repost = from_repost gle.insert() - gle.run_method("on_update_with_args", adv_adj, update_outstanding) + gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) gle.submit() # check against budget - validate_expense_against_budget(args) + if not from_repost: + validate_expense_against_budget(args) def validate_account_for_perpetual_inventory(gl_map): if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): @@ -161,7 +162,7 @@ def validate_account_for_perpetual_inventory(gl_map): # Always use current date to get stock and account balance as there can future entries for # other items account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, - getdate(), gl_map[0].company) + gl_map[0].posting_date, gl_map[0].company) if gl_map[0].voucher_type=="Journal Entry": # In case of Journal Entry, there are no corresponding SL entries, @@ -176,8 +177,8 @@ def validate_account_for_perpetual_inventory(gl_map): currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) diff = flt(stock_bal - account_bal, precision) - error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( - stock_bal, account_bal, frappe.bold(account)) + error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses on {3}.").format( + stock_bal, account_bal, frappe.bold(account), gl_map[0].posting_date) error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") @@ -185,9 +186,10 @@ def validate_account_for_perpetual_inventory(gl_map): db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') journal_entry_args = { - 'accounts':[ - {'account': account, db_or_cr_warehouse_account : abs(diff)}, - {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] + 'accounts':[ + {'account': account, db_or_cr_warehouse_account : abs(diff)}, + {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff)} + ] } frappe.msgprint(msg="""{0}

{1}

""".format(error_reason, error_resolution), diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 550aaef404..540ac84182 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -928,7 +928,7 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for if expected_gle: if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): _delete_gl_entries(voucher_type, voucher_no) - voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True) + voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True) else: _delete_gl_entries(voucher_type, voucher_no) @@ -947,7 +947,10 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no from `tabStock Ledger Entry` sle - where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} + where + timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) + and is_cancelled = 0 + {condition} order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), tuple([posting_date, posting_time] + values), as_dict=True): future_stock_vouchers.append([d.voucher_type, d.voucher_no]) @@ -964,3 +967,20 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) return gl_entries + +def compare_existing_and_expected_gle(existing_gle, expected_gle): + matched = True + for entry in expected_gle: + account_existed = False + for e in existing_gle: + if entry.account == e.account: + account_existed = True + if entry.account == e.account and entry.against_account == e.against_account \ + and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \ + and (entry.debit != e.debit or entry.credit != e.credit): + matched = False + break + if not account_existed: + matched = False + break + return matched \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 10db240a44..c691e9f9f8 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -732,7 +732,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-30 11:59:47.670951", + "modified": "2020-12-07 11:59:47.670951", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 286c4f4451..dc61870df3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -16,6 +16,8 @@ from frappe.contacts.doctype.address.address import get_address_display from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.controllers.stock_controller import StockController +from erpnext.controllers.sales_and_purchase_return import get_rate_for_return +from erpnext.stock.utils import get_incoming_rate class BuyingController(StockController): def __setup__(self): @@ -63,7 +65,7 @@ class BuyingController(StockController): self.set_landed_cost_voucher_amount() if self.doctype in ("Purchase Receipt", "Purchase Invoice"): - self.update_valuation_rate("items") + self.update_valuation_rate() def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) @@ -177,7 +179,7 @@ class BuyingController(StockController): self.in_words = money_in_words(amount, self.currency) # update valuation rate - def update_valuation_rate(self, parentfield): + def update_valuation_rate(self, reset_outgoing_rate=True): """ item_tax_amount is the total tax amount applied on that item stored for valuation @@ -188,7 +190,7 @@ class BuyingController(StockController): stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0 last_item_idx = 1 - for d in self.get(parentfield): + for d in self.get("items"): if d.item_code and d.item_code in stock_and_asset_items: stock_and_asset_items_qty += flt(d.qty) stock_and_asset_items_amount += flt(d.base_net_amount) @@ -198,7 +200,7 @@ class BuyingController(StockController): if d.category in ["Valuation", "Valuation and Total"]]) valuation_amount_adjustment = total_valuation_amount - for i, item in enumerate(self.get(parentfield)): + for i, item in enumerate(self.get("items")): if item.item_code and item.qty and item.item_code in stock_and_asset_items: item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \ else flt(item.qty) / stock_and_asset_items_qty @@ -216,16 +218,34 @@ class BuyingController(StockController): item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0 qty_in_stock_uom = flt(item.qty * item.conversion_factor) - rm_supp_cost = flt(item.rm_supp_cost) if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0 - - landed_cost_voucher_amount = flt(item.landed_cost_voucher_amount) \ - if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0 - - item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + rm_supp_cost - + landed_cost_voucher_amount) / qty_in_stock_uom) + item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate) + item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + item.rm_supp_cost + + flt(item.landed_cost_voucher_amount)) / qty_in_stock_uom) else: item.valuation_rate = 0.0 + def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True): + supplied_items_cost = 0.0 + for d in self.get("supplied_items"): + if d.reference_name == item_row_id: + if reset_outgoing_rate and frappe.db.get_value('Item', d.rm_item_code, 'is_stock_item'): + rate = get_incoming_rate({ + "item_code": d.rm_item_code, + "warehouse": self.supplier_warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1 * d.consumed_qty, + "serial_no": d.serial_no + }) + + if rate > 0: + d.rate = rate + + d.amount = flt(d.consumed_qty) * flt(d.rate) + supplied_items_cost += flt(d.amount) + + return supplied_items_cost + def validate_for_subcontracting(self): if not self.is_subcontracted and self.sub_contracted_items: frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No")) @@ -352,35 +372,17 @@ class BuyingController(StockController): else: self.append_raw_material_to_be_backflushed(item, raw_material, qty) - def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty): + def append_raw_material_to_be_backflushed(self, fg_item_row, raw_material_data, qty): rm = self.append('supplied_items', {}) rm.update(raw_material_data) if not rm.main_item_code: - rm.main_item_code = fg_item_doc.item_code + rm.main_item_code = fg_item_row.item_code - rm.reference_name = fg_item_doc.name + rm.reference_name = fg_item_row.name rm.required_qty = qty rm.consumed_qty = qty - if not raw_material_data.get('non_stock_item'): - from erpnext.stock.utils import get_incoming_rate - rm.rate = get_incoming_rate({ - "item_code": raw_material_data.rm_item_code, - "warehouse": self.supplier_warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1 * qty, - "serial_no": rm.serial_no - }) - - if not rm.rate: - rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse, - self.doctype, self.name, currency=self.company_currency, company=self.company) - - rm.amount = qty * flt(rm.rate) - fg_item_doc.rm_supp_cost += rm.amount - def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table): exploded_item = 1 if hasattr(item, 'include_exploded_items'): @@ -389,7 +391,7 @@ class BuyingController(StockController): bom_items = get_items_from_bom(item.item_code, item.bom, exploded_item) used_alternative_items = [] - if self.doctype == 'Purchase Receipt' and item.purchase_order: + if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order: used_alternative_items = get_used_alternative_items(purchase_order = item.purchase_order) raw_materials_cost = 0 @@ -406,7 +408,7 @@ class BuyingController(StockController): reserve_warehouse = None conversion_factor = item.conversion_factor - if (self.doctype == 'Purchase Receipt' and item.purchase_order and + if (self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order and bom_item.item_code in used_alternative_items): alternative_item_data = used_alternative_items.get(bom_item.item_code) bom_item.item_code = alternative_item_data.item_code @@ -434,9 +436,7 @@ class BuyingController(StockController): rm.rm_item_code = bom_item.item_code rm.stock_uom = bom_item.stock_uom rm.required_qty = required_qty - if self.doctype == "Purchase Order" and not rm.reserve_warehouse: - rm.reserve_warehouse = reserve_warehouse - + rm.rate = bom_item.rate rm.conversion_factor = conversion_factor if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: @@ -444,29 +444,8 @@ class BuyingController(StockController): rm.description = bom_item.description if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no: rm.batch_no = item.batch_no - - # get raw materials rate - if self.doctype == "Purchase Receipt": - from erpnext.stock.utils import get_incoming_rate - rm.rate = get_incoming_rate({ - "item_code": bom_item.item_code, - "warehouse": self.supplier_warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1 * required_qty, - "serial_no": rm.serial_no - }) - if not rm.rate: - rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse, - self.doctype, self.name, currency=self.company_currency, company = self.company) - else: - rm.rate = bom_item.rate - - rm.amount = required_qty * flt(rm.rate) - raw_materials_cost += flt(rm.amount) - - if self.doctype in ("Purchase Receipt", "Purchase Invoice"): - item.rm_supp_cost = raw_materials_cost + elif not rm.reserve_warehouse: + rm.reserve_warehouse = reserve_warehouse def cleanup_raw_materials_supplied(self, parent_items, raw_material_table): """Remove all those child items which are no longer present in main item table""" @@ -579,7 +558,8 @@ class BuyingController(StockController): or (cint(self.is_return) and self.docstatus==2)): from_warehouse_sle = self.get_sl_entries(d, { "actual_qty": -1 * pr_qty, - "warehouse": d.from_warehouse + "warehouse": d.from_warehouse, + "dependant_sle_voucher_detail_no": d.name }) sl_entries.append(from_warehouse_sle) @@ -589,28 +569,20 @@ class BuyingController(StockController): "serial_no": cstr(d.serial_no).strip() }) if self.is_return: - filters = { - "voucher_type": self.doctype, - "voucher_no": self.return_against, - "item_code": d.item_code - } - - if (self.doctype == "Purchase Invoice" and self.update_stock - and d.get("purchase_invoice_item")): - filters["voucher_detail_no"] = d.purchase_invoice_item - elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"): - filters["voucher_detail_no"] = d.purchase_receipt_item - - original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate") + outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d) sle.update({ - "outgoing_rate": original_incoming_rate + "outgoing_rate": outgoing_rate, + "recalculate_rate": 1 }) + if d.from_warehouse: + sle.dependant_sle_voucher_detail_no = d.name else: val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9 incoming_rate = flt(d.valuation_rate, val_rate_db_precision) sle.update({ - "incoming_rate": incoming_rate + "incoming_rate": incoming_rate, + "recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0 }) sl_entries.append(sle) @@ -618,7 +590,8 @@ class BuyingController(StockController): or (cint(self.is_return) and self.docstatus==1)): from_warehouse_sle = self.get_sl_entries(d, { "actual_qty": -1 * pr_qty, - "warehouse": d.from_warehouse + "warehouse": d.from_warehouse, + "recalculate_rate": 1 }) sl_entries.append(from_warehouse_sle) @@ -666,6 +639,7 @@ class BuyingController(StockController): "item_code": d.rm_item_code, "warehouse": self.supplier_warehouse, "actual_qty": -1*flt(d.consumed_qty), + "dependant_sle_voucher_detail_no": d.reference_name })) def on_submit(self): @@ -857,6 +831,7 @@ class BuyingController(StockController): else: validate_item_type(self, "is_purchase_item", "purchase") + def get_items_from_bom(item_code, bom, exploded_item=1): doctype = "BOM Item" if not exploded_item else "BOM Explosion Item" diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 5299b25601..8f65c31f3d 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -365,3 +365,45 @@ def make_return_doc(doctype, source_name, target_doc=None): }, target_doc, set_missing_values) return doclist + +def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None): + if not return_against: + return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against") + + return_against_item_field = get_return_against_item_fields(voucher_type) + + filters = get_filters(voucher_type, voucher_no, voucher_detail_no, + return_against, item_code, return_against_item_field, item_row) + + if voucher_type in ("Purchase Receipt", "Purchase Invoice"): + select_field = "incoming_rate" + else: + select_field = "abs(stock_value_difference / actual_qty)" + + return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field)) + +def get_return_against_item_fields(voucher_type): + return_against_item_fields = { + "Purchase Receipt": "purchase_receipt_item", + "Purchase Invoice": "purchase_invoice_item", + "Delivery Note": "dn_detail", + "Sales Invoice": "sales_invoice_item" + } + return return_against_item_fields[voucher_type] + +def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, item_code, return_against_item_field, item_row): + filters = { + "voucher_type": voucher_type, + "voucher_no": return_against, + "item_code": item_code + } + + if item_row: + reference_voucher_detail_no = item_row.get(return_against_item_field) + else: + reference_voucher_detail_no = frappe.db.get_value(voucher_type + " Item", voucher_detail_no, return_against_item_field) + + if reference_voucher_detail_no: + filters["voucher_detail_no"] = reference_voucher_detail_no + + return filters \ No newline at end of file diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 4dbd7bfa18..85cfb951fc 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -13,6 +13,7 @@ from frappe.contacts.doctype.address.address import get_address_display from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.stock_controller import StockController +from erpnext.controllers.sales_and_purchase_return import get_rate_for_return class SellingController(StockController): def __setup__(self): @@ -48,6 +49,7 @@ class SellingController(StockController): self.set_customer_address() self.validate_for_duplicate_items() self.validate_target_warehouse() + self.set_incoming_rate() def set_missing_values(self, for_validate=False): @@ -230,7 +232,8 @@ class SellingController(StockController): 'voucher_type': self.doctype, 'allow_zero_valuation': d.allow_zero_valuation_rate, 'sales_invoice_item': d.get("sales_invoice_item"), - 'delivery_note_item': d.get("dn_detail") + 'dn_detail': d.get("dn_detail"), + 'incoming_rate': p.incoming_rate })) else: il.append(frappe._dict({ @@ -248,7 +251,8 @@ class SellingController(StockController): 'voucher_type': self.doctype, 'allow_zero_valuation': d.allow_zero_valuation_rate, 'sales_invoice_item': d.get("sales_invoice_item"), - 'delivery_note_item': d.get("dn_detail") + 'dn_detail': d.get("dn_detail"), + 'incoming_rate': d.incoming_rate })) return il @@ -307,69 +311,89 @@ class SellingController(StockController): sales_order.update_reserved_qty(so_item_rows) + def set_incoming_rate(self): + if self.doctype not in ("Delivery Note", "Sales Invoice"): + return + + items = self.get("items") + (self.get("packed_items") or []) + for d in items: + if not cint(self.get("is_return")): + # Get incoming rate based on original item cost based on valuation method + d.incoming_rate = get_incoming_rate({ + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1*flt(d.qty), + "serial_no": d.serial_no, + "company": self.company, + "voucher_type": self.doctype, + "voucher_no": self.name, + "allow_zero_valuation": d.get("allow_zero_valuation") + }, raise_error_if_no_rate=False) + elif self.get("return_against"): + # Get incoming rate of return entry from reference document + # based on original item cost as per valuation method + d.incoming_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d) + def update_stock_ledger(self): self.update_reserved_qty() sl_entries = [] + # Loop over items and packed items table for d in self.get_item_list(): if frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty): if flt(d.conversion_factor)==0.0: d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 - return_rate = 0 - if cint(self.is_return) and self.return_against and self.docstatus==1: - against_document_no = (d.get("sales_invoice_item") - if self.doctype == "Sales Invoice" else d.get("delivery_note_item")) - return_rate = self.get_incoming_rate_for_return(d.item_code, - self.return_against, against_document_no) - - # On cancellation or if return entry submission, make stock ledger entry for + # On cancellation or return entry submission, make stock ledger entry for # target warehouse first, to update serial no values properly if d.warehouse and ((not cint(self.is_return) and self.docstatus==1) or (cint(self.is_return) and self.docstatus==2)): - sl_entries.append(self.get_sl_entries(d, { - "actual_qty": -1*flt(d.qty), - "incoming_rate": return_rate - })) + sl_entries.append(self.get_sle_for_source_warehouse(d)) if d.target_warehouse: - target_warehouse_sle = self.get_sl_entries(d, { - "actual_qty": flt(d.qty), - "warehouse": d.target_warehouse - }) - - if self.docstatus == 1: - if not cint(self.is_return): - args = frappe._dict({ - "item_code": d.item_code, - "warehouse": d.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1*flt(d.qty), - "serial_no": d.serial_no, - "company": d.company, - "voucher_type": d.voucher_type, - "voucher_no": d.name, - "allow_zero_valuation": d.allow_zero_valuation - }) - target_warehouse_sle.update({ - "incoming_rate": get_incoming_rate(args) - }) - else: - target_warehouse_sle.update({ - "outgoing_rate": return_rate - }) - sl_entries.append(target_warehouse_sle) + sl_entries.append(self.get_sle_for_target_warehouse(d)) if d.warehouse and ((not cint(self.is_return) and self.docstatus==2) or (cint(self.is_return) and self.docstatus==1)): - sl_entries.append(self.get_sl_entries(d, { - "actual_qty": -1*flt(d.qty), - "incoming_rate": return_rate - })) + sl_entries.append(self.get_sle_for_source_warehouse(d)) + self.make_sl_entries(sl_entries) + def get_sle_for_source_warehouse(self, item_row): + sle = self.get_sl_entries(item_row, { + "actual_qty": -1*flt(item_row.qty), + "incoming_rate": item_row.incoming_rate, + "recalculate_rate": cint(self.is_return) + }) + if item_row.target_warehouse and not cint(self.is_return): + sle.dependant_sle_voucher_detail_no = item_row.name + + return sle + + def get_sle_for_target_warehouse(self, item_row): + sle = self.get_sl_entries(item_row, { + "actual_qty": flt(item_row.qty), + "warehouse": item_row.target_warehouse + }) + + if self.docstatus == 1: + if not cint(self.is_return): + sle.update({ + "incoming_rate": item_row.incoming_rate, + "recalculate_rate": 1 + }) + else: + sle.update({ + "outgoing_rate": item_row.incoming_rate + }) + if item_row.warehouse: + sle.dependant_sle_voucher_detail_no = item_row.name + + return sle + def set_po_nos(self, for_validate=False): if self.doctype == 'Sales Invoice' and hasattr(self, "items"): if for_validate and self.po_no: @@ -463,4 +487,4 @@ def set_default_income_account_for_item(obj): for d in obj.get("items"): if d.item_code: if getattr(d, "income_account", None): - set_item_default(d.item_code, obj.company, 'income_account', d.income_account) + set_item_default(d.item_code, obj.company, 'income_account', d.income_account) \ No newline at end of file diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 683d7f77b5..51c063c2c0 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -24,7 +24,7 @@ class StockController(AccountsController): self.validate_serialized_batch() self.validate_customer_provided_item() - def make_gl_entries(self, gl_entries=None): + def make_gl_entries(self, gl_entries=None, from_repost=False): if self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) @@ -34,12 +34,12 @@ class StockController(AccountsController): if self.docstatus==1: if not gl_entries: gl_entries = self.get_gl_entries(warehouse_account) - make_gl_entries(gl_entries) + make_gl_entries(gl_entries, from_repost=from_repost) elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1: gl_entries = [] gl_entries = self.get_asset_gl_entry(gl_entries) - make_gl_entries(gl_entries) + make_gl_entries(gl_entries, from_repost=from_repost) def validate_serialized_batch(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -70,7 +70,6 @@ class StockController(AccountsController): gl_list = [] warehouse_with_no_account = [] - precision = frappe.get_precision("GL Entry", "debit_in_account_currency") for item_row in voucher_details: sle_list = sle_map.get(item_row.name) @@ -125,7 +124,7 @@ class StockController(AccountsController): if warehouse_with_no_account: for wh in warehouse_with_no_account: if frappe.db.get_value("Warehouse", wh, "company"): - frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company)) + frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company)) return process_gl_map(gl_list) @@ -309,23 +308,6 @@ class StockController(AccountsController): return serialized_items - def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None): - incoming_rate = 0.0 - cond = '' - if against_document and item_code: - if against_document_no: - cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no)) - - incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty) - from `tabStock Ledger Entry` - where voucher_type = %s and voucher_no = %s - and item_code = %s {0} limit 1""".format(cond), - (self.doctype, against_document, item_code)) - - incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 - - return incoming_rate - def validate_warehouse(self): from erpnext.stock.utils import validate_warehouse_company @@ -409,19 +391,64 @@ class StockController(AccountsController): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): d.allow_zero_valuation_rate = 1 -def compare_existing_and_expected_gle(existing_gle, expected_gle): - matched = True - for entry in expected_gle: - account_existed = False - for e in existing_gle: - if entry.account == e.account: - account_existed = True - if entry.account == e.account and entry.against_account == e.against_account \ - and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \ - and (entry.debit != e.debit or entry.credit != e.credit): - matched = False - break - if not account_existed: - matched = False + def repost_future_sle_and_gle(self): + args = frappe._dict({ + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "voucher_type": self.doctype, + "voucher_no": self.name, + "company": self.company + }) + + if check_if_future_sle_exists(args): + create_repost_item_valuation_entry(args) + +def check_if_future_sle_exists(args): + sl_entries = frappe.db.get_all("Stock Ledger Entry", + filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no}, + fields=["item_code", "warehouse"], + order_by="creation asc") + + distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries])) + + sle_exists = False + for item_code, warehouse in distinct_item_warehouses: + args.update({ + "item_code": item_code, + "warehouse": warehouse + }) + if get_sle(args): + sle_exists = True break - return matched + return sle_exists + +def get_sle(args): + return frappe.db.sql(""" + select name + from `tabStock Ledger Entry` + where + item_code=%(item_code)s + and warehouse=%(warehouse)s + and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) + and voucher_no != %(voucher_no)s + and is_cancelled = 0 + limit 1 + """, args) + +def create_repost_item_valuation_entry(args): + args = frappe._dict(args) + repost_entry = frappe.new_doc("Repost Item Valuation") + repost_entry.based_on = args.based_on + if not args.based_on: + repost_entry.based_on = 'Transaction' if args.voucher_no else "Item and Warehouse" + repost_entry.voucher_type = args.voucher_type + repost_entry.voucher_no = args.voucher_no + repost_entry.item_code = args.item_code + repost_entry.warehouse = args.warehouse + repost_entry.posting_date = args.posting_date + repost_entry.posting_time = args.posting_time + repost_entry.company = args.company + repost_entry.allow_zero_rate = args.allow_zero_rate + repost_entry.flags.ignore_links = True + repost_entry.save() + repost_entry.submit() \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 2bf3fbf75e..ce9699e1b3 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import unittest import frappe from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError) from erpnext.stock.doctype.stock_entry import test_stock_entry @@ -18,7 +17,6 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse class TestWorkOrder(unittest.TestCase): def setUp(self): - set_perpetual_inventory(0) self.warehouse = '_Test Warehouse 2 - _TC' self.item = '_Test Item' diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index eff17f8bc7..159655b74b 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -785,7 +785,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-05-29 20:54:32.309460", + "modified": "2020-012-07 20:54:32.309460", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index 21302417d2..9e55702ddc 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -7,7 +7,8 @@ "doctype": "Company", "domain": "Manufacturing", "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" + "default_holiday_list": "_Test Holiday List", + "enable_perpetual_inventory": 0 }, { "abbr": "_TC1", @@ -17,7 +18,8 @@ "doctype": "Company", "domain": "Retail", "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" + "default_holiday_list": "_Test Holiday List", + "enable_perpetual_inventory": 0 }, { "abbr": "_TC2", @@ -27,7 +29,8 @@ "doctype": "Company", "domain": "Retail", "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" + "default_holiday_list": "_Test Holiday List", + "enable_perpetual_inventory": 0 }, { "abbr": "_TC3", @@ -38,7 +41,8 @@ "doctype": "Company", "domain": "Manufacturing", "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" + "default_holiday_list": "_Test Holiday List", + "enable_perpetual_inventory": 0 }, { "abbr": "_TC4", @@ -50,7 +54,8 @@ "doctype": "Company", "domain": "Manufacturing", "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" + "default_holiday_list": "_Test Holiday List", + "enable_perpetual_inventory": 0 }, { "abbr": "_TC5", @@ -61,7 +66,8 @@ "doctype": "Company", "domain": "Manufacturing", "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" + "default_holiday_list": "_Test Holiday List", + "enable_perpetual_inventory": 0 }, { "abbr": "TCP1", diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index c2a3d3c151..e41f1a8aaa 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -8,13 +8,8 @@ import unittest from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no from frappe.utils import cint, flt -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestBatch(unittest.TestCase): - - def setUp(self): - set_perpetual_inventory(0) - def test_item_has_batch_enabled(self): self.assertRaises(ValidationError, frappe.get_doc({ "doctype": "Batch", diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 7acdec728b..ab19b77ad8 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -16,22 +16,30 @@ class Bin(Document): def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False): '''Called from erpnext.stock.utils.update_bin''' self.update_qty(args) - if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": - from erpnext.stock.stock_ledger import update_entries_after + from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle if not args.get("posting_date"): args["posting_date"] = nowdate() + if args.get("is_cancelled") and via_landed_cost_voucher: + return + + # Reposts only current voucher SL Entries + # Updates valuation rate, stock value, stock queue for current transaction update_entries_after({ "item_code": self.item_code, "warehouse": self.warehouse, "posting_date": args.get("posting_date"), "posting_time": args.get("posting_time"), + "voucher_type": args.get("voucher_type"), "voucher_no": args.get("voucher_no"), - "sle_id": args.sle_id + "sle_id": args.name }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) + # Update qty_after_transaction in future SLEs of this item and warehouse + update_qty_in_future_sle(args) + def update_qty(self, args): # update the stock values (for current quantities) if args.get("voucher_type")=="Stock Reconciliation": diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 3f3407e350..1a6a555092 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -217,6 +217,7 @@ class DeliveryNote(SellingController): # because updating reserved qty in bin depends upon updated delivered qty in SO self.update_stock_ledger() self.make_gl_entries() + self.repost_future_sle_and_gle() def on_cancel(self): super(DeliveryNote, self).on_cancel() @@ -234,7 +235,8 @@ class DeliveryNote(SellingController): self.cancel_packing_slips() self.make_gl_entries_on_cancel() - self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.repost_future_sle_and_gle() + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') def check_credit_limit(self): from erpnext.selling.doctype.customer.customer import check_credit_limit diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 6b4663a688..559f8be0de 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -10,8 +10,7 @@ import frappe.defaults from frappe.utils import cint, nowdate, nowtime, cstr, add_days, flt, today from erpnext.stock.stock_ledger import get_previous_sle from erpnext.accounts.utils import get_balance_on -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ - import get_gl_entries, set_perpetual_inventory +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice, make_delivery_trip from erpnext.stock.doctype.stock_entry.test_stock_entry \ import make_stock_entry, make_serialized_item, get_qty_after_transaction @@ -24,9 +23,6 @@ from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.doctype.item.test_item import create_item class TestDeliveryNote(unittest.TestCase): - def setUp(self): - set_perpetual_inventory(0) - def test_over_billing_against_dn(self): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) @@ -43,7 +39,6 @@ class TestDeliveryNote(unittest.TestCase): def test_delivery_note_no_gl_entry(self): company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(0, company) make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100) stock_queue = json.loads(get_previous_sle({ 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 7b471874af..4bbf3de594 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -56,6 +56,7 @@ "base_net_rate", "base_net_amount", "billed_amt", + "incoming_rate", "item_weight_details", "weight_per_unit", "total_weight", @@ -732,16 +733,22 @@ "depends_on": "returned_qty", "fieldname": "returned_qty", "fieldtype": "Float", - "label": "Returned Qty in Stock UOM", + "label": "Returned Qty in Stock UOM" + }, + { + "fieldname": "incoming_rate", + "fieldtype": "Currency", + "label": "Incoming Rate", "no_copy": 1, "print_hide": 1, "read_only": 1 } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-07-31 20:12:43.054342", + "modified": "2020-12-07 19:59:27.119856", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index 9ca887c77e..8f437b13f0 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -458,5 +458,15 @@ "item_tax_template": "_Test Item Tax Template 1" } ] + }, + { + "description": "_Test", + "doctype": "Item", + "is_stock_item": 1, + "item_code": "138-CMS Shoe", + "item_group": "_Test Item Group", + "item_name": "138-CMS Shoe", + "stock_uom": "_Test UOM", + "gst_hsn_code": "999800" } ] diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index f045e4f911..d5700fe514 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -12,11 +12,9 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_rm_stock_entry import unittest -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestItemAlternative(unittest.TestCase): def setUp(self): - set_perpetual_inventory(0) make_items() def test_alternative_item_for_subcontract_rm(self): diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json index 0cc243d4cb..64331c7d57 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2014-07-11 11:51:00.453717", "doctype": "DocType", "editable_grid": 1, @@ -31,16 +32,19 @@ "reqd": 1 }, { + "depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))", "fieldname": "expense_account", "fieldtype": "Link", "in_list_view": 1, "label": "Expense Account", + "mandatory_depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))", "options": "Account", - "reqd": 1 + "print_hide": 1 } ], "istable": 1, - "modified": "2019-09-30 18:28:32.070655", + "links": [], + "modified": "2020-12-04 00:22:14.373312", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Taxes and Charges", diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index bc3d3266ad..9ec6b8946c 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -77,9 +77,9 @@ class LandedCostVoucher(Document): company_currency = erpnext.get_company_currency(self.company) for account in self.taxes: if get_account_currency(account.expense_account) != company_currency: - frappe.throw(msg=_(""" Row {0}: Expense account currency should be same as company's default currency. - Please select expense account with account currency as {1}""") - .format(account.idx, frappe.bold(company_currency)), title=_("Invalid Account Currency")) + frappe.throw(_("Row {}: Expense account currency should be same as company's default currency.").format(account.idx) + + _("Please select expense account with account currency as {}.").format(frappe.bold(company_currency)), + title=_("Invalid Account Currency")) def set_total_taxes_and_charges(self): self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")]) @@ -121,7 +121,7 @@ class LandedCostVoucher(Document): doc.set_landed_cost_voucher_amount() # set valuation amount in pr item - doc.update_valuation_rate("items") + doc.update_valuation_rate(reset_outgoing_rate=False) # db_update will update and save landed_cost_voucher_amount and voucher_amount in PR for item in doc.get("items"): @@ -143,6 +143,7 @@ class LandedCostVoucher(Document): doc.docstatus = 1 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.make_gl_entries() + doc.repost_future_sle_and_gle() def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): for item in self.get('items'): @@ -152,14 +153,13 @@ class LandedCostVoucher(Document): docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document, 'item_code': item.item_code }, fields=['name', 'docstatus']) if not docs or len(docs) != item.qty: - frappe.throw(_('There are not enough asset created or linked to {0}. \ - Please create or link {1} Assets with respective document.').format(item.receipt_document, item.qty)) + frappe.throw(_('There are not enough asset created or linked to {0}.').format(item.receipt_document) + + _('Please create or link {0} Assets with respective document.').format(item.qty)) if docs: for d in docs: if d.docstatus == 1: - frappe.throw(_('{2} {0} has submitted Assets.\ - Remove Item {1} from table to continue.').format( - item.receipt_document, item.item_code, item.receipt_document_type)) + frappe.throw(_('{0} {1} has submitted Assets. Remove Item {2} from table to continue.') + .format(item.receipt_document_type, frappe.bold(item.receipt_document), frappe.bold(item.item_code))) def update_rate_in_serial_no_for_non_asset_items(self, receipt_document): for item in receipt_document.get("items"): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 3f2c5daf66..b97213e4fb 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -7,7 +7,7 @@ import unittest import frappe from frappe.utils import flt from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ - import set_perpetual_inventory, get_gl_entries, test_records as pr_test_records, make_purchase_receipt + import get_gl_entries, test_records as pr_test_records, make_purchase_receipt from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.account.test_account import get_inventory_account @@ -27,7 +27,7 @@ class TestLandedCostVoucher(unittest.TestCase): }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount") self.assertEqual(pr_lc_value, 25.0) @@ -89,7 +89,7 @@ class TestLandedCostVoucher(unittest.TestCase): }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - submit_landed_cost_voucher("Purchase Invoice", pi.name, pi.company) + create_landed_cost_voucher("Purchase Invoice", pi.name, pi.company) pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, "landed_cost_voucher_amount") @@ -137,7 +137,7 @@ class TestLandedCostVoucher(unittest.TestCase): serial_no_rate = frappe.db.get_value("Serial No", "SN001", "purchase_rate") - submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) serial_no = frappe.db.get_value("Serial No", "SN001", ["warehouse", "purchase_rate"], as_dict=1) @@ -160,7 +160,7 @@ class TestLandedCostVoucher(unittest.TestCase): }) pr.submit() - lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22) + lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22) self.assertEqual(lcv.items[0].applicable_charges, 41.07) self.assertEqual(lcv.items[2].applicable_charges, 41.08) @@ -236,7 +236,7 @@ def make_landed_cost_voucher(** args): return lcv -def submit_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50): +def create_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50): ref_doc = frappe.get_doc(receipt_document_type, receipt_document) lcv = frappe.new_doc("Landed Cost Voucher") diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 19924b1636..0a29fa05e1 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -12,9 +12,6 @@ from erpnext.stock.doctype.material_request.material_request \ from erpnext.stock.doctype.item.test_item import create_item class TestMaterialRequest(unittest.TestCase): - def setUp(self): - erpnext.set_perpetual_inventory(0) - def test_make_purchase_order(self): mr = frappe.copy_doc(test_records[0]).insert() diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index 2ac5c426c0..f1d7f8c8c9 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2013-02-22 01:28:00", "doctype": "DocType", "editable_grid": 1, @@ -14,6 +15,7 @@ "target_warehouse", "column_break_9", "qty", + "uom", "section_break_9", "serial_no", "column_break_11", @@ -23,7 +25,7 @@ "actual_qty", "projected_qty", "column_break_16", - "uom", + "incoming_rate", "page_break", "prevdoc_doctype", "parent_detail_docname" @@ -199,11 +201,21 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "incoming_rate", + "fieldtype": "Currency", + "label": "Incoming Rate", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-11-26 20:09:59.400960", + "links": [], + "modified": "2020-09-24 09:25:13.050151", "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", @@ -212,4 +224,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 97e0fa738c..226064bae7 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -181,6 +181,7 @@ class PurchaseReceipt(BuyingController): update_serial_nos_after_submit(self, "items") self.make_gl_entries() + self.repost_future_sle_and_gle() def check_next_docstatus(self): submit_rv = frappe.db.sql("""select t1.name @@ -209,7 +210,8 @@ class PurchaseReceipt(BuyingController): # because updating ordered qty in bin depends upon updated ordered qty in PO self.update_stock_ledger() self.make_gl_entries_on_cancel() - self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.repost_future_sle_and_gle() + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') self.delete_auto_created_batches() def get_current_stock(self): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 9b8eeed1a1..83012d355f 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -9,14 +9,15 @@ import frappe.defaults from frappe.utils import cint, flt, cstr, today, random_string, add_days from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.item.test_item import create_item -from erpnext import set_perpetual_inventory from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import make_item from six import iteritems +from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + class TestPurchaseReceipt(unittest.TestCase): def setUp(self): - set_perpetual_inventory(0) frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) def test_reverse_purchase_receipt_sle(self): @@ -112,6 +113,8 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) + pr.cancel() + def test_batched_serial_no_purchase(self): item = frappe.db.exists("Item", {'item_name': 'Batched Serialized Item'}) if not item: @@ -183,22 +186,30 @@ class TestPurchaseReceipt(unittest.TestCase): rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")]) self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) + + pr.cancel() def test_subcontracting_gle_fg_item_rate_zero(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - set_perpetual_inventory() frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM") - make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1", qty=100, basic_rate=100, company="_Test Company with perpetual inventory") - make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1", + + se1 = make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1", qty=100, basic_rate=100, company="_Test Company with perpetual inventory") + + se2 = make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1", + qty=100, basic_rate=100, company="_Test Company with perpetual inventory") + pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes", - company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', supplier_warehouse='Work In Progress - TCP1') + company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', + supplier_warehouse='Work In Progress - TCP1') gl_entries = get_gl_entries("Purchase Receipt", pr.name) self.assertFalse(gl_entries) - set_perpetual_inventory(0) + pr.cancel() + se1.cancel() + se2.cancel() def test_subcontracting_over_receipt(self): """ @@ -216,13 +227,13 @@ class TestPurchaseReceipt(unittest.TestCase): item_code = "_Test Subcontracted FG Item 1" make_subcontracted_item(item_code=item_code) - po = create_purchase_order(item_code=item_code, qty=1, + po = create_purchase_order(item_code=item_code, qty=1, include_exploded_items=0, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") #stock raw materials in a warehouse before transfer - make_stock_entry(target="_Test Warehouse - _TC", - item_code = "Test Extra Item 1", qty=1, basic_rate=100) - make_stock_entry(target="_Test Warehouse - _TC", + se1 = make_stock_entry(target="_Test Warehouse - _TC", + item_code = "Test Extra Item 1", qty=10, basic_rate=100) + se2 = make_stock_entry(target="_Test Warehouse - _TC", item_code = "_Test FG Item", qty=1, basic_rate=100) rm_items = [ { @@ -254,6 +265,13 @@ class TestPurchaseReceipt(unittest.TestCase): pr1.submit() self.assertRaises(frappe.ValidationError, pr2.submit) + pr1.cancel() + se.cancel() + se1.cancel() + se2.cancel() + po.reload() + po.cancel() + def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"), @@ -284,6 +302,8 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"), pr.get("items")[0].rejected_warehouse) + pr.cancel() + def test_purchase_return_partial(self): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") @@ -371,6 +391,9 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(pr.per_returned, 100) self.assertEqual(pr.status, 'Return Issued') + return_pr.cancel() + pr.cancel() + def test_purchase_return_for_rejected_qty(self): from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse @@ -388,6 +411,9 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(actual_qty, -2) + return_pr.cancel() + pr.cancel() + def test_purchase_return_for_serialized_items(self): def _check_serial_no_values(serial_no, field_values): @@ -415,6 +441,10 @@ class TestPurchaseReceipt(unittest.TestCase): "delivery_document_no": return_pr.name }) + return_pr.cancel() + pr.reload() + pr.cancel() + def test_purchase_return_for_multi_uom(self): item_code = "_Test Purchase Return For Multi-UOM" if not frappe.db.exists('Item', item_code): @@ -431,6 +461,9 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(abs(return_pr.items[0].stock_qty), 1.0) + return_pr.cancel() + pr.cancel() + def test_closed_purchase_receipt(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_purchase_receipt_status @@ -440,6 +473,9 @@ class TestPurchaseReceipt(unittest.TestCase): update_purchase_receipt_status(pr.name, "Closed") self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed") + pr.reload() + pr.cancel() + def test_pr_billing_status(self): # PO -> PR1 -> PI and PO -> PI and PO -> PR2 from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order @@ -482,6 +518,16 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(pr2.per_billed, 80) self.assertEqual(pr2.status, "To Bill") + pr2.cancel() + pi2.reload() + pi2.cancel() + pi1.reload() + pi1.cancel() + pr1.reload() + pr1.cancel() + po.reload() + po.cancel() + def test_serial_no_against_purchase_receipt(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -509,6 +555,8 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(serial_no, frappe.db.get_value("Serial No", {"purchase_document_type": "Purchase Receipt", "purchase_document_no": new_pr_doc.name}, "name")) + new_pr_doc.cancel() + def test_not_accept_duplicate_serial_no(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note @@ -519,16 +567,19 @@ class TestPurchaseReceipt(unittest.TestCase): item_code = item.name serial_no = random_string(5) - make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no) - create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no) + pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no) + dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no) - pr = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True) - self.assertRaises(SerialNoDuplicateError, pr.submit) + pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True) + self.assertRaises(SerialNoDuplicateError, pr2.submit) se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, serial_no=serial_no, basic_rate=100, do_not_submit=True) self.assertRaises(SerialNoDuplicateError, se.submit) + dn.cancel() + pr1.cancel() + def test_auto_asset_creation(self): asset_item = "Test Asset Item" @@ -549,7 +600,7 @@ class TestPurchaseReceipt(unittest.TestCase): 'company_name': '_Test Company', 'fixed_asset_account': '_Test Fixed Asset - _TC', 'accumulated_depreciation_account': '_Test Accumulated Depreciations - _TC', - 'depreciation_expense_account': '_Test Depreciation - _TC' + 'depreciation_expense_account': '_Test Depreciations - _TC' }] }).insert() @@ -568,6 +619,8 @@ class TestPurchaseReceipt(unittest.TestCase): location = frappe.db.get_value('Asset', assets[0].name, 'location') self.assertEquals(location, "Test Location") + pr.cancel() + def test_purchase_return_with_submitted_asset(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return @@ -594,6 +647,9 @@ class TestPurchaseReceipt(unittest.TestCase): pr_return.submit() + pr_return.cancel() + pr.cancel() + def test_purchase_receipt_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center cost_center = "_Test Cost Center for BS Account - TCP1" @@ -605,7 +661,8 @@ class TestPurchaseReceipt(unittest.TestCase): 'location_name': 'Test Location' }).insert() - pr = make_purchase_receipt(cost_center=cost_center, company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") + pr = make_purchase_receipt(cost_center=cost_center, 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) @@ -623,6 +680,8 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + pr.cancel() + def test_purchase_receipt_cost_center_with_balance_sheet_account(self): if not frappe.db.exists('Location', 'Test Location'): frappe.get_doc({ @@ -648,6 +707,8 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + pr.cancel() + 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 @@ -663,6 +724,12 @@ class TestPurchaseReceipt(unittest.TestCase): pi = make_purchase_invoice(pr.name) self.assertEquals(pi.items[0].qty, 3) + pr1.cancel() + pr.reload() + pr.cancel() + po.reload() + po.cancel() + def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self): pr1 = make_purchase_receipt(qty=8, do_not_submit=True) pr1.append("items", { @@ -689,8 +756,14 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEquals(pi2.items[0].qty, 2) self.assertEquals(pi2.items[1].qty, 1) + pr2.cancel() + pi1.cancel() + pr1.reload() + pr1.cancel() + def test_stock_transfer_from_purchase_receipt(self): - pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1', company="_Test Company with perpetual inventory") + pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1', + company="_Test Company with perpetual inventory") pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", do_not_save=1) @@ -713,18 +786,20 @@ class TestPurchaseReceipt(unittest.TestCase): for sle in sl_entries: self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty) - def test_stock_transfer_from_purchase_receipt_with_valuation(self): - warehouse = frappe.get_doc('Warehouse', 'Work In Progress - TCP1') - warehouse.account = '_Test Account Stock In Hand - TCP1' - warehouse.save() + pr.cancel() + pr1.cancel() - pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1', + def test_stock_transfer_from_purchase_receipt_with_valuation(self): + create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory", + properties={"account": '_Test Account Stock In Hand - TCP1'}) + + pr1 = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1', company="_Test Company with perpetual inventory") pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", do_not_save=1) - pr.items[0].from_warehouse = 'Work In Progress - TCP1' + pr.items[0].from_warehouse = '_Test Warehouse for Valuation - TCP1' pr.supplier_warehouse = '' @@ -749,7 +824,7 @@ class TestPurchaseReceipt(unittest.TestCase): ] expected_sle = { - 'Work In Progress - TCP1': -5, + '_Test Warehouse for Valuation - TCP1': -5, 'Stores - TCP1': 5 } @@ -761,60 +836,9 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(gle.debit, expected_gle[i][1]) self.assertEqual(gle.credit, expected_gle[i][2]) - warehouse.account = '' - warehouse.save() + pr.cancel() + pr1.cancel() - def test_backdated_purchase_receipt(self): - # make purchase receipt for default company - make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4") - - # try to make another backdated PR - posting_date = add_days(today(), -1) - pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4", - do_not_submit=True) - - pr.set_posting_time = 1 - pr.posting_date = posting_date - pr.save() - - self.assertRaises(frappe.ValidationError, pr.submit) - - # make purchase receipt for other company backdated - pr = make_purchase_receipt(company="_Test Company 5", warehouse="Stores - _TC5", - do_not_submit=True) - - pr.set_posting_time = 1 - pr.posting_date = posting_date - pr.submit() - - # Allowed to submit for other company's PR - self.assertEqual(pr.docstatus, 1) - - def test_backdated_purchase_receipt_for_same_company_different_warehouse(self): - # make purchase receipt for default company - make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4") - - # try to make another backdated PR - posting_date = add_days(today(), -1) - pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4", - do_not_submit=True) - - pr.set_posting_time = 1 - pr.posting_date = posting_date - pr.save() - - self.assertRaises(frappe.ValidationError, pr.submit) - - # make purchase receipt for other company backdated - pr = make_purchase_receipt(company="_Test Company 4", warehouse="Finished Goods - _TC4", - do_not_submit=True) - - pr.set_posting_time = 1 - pr.posting_date = posting_date - pr.submit() - - # Allowed to submit for other company's PR - self.assertEqual(pr.docstatus, 1) def test_subcontracted_pr_for_multi_transfer_batches(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -877,6 +901,12 @@ class TestPurchaseReceipt(unittest.TestCase): update_backflush_based_on("BOM") + pr.delete() + se.cancel() + ste2.cancel() + ste1.cancel() + po.cancel() + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s @@ -972,6 +1002,8 @@ def make_purchase_receipt(**args): pr.posting_date = args.posting_date or today() if args.posting_time: pr.posting_time = args.posting_time + if args.posting_date or args.posting_time: + pr.set_posting_time = 1 pr.company = args.company or "_Test Company" pr.supplier = args.supplier or "_Test Supplier" pr.is_subcontracted = args.is_subcontracted or "No" diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 84c64aa8f8..871b255b06 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -866,7 +866,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-11-02 10:00:38.204294", + "modified": "2020-12-07 10:00:38.204294", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/doctype/repost_item_valuation/__init__.py b/erpnext/stock/doctype/repost_item_valuation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js new file mode 100644 index 0000000000..e429cd5e30 --- /dev/null +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -0,0 +1,52 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Repost Item Valuation', { + setup: function(frm) { + frm.set_query("warehouse", () => { + let filters = { + 'is_group': 0 + }; + if (frm.doc.company) filters['company'] = frm.doc.company; + return {filters: filters}; + }); + + frm.set_query("voucher_type", () => { + return { + filters: { + name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note', + 'Sales Invoice', 'Stock Entry', 'Stock Reconciliation']] + } + }; + }); + + if (frm.doc.company) { + frm.set_query("voucher_no", () => { + return { + filters: { + company: frm.doc.company + } + }; + }); + } + }, + refresh: function(frm) { + if (frm.doc.status == "Failed") { + frm.add_custom_button(__('Restart'), function () { + frm.trigger("restart_reposting"); + }).addClass("btn-primary"); + } + }, + + restart_reposting: function(frm) { + frappe.call({ + method: "restart_reposting", + doc: frm.doc, + callback: function(r) { + if (!r.exc) { + frm.refresh(); + } + } + }); + } +}); diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json new file mode 100644 index 0000000000..071fc86d9b --- /dev/null +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -0,0 +1,215 @@ +{ + "actions": [], + "autoname": "REPOST-ITEM-VAL-.######", + "creation": "2020-10-22 22:27:07.742161", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "based_on", + "voucher_type", + "voucher_no", + "item_code", + "warehouse", + "posting_date", + "posting_time", + "column_break_5", + "status", + "company", + "allow_negative_stock", + "via_landed_cost_voucher", + "allow_zero_rate", + "amended_from", + "error_section", + "error_log" + ], + "fields": [ + { + "depends_on": "eval:doc.based_on=='Item and Warehouse'", + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item Code", + "mandatory_depends_on": "eval:doc.based_on=='Item and Warehouse'", + "options": "Item" + }, + { + "depends_on": "eval:doc.based_on=='Item and Warehouse'", + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "mandatory_depends_on": "eval:doc.based_on=='Item and Warehouse'", + "options": "Warehouse" + }, + { + "fetch_from": "voucher_no.posting_date", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "reqd": 1 + }, + { + "fetch_from": "voucher_no.posting_time", + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time" + }, + { + "default": "Queued", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Queued\nIn Progress\nCompleted\nFailed", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Repost Item Valuation", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.status=='Failed'", + "fieldname": "error_section", + "fieldtype": "Section Break", + "label": "Error" + }, + { + "fieldname": "error_log", + "fieldtype": "Long Text", + "label": "Error Log", + "no_copy": 1, + "read_only": 1 + }, + { + "fetch_from": "warehouse.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "depends_on": "eval:doc.based_on=='Transaction'", + "fieldname": "voucher_type", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Voucher Type", + "mandatory_depends_on": "eval:doc.based_on=='Transaction'", + "options": "DocType" + }, + { + "depends_on": "eval:doc.based_on=='Transaction'", + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Voucher No", + "mandatory_depends_on": "eval:doc.based_on=='Transaction'", + "options": "voucher_type" + }, + { + "default": "Transaction", + "fieldname": "based_on", + "fieldtype": "Select", + "label": "Based On", + "options": "Transaction\nItem and Warehouse", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "allow_negative_stock", + "fieldtype": "Check", + "label": "Allow Negative Stock" + }, + { + "default": "0", + "fieldname": "via_landed_cost_voucher", + "fieldtype": "Check", + "label": "Via Landed Cost Voucher" + }, + { + "default": "0", + "fieldname": "allow_zero_rate", + "fieldtype": "Check", + "label": "Allow Zero Rate" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2020-12-10 07:52:12.476589", + "modified_by": "Administrator", + "module": "Stock", + "name": "Repost Item Valuation", + "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": "Stock User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock 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 User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py new file mode 100644 index 0000000000..a942f2edda --- /dev/null +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -0,0 +1,89 @@ +# -*- 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, erpnext +from frappe.model.document import Document +from frappe.utils import cint +from erpnext.stock.stock_ledger import repost_future_sle +from erpnext.accounts.utils import update_gl_entries_after + + +class RepostItemValuation(Document): + def validate(self): + self.set_status() + self.reset_field_values() + self.set_company() + + def reset_field_values(self): + if self.based_on == 'Transaction': + self.item_code = None + self.warehouse = None + else: + self.voucher_type = None + self.voucher_no = None + + def set_company(self): + if self.voucher_type and self.voucher_no: + self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") + elif self.warehouse: + self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") + + def set_status(self, status=None): + if not status: + status = 'Queued' + self.db_set('status', status) + + def on_submit(self): + frappe.enqueue(repost, timeout=1800, queue='long', + job_name='repost_sle', now=frappe.flags.in_test, doc=self) + + def restart_reposting(self): + self.set_status('Queued') + frappe.enqueue(repost, timeout=1800, queue='long', + job_name='repost_sle', now=True, doc=self) + +def repost(doc): + try: + doc.set_status('In Progress') + frappe.db.commit() + + repost_sl_entries(doc) + repost_gl_entries(doc) + doc.set_status('Completed') + except Exception: + frappe.db.rollback() + traceback = frappe.get_traceback() + frappe.log_error(traceback) + frappe.db.set_value(doc.doctype, doc.name, 'error_log', traceback) + doc.set_status('Failed') + raise + finally: + frappe.db.commit() + +def repost_sl_entries(doc): + if doc.based_on == 'Transaction': + repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no, + allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher) + else: + repost_future_sle(args=[frappe._dict({ + "item_code": doc.item_code, + "warehouse": doc.warehouse, + "posting_date": doc.posting_date, + "posting_time": doc.posting_time + })], allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher) + +def repost_gl_entries(doc): + if not cint(erpnext.is_perpetual_inventory_enabled(doc.company)): + return + + if doc.based_on == 'Transaction': + ref_doc = frappe.get_doc(doc.voucher_type, doc.voucher_no) + items, warehouses = ref_doc.get_items_and_warehouses() + else: + items = [doc.item_code] + warehouses = [doc.warehouse] + + update_gl_entries_after(doc.posting_date, doc.posting_time, + warehouses, items, company=doc.company) \ No newline at end of file diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py new file mode 100644 index 0000000000..13ceb68669 --- /dev/null +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.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 TestRepostItemValuation(unittest.TestCase): + pass diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 64bdf3b171..2d89996f2a 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -134,17 +134,13 @@ class SerialNo(StockController): sle_dict = self.get_stock_ledger_entries(serial_no) if sle_dict: if sle_dict.get("incoming", []): - sle_list = [sle for sle in sle_dict["incoming"] if sle.is_cancelled == 0] - if sle_list: - entries["purchase_sle"] = sle_list[0] + entries["purchase_sle"] = sle_dict["incoming"][0] if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0: entries["last_sle"] = sle_dict["incoming"][0] else: entries["last_sle"] = sle_dict["outgoing"][0] - sle_list = [sle for sle in sle_dict["outgoing"] if sle.is_cancelled == 0] - if sle_list: - entries["delivery_sle"] = sle_list[0] + entries["delivery_sle"] = sle_dict["outgoing"][0] return entries @@ -155,11 +151,12 @@ class SerialNo(StockController): for sle in frappe.db.sql(""" SELECT voucher_type, voucher_no, - posting_date, posting_time, incoming_rate, actual_qty, serial_no, is_cancelled + posting_date, posting_time, incoming_rate, actual_qty, serial_no FROM `tabStock Ledger Entry` WHERE item_code=%s AND company = %s + AND is_cancelled = 0 AND (serial_no = %s OR serial_no like %s OR serial_no like %s @@ -179,7 +176,7 @@ class SerialNo(StockController): def on_trash(self): sl_entries = frappe.db.sql("""select serial_no from `tabStock Ledger Entry` - where serial_no like %s and item_code=%s""", + where serial_no like %s and item_code=%s and is_cancelled=0""", ("%%%s%%" % self.name, self.item_code), as_dict=True) # Find the exact match @@ -229,7 +226,7 @@ def validate_serial_no(sle, item_det): if serial_nos: frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code), SerialNoNotRequiredError) - else: + elif not sle.is_cancelled: if serial_nos: if cint(sle.actual_qty) != flt(sle.actual_qty): frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty)) @@ -247,10 +244,6 @@ def validate_serial_no(sle, item_det): "delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_no", "company"], as_dict=1) - if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: - frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") - .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse), SerialNoWarehouseError) - if sr.item_code!=sle.item_code: if not allow_serial_nos_with_different_item(serial_no, sle): frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no, @@ -277,7 +270,7 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Batch {1}").format(serial_no, sle.batch_no), SerialNoBatchError) - if not sr.warehouse: + if not sle.is_cancelled and not sr.warehouse: frappe.throw(_("Serial No {0} does not belong to any Warehouse") .format(serial_no), SerialNoWarehouseError) @@ -327,6 +320,12 @@ def validate_serial_no(sle, item_det): elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series: frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) + elif serial_nos: + for serial_no in serial_nos: + sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1) + if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: + frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") + .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) def validate_material_transfer_entry(sle_doc): sle_doc.update({ @@ -334,7 +333,7 @@ def validate_material_transfer_entry(sle_doc): "skip_serial_no_validaiton": False }) - if (sle_doc.voucher_type == "Stock Entry" and + if (sle_doc.voucher_type == "Stock Entry" and not sle_doc.is_cancelled and frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"): if sle_doc.actual_qty < 0: sle_doc.skip_update_serial_no = True @@ -379,7 +378,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no) if stock_entry.purpose in ("Repack", "Manufacture"): for d in stock_entry.get("items"): - if d.serial_no and (d.s_warehouse or d.t_warehouse): + if d.serial_no and (d.s_warehouse if not sle.is_cancelled else d.t_warehouse): serial_nos = get_serial_nos(d.serial_no) if sle_serial_no in serial_nos: allow_serial_nos = True @@ -388,7 +387,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): def update_serial_nos(sle, item_det): if sle.skip_update_serial_no: return - if not sle.serial_no and cint(sle.actual_qty) > 0 \ + if not sle.is_cancelled and not sle.serial_no and cint(sle.actual_qty) > 0 \ and item_det.has_serial_no == 1 and item_det.serial_no_series: serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) frappe.db.set(sle, "serial_no", serial_nos) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index ab061076e5..ed70790b2c 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -12,7 +12,6 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory test_dependencies = ["Item"] test_records = frappe.get_test_records('Serial No') @@ -38,8 +37,6 @@ class TestSerialNo(unittest.TestCase): self.assertTrue(SerialNoCannotCannotChangeError, sr.save) def test_inter_company_transfer(self): - set_perpetual_inventory(0, "_Test Company 1") - set_perpetual_inventory(0) se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = get_serial_nos(se.get("items")[0].serial_no) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 27fcbb7e2a..98116ec183 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -510,22 +510,31 @@ frappe.ui.form.on('Stock Entry', { calculate_amount: function(frm) { frm.events.calculate_total_additional_costs(frm); - - const total_basic_amount = frappe.utils.sum( - (frm.doc.items || []).map(function(i) { return i.t_warehouse ? flt(i.basic_amount) : 0; }) - ); - + let total_basic_amount = 0; + if (in_list(["Repack", "Manufacture"], frm.doc.purpose)) { + total_basic_amount = frappe.utils.sum( + (frm.doc.items || []).map(function(i) { + return i.is_finished_item ? flt(i.basic_amount) : 0; + }) + ); + } else { + total_basic_amount = frappe.utils.sum( + (frm.doc.items || []).map(function(i) { + return i.t_warehouse ? flt(i.basic_amount) : 0; + }) + ); + } + for (let i in frm.doc.items) { let item = frm.doc.items[i]; - if (item.t_warehouse && total_basic_amount) { + if (((in_list(["Repack", "Manufacture"], frm.doc.purpose) && item.is_finished_item) || item.t_warehouse) && total_basic_amount) { item.additional_cost = (flt(item.basic_amount) / total_basic_amount) * frm.doc.total_additional_costs; } else { item.additional_cost = 0; } - item.amount = flt(item.basic_amount + flt(item.additional_cost), - precision("amount", item)); + item.amount = flt(item.basic_amount + flt(item.additional_cost), precision("amount", item)); if (flt(item.transfer_qty)) { item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)), diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 61e0df6723..5aed08102c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -644,9 +644,10 @@ ], "icon": "fa fa-file-text", "idx": 1, + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-08-11 19:10:07.954981", + "modified": "2020-09-09 12:59:02.508943", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 32d7e6eb34..afdb54ceaa 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -18,7 +18,7 @@ from erpnext.stock.utils import get_bin from frappe.model.mapper import get_mapped_doc from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError - +from erpnext.accounts.general_ledger import process_gl_map import json from six import string_types, itervalues, iteritems @@ -58,6 +58,7 @@ class StockEntry(StockController): self.validate_warehouse() self.validate_work_order() self.validate_bom() + self.mark_finished_and_scrap_items() self.validate_finished_goods() self.validate_with_material_request() self.validate_batch() @@ -75,13 +76,11 @@ class StockEntry(StockController): else: set_batch_nos(self, 's_warehouse') - self.set_incoming_rate() self.validate_serialized_batch() self.set_actual_qty() - self.calculate_rate_and_amount(update_finished_item_rate=False) + self.calculate_rate_and_amount() def on_submit(self): - self.update_stock_ledger() update_serial_nos_after_submit(self, "items") @@ -89,11 +88,15 @@ class StockEntry(StockController): self.validate_purchase_order() if self.purchase_order and self.purpose == "Send to Subcontractor": self.update_purchase_order_supplied_items() + self.make_gl_entries() + + self.repost_future_sle_and_gle() self.update_cost_in_project() self.validate_reserved_serial_no_consumption() self.update_transferred_qty() self.update_quality_inspection() + if self.work_order and self.purpose == "Manufacture": self.update_so_in_serial_number() @@ -113,9 +116,10 @@ class StockEntry(StockController): self.update_work_order() self.update_stock_ledger() - self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') self.make_gl_entries_on_cancel() + self.repost_future_sle_and_gle() self.update_cost_in_project() self.update_transferred_qty() self.update_quality_inspection() @@ -256,11 +260,10 @@ class StockEntry(StockController): def validate_fg_completed_qty(self): if self.purpose == "Manufacture" and self.work_order: - production_item = frappe.get_value('Work Order', self.work_order, 'production_item') - for item in self.items: - if item.item_code == production_item and item.t_warehouse and item.qty != self.fg_completed_qty: + for d in self.items: + if d.is_finished_item and d.qty != self.fg_completed_qty: frappe.throw(_("Finished product quantity {0} and For Quantity {1} cannot be different") - .format(item.qty, self.fg_completed_qty)) + .format(d.qty, self.fg_completed_qty)) def validate_difference_account(self): if not cint(erpnext.is_perpetual_inventory_enabled(self.company)): @@ -382,21 +385,6 @@ class StockEntry(StockController): frappe.throw(_("Stock Entries already created for Work Order ") + self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError) - def set_incoming_rate(self): - if self.purpose == "Repack": - self.set_basic_rate_for_finished_goods() - - for d in self.items: - if d.s_warehouse: - args = self.get_args_for_incoming_rate(d) - d.basic_rate = get_incoming_rate(args) - elif d.allow_zero_valuation_rate and not d.s_warehouse: - d.basic_rate = 0.0 - elif d.t_warehouse and not d.basic_rate: - d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse, - self.doctype, self.name, d.allow_zero_valuation_rate, - currency=erpnext.get_company_currency(self.company), company=self.company) - def set_actual_qty(self): allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")) @@ -432,57 +420,64 @@ class StockEntry(StockController): d.serial_no = transferred_serial_no def get_stock_and_rate(self): + """ + Updates rate and availability of all the items. + Called from Update Rate and Availability button. + """ self.set_work_order_details() self.set_transfer_qty() self.set_actual_qty() self.calculate_rate_and_amount() - def calculate_rate_and_amount(self, force=False, - update_finished_item_rate=True, raise_error_if_no_rate=True): - self.set_basic_rate(force, update_finished_item_rate, raise_error_if_no_rate) + def calculate_rate_and_amount(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): + self.set_basic_rate(reset_outgoing_rate, raise_error_if_no_rate) self.distribute_additional_costs() self.update_valuation_rate() self.set_total_incoming_outgoing_value() self.set_total_amount() - def set_basic_rate(self, force=False, update_finished_item_rate=True, raise_error_if_no_rate=True): - """get stock and incoming rate on posting date""" - raw_material_cost = 0.0 - scrap_material_cost = 0.0 - fg_basic_rate = 0.0 + def set_basic_rate(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): + """ + Set rate for outgoing, scrapped and finished items + """ + # Set rate for outgoing items + outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate) + # Set basic rate for incoming items for d in self.get('items'): - if d.t_warehouse: fg_basic_rate = flt(d.basic_rate) - args = self.get_args_for_incoming_rate(d) + if d.s_warehouse or d.set_basic_rate_manually: continue - # get basic rate - if not d.bom_no: - if (not flt(d.basic_rate) and not d.allow_zero_valuation_rate) or d.s_warehouse or force: - basic_rate = flt(get_incoming_rate(args, raise_error_if_no_rate), self.precision("basic_rate", d)) - if basic_rate > 0: - d.basic_rate = basic_rate + if d.allow_zero_valuation_rate: + d.basic_rate = 0.0 + elif d.is_finished_item: + if self.purpose == "Manufacture": + d.basic_rate = self.get_basic_rate_for_manufactured_item(d.transfer_qty, outgoing_items_cost) + elif self.purpose == "Repack": + d.basic_rate = self.get_basic_rate_for_repacked_items(d.transfer_qty, outgoing_items_cost) + + if not d.basic_rate and not d.allow_zero_valuation_rate: + d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse, + self.doctype, self.name, d.allow_zero_valuation_rate, + currency=erpnext.get_company_currency(self.company), company=self.company, + raise_error_if_no_rate=raise_error_if_no_rate) + + d.basic_rate = flt(d.basic_rate, d.precision("basic_rate")) + d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount")) + + def set_rate_for_outgoing_items(self, reset_outgoing_rate=True): + outgoing_items_cost = 0.0 + for d in self.get('items'): + if d.s_warehouse: + if reset_outgoing_rate: + args = self.get_args_for_incoming_rate(d) + rate = get_incoming_rate(args) + if rate > 0: + d.basic_rate = rate d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount")) if not d.t_warehouse: - raw_material_cost += flt(d.basic_amount) - - # get scrap items basic rate - if d.bom_no: - if not flt(d.basic_rate) and not d.allow_zero_valuation_rate and \ - getattr(self, "pro_doc", frappe._dict()).scrap_warehouse == d.t_warehouse: - basic_rate = flt(get_incoming_rate(args, raise_error_if_no_rate), - self.precision("basic_rate", d)) - if basic_rate > 0: - d.basic_rate = basic_rate - d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount")) - - if getattr(self, "pro_doc", frappe._dict()).scrap_warehouse == d.t_warehouse: - - scrap_material_cost += flt(d.basic_amount) - - number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse]) - if (fg_basic_rate == 0.0 and number_of_fg_items == 1) or update_finished_item_rate: - self.set_basic_rate_for_finished_goods(raw_material_cost, scrap_material_cost) + outgoing_items_cost += flt(d.basic_amount) + return outgoing_items_cost def get_args_for_incoming_rate(self, item): return frappe._dict({ @@ -498,44 +493,44 @@ class StockEntry(StockController): "allow_zero_valuation": item.allow_zero_valuation_rate, }) - def set_basic_rate_for_finished_goods(self, raw_material_cost=0, scrap_material_cost=0): - total_fg_qty = 0 - if not raw_material_cost and self.get("items"): - raw_material_cost = sum([flt(row.basic_amount) for row in self.items - if row.s_warehouse and not row.t_warehouse]) + def get_basic_rate_for_repacked_items(self, finished_item_qty, outgoing_items_cost): + finished_items = [d.item_code for d in self.get("items") if d.is_finished_item] + if len(finished_items) == 1: + return flt(outgoing_items_cost / finished_item_qty) + else: + unique_finished_items = set(finished_items) + if len(unique_finished_items) == 1: + total_fg_qty = sum([flt(d.transfer_qty) for d in self.items if d.is_finished_item]) + return flt(outgoing_items_cost / total_fg_qty) - total_fg_qty = sum([flt(row.qty) for row in self.items - if row.t_warehouse and not row.s_warehouse]) + def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0): + scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item]) - if self.purpose in ["Manufacture", "Repack"]: - for d in self.get("items"): - if (d.transfer_qty and (d.bom_no or d.t_warehouse) - and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)): + # Get raw materials cost from BOM if multiple material consumption entries + if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"): + bom_items = self.get_bom_raw_materials(finished_item_qty) + outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()]) - if (self.work_order and self.purpose == "Manufacture" - and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")): - bom_items = self.get_bom_raw_materials(d.transfer_qty) - raw_material_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()]) - - if raw_material_cost and self.purpose == "Manufacture": - d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate")) - d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount")) - elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually: - d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty) - d.basic_amount = d.basic_rate * flt(d.qty) + return flt(outgoing_items_cost - scrap_items_cost) def distribute_additional_costs(self): - if self.purpose == "Material Issue": + # If no incoming items, set additional costs blank + if not any([d.item_code for d in self.items if d.t_warehouse]): self.additional_costs = [] self.total_additional_costs = sum([flt(t.amount) for t in self.get("additional_costs")]) - total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) - for d in self.get("items"): - if d.t_warehouse and total_basic_amount: - d.additional_cost = (flt(d.basic_amount) / total_basic_amount) * self.total_additional_costs - else: - d.additional_cost = 0 + if self.purpose in ("Repack", "Manufacture"): + incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item]) + else: + incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + + if incoming_items_cost: + for d in self.get("items"): + if (self.purpose in ("Repack", "Manufacture") and d.is_finished_item) or d.t_warehouse: + d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs + else: + d.additional_cost = 0 def update_valuation_rate(self): for d in self.get("items"): @@ -638,71 +633,115 @@ class StockEntry(StockController): item_code = d.original_item or d.item_code validate_bom_no(item_code, d.bom_no) + def mark_finished_and_scrap_items(self): + if self.purpose in ("Repack", "Manufacture"): + if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]): + return + + finished_item = self.get_finished_item() + + for d in self.items: + if d.t_warehouse and not d.s_warehouse: + if self.purpose=="Repack" or d.item_code == finished_item: + d.is_finished_item = 1 + else: + d.is_scrap_item = 1 + else: + d.is_finished_item = 0 + d.is_scrap_item = 0 + + def get_finished_item(self): + finished_item = None + if self.work_order: + finished_item = frappe.db.get_value("Work Order", self.work_order, "production_item") + elif self.bom_no: + finished_item = frappe.db.get_value("BOM", self.bom_no, "item") + + return finished_item + def validate_finished_goods(self): """validation: finished good quantity should be same as manufacturing quantity""" if not self.work_order: return - items_with_target_warehouse = [] - allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", - "overproduction_percentage_for_work_order")) - production_item, wo_qty = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"]) + number_of_finished_items = 0 for d in self.get('items'): - if (self.purpose != "Send to Subcontractor" and d.bom_no - and flt(d.transfer_qty) > flt(self.fg_completed_qty) and d.item_code == production_item): - frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \ - format(d.idx, d.transfer_qty, self.fg_completed_qty)) + if d.is_finished_item: + if d.item_code != production_item: + frappe.throw(_("Finished Item {0} does not match with Work Order {1}") + .format(d.item_code, self.work_order)) + elif flt(d.transfer_qty) > flt(self.fg_completed_qty): + frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \ + format(d.idx, d.transfer_qty, self.fg_completed_qty)) + number_of_finished_items += 1 - if self.work_order and self.purpose == "Manufacture" and d.t_warehouse: - items_with_target_warehouse.append(d.item_code) + if number_of_finished_items > 1: + frappe.throw(_("Multiple items cannot be marked as finished item")) + + if self.purpose == "Manufacture": + allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", + "overproduction_percentage_for_work_order")) - if self.work_order and self.purpose == "Manufacture": allowed_qty = wo_qty + (allowance_percentage/100 * wo_qty) if self.fg_completed_qty > allowed_qty: frappe.throw(_("For quantity {0} should not be greater than work order quantity {1}") .format(flt(self.fg_completed_qty), wo_qty)) - if production_item not in items_with_target_warehouse: - frappe.throw(_("Finished Item {0} must be entered for Manufacture type entry") - .format(production_item)) - def update_stock_ledger(self): sl_entries = [] + finished_item_row = self.get_finished_item_row() - # make sl entries for source warehouse first, then do for target warehouse - for d in self.get('items'): - if cstr(d.s_warehouse): - sl_entries.append(self.get_sl_entries(d, { - "warehouse": cstr(d.s_warehouse), - "actual_qty": -flt(d.transfer_qty), - "incoming_rate": 0 - })) - - for d in self.get('items'): - if cstr(d.t_warehouse): - sl_entries.append(self.get_sl_entries(d, { - "warehouse": cstr(d.t_warehouse), - "actual_qty": flt(d.transfer_qty), - "incoming_rate": flt(d.valuation_rate) - })) - - # On cancellation, make stock ledger entry for - # target warehouse first, to update serial no values properly - - # if cstr(d.s_warehouse) and self.docstatus == 2: - # sl_entries.append(self.get_sl_entries(d, { - # "warehouse": cstr(d.s_warehouse), - # "actual_qty": -flt(d.transfer_qty), - # "incoming_rate": 0 - # })) + # make sl entries for source warehouse first + self.get_sle_for_source_warehouse(sl_entries, finished_item_row) + # SLE for target warehouse + self.get_sle_for_target_warehouse(sl_entries, finished_item_row) + + # reverse sl entries if cancel if self.docstatus == 2: sl_entries.reverse() self.make_sl_entries(sl_entries) + def get_finished_item_row(self): + finished_item_row = None + if self.purpose in ("Manufacture", "Repack"): + for d in self.get('items'): + if d.is_finished_item: + finished_item_row = d + + return finished_item_row + + def get_sle_for_source_warehouse(self, sl_entries, finished_item_row): + for d in self.get('items'): + if cstr(d.s_warehouse): + sle = self.get_sl_entries(d, { + "warehouse": cstr(d.s_warehouse), + "actual_qty": -flt(d.transfer_qty), + "incoming_rate": 0 + }) + if cstr(d.t_warehouse): + sle.dependant_sle_voucher_detail_no = d.name + elif finished_item_row and (finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse): + sle.dependant_sle_voucher_detail_no = finished_item_row.name + + sl_entries.append(sle) + + def get_sle_for_target_warehouse(self, sl_entries, finished_item_row): + for d in self.get('items'): + if cstr(d.t_warehouse): + sle = self.get_sl_entries(d, { + "warehouse": cstr(d.t_warehouse), + "actual_qty": flt(d.transfer_qty), + "incoming_rate": flt(d.valuation_rate) + }) + if cstr(d.s_warehouse) or (finished_item_row and d.name == finished_item_row.name): + sle.recalculate_rate = 1 + + sl_entries.append(sle) + def get_gl_entries(self, warehouse_account): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) @@ -747,7 +786,7 @@ class StockEntry(StockController): "credit": -1 * amount # put it as negative credit instead of debit purposefully }, item=d)) - return gl_entries + return process_gl_map(gl_entries) def update_work_order(self): def _validate_work_order(pro_doc): @@ -996,6 +1035,7 @@ class StockEntry(StockController): "stock_uom": item.stock_uom, "expense_account": item.get("expense_account"), "cost_center": item.get("buying_cost_center"), + "is_finished_item": 1 } }, bom_no = self.bom_no) @@ -1034,6 +1074,7 @@ class StockEntry(StockController): for item in itervalues(item_dict): item.from_warehouse = "" + item.is_scrap_item = 1 return item_dict def get_unconsumed_raw_materials(self): @@ -1246,6 +1287,8 @@ class StockEntry(StockController): se_child.subcontracted_item = item_dict[d].get("main_item_code") se_child.cost_center = (item_dict[d].get("cost_center") or get_default_cost_center(item_dict[d], company = self.company)) + se_child.is_finished_item = item_dict[d].get("is_finished_item", 0) + se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0) for field in ["idx", "po_detail", "original_item", "expense_account", "description", "item_name"]: diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 9b6744ca3c..1a641855aa 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -6,7 +6,6 @@ import frappe, unittest import frappe.defaults from frappe.utils import flt, nowdate, nowtime from erpnext.stock.doctype.serial_no.serial_no import * -from erpnext import set_perpetual_inventory from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError from erpnext.stock.stock_ledger import get_previous_sle from frappe.permissions import add_user_permission, remove_user_permission @@ -32,7 +31,6 @@ def get_sle(**args): class TestStockEntry(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") - set_perpetual_inventory(0) def test_fifo(self): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) @@ -213,7 +211,6 @@ class TestStockEntry(unittest.TestCase): def test_repack_no_change_in_valuation(self): company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(0, company) make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100) make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", @@ -235,8 +232,6 @@ class TestStockEntry(unittest.TestCase): order by account desc""", repack.name, as_dict=1) self.assertFalse(gl_entries) - set_perpetual_inventory(0, repack.company) - def test_repack_with_additional_costs(self): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') @@ -474,7 +469,6 @@ class TestStockEntry(unittest.TestCase): def test_warehouse_company_validation(self): company = frappe.db.get_value('Warehouse', '_Test Warehouse 2 - _TC1', 'company') - set_perpetual_inventory(0, company) frappe.get_doc("User", "test2@example.com")\ .add_roles("Sales User", "Sales Manager", "Stock User", "Stock Manager") frappe.set_user("test2@example.com") @@ -500,7 +494,7 @@ class TestStockEntry(unittest.TestCase): st1 = frappe.copy_doc(test_records[0]) st1.company = "_Test Company 1" - set_perpetual_inventory(0, st1.company) + frappe.set_user("test@example.com") st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1" self.assertRaises(frappe.PermissionError, st1.insert) @@ -698,47 +692,54 @@ class TestStockEntry(unittest.TestCase): repack.insert() self.assertRaises(frappe.ValidationError, repack.submit) - def test_material_consumption(self): - from erpnext.manufacturing.doctype.work_order.work_order \ - import make_stock_entry as _make_stock_entry - bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", - "is_default": 1, "docstatus": 1}) + # def test_material_consumption(self): + # frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM") + # frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "0") - work_order = frappe.new_doc("Work Order") - work_order.update({ - "company": "_Test Company", - "fg_warehouse": "_Test Warehouse 1 - _TC", - "production_item": "_Test FG Item 2", - "bom_no": bom_no, - "qty": 4.0, - "stock_uom": "_Test UOM", - "wip_warehouse": "_Test Warehouse - _TC", - "additional_operating_cost": 1000 - }) - work_order.insert() - work_order.submit() + # from erpnext.manufacturing.doctype.work_order.work_order \ + # import make_stock_entry as _make_stock_entry + # bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", + # "is_default": 1, "docstatus": 1}) - make_stock_entry(item_code="_Test Serialized Item With Series", target="_Test Warehouse - _TC", qty=50, basic_rate=100) - make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20) + # work_order = frappe.new_doc("Work Order") + # work_order.update({ + # "company": "_Test Company", + # "fg_warehouse": "_Test Warehouse 1 - _TC", + # "production_item": "_Test FG Item 2", + # "bom_no": bom_no, + # "qty": 4.0, + # "stock_uom": "_Test UOM", + # "wip_warehouse": "_Test Warehouse - _TC", + # "additional_operating_cost": 1000, + # "use_multi_level_bom": 1 + # }) + # work_order.insert() + # work_order.submit() - item_quantity = { - '_Test Item': 10.0, - '_Test Item 2': 12.0, - '_Test Serialized Item With Series': 6.0 - } + # make_stock_entry(item_code="_Test Serialized Item With Series", target="_Test Warehouse - _TC", qty=50, basic_rate=100) + # make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20) - stock_entry = frappe.get_doc(_make_stock_entry(work_order.name, "Material Consumption for Manufacture", 2)) - for d in stock_entry.get('items'): - self.assertEqual(item_quantity.get(d.item_code), d.qty) + # item_quantity = { + # '_Test Item': 2.0, + # '_Test Item 2': 12.0, + # '_Test Serialized Item With Series': 6.0 + # } + + # stock_entry = frappe.get_doc(_make_stock_entry(work_order.name, "Material Consumption for Manufacture", 2)) + # for d in stock_entry.get('items'): + # self.assertEqual(item_quantity.get(d.item_code), d.qty) def test_customer_provided_parts_se(self): create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) - se = make_stock_entry(item_code='CUST-0987', purpose = 'Material Receipt', qty=4, to_warehouse = "_Test Warehouse - _TC") + se = make_stock_entry(item_code='CUST-0987', purpose = 'Material Receipt', + qty=4, to_warehouse = "_Test Warehouse - _TC") self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1) self.assertEqual(se.get("items")[0].amount, 0) def test_gle_for_opening_stock_entry(self): - mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True) + mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", + company="_Test Company with perpetual inventory", qty=50, basic_rate=100, + expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True) self.assertRaises(OpeningEntryAccountError, mr.save) @@ -759,8 +760,8 @@ class TestStockEntry(unittest.TestCase): "company":"_Test Company with perpetual inventory", "items":[ { - "item_code":"Basil Leaves", - "description":"Basil Leaves", + "item_code":"_Test Item", + "description":"_Test Item", "qty": 1, "basic_rate": 0, "uom":"Nos", @@ -769,8 +770,8 @@ class TestStockEntry(unittest.TestCase): "cost_center": "Main - TCP1" }, { - "item_code":"Basil Leaves", - "description":"Basil Leaves", + "item_code":"_Test Item", + "description":"_Test Item", "qty": 2, "basic_rate": 0, "uom":"Nos", diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 79e8f9af8f..6fe60298ee 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -13,8 +13,10 @@ "t_warehouse", "sec_break1", "item_code", - "col_break2", "item_name", + "col_break2", + "is_finished_item", + "is_scrap_item", "subcontracted_item", "section_break_8", "description", @@ -22,35 +24,37 @@ "item_group", "image", "image_view", - "quantity_and_rate", - "set_basic_rate_manually", + "quantity_section", "qty", - "basic_rate", - "basic_amount", - "additional_cost", - "amount", - "valuation_rate", - "col_break3", - "uom", - "conversion_factor", - "stock_uom", "transfer_qty", "retain_sample", + "column_break_20", + "uom", + "stock_uom", + "conversion_factor", "sample_quantity", + "rates_section", + "basic_rate", + "additional_cost", + "valuation_rate", + "allow_zero_valuation_rate", + "col_break3", + "set_basic_rate_manually", + "basic_amount", + "amount", "serial_no_batch", "serial_no", "col_break4", "batch_no", - "quality_inspection", "accounting", "expense_account", - "col_break5", "accounting_dimensions_section", "cost_center", + "project", "dimension_col_break", "more_info", - "allow_zero_valuation_rate", "actual_qty", + "transferred_qty", "bom_no", "allow_alternative_item", "col_break6", @@ -62,9 +66,8 @@ "ste_detail", "po_detail", "column_break_51", - "transferred_qty", "reference_purchase_receipt", - "project" + "quality_inspection" ], "fields": [ { @@ -159,11 +162,6 @@ "options": "image", "print_hide": 1 }, - { - "fieldname": "quantity_and_rate", - "fieldtype": "Section Break", - "label": "Quantity and Rate" - }, { "bold": 1, "fieldname": "qty", @@ -321,10 +319,6 @@ "options": "Account", "print_hide": 1 }, - { - "fieldname": "col_break5", - "fieldtype": "Column Break" - }, { "default": ":Company", "depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))", @@ -335,6 +329,7 @@ "print_hide": 1 }, { + "collapsible": 1, "fieldname": "more_info", "fieldtype": "Section Break", "label": "More Information" @@ -456,6 +451,7 @@ "read_only": 1 }, { + "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", "label": "Accounting Dimensions" @@ -498,6 +494,32 @@ "fieldname": "set_basic_rate_manually", "fieldtype": "Check", "label": "Set Basic Rate Manually" + }, + { + "fieldname": "quantity_section", + "fieldtype": "Section Break", + "label": "Quantity" + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, + { + "fieldname": "rates_section", + "fieldtype": "Section Break", + "label": "Rates" + }, + { + "default": "0", + "fieldname": "is_scrap_item", + "fieldtype": "Check", + "label": "Is Scrap Item" + }, + { + "default": "0", + "fieldname": "is_finished_item", + "fieldtype": "Check", + "label": "Is Finished Item" } ], "idx": 1, diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index fda17e08ab..2463a21ed6 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -8,26 +8,33 @@ "engine": "InnoDB", "field_order": [ "item_code", - "serial_no", - "batch_no", "warehouse", "posting_date", "posting_time", + "column_break_6", "voucher_type", "voucher_no", "voucher_detail_no", + "dependant_sle_voucher_detail_no", + "recalculate_rate", + "section_break_11", "actual_qty", + "qty_after_transaction", "incoming_rate", "outgoing_rate", - "stock_uom", - "qty_after_transaction", + "column_break_17", "valuation_rate", "stock_value", "stock_value_difference", "stock_queue", - "project", + "section_break_21", "company", + "stock_uom", + "project", + "batch_no", + "column_break_26", "fiscal_year", + "serial_no", "is_cancelled", "to_rename" ], @@ -50,7 +57,6 @@ { "fieldname": "serial_no", "fieldtype": "Long Text", - "in_list_view": 1, "label": "Serial No", "print_width": "100px", "read_only": 1, @@ -59,7 +65,6 @@ { "fieldname": "batch_no", "fieldtype": "Data", - "in_list_view": 1, "label": "Batch No", "oldfieldname": "batch_no", "oldfieldtype": "Data", @@ -119,6 +124,7 @@ "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "in_filter": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Voucher No", "oldfieldname": "voucher_no", @@ -142,6 +148,7 @@ "fieldname": "actual_qty", "fieldtype": "Float", "in_filter": 1, + "in_list_view": 1, "label": "Actual Quantity", "oldfieldname": "actual_qty", "oldfieldtype": "Currency", @@ -152,6 +159,7 @@ { "fieldname": "incoming_rate", "fieldtype": "Currency", + "in_list_view": 1, "label": "Incoming Rate", "oldfieldname": "incoming_rate", "oldfieldtype": "Currency", @@ -217,13 +225,11 @@ { "fieldname": "stock_queue", "fieldtype": "Text", - "hidden": 1, "label": "Stock Queue (FIFO)", "oldfieldname": "fcfs_stack", "oldfieldtype": "Text", "print_hide": 1, - "read_only": 1, - "report_hide": 1 + "read_only": 1 }, { "fieldname": "project", @@ -269,14 +275,48 @@ "hidden": 1, "label": "To Rename", "search_index": 1 + }, + { + "fieldname": "dependant_sle_voucher_detail_no", + "fieldtype": "Data", + "label": "Dependant SLE Voucher Detail No" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_21", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "recalculate_rate", + "fieldtype": "Check", + "label": "Recalculate Incoming/Outgoing Rate", + "no_copy": 1, + "read_only": 1 } ], "hide_toolbar": 1, "icon": "fa fa-list", "idx": 1, "in_create": 1, + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-04-23 05:57:03.985520", + "modified": "2020-09-07 11:10:35.318872", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index bb356f694a..a5c303ccb4 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -10,8 +10,10 @@ from frappe.model.document import Document from datetime import date from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock from erpnext.accounts.utils import get_fiscal_year +from frappe.core.doctype.role.role import get_users class StockFreezeError(frappe.ValidationError): pass +class BackDatedStockTransaction(frappe.ValidationError): pass exclude_from_linked_with = True @@ -34,7 +36,6 @@ class StockLedgerEntry(Document): self.validate_and_set_fiscal_year() self.block_transactions_against_group_warehouse() self.validate_with_last_transaction_posting_time() - self.validate_future_posting() def on_submit(self): self.check_stock_frozen_date() @@ -48,7 +49,7 @@ class StockLedgerEntry(Document): def calculate_batch_qty(self): if self.batch_no: batch_qty = frappe.db.get_value("Stock Ledger Entry", - {"docstatus": 1, "batch_no": self.batch_no}, + {"docstatus": 1, "batch_no": self.batch_no, "is_cancelled": 0}, "sum(actual_qty)") or 0 frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty) @@ -88,14 +89,14 @@ class StockLedgerEntry(Document): # check if batch number is required if self.voucher_type != 'Stock Reconciliation': - if item_det.has_batch_no ==1: + if item_det.has_batch_no == 1: batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name if not self.batch_no: frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item)) elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) - elif item_det.has_batch_no ==0 and self.batch_no: + elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0: frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) if item_det.has_variants: @@ -142,28 +143,28 @@ class StockLedgerEntry(Document): is_group_warehouse(self.warehouse) def validate_with_last_transaction_posting_time(self): - last_transaction_time = frappe.db.sql(""" - select MAX(timestamp(posting_date, posting_time)) as posting_time - from `tabStock Ledger Entry` - where docstatus = 1 and item_code = %s - and warehouse = %s""", (self.item_code, self.warehouse))[0][0] + authorized_role = frappe.db.get_single_value("Stock Settings", "role_allowed_to_create_edit_back_dated_transactions") + if authorized_role: + authorized_users = get_users(authorized_role) + if authorized_users and frappe.session.user not in authorized_users: + last_transaction_time = frappe.db.sql(""" + select MAX(timestamp(posting_date, posting_time)) as posting_time + from `tabStock Ledger Entry` + where docstatus = 1 and item_code = %s + and warehouse = %s""", (self.item_code, self.warehouse))[0][0] - cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00") + cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00") - if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time): - msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(frappe.bold(self.item_code), - frappe.bold(self.warehouse), frappe.bold(last_transaction_time)) + if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time): + msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(frappe.bold(self.item_code), + frappe.bold(self.warehouse), frappe.bold(last_transaction_time)) - msg += "

" + _("Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.").format( - frappe.bold(self.item_code), frappe.bold(self.warehouse)) + msg += "

" + _("You are not authorized to make/edit Stock Transactions for Item {0} under warehouse {1} before this time.").format( + frappe.bold(self.item_code), frappe.bold(self.warehouse)) - msg += "

" + _("Please remove this item and try to submit again or update the posting time.") - frappe.throw(msg, title=_("Backdated Stock Entry")) - - def validate_future_posting(self): - if date_diff(self.posting_date, getdate()) > 0: - msg = _("Posting future stock transactions are not allowed due to Immutable Ledger") - frappe.throw(msg, title=_("Future Posting Not Allowed")) + msg += "

" + _("Please contact any of the following users to {} this transaction.") + msg += "
" + "
".join(authorized_users) + frappe.throw(msg, BackDatedStockTransaction, title=_("Backdated Stock Entry")) def on_doctype_update(): if not frappe.db.has_index('tabStock Ledger Entry', 'posting_sort_index'): diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 04dae83447..59f1f3961b 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -5,8 +5,397 @@ from __future__ import unicode_literals import frappe import unittest - -# test_records = frappe.get_test_records('Stock Ledger Entry') +from frappe.utils import today, add_days +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \ + import create_stock_reconciliation +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import create_landed_cost_voucher +from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import BackDatedStockTransaction class TestStockLedgerEntry(unittest.TestCase): - pass + def setUp(self): + items = create_items() + + # delete SLE and BINs for all items + frappe.db.sql("delete from `tabStock Ledger Entry` where item_code in (%s)" % (', '.join(['%s']*len(items))), items) + frappe.db.sql("delete from `tabBin` where item_code in (%s)" % (', '.join(['%s']*len(items))), items) + + def test_item_cost_reposting(self): + company = "_Test Company" + + # _Test Item for Reposting at Stores warehouse on 10-04-2020: Qty = 50, Rate = 100 + create_stock_reconciliation( + item_code="_Test Item for Reposting", + warehouse="Stores - _TC", + qty=50, + rate=100, + company=company, + expense_account = "Stock Adjustment - _TC", + posting_date='2020-04-10', + posting_time='14:00' + ) + + # _Test Item for Reposting at FG warehouse on 20-04-2020: Qty = 10, Rate = 200 + create_stock_reconciliation( + item_code="_Test Item for Reposting", + warehouse="Finished Goods - _TC", + qty=10, + rate=200, + company=company, + expense_account = "Stock Adjustment - _TC", + posting_date='2020-04-20', + posting_time='14:00' + ) + + # _Test Item for Reposting transferred from Stores to FG warehouse on 30-04-2020 + make_stock_entry( + item_code="_Test Item for Reposting", + source="Stores - _TC", + target="Finished Goods - _TC", + company=company, + qty=10, + expense_account="Stock Adjustment - _TC", + posting_date='2020-04-30', + posting_time='14:00' + ) + target_wh_sle = get_previous_sle({ + "item_code": "_Test Item for Reposting", + "warehouse": "Finished Goods - _TC", + "posting_date": '2020-04-30', + "posting_time": '14:00' + }) + + self.assertEqual(target_wh_sle.get("valuation_rate"), 150) + + # Repack entry on 5-5-2020 + repack = create_repack_entry(company=company, posting_date='2020-05-05', posting_time='14:00') + + finished_item_sle = get_previous_sle({ + "item_code": "_Test Finished Item for Reposting", + "warehouse": "Finished Goods - _TC", + "posting_date": '2020-05-05', + "posting_time": '14:00' + }) + self.assertEqual(finished_item_sle.get("incoming_rate"), 540) + self.assertEqual(finished_item_sle.get("valuation_rate"), 540) + + # Reconciliation for _Test Item for Reposting at Stores on 12-04-2020: Qty = 50, Rate = 150 + create_stock_reconciliation( + item_code="_Test Item for Reposting", + warehouse="Stores - _TC", + qty=50, + rate=150, + company=company, + expense_account = "Stock Adjustment - _TC", + posting_date='2020-04-12', + posting_time='14:00' + ) + + + # Check valuation rate of finished goods warehouse after back-dated entry at Stores + target_wh_sle = get_previous_sle({ + "item_code": "_Test Item for Reposting", + "warehouse": "Finished Goods - _TC", + "posting_date": '2020-04-30', + "posting_time": '14:00' + }) + self.assertEqual(target_wh_sle.get("incoming_rate"), 150) + self.assertEqual(target_wh_sle.get("valuation_rate"), 175) + + # Check valuation rate of repacked item after back-dated entry at Stores + finished_item_sle = get_previous_sle({ + "item_code": "_Test Finished Item for Reposting", + "warehouse": "Finished Goods - _TC", + "posting_date": '2020-05-05', + "posting_time": '14:00' + }) + self.assertEqual(finished_item_sle.get("incoming_rate"), 790) + self.assertEqual(finished_item_sle.get("valuation_rate"), 790) + + # Check updated rate in Repack entry + repack.reload() + self.assertEqual(repack.items[0].get("basic_rate"), 150) + self.assertEqual(repack.items[1].get("basic_rate"), 750) + + def test_purchase_return_valuation_reposting(self): + pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-10', + warehouse="Stores - _TC", item_code="_Test Item for Reposting", qty=5, rate=100) + + return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15', + warehouse="Stores - _TC", item_code="_Test Item for Reposting", is_return=1, return_against=pr.name, qty=-2) + + # check sle + outgoing_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", + "voucher_no": return_pr.name}, ["outgoing_rate", "stock_value_difference"]) + + self.assertEqual(outgoing_rate, 100) + self.assertEqual(stock_value_difference, -200) + + create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + + outgoing_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", + "voucher_no": return_pr.name}, ["outgoing_rate", "stock_value_difference"]) + + self.assertEqual(outgoing_rate, 110) + self.assertEqual(stock_value_difference, -220) + + def test_sales_return_valuation_reposting(self): + company = "_Test Company" + item_code="_Test Item for Reposting" + + # Purchase Return: Qty = 5, Rate = 100 + pr = make_purchase_receipt(company=company, posting_date='2020-04-10', + warehouse="Stores - _TC", item_code=item_code, qty=5, rate=100) + + #Delivery Note: Qty = 5, Rate = 150 + dn = create_delivery_note(item_code=item_code, qty=5, rate=150, warehouse="Stores - _TC", + company=company, expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC") + + # check outgoing_rate for DN + outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note", + "voucher_no": dn.name}, "stock_value_difference") / 5) + + self.assertEqual(dn.items[0].incoming_rate, 100) + self.assertEqual(outgoing_rate, 100) + + # Return Entry: Qty = -2, Rate = 150 + return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=item_code, qty=-2, rate=150, + company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC") + + # check incoming rate for Return entry + incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": return_dn.name}, + ["incoming_rate", "stock_value_difference"]) + + self.assertEqual(return_dn.items[0].incoming_rate, 100) + self.assertEqual(incoming_rate, 100) + self.assertEqual(stock_value_difference, 200) + + #------------------------------- + + # Landed Cost Voucher to update the rate of incoming Purchase Return: Additional cost = 50 + lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + + # check outgoing_rate for DN after reposting + outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note", + "voucher_no": dn.name}, "stock_value_difference") / 5) + self.assertEqual(outgoing_rate, 110) + + dn.reload() + self.assertEqual(dn.items[0].incoming_rate, 110) + + # check incoming rate for Return entry after reposting + incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": return_dn.name}, + ["incoming_rate", "stock_value_difference"]) + + self.assertEqual(incoming_rate, 110) + self.assertEqual(stock_value_difference, 220) + + return_dn.reload() + self.assertEqual(return_dn.items[0].incoming_rate, 110) + + # Cleanup data + return_dn.cancel() + dn.cancel() + lcv.cancel() + pr.cancel() + + def test_reposting_of_sales_return_for_packed_item(self): + company = "_Test Company" + packed_item_code="_Test Item for Reposting" + bundled_item = "_Test Bundled Item for Reposting" + create_product_bundle_item(bundled_item, [[packed_item_code, 4]]) + + # Purchase Return: Qty = 50, Rate = 100 + pr = make_purchase_receipt(company=company, posting_date='2020-04-10', + warehouse="Stores - _TC", item_code=packed_item_code, qty=50, rate=100) + + #Delivery Note: Qty = 5, Rate = 150 + dn = create_delivery_note(item_code=bundled_item, qty=5, rate=150, warehouse="Stores - _TC", + company=company, expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC") + + # check outgoing_rate for DN + outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note", + "voucher_no": dn.name}, "stock_value_difference") / 20) + + self.assertEqual(dn.packed_items[0].incoming_rate, 100) + self.assertEqual(outgoing_rate, 100) + + # Return Entry: Qty = -2, Rate = 150 + return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=bundled_item, qty=-2, rate=150, + company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC") + + # check incoming rate for Return entry + incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": return_dn.name}, + ["incoming_rate", "stock_value_difference"]) + + self.assertEqual(return_dn.packed_items[0].incoming_rate, 100) + self.assertEqual(incoming_rate, 100) + self.assertEqual(stock_value_difference, 800) + + #------------------------------- + + # Landed Cost Voucher to update the rate of incoming Purchase Return: Additional cost = 50 + lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + + # check outgoing_rate for DN after reposting + outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note", + "voucher_no": dn.name}, "stock_value_difference") / 20) + self.assertEqual(outgoing_rate, 101) + + dn.reload() + self.assertEqual(dn.packed_items[0].incoming_rate, 101) + + # check incoming rate for Return entry after reposting + incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": return_dn.name}, + ["incoming_rate", "stock_value_difference"]) + + self.assertEqual(incoming_rate, 101) + self.assertEqual(stock_value_difference, 808) + + return_dn.reload() + self.assertEqual(return_dn.packed_items[0].incoming_rate, 101) + + # Cleanup data + return_dn.cancel() + dn.cancel() + lcv.cancel() + pr.cancel() + + def test_sub_contracted_item_costing(self): + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + company = "_Test Company" + rm_item_code="_Test Item for Reposting" + subcontracted_item = "_Test Subcontracted Item for Reposting" + + frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM") + make_bom(item = subcontracted_item, raw_materials =[rm_item_code], currency="INR") + + # Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100 + pr = make_purchase_receipt(company=company, posting_date='2020-04-10', + warehouse="Stores - _TC", item_code=rm_item_code, qty=10, rate=100) + + # Purchase Receipt for subcontracted item + pr1 = make_purchase_receipt(company=company, posting_date='2020-04-20', + warehouse="Finished Goods - _TC", supplier_warehouse="Stores - _TC", + item_code=subcontracted_item, qty=10, rate=20, is_subcontracted="Yes") + + self.assertEqual(pr1.items[0].valuation_rate, 120) + + # Update raw material's valuation via LCV, Additional cost = 50 + lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + + pr1.reload() + self.assertEqual(pr1.items[0].valuation_rate, 125) + + # check outgoing_rate for DN after reposting + incoming_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", + "voucher_no": pr1.name, "item_code": subcontracted_item}, "incoming_rate") + self.assertEqual(incoming_rate, 125) + + # cleanup data + pr1.cancel() + lcv.cancel() + pr.cancel() + + def test_back_dated_entry_not_allowed(self): + # Back dated stock transactions are only allowed to stock managers + frappe.db.set_value("Stock Settings", None, + "role_allowed_to_create_edit_back_dated_transactions", "Stock Manager") + + # Set User with Stock User role but not Stock Manager + frappe.set_user("test@example.com") + user = frappe.get_doc("User", "test@example.com") + user.add_roles("Stock User") + user.remove_roles("Stock Manager") + + stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) + back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, + posting_date=add_days(today(), -1), do_not_submit=True) + + # Block back-dated entry + self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit) + + user.add_roles("Stock Manager") + + # Back dated entry allowed to Stock Manager + back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, + posting_date=add_days(today(), -1)) + + back_dated_se_2.cancel() + stock_entry_on_today.cancel() + + frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None) + frappe.set_user("Administrator") + + +def create_repack_entry(**args): + args = frappe._dict(args) + repack = frappe.new_doc("Stock Entry") + repack.stock_entry_type = "Repack" + repack.company = args.company or "_Test Company" + repack.posting_date = args.posting_date + repack.set_posting_time = 1 + repack.append("items", { + "item_code": "_Test Item for Reposting", + "s_warehouse": "Stores - _TC", + "qty": 5, + "conversion_factor": 1, + "expense_account": "Stock Adjustment - _TC", + "cost_center": "Main - _TC" + }) + + repack.append("items", { + "item_code": "_Test Finished Item for Reposting", + "t_warehouse": "Finished Goods - _TC", + "qty": 1, + "conversion_factor": 1, + "expense_account": "Stock Adjustment - _TC", + "cost_center": "Main - _TC" + }) + + repack.append("additional_costs", { + "expense_account": "Freight and Forwarding Charges - _TC", + "description": "transport cost", + "amount": 40 + }) + + repack.save() + repack.submit() + + return repack + +def create_product_bundle_item(new_item_code, packed_items): + if not frappe.db.exists("Product Bundle", new_item_code): + item = frappe.new_doc("Product Bundle") + item.new_item_code = new_item_code + + for d in packed_items: + item.append("items", { + "item_code": d[0], + "qty": d[1] + }) + + item.save() + +def create_items(): + items = ["_Test Item for Reposting", "_Test Finished Item for Reposting", + "_Test Subcontracted Item for Reposting", "_Test Bundled Item for Reposting"] + for d in items: + properties = {"valuation_method": "FIFO"} + if d == "_Test Bundled Item for Reposting": + properties.update({"is_stock_item": 0}) + elif d == "_Test Subcontracted Item for Reposting": + properties.update({"is_sub_contracted_item": 1}) + + make_item(d, properties=properties) + + return items \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 00b8f69c08..5b40292ea8 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -37,14 +37,16 @@ class StockReconciliation(StockController): def on_submit(self): self.update_stock_ledger() self.make_gl_entries() + self.repost_future_sle_and_gle() from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit update_serial_nos_after_submit(self, "items") def on_cancel(self): - self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') self.make_sle_on_cancel() self.make_gl_entries_on_cancel() + self.repost_future_sle_and_gle() def remove_items_with_no_change(self): """Remove items if qty or rate is not changed""" diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 23d48d4ac7..088456f865 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -8,12 +8,11 @@ from __future__ import unicode_literals import frappe, unittest from frappe.utils import flt, nowdate, nowtime from erpnext.accounts.utils import get_stock_and_account_balance -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.item.test_item import create_item -from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos, get_stock_value_on +from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class TestStockReconciliation(unittest.TestCase): @@ -29,16 +28,17 @@ class TestStockReconciliation(unittest.TestCase): self._test_reco_sle_gle("Moving Average") def _test_reco_sle_gle(self, valuation_method): - insert_existing_sle(warehouse='Stores - TCP1') + se1, se2, se3 = insert_existing_sle(warehouse='Stores - TCP1') company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') # [[qty, valuation_rate, posting_date, # posting_time, expected_stock_value, bin_qty, bin_valuation]] + input_data = [ - [50, 1000], - [25, 900], - ["", 1000], - [20, ""], - [0, ""] + [50, 1000, "2012-12-26", "12:00"], + [25, 900, "2012-12-26", "12:00"], + ["", 1000, "2012-12-20", "12:05"], + [20, "", "2012-12-26", "12:05"], + [0, "", "2012-12-31", "12:10"] ] for d in input_data: @@ -47,13 +47,13 @@ class TestStockReconciliation(unittest.TestCase): last_sle = get_previous_sle({ "item_code": "_Test Item", "warehouse": "Stores - TCP1", - "posting_date": nowdate(), - "posting_time": nowtime() + "posting_date": d[2], + "posting_time": d[3] }) # submit stock reconciliation stock_reco = create_stock_reconciliation(qty=d[0], rate=d[1], - posting_date=nowdate(), posting_time=nowtime(), warehouse="Stores - TCP1", + posting_date=d[2], posting_time=d[3], warehouse="Stores - TCP1", company=company, expense_account = "Stock Adjustment - TCP1") # check stock value @@ -81,10 +81,15 @@ class TestStockReconciliation(unittest.TestCase): stock_reco.cancel() + se3.cancel() + se2.cancel() + se1.cancel() + def test_get_items(self): - create_warehouse("_Test Warehouse Group 1", {"is_group": 1}) + create_warehouse("_Test Warehouse Group 1", + {"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"}) create_warehouse("_Test Warehouse Ledger 1", - {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"}) + {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC", "company": "_Test Company"}) create_item("_Test Stock Reco Item", is_stock_item=1, valuation_rate=100, warehouse="_Test Warehouse Ledger 1 - _TC", opening_stock=100) @@ -95,8 +100,6 @@ class TestStockReconciliation(unittest.TestCase): [items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]]) def test_stock_reco_for_serialized_item(self): - set_perpetual_inventory() - to_delete_records = [] to_delete_serial_nos = [] @@ -148,8 +151,6 @@ class TestStockReconciliation(unittest.TestCase): stock_doc.cancel() def test_stock_reco_for_batch_item(self): - set_perpetual_inventory() - to_delete_records = [] to_delete_serial_nos = [] @@ -196,15 +197,17 @@ class TestStockReconciliation(unittest.TestCase): def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item", + se1 = make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item", target=warehouse, qty=10, basic_rate=700) - make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item", + se2 = make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item", source=warehouse, qty=15) - make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item", + se3 = make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item", target=warehouse, qty=15, basic_rate=1200) + return se1, se2, se3 + def create_batch_or_serial_no_items(): create_warehouse("_Test Warehouse for Stock Reco1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"}) @@ -256,6 +259,10 @@ def create_stock_reconciliation(**args): return sr def set_valuation_method(item_code, valuation_method): + existing_valuation_method = get_valuation_method(item_code) + if valuation_method == existing_valuation_method: + return + frappe.db.set_value("Item", item_code, "valuation_method", valuation_method) for warehouse in frappe.get_all("Warehouse", filters={"company": "_Test Company"}, fields=["name", "is_group"]): diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index a1666579d1..859aea2eb6 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -28,7 +28,9 @@ "inter_warehouse_transfer_settings_section", "allow_from_dn", "allow_from_pr", - "freeze_stock_entries", + "control_historical_stock_transactions_section", + "role_allowed_to_create_edit_back_dated_transactions", + "column_break_26", "stock_frozen_upto", "stock_frozen_upto_days", "stock_auth_role", @@ -156,21 +158,20 @@ "label": "Notify by Email on Creation of Automatic Material Request" }, { - "fieldname": "freeze_stock_entries", - "fieldtype": "Section Break", - "label": "Freeze Stock Entries" - }, - { + "description": "No stock transactions can be created or modified before this date.", "fieldname": "stock_frozen_upto", "fieldtype": "Date", "label": "Stock Frozen Upto" }, { + "description": "Stock transactions that are older than the mentioned days cannot be modified.", "fieldname": "stock_frozen_upto_days", "fieldtype": "Int", "label": "Freeze Stocks Older Than (Days)" }, { + "depends_on": "eval:(doc.stock_frozen_upto || doc.stock_frozen_upto_days)", + "description": "The users with this Role are allowed to create/modify a stock transaction, even though the transaction is frozen.", "fieldname": "stock_auth_role", "fieldtype": "Link", "label": "Role Allowed to Edit Frozen Stock", @@ -210,6 +211,22 @@ "fieldname": "allow_from_pr", "fieldtype": "Check", "label": "Allow Material Transfer from Purchase Receipt to Purchase Invoice" + }, + { + "description": "If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.", + "fieldname": "role_allowed_to_create_edit_back_dated_transactions", + "fieldtype": "Link", + "label": "Role Allowed to Create/Edit Back-dated Transactions", + "options": "User" + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, + { + "fieldname": "control_historical_stock_transactions_section", + "fieldtype": "Section Break", + "label": "Control Historical Stock Transactions" } ], "icon": "icon-cog", @@ -217,7 +234,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-11-23 15:26:54.225608", + "modified": "2020-11-23 22:26:54.225608", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 3101e8af4c..95478f61f0 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -10,13 +10,10 @@ from frappe.test_runner import make_test_records import erpnext from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -from erpnext import set_perpetual_inventory from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account - test_records = frappe.get_test_records('Warehouse') - class TestWarehouse(unittest.TestCase): def setUp(self): if not frappe.get_value('Item', '_Test Item'): @@ -37,63 +34,63 @@ class TestWarehouse(unittest.TestCase): self.assertEqual(child_warehouse.is_group, 0) def test_warehouse_renaming(self): - set_perpetual_inventory(1) - create_warehouse("Test Warehouse for Renaming 1") - account = get_inventory_account("_Test Company", "Test Warehouse for Renaming 1 - _TC") + create_warehouse("Test Warehouse for Renaming 1", company="_Test Company with perpetual inventory") + account = get_inventory_account("_Test Company with perpetual inventory", "Test Warehouse for Renaming 1 - TCP1") self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": account})) # Rename with abbr - if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 2 - _TC"): - frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC") - frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 1 - _TC", "Test Warehouse for Renaming 2 - _TC") + if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 2 - TCP1"): + frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 2 - TCP1") + frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 1 - TCP1", "Test Warehouse for Renaming 2 - TCP1") self.assertTrue(frappe.db.get_value("Warehouse", - filters={"account": "Test Warehouse for Renaming 1 - _TC"})) + filters={"account": "Test Warehouse for Renaming 1 - TCP1"})) # Rename without abbr - if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 3 - _TC"): - frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC") + if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 3 - TCP1"): + frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 3 - TCP1") - frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC", "Test Warehouse for Renaming 3") + frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 2 - TCP1", "Test Warehouse for Renaming 3") self.assertTrue(frappe.db.get_value("Warehouse", - filters={"account": "Test Warehouse for Renaming 1 - _TC"})) + filters={"account": "Test Warehouse for Renaming 1 - TCP1"})) # Another rename with multiple dashes - if frappe.db.exists("Warehouse", "Test - Warehouse - Company - _TC"): - frappe.delete_doc("Warehouse", "Test - Warehouse - Company - _TC") - frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC", "Test - Warehouse - Company") + if frappe.db.exists("Warehouse", "Test - Warehouse - Company - TCP1"): + frappe.delete_doc("Warehouse", "Test - Warehouse - Company - TCP1") + frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 3 - TCP1", "Test - Warehouse - Company") def test_warehouse_merging(self): - set_perpetual_inventory(1) + company = "_Test Company with perpetual inventory" + create_warehouse("Test Warehouse for Merging 1", company=company, + properties={"parent_warehouse": "All Warehouses - TCP1"}) + create_warehouse("Test Warehouse for Merging 2", company=company, + properties={"parent_warehouse": "All Warehouses - TCP1"}) - create_warehouse("Test Warehouse for Merging 1") - create_warehouse("Test Warehouse for Merging 2") - - make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 1 - _TC", - qty=1, rate=100) - make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 2 - _TC", - qty=1, rate=100) + make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 1 - TCP1", + qty=1, rate=100, company=company) + make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 2 - TCP1", + qty=1, rate=100, company=company) existing_bin_qty = ( cint(frappe.db.get_value("Bin", - {"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 1 - _TC"}, "actual_qty")) + {"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 1 - TCP1"}, "actual_qty")) + cint(frappe.db.get_value("Bin", - {"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - _TC"}, "actual_qty")) + {"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - TCP1"}, "actual_qty")) ) - frappe.rename_doc("Warehouse", "Test Warehouse for Merging 1 - _TC", - "Test Warehouse for Merging 2 - _TC", merge=True) + frappe.rename_doc("Warehouse", "Test Warehouse for Merging 1 - TCP1", + "Test Warehouse for Merging 2 - TCP1", merge=True) - self.assertFalse(frappe.db.exists("Warehouse", "Test Warehouse for Merging 1 - _TC")) + self.assertFalse(frappe.db.exists("Warehouse", "Test Warehouse for Merging 1 - TCP1")) bin_qty = frappe.db.get_value("Bin", - {"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - _TC"}, "actual_qty") + {"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - TCP1"}, "actual_qty") self.assertEqual(bin_qty, existing_bin_qty) self.assertTrue(frappe.db.get_value("Warehouse", - filters={"account": "Test Warehouse for Merging 2 - _TC"})) + filters={"account": "Test Warehouse for Merging 2 - TCP1"})) def create_warehouse(warehouse_name, properties=None, company=None): if not company: diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index cd86be3115..6c84f168fd 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -29,7 +29,6 @@ class Warehouse(NestedSet): self.set_onload('account', account) load_address_and_contact(self) - def on_update(self): self.update_nsm_model() diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index 54eefdfaaa..0cc8ca48aa 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -7,9 +7,11 @@ from frappe import _, scrub from frappe.utils import getdate, flt from erpnext.stock.report.stock_balance.stock_balance import (get_items, get_stock_ledger_entries, get_item_details) from erpnext.accounts.utils import get_fiscal_year +from erpnext.stock.utils import is_reposting_item_valuation_in_progress from six import iteritems def execute(filters=None): + is_reposting_item_valuation_in_progress() filters = frappe._dict(filters or {}) columns = get_columns(filters) data = get_data(filters) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index ccd01001bb..e5d4d626c4 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -7,12 +7,13 @@ from frappe import _ from frappe.utils import flt, cint, getdate, now, date_diff from erpnext.stock.utils import add_additional_uom_columns from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition - +from erpnext.stock.utils import is_reposting_item_valuation_in_progress from erpnext.stock.report.stock_ageing.stock_ageing import get_fifo_queue, get_average_age from six import iteritems def execute(filters=None): + is_reposting_item_valuation_in_progress() if not filters: filters = {} validate_filters(filters) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 86af5e0c86..7b5701a993 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -5,11 +5,12 @@ from __future__ import unicode_literals import frappe from frappe.utils import cint, flt -from erpnext.stock.utils import update_included_uom_in_report +from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress from frappe import _ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos def execute(filters=None): + is_reposting_item_valuation_in_progress() include_uom = filters.get("include_uom") columns = get_columns() items = get_items(filters) diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index c8efb1637f..1183e41d04 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -5,9 +5,10 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt, today -from erpnext.stock.utils import update_included_uom_in_report +from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress def execute(filters=None): + is_reposting_item_valuation_in_progress() filters = frappe._dict(filters or {}) include_uom = filters.get("include_uom") columns = get_columns() diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py index ebcb106b02..04f7d347ba 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py @@ -11,9 +11,11 @@ from frappe.utils import flt, cint, getdate from erpnext.stock.report.stock_balance.stock_balance import (get_item_details, get_item_reorder_details, get_item_warehouse_map, get_items, get_stock_ledger_entries) from erpnext.stock.report.stock_ageing.stock_ageing import get_fifo_queue, get_average_age +from erpnext.stock.utils import is_reposting_item_valuation_in_progress from six import iteritems def execute(filters=None): + is_reposting_item_valuation_in_progress() if not filters: filters = {} validate_filters(filters) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index b5ae1b78eb..8ba1f1ca5c 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -6,6 +6,7 @@ import frappe from frappe.utils import flt, cstr, nowdate, nowtime from erpnext.stock.utils import update_bin from erpnext.stock.stock_ledger import update_entries_after +from erpnext.controllers.stock_controller import create_repost_item_valuation_entry def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, only_bin=False): """ @@ -56,12 +57,18 @@ def repost_stock(item_code, warehouse, allow_zero_rate=False, update_bin_qty(item_code, warehouse, qty_dict) def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False): - update_entries_after({ "item_code": item_code, "warehouse": warehouse }, - allow_zero_rate=allow_zero_rate, allow_negative_stock=allow_negative_stock) + create_repost_item_valuation_entry({ + "item_code": item_code, + "warehouse": warehouse, + "posting_date": "1900-01-01", + "posting_time": "00:01", + "allow_negative_stock": allow_negative_stock, + "allow_zero_rate": allow_zero_rate + }) def get_balance_qty_from_sle(item_code, warehouse): balance_qty = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry` - where item_code=%s and warehouse=%s + where item_code=%s and warehouse=%s and is_cancelled=0 order by posting_date desc, posting_time desc, creation desc limit 1""", (item_code, warehouse)) @@ -191,7 +198,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin print(d[0], d[1], d[2], serial_nos[0][0]) sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry` - where item_code = %s and warehouse = %s + where item_code = %s and warehouse = %s and is_cancelled = 0 order by posting_date desc limit 1""", (d[0], d[1])) sle_dict = { @@ -223,7 +230,8 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin }) update_bin(args) - update_entries_after({ + + create_repost_item_valuation_entry({ "item_code": d[0], "warehouse": d[1], "posting_date": posting_date, diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f4490f1b01..5b9ada0ee5 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -5,9 +5,10 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ from frappe.utils import cint, flt, cstr, now, now_datetime +from frappe.model.meta import get_field_precision from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel +from erpnext.stock.utils import get_bin import json - from six import iteritems # future reposting @@ -25,32 +26,23 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) for sle in sl_entries: - sle_id = None - if via_landed_cost_voucher or cancel: - sle['posting_date'] = now_datetime().strftime('%Y-%m-%d') - sle['posting_time'] = now_datetime().strftime('%H:%M:%S.%f') + if cancel: + sle['actual_qty'] = -flt(sle.get('actual_qty')) - if cancel: - sle['actual_qty'] = -flt(sle.get('actual_qty')) - - if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'): - sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, - sle.voucher_type, sle.voucher_no, sle.voucher_detail_no) - sle['incoming_rate'] = 0.0 - - if sle['actual_qty'] > 0 and not sle.get('incoming_rate'): - sle['incoming_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, - sle.voucher_type, sle.voucher_no, sle.voucher_detail_no) - sle['outgoing_rate'] = 0.0 + if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'): + sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, + sle.voucher_type, sle.voucher_no, sle.voucher_detail_no) + sle['incoming_rate'] = 0.0 + if sle['actual_qty'] > 0 and not sle.get('incoming_rate'): + sle['incoming_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, + sle.voucher_type, sle.voucher_no, sle.voucher_detail_no) + sle['outgoing_rate'] = 0.0 if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation": - sle_id = make_entry(sle, allow_negative_stock, via_landed_cost_voucher) - - args = sle.copy() - args.update({ - "sle_id": sle_id - }) + sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher) + + args = sle_doc.as_dict() update_bin(args, allow_negative_stock, via_landed_cost_voucher) @@ -68,8 +60,36 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): sle.via_landed_cost_voucher = via_landed_cost_voucher sle.insert() sle.submit() - return sle.name + return sle +def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=False, via_landed_cost_voucher=False): + if not args and voucher_type and voucher_no: + args = get_args_for_voucher(voucher_type, voucher_no) + + distinct_item_warehouses = [(d.item_code, d.warehouse) for d in args] + + i = 0 + while i < len(args): + obj = update_entries_after({ + "item_code": args[i].item_code, + "warehouse": args[i].warehouse, + "posting_date": args[i].posting_date, + "posting_time": args[i].posting_time + }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) + + for item_wh, new_sle in iteritems(obj.new_items): + if item_wh not in distinct_item_warehouses: + args.append(new_sle) + + i += 1 + +def get_args_for_voucher(voucher_type, voucher_no): + return frappe.db.get_all("Stock Ledger Entry", + filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, + fields=["item_code", "warehouse", "posting_date", "posting_time"], + order_by="creation asc", + group_by="item_code, warehouse" + ) class update_entries_after(object): """ @@ -86,141 +106,299 @@ class update_entries_after(object): } """ def __init__(self, args, allow_zero_rate=False, allow_negative_stock=None, via_landed_cost_voucher=False, verbose=1): - from frappe.model.meta import get_field_precision - - self.exceptions = [] + self.exceptions = {} self.verbose = verbose self.allow_zero_rate = allow_zero_rate - self.allow_negative_stock = allow_negative_stock self.via_landed_cost_voucher = via_landed_cost_voucher - if not self.allow_negative_stock: - self.allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", - "allow_negative_stock")) + self.allow_negative_stock = allow_negative_stock \ + or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) - self.args = args - for key, value in iteritems(args): - setattr(self, key, value) + self.args = frappe._dict(args) + self.item_code = args.get("item_code") + if self.args.sle_id: + self.args['name'] = self.args.sle_id - self.previous_sle = self.get_sle_before_datetime() - self.previous_sle = self.previous_sle[0] if self.previous_sle else frappe._dict() + self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") + self.get_precision() + self.valuation_method = get_valuation_method(self.item_code) + self.new_items = {} + + self.data = frappe._dict() + self.initialize_previous_data(self.args) + + self.build() + + def get_precision(self): + company_base_currency = frappe.get_cached_value('Company', self.company, "default_currency") + self.precision = get_field_precision(frappe.get_meta("Stock Ledger Entry").get_field("stock_value"), + currency=company_base_currency) + + def initialize_previous_data(self, args): + """ + Get previous sl entries for current item for each related warehouse + and assigns into self.data dict + + :Data Structure: + + self.data = { + warehouse1: { + 'previus_sle': {}, + 'qty_after_transaction': 10, + 'valuation_rate': 100, + 'stock_value': 1000, + 'prev_stock_value': 1000, + 'stock_queue': '[[10, 100]]', + 'stock_value_difference': 1000 + } + } + + """ + self.data.setdefault(args.warehouse, frappe._dict()) + warehouse_dict = self.data[args.warehouse] + previous_sle = self.get_sle_before_datetime(args) + warehouse_dict.previous_sle = previous_sle for key in ("qty_after_transaction", "valuation_rate", "stock_value"): - setattr(self, key, flt(self.previous_sle.get(key))) + setattr(warehouse_dict, key, flt(previous_sle.get(key))) - self.company = frappe.db.get_value("Warehouse", self.warehouse, "company") - self.precision = get_field_precision(frappe.get_meta("Stock Ledger Entry").get_field("stock_value"), - currency=frappe.get_cached_value('Company', self.company, "default_currency")) + warehouse_dict.update({ + "prev_stock_value": previous_sle.stock_value or 0.0, + "stock_queue": json.loads(previous_sle.stock_queue or "[]"), + "stock_value_difference": 0.0 + }) - self.prev_stock_value = self.previous_sle.stock_value or 0.0 - self.stock_queue = json.loads(self.previous_sle.stock_queue or "[]") - self.valuation_method = get_valuation_method(self.item_code) - self.stock_value_difference = 0.0 - self.build(args.get('sle_id')) - - def build(self, sle_id): - if sle_id: - sle = get_sle_by_id(sle_id) - self.process_sle(sle) + def build(self): + if self.args.get("sle_id"): + self.process_sle_against_current_voucher() else: - # includes current entry! - entries_to_fix = self.get_sle_after_datetime() - for sle in entries_to_fix: + entries_to_fix = self.get_future_entries_to_fix() + + i = 0 + while i < len(entries_to_fix): + sle = entries_to_fix[i] + i += 1 + self.process_sle(sle) + if sle.dependant_sle_voucher_detail_no: + self.get_dependent_entries_to_fix(entries_to_fix, sle) + if self.exceptions: self.raise_exceptions() self.update_bin() - def update_bin(self): - # update bin - bin_name = frappe.db.get_value("Bin", { - "item_code": self.item_code, - "warehouse": self.warehouse - }) + def process_sle_against_current_voucher(self): + sl_entries = self.get_sle_against_current_voucher() + for sle in sl_entries: + self.process_sle(sle) - if not bin_name: - bin_doc = frappe.get_doc({ - "doctype": "Bin", - "item_code": self.item_code, - "warehouse": self.warehouse - }) - bin_doc.insert(ignore_permissions=True) - else: - bin_doc = frappe.get_doc("Bin", bin_name) + def get_sle_against_current_voucher(self): + return frappe.db.sql(""" + select + *, timestamp(posting_date, posting_time) as "timestamp" + from + `tabStock Ledger Entry` + where + item_code = %(item_code)s + and warehouse = %(warehouse)s + and voucher_type = %(voucher_type)s + and voucher_no = %(voucher_no)s + order by + creation ASC + for update + """, self.args, as_dict=1) - bin_doc.update({ - "valuation_rate": self.valuation_rate, - "actual_qty": self.qty_after_transaction, - "stock_value": self.stock_value - }) - bin_doc.flags.via_stock_ledger_entry = True + def get_future_entries_to_fix(self): + # includes current entry! + args = self.data[self.args.warehouse].previous_sle \ + or frappe._dict({"item_code": self.item_code, "warehouse": self.args.warehouse}) + + return list(self.get_sle_after_datetime(args)) - bin_doc.save(ignore_permissions=True) + def get_dependent_entries_to_fix(self, entries_to_fix, sle): + dependant_sle = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no, + excluded_sle=sle.name) + + if not dependant_sle: + return + elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse: + return + elif dependant_sle.item_code != self.item_code \ + and (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items: + self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle + return + + self.initialize_previous_data(dependant_sle) + + args = self.data[dependant_sle.warehouse].previous_sle \ + or frappe._dict({"item_code": self.item_code, "warehouse": dependant_sle.warehouse}) + future_sle_for_dependant = list(self.get_sle_after_datetime(args)) + + entries_to_fix.extend(future_sle_for_dependant) + entries_to_fix = sorted(entries_to_fix, key=lambda k: k['timestamp']) def process_sle(self, sle): + # previous sle data for this warehouse + self.wh_data = self.data[sle.warehouse] + if (sle.serial_no and not self.via_landed_cost_voucher) or not cint(self.allow_negative_stock): # validate negative stock for serialized items, fifo valuation # or when negative stock is not allowed for moving average if not self.validate_negative_stock(sle): - self.qty_after_transaction += flt(sle.actual_qty) + self.wh_data.qty_after_transaction += flt(sle.actual_qty) return + # Get dynamic incoming/outgoing rate + self.get_dynamic_incoming_outgoing_rate(sle) + if sle.serial_no: self.get_serialized_values(sle) - self.qty_after_transaction += flt(sle.actual_qty) + self.wh_data.qty_after_transaction += flt(sle.actual_qty) if sle.voucher_type == "Stock Reconciliation": - self.qty_after_transaction = sle.qty_after_transaction + self.wh_data.qty_after_transaction = sle.qty_after_transaction - self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate) + self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate) else: if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no: # assert - self.valuation_rate = sle.valuation_rate - self.qty_after_transaction = sle.qty_after_transaction - self.stock_queue = [[self.qty_after_transaction, self.valuation_rate]] - self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate) + self.wh_data.valuation_rate = sle.valuation_rate + self.wh_data.qty_after_transaction = sle.qty_after_transaction + self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]] + self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate) else: if self.valuation_method == "Moving Average": self.get_moving_average_values(sle) - self.qty_after_transaction += flt(sle.actual_qty) - self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate) + self.wh_data.qty_after_transaction += flt(sle.actual_qty) + self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate) else: self.get_fifo_values(sle) - self.qty_after_transaction += flt(sle.actual_qty) - self.stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue)) + self.wh_data.qty_after_transaction += flt(sle.actual_qty) + self.wh_data.stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue)) # rounding as per precision - self.stock_value = flt(self.stock_value, self.precision) - - stock_value_difference = self.stock_value - self.prev_stock_value - - self.prev_stock_value = self.stock_value + self.wh_data.stock_value = flt(self.wh_data.stock_value, self.precision) + stock_value_difference = self.wh_data.stock_value - self.wh_data.prev_stock_value + self.wh_data.prev_stock_value = self.wh_data.stock_value # update current sle - sle.qty_after_transaction = self.qty_after_transaction - sle.valuation_rate = self.valuation_rate - sle.stock_value = self.stock_value - sle.stock_queue = json.dumps(self.stock_queue) + sle.qty_after_transaction = self.wh_data.qty_after_transaction + sle.valuation_rate = self.wh_data.valuation_rate + sle.stock_value = self.wh_data.stock_value + sle.stock_queue = json.dumps(self.wh_data.stock_queue) sle.stock_value_difference = stock_value_difference sle.doctype="Stock Ledger Entry" frappe.get_doc(sle).db_update() + self.update_outgoing_rate_on_transaction(sle) + def validate_negative_stock(self, sle): """ validate negative stock for entries current datetime onwards will not consider cancelled entries """ - diff = self.qty_after_transaction + flt(sle.actual_qty) + diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty) if diff < 0 and abs(diff) > 0.0001: # negative stock! exc = sle.copy().update({"diff": diff}) - self.exceptions.append(exc) + self.exceptions.setdefault(sle.warehouse, []).append(exc) return False else: return True + def get_dynamic_incoming_outgoing_rate(self, sle): + # Get updated incoming/outgoing rate from transaction + if sle.recalculate_rate: + rate = self.get_incoming_outgoing_rate_from_transaction(sle) + + if flt(sle.actual_qty) >= 0: + sle.incoming_rate = rate + else: + sle.outgoing_rate = rate + + def get_incoming_outgoing_rate_from_transaction(self, sle): + rate = 0 + # Material Transfer, Repack, Manufacturing + if sle.voucher_type == "Stock Entry": + rate = frappe.db.get_value("Stock Entry Detail", sle.voucher_detail_no, "valuation_rate") + # Sales and Purchase Return + elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"): + if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"): + from erpnext.controllers.sales_and_purchase_return import get_rate_for_return # don't move this import to top + rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no) + else: + if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): + rate_field = "valuation_rate" + else: + rate_field = "incoming_rate" + + # check in item table + item_code, incoming_rate = frappe.db.get_value(sle.voucher_type + " Item", + sle.voucher_detail_no, ["item_code", rate_field]) + + if item_code == sle.item_code: + rate = incoming_rate + else: + if sle.voucher_type in ("Delivery Note", "Sales Invoice"): + ref_doctype = "Packed Item" + else: + ref_doctype = "Purchase Receipt Item Supplied" + + rate = frappe.db.get_value(ref_doctype, {"parent_detail_docname": sle.voucher_detail_no, + "item_code": sle.item_code}, rate_field) + + return rate + + def update_outgoing_rate_on_transaction(self, sle): + """ + Update outgoing rate in Stock Entry, Delivery Note, Sales Invoice and Sales Return + In case of Stock Entry, also calculate FG Item rate and total incoming/outgoing amount + """ + if sle.actual_qty and sle.voucher_detail_no: + outgoing_rate = abs(flt(sle.stock_value_difference)) / abs(sle.actual_qty) + + if flt(sle.actual_qty) < 0 and sle.voucher_type == "Stock Entry": + self.update_rate_on_stock_entry(sle, outgoing_rate) + elif sle.voucher_type in ("Delivery Note", "Sales Invoice"): + self.update_rate_on_delivery_and_sales_return(sle, outgoing_rate) + elif flt(sle.actual_qty) < 0 and sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): + self.update_rate_on_purchase_receipt(sle, outgoing_rate) + + def update_rate_on_stock_entry(self, sle, outgoing_rate): + frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) + + # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount + stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) + stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) + stock_entry.db_update() + for d in stock_entry.items: + d.db_update() + + def update_rate_on_delivery_and_sales_return(self, sle, outgoing_rate): + # Update item's incoming rate on transaction + item_code = frappe.db.get_value(sle.voucher_type + " Item", sle.voucher_detail_no, "item_code") + if item_code == sle.item_code: + frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "incoming_rate", outgoing_rate) + else: + # packed item + frappe.db.set_value("Packed Item", + {"parent_detail_docname": sle.voucher_detail_no, "item_code": sle.item_code}, + "incoming_rate", outgoing_rate) + + def update_rate_on_purchase_receipt(self, sle, outgoing_rate): + if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): + frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "base_net_rate", outgoing_rate) + else: + frappe.db.set_value("Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate) + + # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice + if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): + doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no) + doc.update_valuation_rate(reset_outgoing_rate=False) + for d in (doc.items + doc.supplied_items): + d.db_update() + def get_serialized_values(self, sle): incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) @@ -228,7 +406,7 @@ class update_entries_after(object): if incoming_rate < 0: # wrong incoming rate - incoming_rate = self.valuation_rate + incoming_rate = self.wh_data.valuation_rate stock_value_change = 0 if incoming_rate: @@ -236,22 +414,25 @@ class update_entries_after(object): elif actual_qty < 0: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry - outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) - stock_value_change = -1 * outgoing_value + if not sle.is_cancelled: + outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) + stock_value_change = -1 * outgoing_value + else: + stock_value_change = actual_qty * sle.outgoing_rate - new_stock_qty = self.qty_after_transaction + actual_qty + new_stock_qty = self.wh_data.qty_after_transaction + actual_qty if new_stock_qty > 0: - new_stock_value = (self.qty_after_transaction * self.valuation_rate) + stock_value_change + new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + stock_value_change if new_stock_value >= 0: # calculate new valuation rate only if stock value is positive # else it remains the same as that of previous entry - self.valuation_rate = new_stock_value / new_stock_qty + self.wh_data.valuation_rate = new_stock_value / new_stock_qty - if not self.valuation_rate and sle.voucher_detail_no: + if not self.wh_data.valuation_rate and sle.voucher_detail_no: allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no) if not allow_zero_rate: - self.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, + self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, currency=erpnext.get_company_currency(sle.company)) @@ -287,39 +468,39 @@ class update_entries_after(object): def get_moving_average_values(self, sle): actual_qty = flt(sle.actual_qty) - new_stock_qty = flt(self.qty_after_transaction) + actual_qty + new_stock_qty = flt(self.wh_data.qty_after_transaction) + actual_qty if new_stock_qty >= 0: if actual_qty > 0: - if flt(self.qty_after_transaction) <= 0: - self.valuation_rate = sle.incoming_rate + if flt(self.wh_data.qty_after_transaction) <= 0: + self.wh_data.valuation_rate = sle.incoming_rate else: - new_stock_value = (self.qty_after_transaction * self.valuation_rate) + \ + new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + \ (actual_qty * sle.incoming_rate) - self.valuation_rate = new_stock_value / new_stock_qty + self.wh_data.valuation_rate = new_stock_value / new_stock_qty elif sle.outgoing_rate: if new_stock_qty: - new_stock_value = (self.qty_after_transaction * self.valuation_rate) + \ + new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + \ (actual_qty * sle.outgoing_rate) - self.valuation_rate = new_stock_value / new_stock_qty + self.wh_data.valuation_rate = new_stock_value / new_stock_qty else: - self.valuation_rate = sle.outgoing_rate + self.wh_data.valuation_rate = sle.outgoing_rate else: - if flt(self.qty_after_transaction) >= 0 and sle.outgoing_rate: - self.valuation_rate = sle.outgoing_rate + if flt(self.wh_data.qty_after_transaction) >= 0 and sle.outgoing_rate: + self.wh_data.valuation_rate = sle.outgoing_rate - if not self.valuation_rate and actual_qty > 0: - self.valuation_rate = sle.incoming_rate + if not self.wh_data.valuation_rate and actual_qty > 0: + self.wh_data.valuation_rate = sle.incoming_rate # Get valuation rate from previous SLE or Item master, if item does not have the # allow zero valuration rate flag set - if not self.valuation_rate and sle.voucher_detail_no: + if not self.wh_data.valuation_rate and sle.voucher_detail_no: allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no) if not allow_zero_valuation_rate: - self.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, + self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, currency=erpnext.get_company_currency(sle.company)) @@ -329,22 +510,22 @@ class update_entries_after(object): outgoing_rate = flt(sle.outgoing_rate) if actual_qty > 0: - if not self.stock_queue: - self.stock_queue.append([0, 0]) + if not self.wh_data.stock_queue: + self.wh_data.stock_queue.append([0, 0]) # last row has the same rate, just updated the qty - if self.stock_queue[-1][1]==incoming_rate: - self.stock_queue[-1][0] += actual_qty + if self.wh_data.stock_queue[-1][1]==incoming_rate: + self.wh_data.stock_queue[-1][0] += actual_qty else: - if self.stock_queue[-1][0] > 0: - self.stock_queue.append([actual_qty, incoming_rate]) + if self.wh_data.stock_queue[-1][0] > 0: + self.wh_data.stock_queue.append([actual_qty, incoming_rate]) else: - qty = self.stock_queue[-1][0] + actual_qty - self.stock_queue[-1] = [qty, incoming_rate] + qty = self.wh_data.stock_queue[-1][0] + actual_qty + self.wh_data.stock_queue[-1] = [qty, incoming_rate] else: qty_to_pop = abs(actual_qty) while qty_to_pop: - if not self.stock_queue: + if not self.wh_data.stock_queue: # Get valuation rate from last sle if exists or from valuation rate field in item master allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no) if not allow_zero_valuation_rate: @@ -354,35 +535,35 @@ class update_entries_after(object): else: _rate = 0 - self.stock_queue.append([0, _rate]) + self.wh_data.stock_queue.append([0, _rate]) index = None if outgoing_rate > 0: # Find the entry where rate matched with outgoing rate - for i, v in enumerate(self.stock_queue): + for i, v in enumerate(self.wh_data.stock_queue): if v[1] == outgoing_rate: index = i break # If no entry found with outgoing rate, collapse stack if index == None: - new_stock_value = sum((d[0]*d[1] for d in self.stock_queue)) - qty_to_pop*outgoing_rate - new_stock_qty = sum((d[0] for d in self.stock_queue)) - qty_to_pop - self.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]] + new_stock_value = sum((d[0]*d[1] for d in self.wh_data.stock_queue)) - qty_to_pop*outgoing_rate + new_stock_qty = sum((d[0] for d in self.wh_data.stock_queue)) - qty_to_pop + self.wh_data.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]] break else: index = 0 # select first batch or the batch with same rate - batch = self.stock_queue[index] + batch = self.wh_data.stock_queue[index] if qty_to_pop >= batch[0]: # consume current batch qty_to_pop = qty_to_pop - batch[0] - self.stock_queue.pop(index) - if not self.stock_queue and qty_to_pop: + self.wh_data.stock_queue.pop(index) + if not self.wh_data.stock_queue and qty_to_pop: # stock finished, qty still remains to be withdrawn # negative stock, keep in as a negative batch - self.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]]) + self.wh_data.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]]) break else: @@ -391,14 +572,14 @@ class update_entries_after(object): batch[0] = batch[0] - qty_to_pop qty_to_pop = 0 - stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue)) - stock_qty = sum((flt(batch[0]) for batch in self.stock_queue)) + stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue)) + stock_qty = sum((flt(batch[0]) for batch in self.wh_data.stock_queue)) if stock_qty: - self.valuation_rate = stock_value / flt(stock_qty) + self.wh_data.valuation_rate = stock_value / flt(stock_qty) - if not self.stock_queue: - self.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.valuation_rate]) + if not self.wh_data.stock_queue: + self.wh_data.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.wh_data.valuation_rate]) def check_if_allow_zero_valuation_rate(self, voucher_type, voucher_detail_no): ref_item_dt = "" @@ -413,39 +594,56 @@ class update_entries_after(object): else: return 0 - def get_sle_before_datetime(self): + def get_sle_before_datetime(self, args): """get previous stock ledger entry before current time-bucket""" - if self.args.get('sle_id'): - self.args['name'] = self.args.get('sle_id') + sle = get_stock_ledger_entries(args, "<", "desc", "limit 1", for_update=False) + sle = sle[0] if sle else frappe._dict() + return sle - return get_stock_ledger_entries(self.args, "<=", "desc", "limit 1", for_update=False) - - def get_sle_after_datetime(self): + def get_sle_after_datetime(self, args): """get Stock Ledger Entries after a particular datetime, for reposting""" - return get_stock_ledger_entries(self.previous_sle or frappe._dict({ - "item_code": self.args.get("item_code"), "warehouse": self.args.get("warehouse") }), - ">", "asc", for_update=True, check_serial_no=False) + return get_stock_ledger_entries(args, ">", "asc", for_update=True, check_serial_no=False) def raise_exceptions(self): - deficiency = min(e["diff"] for e in self.exceptions) + msg_list = [] + for warehouse, exceptions in iteritems(self.exceptions): + deficiency = min(e["diff"] for e in exceptions) - if ((self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"]) in - frappe.local.flags.currently_saving): + if ((exceptions[0]["voucher_type"], exceptions[0]["voucher_no"]) in + frappe.local.flags.currently_saving): - msg = _("{0} units of {1} needed in {2} to complete this transaction.").format( - abs(deficiency), frappe.get_desk_link('Item', self.item_code), - frappe.get_desk_link('Warehouse', self.warehouse)) - else: - msg = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format( - abs(deficiency), frappe.get_desk_link('Item', self.item_code), - frappe.get_desk_link('Warehouse', self.warehouse), - self.exceptions[0]["posting_date"], self.exceptions[0]["posting_time"], - frappe.get_desk_link(self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"])) + msg = _("{0} units of {1} needed in {2} to complete this transaction.").format( + abs(deficiency), frappe.get_desk_link('Item', self.item_code), + frappe.get_desk_link('Warehouse', warehouse)) + else: + msg = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format( + abs(deficiency), frappe.get_desk_link('Item', self.item_code), + frappe.get_desk_link('Warehouse', warehouse), + exceptions[0]["posting_date"], exceptions[0]["posting_time"], + frappe.get_desk_link(exceptions[0]["voucher_type"], exceptions[0]["voucher_no"])) - if self.verbose: - frappe.throw(msg, NegativeStockError, title='Insufficient Stock') - else: - raise NegativeStockError(msg) + if msg: + msg_list.append(msg) + + if msg_list: + message = "\n\n".join(msg_list) + if self.verbose: + frappe.throw(message, NegativeStockError, title='Insufficient Stock') + else: + raise NegativeStockError(message) + + def update_bin(self): + # update bin for each warehouse + for warehouse, data in iteritems(self.data): + bin_doc = get_bin(self.item_code, warehouse) + + bin_doc.update({ + "valuation_rate": data.valuation_rate, + "actual_qty": data.qty_after_transaction, + "stock_value": data.stock_value + }) + bin_doc.flags.via_stock_ledger_entry = True + bin_doc.save(ignore_permissions=True) def get_previous_sle(args, for_update=False): """ @@ -489,6 +687,7 @@ def get_stock_ledger_entries(previous_sle, operator=None, select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry` where item_code = %%(item_code)s + and is_cancelled = 0 %(conditions)s order by timestamp(posting_date, posting_time) %(order)s, creation %(order)s %(limit)s %(for_update)s""" % { @@ -498,10 +697,11 @@ def get_stock_ledger_entries(previous_sle, operator=None, "order": order }, previous_sle, as_dict=1, debug=debug) -def get_sle_by_id(sle_id): - return frappe.db.get_all('Stock Ledger Entry', - fields=['*', 'timestamp(posting_date, posting_time) as timestamp'], - filters={'name': sle_id})[0] +def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): + return frappe.db.get_value('Stock Ledger Entry', + {'voucher_detail_no': voucher_detail_no, 'name': ['!=', excluded_sle]}, + ['item_code', 'warehouse', 'posting_date', 'posting_time', 'timestamp(posting_date, posting_time) as timestamp'], + as_dict=1) def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True): @@ -529,7 +729,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type)) if last_valuation_rate: - return flt(last_valuation_rate[0][0]) # as there is previous records, it might come with zero rate + return flt(last_valuation_rate[0][0]) # If negative stock allowed, and item delivered without any incoming entry, # system does not found any SLE, then take valuation rate from Item @@ -561,3 +761,54 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, frappe.throw(msg=msg, title=_("Valuation Rate Missing")) return valuation_rate + +def update_qty_in_future_sle(args, allow_negative_stock=None): + frappe.db.sql(""" + update `tabStock Ledger Entry` + set qty_after_transaction = qty_after_transaction + {qty} + where + item_code = %(item_code)s + and warehouse = %(warehouse)s + and voucher_no != %(voucher_no)s + and is_cancelled = 0 + and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) + or ( + timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) + and creation > %(creation)s + ) + ) + """.format(qty=args.actual_qty), args) + + validate_negative_qty_in_future_sle(args, allow_negative_stock) + +def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): + allow_negative_stock = allow_negative_stock \ + or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + + if args.actual_qty < 0 and not allow_negative_stock: + sle = get_future_sle_with_negative_qty(args) + if sle: + message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format( + abs(sle[0]["qty_after_transaction"]), + frappe.get_desk_link('Item', args.item_code), + frappe.get_desk_link('Warehouse', args.warehouse), + sle[0]["posting_date"], sle[0]["posting_time"], + frappe.get_desk_link(sle[0]["voucher_type"], sle[0]["voucher_no"])) + + frappe.throw(message, NegativeStockError, title='Insufficient Stock') + +def get_future_sle_with_negative_qty(args): + return frappe.db.sql(""" + select + qty_after_transaction, posting_date, posting_time, + voucher_type, voucher_no + from `tabStock Ledger Entry` + where + item_code = %(item_code)s + and warehouse = %(warehouse)s + and voucher_no != %(voucher_no)s + and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) + and is_cancelled = 0 + and qty_after_transaction < 0 + limit 1 + """, args, as_dict=1) \ No newline at end of file diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index f9ac25443e..4ea7e4fcd6 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -63,6 +63,7 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None): SELECT item_code, stock_value, name, warehouse FROM `tabStock Ledger Entry` sle WHERE posting_date <= %s {0} + and is_cancelled = 0 ORDER BY timestamp(posting_date, posting_time) DESC, creation DESC """.format(condition), values, as_dict=1) @@ -211,7 +212,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'), raise_error_if_no_rate=raise_error_if_no_rate) - return in_rate + return flt(in_rate) def get_avg_purchase_rate(serial_nos): """get average value of serial numbers""" @@ -375,4 +376,10 @@ def get_incoming_outgoing_rate_for_cancel(item_code, voucher_type, voucher_no, v outgoing_rate = outgoing_rate[0][0] if outgoing_rate else 0.0 - return outgoing_rate \ No newline at end of file + return outgoing_rate + +def is_reposting_item_valuation_in_progress(): + reposting_in_progress = frappe.db.exists("Repost Item Valuation", + {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) + if reposting_in_progress: + frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) \ No newline at end of file From 0a1390a7ca2c67f53c33119bb429e2f8efaaf943 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Tue, 22 Dec 2020 11:16:36 +0530 Subject: [PATCH 0218/1264] fix: leave policy dashboard fix and roles (#24170) --- .../doctype/leave_policy/leave_policy_dashboard.py | 14 +------------- .../leave_policy_assignment.json | 5 ++++- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index ff5dc2ff3e..e0ec4be2dc 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -4,22 +4,10 @@ from frappe import _ def get_data(): return { 'fieldname': 'leave_policy', - 'non_standard_fieldnames': { - 'Employee Grade': 'default_leave_policy' - }, 'transactions': [ - { - 'label': _('Employees'), - 'items': ['Employee', 'Employee Grade'] - }, { 'label': _('Leaves'), 'items': ['Leave Allocation'] }, ] - } - - - - - \ No newline at end of file + } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json index ecebb3b7d6..bbb4222715 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json @@ -111,7 +111,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-10-15 15:18:15.227848", + "modified": "2020-12-17 16:27:20.311060", "modified_by": "Administrator", "module": "HR", "name": "Leave Policy Assignment", @@ -127,6 +127,7 @@ "report": 1, "role": "HR Manager", "share": 1, + "submit": 1, "write": 1 }, { @@ -139,6 +140,7 @@ "report": 1, "role": "HR User", "share": 1, + "submit": 1, "write": 1 }, { @@ -151,6 +153,7 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, "write": 1 } ], From adf9b49e1462862a7743026def8bb7e1a3c48311 Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 23 Dec 2020 11:39:38 +0530 Subject: [PATCH 0219/1264] fix: sal slip popup error --- .../payroll/doctype/salary_slip/salary_slip.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index f7e22c6387..abe873d839 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -214,14 +214,16 @@ frappe.ui.form.on('Salary Slip Timesheet', { }); var calculate_totals = function(frm) { - if (frm.doc.earnings || frm.doc.deductions) { - frappe.call({ - method: "set_totals", - doc: frm.doc, - callback: function() { - frm.refresh_fields(); - } - }); + if (frm.doc.docstatus === 0) { + if (frm.doc.earnings || frm.doc.deductions) { + frappe.call({ + method: "set_totals", + doc: frm.doc, + callback: function() { + frm.refresh_fields(); + } + }); + } } }; From 9fe7f235d0c2d40b4c0bd6cf40102b6d367eb73a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 24 Dec 2020 11:03:48 +0530 Subject: [PATCH 0220/1264] fix: Do not cancel reference document on Quality Inspection cancellation (#24198) --- .../stock/doctype/quality_inspection/quality_inspection.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index 376848afaa..03e3de115b 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -4,6 +4,11 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type; frappe.ui.form.on("Quality Inspection", { + refresh: function(frm) { + // Ignore cancellation of reference doctype on cancel all. + frm.ignore_doctypes_on_cancel_all = [frm.doc.reference_type]; + }, + item_code: function(frm) { if (frm.doc.item_code) { return frm.call({ From ba5b603a0bb73bf2a03e5efd0c61286e58114067 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 24 Dec 2020 22:44:31 +0530 Subject: [PATCH 0221/1264] fix: multiple pricing rule with margin type not working --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 6 +++++- erpnext/accounts/doctype/pricing_rule/utils.py | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 55a5b0e513..05652642eb 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -345,9 +345,13 @@ def apply_price_discount_rule(pricing_rule, item_details, args): if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency) or (pricing_rule.margin_type == 'Percentage')): item_details.margin_type = pricing_rule.margin_type - item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount item_details.has_margin = True + if pricing_rule.apply_multiple_pricing_rules and item_details.margin_rate_or_amount is not None: + item_details.margin_rate_or_amount += pricing_rule.margin_rate_or_amount + else: + item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount + if pricing_rule.rate_or_discount == 'Rate': pricing_rule_rate = 0.0 if pricing_rule.currency == args.currency: diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 2c7cd14451..fb1fbe484e 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -164,7 +164,15 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): frappe.throw(_("Invalid {0}").format(args.get(field))) parent_groups = frappe.db.sql_list("""select name from `tab%s` - where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) + where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) + + if parenttype in ["Customer Group", "Item Group", "Territory"]: + parent_field = "parent_{0}".format(frappe.scrub(parenttype)) + root_name = frappe.db.get_list(parenttype, + {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1) + + if root_name and root_name[0][0]: + parent_groups.append(root_name[0][0]) if parent_groups: if allow_blank: parent_groups.append('') From adc369f7269096dd907d9ca8edbe5cc1fb1aa257 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 25 Dec 2020 13:23:12 +0530 Subject: [PATCH 0222/1264] fix: partial serial no return issue --- .../controllers/sales_and_purchase_return.py | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 8f65c31f3d..7b03705f7f 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -205,20 +205,22 @@ def get_already_returned_items(doc): def get_returned_qty_map_for_row(row_name, doctype): child_doctype = doctype + " Item" - reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail" + reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype) fields = [ "sum(abs(`tab{0}`.qty)) as qty".format(child_doctype), "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype) ] - if doctype == "Purchase Receipt": + if doctype in ("Purchase Receipt", "Purchase Invoice"): fields += [ "sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype), - "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype), - "sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype) + "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype) ] + if doctype == "Purchase Receipt": + fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)] + data = frappe.db.get_list(doctype, fields = fields, filters = [ @@ -231,6 +233,7 @@ def get_returned_qty_map_for_row(row_name, doctype): def make_return_doc(doctype, source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos company = frappe.db.get_value("Delivery Note", source_name, "company") default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return") @@ -290,6 +293,12 @@ def make_return_doc(doctype, source_name, target_doc=None): def update_item(source_doc, target_doc, source_parent): target_doc.qty = -1 * source_doc.qty + if source_doc.serial_no: + returned_serial_nos = get_returned_serial_nos(source_doc, source_parent) + serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos)) + if serial_nos: + target_doc.serial_no = '\n'.join(serial_nos) + if doctype == "Purchase Receipt": returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0)) @@ -305,10 +314,12 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.purchase_receipt_item = source_doc.name elif doctype == "Purchase Invoice": - target_doc.received_qty = -1 * source_doc.received_qty - target_doc.rejected_qty = -1 * source_doc.rejected_qty - target_doc.qty = -1* source_doc.qty - target_doc.stock_qty = -1 * source_doc.stock_qty + returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) + target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0)) + target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0)) + target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) + + target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0)) target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_receipt = source_doc.purchase_receipt target_doc.rejected_warehouse = source_doc.rejected_warehouse @@ -330,6 +341,10 @@ def make_return_doc(doctype, source_name, target_doc=None): if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return elif doctype == "Sales Invoice" or doctype == "POS Invoice": + returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) + target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) + target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0)) + target_doc.sales_order = source_doc.sales_order target_doc.delivery_note = source_doc.delivery_note target_doc.so_detail = source_doc.so_detail @@ -406,4 +421,22 @@ def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, ite if reference_voucher_detail_no: filters["voucher_detail_no"] = reference_voucher_detail_no - return filters \ No newline at end of file + return filters + +def get_returned_serial_nos(child_doc, parent_doc): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + return_ref_field = frappe.scrub(child_doc.doctype) + if child_doc.doctype == "Delivery Note Item": + return_ref_field = "dn_detail" + + serial_nos = [] + + fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)] + + filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1], + [child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]] + + for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters): + serial_nos.extend(get_serial_nos(row.serial_no)) + + return serial_nos \ No newline at end of file From 327f03566ac8dec8f311aea0a243549cb163c66a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 25 Dec 2020 18:11:54 +0530 Subject: [PATCH 0223/1264] Reposting fixes pre release (#24203) * fix: finished item validation and rate * fix: Check if stock and account balance in sync after reposting * fix: validate stock accounts in journal entry * fix: validate expense against budget --- .../doctype/journal_entry/journal_entry.py | 21 +++- .../journal_entry/test_journal_entry.py | 18 ++- erpnext/accounts/general_ledger.py | 62 ---------- erpnext/accounts/utils.py | 115 ++++++++++++++---- erpnext/controllers/buying_controller.py | 2 +- erpnext/controllers/stock_controller.py | 10 +- .../purchase_receipt/purchase_receipt.py | 2 +- .../repost_item_valuation.py | 35 +++++- .../stock/doctype/stock_entry/stock_entry.py | 9 +- .../doctype/stock_entry/test_stock_entry.py | 78 ++++++------ .../stock_entry_detail.json | 2 +- .../stock_and_account_value_comparison.py | 3 +- 12 files changed, 205 insertions(+), 152 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index cd712738aa..cb90f8036e 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -6,14 +6,18 @@ import frappe, erpnext, json from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form from frappe import msgprint, _, scrub from erpnext.controllers.accounts_controller import AccountsController -from erpnext.accounts.utils import get_balance_on, get_account_currency +from erpnext.accounts.utils import get_balance_on, get_stock_accounts, get_stock_and_account_balance, \ + get_account_currency, check_if_stock_and_account_balance_synced from erpnext.accounts.party import get_party_account from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount -from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting +from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \ + import get_party_account_based_on_invoice_discounting from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from six import string_types, iteritems +class StockAccountInvalidTransaction(frappe.ValidationError): pass + class JournalEntry(AccountsController): def __init__(self, *args, **kwargs): super(JournalEntry, self).__init__(*args, **kwargs) @@ -46,6 +50,7 @@ class JournalEntry(AccountsController): self.validate_empty_accounts_table() self.set_account_and_party_balance() self.validate_inter_company_accounts() + self.validate_stock_accounts() if not self.title: self.title = self.get_title() @@ -57,6 +62,8 @@ class JournalEntry(AccountsController): self.update_expense_claim() self.update_inter_company_jv() self.update_invoice_discounting() + check_if_stock_and_account_balance_synced(self.posting_date, + self.company, self.doctype, self.name) def on_cancel(self): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries @@ -95,6 +102,16 @@ class JournalEntry(AccountsController): if account_currency == previous_account_currency: if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit: frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry")) + + def validate_stock_accounts(self): + stock_accounts = get_stock_accounts(self.company, self.doctype, self.name) + for account in stock_accounts: + account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, + self.posting_date, self.company) + + if account_bal == stock_bal: + frappe.throw(_("Account: {0} can only be updated via Stock Transactions") + .format(account), StockAccountInvalidTransaction) def update_inter_company_jv(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 1d2eacdb80..b56f8e5fe2 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -6,7 +6,7 @@ import unittest, frappe from frappe.utils import flt, nowdate from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.exceptions import InvalidAccountCurrency -from erpnext.accounts.general_ledger import StockAccountInvalidTransaction +from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction class TestJournalEntry(unittest.TestCase): def test_journal_entry_with_against_jv(self): @@ -84,25 +84,31 @@ class TestJournalEntry(unittest.TestCase): company = "_Test Company with perpetual inventory" stock_account = get_inventory_account(company) + from erpnext.accounts.utils import get_stock_and_account_balance + account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company) + diff = flt(account_bal) - flt(stock_bal) + + if not diff: + diff = 100 + jv = frappe.new_doc("Journal Entry") jv.company = company jv.posting_date = nowdate() jv.append("accounts", { "account": stock_account, "cost_center": "Main - TCP1", - "debit_in_account_currency": 100 + "debit_in_account_currency": 0 if diff > 0 else abs(diff), + "credit_in_account_currency": diff if diff > 0 else 0 }) jv.append("accounts", { "account": "Stock Adjustment - TCP1", - "credit_in_account_currency": 100, "cost_center": "Main - TCP1", + "debit_in_account_currency": diff if diff > 0 else 0, + "credit_in_account_currency": 0 if diff > 0 else abs(diff) }) jv.insert() - from erpnext.accounts.utils import get_stock_and_account_balance - account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company) - if account_bal == stock_bal: self.assertRaises(StockAccountInvalidTransaction, jv.submit) frappe.db.rollback() diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index c7f0c8781c..287c79f13f 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -5,15 +5,11 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now from frappe import _ -from erpnext.accounts.utils import get_stock_and_account_balance from frappe.model.meta import get_field_precision from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions - class ClosedAccountingPeriod(frappe.ValidationError): pass -class StockAccountInvalidTransaction(frappe.ValidationError): pass -class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): if gl_map: @@ -131,10 +127,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): for entry in gl_map: make_entry(entry, adv_adj, update_outstanding, from_repost) - if not from_repost: - validate_account_for_perpetual_inventory(gl_map) - - def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle = frappe.new_doc("GL Entry") gle.update(args) @@ -144,63 +136,9 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) gle.submit() - # check against budget if not from_repost: validate_expense_against_budget(args) -def validate_account_for_perpetual_inventory(gl_map): - if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): - account_list = [gl_entries.account for gl_entries in gl_map] - - aii_accounts = [d.name for d in frappe.get_all("Account", - filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})] - - for account in account_list: - if account not in aii_accounts: - continue - - # Always use current date to get stock and account balance as there can future entries for - # other items - account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, - gl_map[0].posting_date, gl_map[0].company) - - if gl_map[0].voucher_type=="Journal Entry": - # In case of Journal Entry, there are no corresponding SL entries, - # hence deducting currency amount - account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit) - if account_bal == stock_bal: - frappe.throw(_("Account: {0} can only be updated via Stock Transactions") - .format(account), StockAccountInvalidTransaction) - - elif abs(account_bal - stock_bal) > 0.1: - precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), - currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) - - diff = flt(stock_bal - account_bal, precision) - error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses on {3}.").format( - stock_bal, account_bal, frappe.bold(account), gl_map[0].posting_date) - error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) - stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") - - db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') - db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') - - journal_entry_args = { - 'accounts':[ - {'account': account, db_or_cr_warehouse_account : abs(diff)}, - {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff)} - ] - } - - frappe.msgprint(msg="""{0}

{1}

""".format(error_reason, error_resolution), - raise_exception=StockValueAndAccountBalanceOutOfSync, - title=_('Values Out Of Sync'), - primary_action={ - 'label': _('Make Journal Entry'), - 'client_action': 'erpnext.route_to_adjustment_jv', - 'args': journal_entry_args - }) - def validate_cwip_accounts(gl_map): cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 540ac84182..67c7fd2d22 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -12,11 +12,12 @@ from frappe.utils import formatdate, get_number_format_info from six import iteritems # imported to enable erpnext.accounts.utils.get_account_currency from erpnext.accounts.doctype.account.account import get_account_currency +from frappe.model.meta import get_field_precision from erpnext.stock.utils import get_stock_value_on from erpnext.stock import get_warehouse_account_map - +class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass class FiscalYearError(frappe.ValidationError): pass @frappe.whitelist() @@ -585,24 +586,6 @@ def fix_total_debit_credit(): (dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr), (d.diff, d.voucher_type, d.voucher_no)) -def get_stock_and_account_balance(account=None, posting_date=None, company=None): - if not posting_date: posting_date = nowdate() - - warehouse_account = get_warehouse_account_map(company) - - account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True) - - related_warehouses = [wh for wh, wh_details in warehouse_account.items() - if wh_details.account == account and not wh_details.is_group] - - total_stock_value = 0.0 - for warehouse in related_warehouses: - value = get_stock_value_on(warehouse, posting_date) - total_stock_value += value - - precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") - return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses - def get_currency_precision(): precision = cint(frappe.db.get_default("currency_precision")) if not precision: @@ -903,12 +886,6 @@ def get_coa(doctype, parent, is_root, chart=None): return accounts -def get_stock_accounts(company): - return frappe.get_all("Account", filters = { - "account_type": "Stock", - "company": company - }) - def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None, company=None): def _delete_gl_entries(voucher_type, voucher_no): @@ -983,4 +960,90 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle): if not account_existed: matched = False break - return matched \ No newline at end of file + return matched + +def check_if_stock_and_account_balance_synced(posting_date, company, voucher_type=None, voucher_no=None): + if not cint(erpnext.is_perpetual_inventory_enabled(company)): + return + + accounts = get_stock_accounts(company, voucher_type, voucher_no) + stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account") + + for account in accounts: + account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, + posting_date, company) + + if abs(account_bal - stock_bal) > 0.1: + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), + currency=frappe.get_cached_value('Company', company, "default_currency")) + + diff = flt(stock_bal - account_bal, precision) + + error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format( + stock_bal, account_bal, frappe.bold(account), posting_date) + error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\ + .format(frappe.bold(diff), frappe.bold(posting_date)) + + frappe.msgprint( + msg="""{0}

{1}

""".format(error_reason, error_resolution), + raise_exception=StockValueAndAccountBalanceOutOfSync, + title=_('Values Out Of Sync'), + primary_action={ + 'label': _('Make Journal Entry'), + 'client_action': 'erpnext.route_to_adjustment_jv', + 'args': get_journal_entry(account, stock_adjustment_account, diff) + }) + +def get_stock_accounts(company, voucher_type=None, voucher_no=None): + stock_accounts = [d.name for d in frappe.db.get_all("Account", { + "account_type": "Stock", + "company": company, + "is_group": 0 + })] + if voucher_type and voucher_no: + if voucher_type == "Journal Entry": + stock_accounts = [d.account for d in frappe.db.get_all("Journal Entry Account", { + "parent": voucher_no, + "account": ["in", stock_accounts] + }, "account")] + + else: + stock_accounts = [d.account for d in frappe.db.get_all("GL Entry", { + "voucher_type": voucher_type, + "voucher_no": voucher_no, + "account": ["in", stock_accounts] + }, "account")] + + return stock_accounts + +def get_stock_and_account_balance(account=None, posting_date=None, company=None): + if not posting_date: posting_date = nowdate() + + warehouse_account = get_warehouse_account_map(company) + + account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True) + + related_warehouses = [wh for wh, wh_details in warehouse_account.items() + if wh_details.account == account and not wh_details.is_group] + + total_stock_value = 0.0 + for warehouse in related_warehouses: + value = get_stock_value_on(warehouse, posting_date) + total_stock_value += value + + precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") + return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses + +def get_journal_entry(account, stock_adjustment_account, amount): + db_or_cr_warehouse_account =('credit_in_account_currency' if amount < 0 else 'debit_in_account_currency') + db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if amount < 0 else 'credit_in_account_currency') + + return { + 'accounts':[{ + 'account': account, + db_or_cr_warehouse_account: abs(amount) + }, { + 'account': stock_adjustment_account, + db_or_cr_stock_adjustment_account : abs(amount) + }] + } diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index dc61870df3..6edc020701 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -241,7 +241,7 @@ class BuyingController(StockController): if rate > 0: d.rate = rate - d.amount = flt(d.consumed_qty) * flt(d.rate) + d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount")) supplied_items_cost += flt(d.amount) return supplied_items_cost diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 51c063c2c0..439997616c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -6,7 +6,7 @@ import frappe, erpnext from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate from frappe import _ import frappe.defaults -from erpnext.accounts.utils import get_fiscal_year +from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock.stock_ledger import get_valuation_rate @@ -402,6 +402,14 @@ class StockController(AccountsController): if check_if_future_sle_exists(args): create_repost_item_valuation_entry(args) + elif not is_reposting_pending(): + check_if_stock_and_account_balance_synced(self.posting_date, + self.company, self.doctype, self.name) + +def is_reposting_pending(): + return frappe.db.exists("Repost Item Valuation", + {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) + def check_if_future_sle_exists(args): sl_entries = frappe.db.get_all("Stock Ledger Entry", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 226064bae7..159a6085ff 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -408,7 +408,7 @@ class PurchaseReceipt(BuyingController): if warehouse_with_no_account: frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + "\n".join(warehouse_with_no_account)) - + return process_gl_map(gl_entries) def get_asset_gl_entry(self, gl_entries): diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index a942f2edda..ba2c2c6f44 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,11 +5,11 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint +from frappe.utils import cint, get_link_to_form from erpnext.stock.stock_ledger import repost_future_sle -from erpnext.accounts.utils import update_gl_entries_after - - +from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced +from frappe.utils.user import get_users_with_role +from frappe import _ class RepostItemValuation(Document): def validate(self): self.set_status() @@ -51,12 +51,20 @@ def repost(doc): repost_sl_entries(doc) repost_gl_entries(doc) + check_if_stock_and_account_balance_synced(doc.posting_date, doc.company) + doc.set_status('Completed') except Exception: frappe.db.rollback() traceback = frappe.get_traceback() frappe.log_error(traceback) - frappe.db.set_value(doc.doctype, doc.name, 'error_log', traceback) + + message = frappe.message_log.pop() + if traceback: + message += "
" + "Traceback:
" + traceback + frappe.db.set_value(doc.doctype, doc.name, 'error_log', message) + + notify_error_to_stock_managers(doc) doc.set_status('Failed') raise finally: @@ -86,4 +94,19 @@ def repost_gl_entries(doc): warehouses = [doc.warehouse] update_gl_entries_after(doc.posting_date, doc.posting_time, - warehouses, items, company=doc.company) \ No newline at end of file + warehouses, items, company=doc.company) + +def notify_error_to_stock_managers(doc, traceback): + recipients = get_users_with_role("Stock Manager") + if not recipients: + get_users_with_role("System Manager") + + subject = _("Error while reposting item valuation") + message = (_("Hi,") + "
" + + _("An error has been appeared while reposting item valuation via {0}") + .format(get_link_to_form(doc.doctype, doc.name)) + "
" + + _("Please check the error message and take necessary actions to fix the error and then restart the reposting again.") + ) + frappe.sendmail(recipients=recipients, subject=subject, message=message) + + diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index afdb54ceaa..60758b4f8a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -442,6 +442,7 @@ class StockEntry(StockController): """ # Set rate for outgoing items outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate) + finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item]) # Set basic rate for incoming items for d in self.get('items'): @@ -451,7 +452,7 @@ class StockEntry(StockController): d.basic_rate = 0.0 elif d.is_finished_item: if self.purpose == "Manufacture": - d.basic_rate = self.get_basic_rate_for_manufactured_item(d.transfer_qty, outgoing_items_cost) + d.basic_rate = self.get_basic_rate_for_manufactured_item(finished_item_qty, outgoing_items_cost) elif self.purpose == "Repack": d.basic_rate = self.get_basic_rate_for_repacked_items(d.transfer_qty, outgoing_items_cost) @@ -666,7 +667,7 @@ class StockEntry(StockController): production_item, wo_qty = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"]) - number_of_finished_items = 0 + finished_items = [] for d in self.get('items'): if d.is_finished_item: if d.item_code != production_item: @@ -675,9 +676,9 @@ class StockEntry(StockController): elif flt(d.transfer_qty) > flt(self.fg_completed_qty): frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \ format(d.idx, d.transfer_qty, self.fg_completed_qty)) - number_of_finished_items += 1 + finished_items.append(d.item_code) - if number_of_finished_items > 1: + if len(set(finished_items)) > 1: frappe.throw(_("Multiple items cannot be marked as finished item")) if self.purpose == "Manufacture": diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 1a641855aa..123f0c8647 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -179,22 +179,20 @@ class TestStockEntry(unittest.TestCase): def test_material_transfer_gl_entry(self): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - create_stock_reconciliation(qty=100, rate=100) - mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1", - target="Finished Goods - TCP1", qty=45) + target="Finished Goods - TCP1", qty=45, company=company) self.check_stock_ledger_entries("Stock Entry", mtn.name, [["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]]) - stock_in_hand_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse) + source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse) - fixed_asset_account = get_inventory_account(mtn.company, mtn.get("items")[0].t_warehouse) + target_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].t_warehouse) - if stock_in_hand_account == fixed_asset_account: + if source_warehouse_account == target_warehouse_account: # no gl entry as both source and target warehouse has linked to same account. self.assertFalse(frappe.db.sql("""select * from `tabGL Entry` - where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name)) + where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name, as_dict=1)) else: stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry", @@ -202,8 +200,8 @@ class TestStockEntry(unittest.TestCase): self.check_gl_entries("Stock Entry", mtn.name, sorted([ - [stock_in_hand_account, 0.0, stock_value_diff], - [fixed_asset_account, stock_value_diff, 0.0], + [source_warehouse_account, 0.0, stock_value_diff], + [target_warehouse_account, stock_value_diff, 0.0], ]) ) @@ -754,37 +752,37 @@ class TestStockEntry(unittest.TestCase): def test_total_basic_amount_zero(self): se = frappe.get_doc({"doctype":"Stock Entry", - "purpose":"Material Receipt", - "stock_entry_type":"Material Receipt", - "posting_date": nowdate(), - "company":"_Test Company with perpetual inventory", - "items":[ - { - "item_code":"_Test Item", - "description":"_Test Item", - "qty": 1, - "basic_rate": 0, - "uom":"Nos", - "t_warehouse": "Stores - TCP1", - "allow_zero_valuation_rate": 1, - "cost_center": "Main - TCP1" - }, - { - "item_code":"_Test Item", - "description":"_Test Item", - "qty": 2, - "basic_rate": 0, - "uom":"Nos", - "t_warehouse": "Stores - TCP1", - "allow_zero_valuation_rate": 1, - "cost_center": "Main - TCP1" - }, - ], - "additional_costs":[ - {"expense_account":"Miscellaneous Expenses - TCP1", - "amount":100, - "description": "miscellanous"} - ] + "purpose":"Material Receipt", + "stock_entry_type":"Material Receipt", + "posting_date": nowdate(), + "company":"_Test Company with perpetual inventory", + "items":[ + { + "item_code":"_Test Item", + "description":"_Test Item", + "qty": 1, + "basic_rate": 0, + "uom":"Nos", + "t_warehouse": "Stores - TCP1", + "allow_zero_valuation_rate": 1, + "cost_center": "Main - TCP1" + }, + { + "item_code":"_Test Item", + "description":"_Test Item", + "qty": 2, + "basic_rate": 0, + "uom":"Nos", + "t_warehouse": "Stores - TCP1", + "allow_zero_valuation_rate": 1, + "cost_center": "Main - TCP1" + }, + ], + "additional_costs":[ + {"expense_account":"Miscellaneous Expenses - TCP1", + "amount":100, + "description": "miscellanous" + }] }) se.insert() se.submit() diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 6fe60298ee..b78ae6d79b 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -526,7 +526,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-23 17:55:03.384138", + "modified": "2020-12-23 17:55:03.384138", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 1af68dd7f2..14d543b174 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -57,8 +57,7 @@ def get_gl_data(report_filters, filters): if report_filters.account: stock_accounts = [report_filters.account] else: - stock_accounts = [k.name - for k in get_stock_accounts(report_filters.company)] + stock_accounts = get_stock_accounts(report_filters.company) filters.update({ "account": ("in", stock_accounts) From 0917c1ec7c21dcb31d88c1befd5199c1dc54b0a8 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 25 Dec 2020 18:13:12 +0530 Subject: [PATCH 0224/1264] fix: travis failing (#24211) --- erpnext/controllers/sales_and_purchase_return.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 7b03705f7f..79792262c0 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -204,6 +204,8 @@ def get_already_returned_items(doc): return items def get_returned_qty_map_for_row(row_name, doctype): + if doctype == "POS Invoice": return {} + child_doctype = doctype + " Item" reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype) From 49d7499990ab881fcca6a2cde029443bda819abb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 25 Dec 2020 18:15:35 +0530 Subject: [PATCH 0225/1264] fix: added shipment link in delivery note dashboard (#24210) * fix: added shipment link in delivery note dashboard * Update shipment.py --- .../stock/doctype/delivery_note/delivery_note.js | 1 + .../stock/doctype/delivery_note/delivery_note.py | 14 +++++++++++--- .../delivery_note/delivery_note_dashboard.py | 2 +- erpnext/stock/doctype/shipment/shipment.json | 5 +++-- erpnext/stock/doctype/shipment/shipment.py | 7 ++++++- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 03921c554e..5f2658c102 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -15,6 +15,7 @@ frappe.ui.form.on("Delivery Note", { 'Installation Note': 'Installation Note', 'Sales Invoice': 'Invoice', 'Stock Entry': 'Return', + 'Shipment': 'Shipment' }, frm.set_indicator_formatter('item_code', function(doc) { diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 1a6a555092..a30cadf0a0 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -598,6 +598,9 @@ def make_shipment(source_name, target_doc=None): pickup_contact_display += '
' + user.mobile_no target.pickup_contact = pickup_contact_display + # As we are using session user details in the pickup_contact then pickup_contact_person will be session user + target.pickup_contact_person = frappe.session.user + contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1) delivery_contact_display = '{}'.format(source.contact_display) if contact: @@ -609,6 +612,13 @@ def make_shipment(source_name, target_doc=None): delivery_contact_display += '
' + contact.mobile_no target.delivery_contact = delivery_contact_display + if source.shipping_address_name: + target.delivery_address_name = source.shipping_address_name + target.delivery_address = source.shipping_address + elif source.customer_address: + target.delivery_address_name = source.customer_address + target.delivery_address = source.address_display + doclist = get_mapped_doc("Delivery Note", source_name, { "Delivery Note": { "doctype": "Shipment", @@ -617,9 +627,7 @@ def make_shipment(source_name, target_doc=None): "company": "pickup_company", "company_address": "pickup_address_name", "company_address_display": "pickup_address", - "address_display": "delivery_address", "customer": "delivery_customer", - "shipping_address_name": "delivery_address_name", "contact_person": "delivery_contact_name", "contact_email": "delivery_contact_email" }, @@ -637,7 +645,7 @@ def make_shipment(source_name, target_doc=None): } } }, target_doc, postprocess) - + return doclist @frappe.whitelist() diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py index beeb9ebb05..47684d5c6e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py @@ -19,7 +19,7 @@ def get_data(): }, { 'label': _('Reference'), - 'items': ['Sales Order', 'Quality Inspection'] + 'items': ['Sales Order', 'Shipment', 'Quality Inspection'] }, { 'label': _('Returns'), diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index 37a9cc6c02..76c331c5c2 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -345,7 +345,8 @@ "label": "Status", "no_copy": 1, "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "fieldname": "tracking_url", @@ -430,7 +431,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-12-02 15:43:44.607039", + "modified": "2020-12-25 15:02:34.891976", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index de0c243b05..4697a7b323 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt +from frappe.utils import flt, get_time from frappe.model.document import Document from erpnext.accounts.party import get_party_shipping_address from frappe.contacts.doctype.contact.contact import get_default_contact @@ -13,6 +13,7 @@ from frappe.contacts.doctype.contact.contact import get_default_contact class Shipment(Document): def validate(self): self.validate_weight() + self.validate_pickup_time() self.set_value_of_goods() if self.docstatus == 0: self.status = 'Draft' @@ -32,6 +33,10 @@ class Shipment(Document): if flt(parcel.weight) <= 0: frappe.throw(_('Parcel weight cannot be 0')) + def validate_pickup_time(self): + if self.pickup_from and self.pickup_to and get_time(self.pickup_to) < get_time(self.pickup_from): + frappe.throw(_("Pickup To time should be greater than Pickup From time")) + def set_value_of_goods(self): value_of_goods = 0 for entry in self.get("shipment_delivery_note"): From f8c1cb5dc3017a34c7a056d893c79b91afcfadf0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 25 Dec 2020 20:52:59 +0530 Subject: [PATCH 0226/1264] fix: Removed permissions from UAE VAT Settings --- .../uae_vat_settings/uae_vat_settings.json | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json index ce2c1d4e14..1ff5680bfe 100644 --- a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json +++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json @@ -29,25 +29,12 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-30 20:08:18.764798", + "modified": "2020-12-25 20:20:22.342426", "modified_by": "Administrator", "module": "Regional", "name": "UAE VAT Settings", "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", From 17e96901db5755b1c349796ff9369b69e279a1eb Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 25 Dec 2020 10:26:43 +0530 Subject: [PATCH 0227/1264] feat: GST E Invoicing (#23455) * feat: init e-invoice settings * feat: read public key file * feat: rsa encryption with public key * feat: save token and sek from auth request * chore: handle error response * feat: AES decryption of SEK with appkey * feat: decrypt json data with SEK * feat: make e invoice from erpnext sales invoice * feat: generate IRN * feat: decode signed json and QR code * chore: validations * feat: cancel IRN * feat: complete e-invoice schema * chore: move e-invoice settings to regional * chore: split einvoice settings and operations * chore: rename schema to template & js cleanup * feat: make IRN field on regional setup * feat: Generate & Cancel IRN from Sales Invoice * chore: minor fixes * fix: item discount * chore: show irn cancelled check after cancellation * fix: hide cancel irn dialog on error * fix: public key is required on validate * fix: cannot find attached key file * fix: validation if e invoicing is disabled * fix: do not show generate irn for invalid supply type * fix: update irn_cancelled after cancelling irn * chore: show irn field for proper gst_category * feat: e-way bill details in e-invoice * fix: save e-way bill no on irn generation * chore: no copy on e invoice custom fields * feat: cancel e-way bill before cancelling IRN * feat: manual download / upload json * chore: group e-invoicing actions * fix: fn name * chore: save signed invoice and qrcode after uplaoding irn * fix: fetch token if not valid * chore: move einvoicing stuff to seperate folder * feat: QRCode Image and E-Invoice Print Format * fix: bug * fix: invalid syntax * chore: code cleanup * chore: clean up e invoice actions * fix: download & upload e-invoice * fix: print format * fix: validations * fix: add permissions on regional setup * feat: add patch * fix: validate document name * fix: return date * fix: credit note einvoice * fix: validations * fix: error logging * fix: e_invoice module not found * fix: add missing package * fix: rename e_invoice_utils.py * fix: einvoice field validation * fix: patch * fix: invoice totals calculation * fix: other charges calculation * chore: improve document name validation message * fix: qr code image string * feat: initialize GSP connector * chore: remove unwanted fields * fix: qr code generation * feat: fetch and cache GSTIN details * feat: generate & cancel IRN * feat: cancel eway bill * chore: remove unwanted fuctions * chore: clean up einvoice actions * fix: attach qrcode on irn generation * fix: generate & cancel IRN * fix: show/hide eway bill fields * fix: valiations * feat: generate eway bill from IRN * chore: remove unwanted imports * chore: error logging * feat: header & footer in GST E Invoice * chore: remove test pincode * fix: invalid syntax * feat: cess non advolem on einvoice item * chore: remove fetch token from e invocie settings * fix: imports * fix: error handling * feat: update timeline on einvoice actions * fix: qrcode image size * fix: exclude intra company transactions * fix: eway bill test * fix: ewaybill mandatory conditions * chore: add tests * fix: returning condition * feat: log e-invocing requests * chore: add ack date and ack no field for print formats * fix: sider issues * feat: show e-invoice preview before IRN generation * fix: use as_list for error message * fix: minor ux issues * fix: dialog is undefined * fix: error handling * feat: add docs link to e invoice settings * feat: multiple gstins for e invoicing * fix: uncomment test condition * fix: remove test pincode * fix: cannot cancel irn without submitting sales invoice * chore: code cleanup * fix: sider issues * fix: e invoice request log permissions Co-authored-by: Nabin Hait --- .../doctype/sales_invoice/regional/india.js | 2 + .../doctype/sales_invoice/sales_invoice.py | 2 +- .../sales_invoice/test_sales_invoice.py | 269 +++-- .../print_format/gst_e_invoice/__init__.py | 0 .../gst_e_invoice/gst_e_invoice.html | 162 +++ .../gst_e_invoice/gst_e_invoice.json | 24 + erpnext/controllers/accounts_controller.py | 10 + erpnext/hooks.py | 3 +- erpnext/patches.txt | 1 + .../patches/v12_0/setup_einvoice_fields.py | 55 + .../doctype/e_invoice_request_log/__init__.py | 0 .../e_invoice_request_log.js | 8 + .../e_invoice_request_log.json | 103 ++ .../e_invoice_request_log.py | 10 + .../test_e_invoice_request_log.py | 10 + .../doctype/e_invoice_settings/__init__.py | 0 .../e_invoice_settings/e_invoice_settings.js | 11 + .../e_invoice_settings.json | 58 ++ .../e_invoice_settings/e_invoice_settings.py | 14 + .../test_e_invoice_settings.py | 10 + .../doctype/e_invoice_user/__init__.py | 0 .../e_invoice_user/e_invoice_user.json | 48 + .../doctype/e_invoice_user/e_invoice_user.py | 10 + erpnext/regional/india/e_invoice/__init__.py | 0 .../india/e_invoice/einv_item_template.json | 31 + .../india/e_invoice/einv_template.json | 110 ++ .../india/e_invoice/einv_validation.json | 956 ++++++++++++++++++ erpnext/regional/india/e_invoice/einvoice.js | 305 ++++++ erpnext/regional/india/e_invoice/utils.py | 772 ++++++++++++++ erpnext/regional/india/setup.py | 31 +- requirements.txt | 1 + 31 files changed, 2922 insertions(+), 94 deletions(-) create mode 100644 erpnext/accounts/print_format/gst_e_invoice/__init__.py create mode 100644 erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html create mode 100644 erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json create mode 100644 erpnext/patches/v12_0/setup_einvoice_fields.py create mode 100644 erpnext/regional/doctype/e_invoice_request_log/__init__.py create mode 100644 erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js create mode 100644 erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json create mode 100644 erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py create mode 100644 erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py create mode 100644 erpnext/regional/doctype/e_invoice_settings/__init__.py create mode 100644 erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js create mode 100644 erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json create mode 100644 erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py create mode 100644 erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py create mode 100644 erpnext/regional/doctype/e_invoice_user/__init__.py create mode 100644 erpnext/regional/doctype/e_invoice_user/e_invoice_user.json create mode 100644 erpnext/regional/doctype/e_invoice_user/e_invoice_user.py create mode 100644 erpnext/regional/india/e_invoice/__init__.py create mode 100644 erpnext/regional/india/e_invoice/einv_item_template.json create mode 100644 erpnext/regional/india/e_invoice/einv_template.json create mode 100644 erpnext/regional/india/e_invoice/einv_validation.json create mode 100644 erpnext/regional/india/e_invoice/einvoice.js create mode 100644 erpnext/regional/india/e_invoice/utils.py diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index 6336db16eb..f54bce8aac 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -1,6 +1,8 @@ {% include "erpnext/regional/india/taxes.js" %} +{% include "erpnext/regional/india/e_invoice/einvoice.js" %} erpnext.setup_auto_gst_taxation('Sales Invoice'); +erpnext.setup_einvoice_actions('Sales Invoice') frappe.ui.form.on("Sales Invoice", { setup: function(frm) { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 50734c865c..40009ac69d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -232,9 +232,9 @@ class SalesInvoice(SellingController): frappe.throw(_("At least one mode of payment is required for POS invoice.")) def before_cancel(self): + super(SalesInvoice, self).before_cancel() self.update_time_sheet(None) - def on_cancel(self): super(SalesInvoice, self).on_cancel() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ceb7907989..3c681eeecf 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1825,93 +1825,7 @@ class TestSalesInvoice(unittest.TestCase): # check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) def test_eway_bill_json(self): - if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): - address = frappe.get_doc({ - "address_line1": "_Test Address Line 1", - "address_title": "_Test Address for Eway bill", - "address_type": "Billing", - "city": "_Test City", - "state": "Test State", - "country": "India", - "doctype": "Address", - "is_primary_address": 1, - "phone": "+91 0000000000", - "gstin": "27AAECE4835E1ZR", - "gst_state": "Maharashtra", - "gst_state_number": "27", - "pincode": "401108" - }).insert() - - address.append("links", { - "link_doctype": "Company", - "link_name": "_Test Company" - }) - - address.save() - - if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): - address = frappe.get_doc({ - "address_line1": "_Test Address Line 1", - "address_title": "_Test Customer-Address for Eway bill", - "address_type": "Shipping", - "city": "_Test City", - "state": "Test State", - "country": "India", - "doctype": "Address", - "is_primary_address": 1, - "phone": "+91 0000000000", - "gst_state": "Maharashtra", - "gst_state_number": "27", - "pincode": "410038" - }).insert() - - address.append("links", { - "link_doctype": "Customer", - "link_name": "_Test Customer" - }) - - address.save() - - gst_settings = frappe.get_doc("GST Settings") - - gst_account = frappe.get_all( - "GST Account", - fields=["cgst_account", "sgst_account", "igst_account"], - filters = {"company": "_Test Company"}) - - if not gst_account: - gst_settings.append("gst_accounts", { - "company": "_Test Company", - "cgst_account": "CGST - _TC", - "sgst_account": "SGST - _TC", - "igst_account": "IGST - _TC", - }) - - gst_settings.save() - - si = create_sales_invoice(do_not_save =1, rate = '60000') - - si.distance = 2000 - si.company_address = "_Test Address for Eway bill-Billing" - si.customer_address = "_Test Customer-Address for Eway bill-Shipping" - si.vehicle_no = "KA12KA1234" - si.gst_category = "Registered Regular" - - si.append("taxes", { - "charge_type": "On Net Total", - "account_head": "CGST - _TC", - "cost_center": "Main - _TC", - "description": "CGST @ 9.0", - "rate": 9 - }) - - si.append("taxes", { - "charge_type": "On Net Total", - "account_head": "SGST - _TC", - "cost_center": "Main - _TC", - "description": "SGST @ 9.0", - "rate": 9 - }) + si = make_sales_invoice_for_ewaybill() si.submit() @@ -1927,6 +1841,187 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['sgstValue'], 5400) self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) + + def test_einvoice_submission_without_irn(self): + # init + frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1) + country = frappe.flags.country + frappe.flags.country = 'India' + + si = make_sales_invoice_for_ewaybill() + self.assertRaises(frappe.ValidationError, si.submit) + + si.irn = 'test_irn' + si.submit() + + # reset + frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0) + frappe.flags.country = country + + def test_einvoice_json(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice + + customer_gstin = '27AACCM7806M1Z3' + customer_gstin_dtls = { + 'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City', + 'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg', + 'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street' + } + company_gstin = '27AAECE4835E1ZR' + company_gstin_dtls = { + 'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City', + 'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg', + 'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street' + } + # set cache gstin details to avoid fetching details which will require connection to GSP servers + frappe.local.gstin_cache = {} + frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls + frappe.local.gstin_cache[company_gstin] = company_gstin_dtls + + si = make_sales_invoice_for_ewaybill() + si.naming_series = 'INV-2020-.#####' + si.items = [] + si.append("items", { + "item_code": "_Test Item", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 2, + "rate": 100, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + si.append("items", { + "item_code": "_Test Item 2", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 4, + "rate": 150, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + si.save() + + einvoice = make_einvoice(si) + + total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']]) + total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']]) + total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']]) + total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']]) + total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']]) + + self.assertEqual(einvoice['Version'], '1.1') + self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value) + self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value) + self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value) + self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value) + self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value) + self.assertTrue(einvoice['EwbDtls']) + +def make_sales_invoice_for_ewaybill(): + if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Address for Eway bill", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+910000000000", + "gstin": "27AAECE4835E1ZR", + "gst_state": "Maharashtra", + "gst_state_number": "27", + "pincode": "401108" + }).insert() + + address.append("links", { + "link_doctype": "Company", + "link_name": "_Test Company" + }) + + address.save() + + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Customer-Address for Eway bill", + "address_type": "Shipping", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+910000000000", + "gstin": "27AACCM7806M1Z3", + "gst_state": "Maharashtra", + "gst_state_number": "27", + "pincode": "410038" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Customer" + }) + + address.save() + + if not frappe.db.exists('Supplier', '_Test Transporter'): + frappe.get_doc({ + "doctype": "Supplier", + "supplier_name": "_Test Transporter", + "country": "India", + "supplier_group": "_Test Supplier Group", + "supplier_type": "Company", + "is_transporter": 1 + }).insert() + + gst_settings = frappe.get_doc("GST Settings") + + gst_account = frappe.get_all( + "GST Account", + fields=["cgst_account", "sgst_account", "igst_account"], + filters = {"company": "_Test Company"}) + + if not gst_account: + gst_settings.append("gst_accounts", { + "company": "_Test Company", + "cgst_account": "CGST - _TC", + "sgst_account": "SGST - _TC", + "igst_account": "IGST - _TC", + }) + + gst_settings.save() + + si = create_sales_invoice(do_not_save =1, rate = '60000') + + si.distance = 2000 + si.company_address = "_Test Address for Eway bill-Billing" + si.customer_address = "_Test Customer-Address for Eway bill-Shipping" + si.vehicle_no = "KA12KA1234" + si.gst_category = "Registered Regular" + si.mode_of_transport = 'Road' + si.transporter = '_Test Transporter' + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "CGST - _TC", + "cost_center": "Main - _TC", + "description": "CGST @ 9.0", + "rate": 9 + }) + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "SGST - _TC", + "cost_center": "Main - _TC", + "description": "SGST @ 9.0", + "rate": 9 + }) + + return si def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql("""select account, debit, credit, posting_date diff --git a/erpnext/accounts/print_format/gst_e_invoice/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html new file mode 100644 index 0000000000..9827e00b71 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -0,0 +1,162 @@ +{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%} +{%- set einvoice = json.loads(doc.signed_einvoice) -%} + +
+
+ {% if letter_head and not no_letterhead %} +
{{ letter_head }}
+ {% endif %} + +
+ {% if print_settings.repeat_header_footer %} + + {% endif %} +
+
1. Transaction Details
+
+
+
+
{{ einvoice.Irn }}
+
+
+
+
{{ einvoice.AckNo }}
+
+
+
+
{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}
+
+
+
+
{{ einvoice.TranDtls.SupTyp }}
+
+
+
+
{{ einvoice.DocDtls.Typ }}
+
+
+
+
{{ einvoice.DocDtls.No }}
+
+
+
+ +
+
+
+
2. Party Details
+ {%- set seller = einvoice.SellerDtls -%} +
+
Seller
+

{{ seller.Gstin }}

+

{{ seller.LglNm }}

+

{{ seller.Addr1 }}

+ {%- if seller.Addr2 -%}

{{ seller.Addr2 }}

{% endif %} +

{{ seller.Loc }}

+

{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}

+ + {%- if einvoice.ShipDtls -%} + {%- set shipping = einvoice.ShipDtls -%} +
Shipping
+

{{ shipping.Gstin }}

+

{{ shipping.LglNm }}

+

{{ shipping.Addr1 }}

+ {%- if shipping.Addr2 -%}

{{ shipping.Addr2 }}

{% endif %} +

{{ shipping.Loc }}

+

{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}

+ {% endif %} +
+ {%- set buyer = einvoice.BuyerDtls -%} +
+
Buyer
+

{{ buyer.Gstin }}

+

{{ buyer.LglNm }}

+

{{ buyer.Addr1 }}

+ {%- if buyer.Addr2 -%}

{{ buyer.Addr2 }}

{% endif %} +

{{ buyer.Loc }}

+

{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}

+
+
+
+
3. Item Details
+ + + + + + + + + + + + + + + + + + {% for item in einvoice.ItemList %} + + + + + + + + + + + + + + {% endfor %} + +
Sr. No.ItemHSN CodeQtyUOMRateDiscountTaxable AmountTax RateOther ChargesTotal
{{ item.SlNo }}{{ item.PrdDesc }}{{ item.HsnCd }}{{ item.Qty }}{{ item.Unit }}{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}{{ item.GstRt + item.CesRt }} %{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}
+
+
+
4. Value Details
+ + + + + + + + + + + + + + + + + {%- set value_details = einvoice.ValDtls -%} + + + + + + + + + + + + + +
Taxable AmountCGSTSGSTIGSTCESSState CESSDiscountOther ChargesRound OffTotal Value
{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}
+
+
\ No newline at end of file diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json new file mode 100644 index 0000000000..1001199a09 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json @@ -0,0 +1,24 @@ +{ + "align_labels_right": 1, + "creation": "2020-10-10 18:01:21.032914", + "custom_format": 0, + "default_print_language": "en-US", + "disabled": 1, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "", + "idx": 0, + "line_breaks": 1, + "modified": "2020-10-23 19:54:40.634936", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST E-Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 1, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 32c5d3a3b1..0f1aa23064 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -110,8 +110,14 @@ class AccountsController(TransactionBase): self.set_inter_company_account() validate_regional(self) + + validate_einvoice_fields(self) + if self.doctype != 'Material Request': apply_pricing_rule_on_transaction(self) + + def before_cancel(self): + validate_einvoice_fields(self) def validate_deferred_start_and_end_date(self): for d in self.items: @@ -1518,3 +1524,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil @erpnext.allow_regional def validate_regional(doc): pass + +@erpnext.allow_regional +def validate_einvoice_fields(doc): + pass diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1e3bb6a5cf..a2d9d861bb 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -397,7 +397,8 @@ regional_overrides = { '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.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries' + 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields' }, '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/patches.txt b/erpnext/patches.txt index 86ac613ae5..5b37b38e68 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -733,6 +733,7 @@ erpnext.patches.v13_0.set_youtube_video_id erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail +erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.update_reason_for_resignation_in_employee erpnext.patches.v13_0.update_custom_fields_for_shopify diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py new file mode 100644 index 0000000000..d0782765de --- /dev/null +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -0,0 +1,55 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from erpnext.regional.india.setup import add_permissions, add_print_formats + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + frappe.reload_doc("regional", "doctype", "e_invoice_settings") + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + add_permissions() + add_print_formats() + + einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)' + t = { + 'mode_of_transport': [{'default': None}], + 'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}], + 'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}], + 'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}], + 'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}], + 'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}], + 'ewaybill': [ + {'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'}, + {'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'} + ] + } + + for field, conditions in t.items(): + for c in conditions: + [(prop, value)] = c.items() + frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value) diff --git a/erpnext/regional/doctype/e_invoice_request_log/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js new file mode 100644 index 0000000000..7b7ba964e5 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.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('E Invoice Request Log', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json new file mode 100644 index 0000000000..5c1c79dc04 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json @@ -0,0 +1,103 @@ +{ + "actions": [], + "autoname": "EINV-REQ-.#####", + "creation": "2020-12-08 12:54:08.175992", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user", + "url", + "headers", + "response", + "column_break_7", + "timestamp", + "reference_invoice", + "data" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User" + }, + { + "fieldname": "reference_invoice", + "fieldtype": "Link", + "label": "Reference Invoice", + "options": "Sales Invoice" + }, + { + "fieldname": "headers", + "fieldtype": "Code", + "label": "Headers", + "options": "JSON" + }, + { + "fieldname": "data", + "fieldtype": "Code", + "label": "Data", + "options": "JSON" + }, + { + "default": "Now", + "fieldname": "timestamp", + "fieldtype": "Datetime", + "label": "Timestamp" + }, + { + "fieldname": "response", + "fieldtype": "Code", + "label": "Response", + "options": "JSON" + }, + { + "fieldname": "url", + "fieldtype": "Data", + "label": "URL" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-12-24 21:09:38.882866", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice Request Log", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py new file mode 100644 index 0000000000..9150bdd926 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.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 EInvoiceRequestLog(Document): + pass diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py new file mode 100644 index 0000000000..c84e9a249b --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.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 TestEInvoiceRequestLog(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/e_invoice_settings/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js new file mode 100644 index 0000000000..cc2d9f06d2 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -0,0 +1,11 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('E Invoice Settings', { + refresh(frm) { + const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing'; + frm.dashboard.set_headline( + __("Read {0} for more information on E Invoicing features.", [`documentation`]) + ); + } +}); diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json new file mode 100644 index 0000000000..4dcb22a54c --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -0,0 +1,58 @@ +{ + "actions": [], + "creation": "2020-09-24 16:23:16.235722", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable", + "section_break_2", + "credentials", + "auth_token", + "token_expiry" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "auth_token", + "fieldtype": "Data", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "token_expiry", + "fieldtype": "Datetime", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "credentials", + "fieldtype": "Table", + "label": "Credentials", + "mandatory_depends_on": "enable", + "options": "E Invoice User" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2020-12-22 15:34:57.280044", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice Settings", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py new file mode 100644 index 0000000000..c24ad886ea --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -0,0 +1,14 @@ +# -*- 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.model.document import Document + +class EInvoiceSettings(Document): + def validate(self): + if self.enable and not self.credentials: + frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.')) + diff --git a/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py new file mode 100644 index 0000000000..a11ce63ee6 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.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 TestEInvoiceSettings(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/e_invoice_user/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json new file mode 100644 index 0000000000..dd9d99773a --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json @@ -0,0 +1,48 @@ +{ + "actions": [], + "creation": "2020-12-22 15:02:46.229474", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "gstin", + "username", + "password" + ], + "fields": [ + { + "fieldname": "gstin", + "fieldtype": "Data", + "in_list_view": 1, + "label": "GSTIN", + "reqd": 1 + }, + { + "fieldname": "username", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Username", + "reqd": 1 + }, + { + "fieldname": "password", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Password", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-12-22 15:10:53.466205", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice User", + "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/regional/doctype/e_invoice_user/e_invoice_user.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py new file mode 100644 index 0000000000..056c54f069 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.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 EInvoiceUser(Document): + pass diff --git a/erpnext/regional/india/e_invoice/__init__.py b/erpnext/regional/india/e_invoice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json new file mode 100644 index 0000000000..78e56518df --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_item_template.json @@ -0,0 +1,31 @@ +{{ + "SlNo": "{item.sr_no}", + "PrdDesc": "{item.description}", + "IsServc": "{item.is_service_item}", + "HsnCd": "{item.gst_hsn_code}", + "Barcde": "{item.barcode}", + "Unit": "{item.uom}", + "Qty": "{item.qty}", + "FreeQty": "{item.free_qty}", + "UnitPrice": "{item.unit_rate}", + "TotAmt": "{item.gross_amount}", + "Discount": "{item.discount_amount}", + "AssAmt": "{item.taxable_value}", + "PrdSlNo": "{item.serial_no}", + "GstRt": "{item.tax_rate}", + "IgstAmt": "{item.igst_amount}", + "CgstAmt": "{item.cgst_amount}", + "SgstAmt": "{item.sgst_amount}", + "CesRt": "{item.cess_rate}", + "CesAmt": "{item.cess_amount}", + "CesNonAdvlAmt": "{item.cess_nadv_amount}", + "StateCesRt": "{item.state_cess_rate}", + "StateCesAmt": "{item.state_cess_amount}", + "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}", + "OthChrg": "{item.other_charges}", + "TotItemVal": "{item.total_value}", + "BchDtls": {{ + "Nm": "{item.batch_no}", + "ExpDt": "{item.batch_expiry_date}" + }} +}} \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json new file mode 100644 index 0000000000..e5751da561 --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -0,0 +1,110 @@ +{{ + "Version": "1.1", + "TranDtls": {{ + "TaxSch": "{transaction_details.tax_scheme}", + "SupTyp": "{transaction_details.supply_type}", + "RegRev": "{transaction_details.reverse_charge}", + "EcmGstin": "{transaction_details.ecom_gstin}", + "IgstOnIntra": "{transaction_details.igst_on_intra}" + }}, + "DocDtls": {{ + "Typ": "{doc_details.invoice_type}", + "No": "{doc_details.invoice_name}", + "Dt": "{doc_details.invoice_date}" + }}, + "SellerDtls": {{ + "Gstin": "{seller_details.gstin}", + "LglNm": "{seller_details.legal_name}", + "TrdNm": "{seller_details.trade_name}", + "Loc": "{seller_details.location}", + "Pin": "{seller_details.pincode}", + "Stcd": "{seller_details.state_code}", + "Addr1": "{seller_details.address_line1}", + "Addr2": "{seller_details.address_line2}", + "Ph": "{seller_details.phone}", + "Em": "{seller_details.email}" + }}, + "BuyerDtls": {{ + "Gstin": "{buyer_details.gstin}", + "LglNm": "{buyer_details.legal_name}", + "TrdNm": "{buyer_details.trade_name}", + "Addr1": "{buyer_details.address_line1}", + "Addr2": "{buyer_details.address_line2}", + "Loc": "{buyer_details.location}", + "Pin": "{buyer_details.pincode}", + "Stcd": "{buyer_details.state_code}", + "Ph": "{buyer_details.phone}", + "Em": "{buyer_details.email}", + "Pos": "{buyer_details.place_of_supply}" + }}, + "DispDtls": {{ + "Nm": "{dispatch_details.company_name}", + "Addr1": "{dispatch_details.address_line1}", + "Addr2": "{dispatch_details.address_line2}", + "Loc": "{dispatch_details.location}", + "Pin": "{dispatch_details.pincode}", + "Stcd": "{dispatch_details.state_code}" + }}, + "ShipDtls": {{ + "Gstin": "{shipping_details.gstin}", + "LglNm": "{shipping_details.legal_name}", + "TrdNm": "{shipping_details.trader_name}", + "Addr1": "{shipping_details.address_line1}", + "Addr2": "{shipping_details.address_line2}", + "Loc": "{shipping_details.location}", + "Pin": "{shipping_details.pincode}", + "Stcd": "{shipping_details.state_code}" + }}, + "ItemList": [ + {item_list} + ], + "ValDtls": {{ + "AssVal": "{invoice_value_details.base_net_total}", + "CgstVal": "{invoice_value_details.total_cgst_amt}", + "SgstVal": "{invoice_value_details.total_sgst_amt}", + "IgstVal": "{invoice_value_details.total_igst_amt}", + "CesVal": "{invoice_value_details.total_cess_amt}", + "Discount": "{invoice_value_details.invoice_discount_amt}", + "RndOffAmt": "{invoice_value_details.round_off}", + "OthChrg": "{invoice_value_details.total_other_charges}", + "TotInvVal": "{invoice_value_details.base_grand_total}", + "TotInvValFc": "{invoice_value_details.grand_total}" + }}, + "PayDtls": {{ + "Nm": "{payment_details.payee_name}", + "AccDet": "{payment_details.account_no}", + "Mode": "{payment_details.mode_of_payment}", + "FinInsBr": "{payment_details.ifsc_code}", + "PayTerm": "{payment_details.terms}", + "PaidAmt": "{payment_details.paid_amount}", + "PaymtDue": "{payment_details.outstanding_amount}" + }}, + "RefDtls": {{ + "DocPerdDtls": {{ + "InvStDt": "{period_details.start_date}", + "InvEndDt": "{period_details.end_date}" + }}, + "PrecDocDtls": [{{ + "InvNo": "{prev_doc_details.invoice_name}", + "InvDt": "{prev_doc_details.invoice_date}" + }}] + }}, + "ExpDtls": {{ + "ShipBNo": "{export_details.bill_no}", + "ShipBDt": "{export_details.bill_date}", + "Port": "{export_details.port}", + "ForCur": "{export_details.foreign_curr_code}", + "CntCode": "{export_details.country_code}", + "ExpDuty": "{export_details.export_duty}" + }}, + "EwbDtls": {{ + "TransId": "{eway_bill_details.gstin}", + "TransName": "{eway_bill_details.name}", + "TransMode": "{eway_bill_details.mode_of_transport}", + "Distance": "{eway_bill_details.distance}", + "TransDocNo": "{eway_bill_details.document_name}", + "TransDocDt": "{eway_bill_details.document_date}", + "VehNo": "{eway_bill_details.vehicle_no}", + "VehType": "{eway_bill_details.vehicle_type}" + }} +}} \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json new file mode 100644 index 0000000000..86290cfe52 --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_validation.json @@ -0,0 +1,956 @@ +{ + "Version": { + "type": "string", + "minLength": 1, + "maxLength": 6, + "description": "Version of the schema" + }, + "Irn": { + "type": "string", + "minLength": 64, + "maxLength": 64, + "description": "Invoice Reference Number" + }, + "TranDtls": { + "type": "object", + "properties": { + "TaxSch": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["GST"], + "description": "GST- Goods and Services Tax Scheme" + }, + "SupTyp": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"], + "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export" + }, + "RegRev": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Y- whether the tax liability is payable under reverse charge" + }, + "EcmGstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "description": "E-Commerce GSTIN", + "validationMsg": "E-Commerce GSTIN is invalid" + }, + "IgstOnIntra": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Y- indicates the supply is intra state but chargeable to IGST" + } + }, + "required": ["TaxSch", "SupTyp"] + }, + "DocDtls": { + "type": "object", + "properties": { + "Typ": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "enum": ["INV", "CRN", "DBN"], + "description": "Document Type" + }, + "No": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$", + "description": "Document Number", + "validationMsg": "Document Number should not be starting with 0, / and -" + }, + "Dt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Document Date" + } + }, + "required": ["Typ", "No", "Dt"] + }, + "SellerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "description": "Supplier GSTIN", + "validationMsg": "Company GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Tradename" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 50, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Supplier State Code" + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12, + "description": "Phone" + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100, + "description": "Email-Id" + } + }, + "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "BuyerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 3, + "maxLength": 15, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "description": "Buyer GSTIN", + "validationMsg": "Customer GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Trade Name" + }, + "Pos": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Place of Supply State code" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Buyer State Code" + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12, + "description": "Phone" + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100, + "description": "Email-Id" + } + }, + "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"] + }, + "DispDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Dispatch Address Name" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "State Code" + } + }, + "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ShipDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "maxLength": 15, + "minLength": 3, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "description": "Shipping Address GSTIN", + "validationMsg": "Shipping Address GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Trade Name" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "State Code" + } + }, + "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ItemList": { + "type": "Array", + "properties": { + "SlNo": { + "type": "string", + "minLength": 1, + "maxLength": 6, + "description": "Serial No. of Item" + }, + "PrdDesc": { + "type": "string", + "minLength": 3, + "maxLength": 300, + "description": "Item Name" + }, + "IsServc": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Is Service Item" + }, + "HsnCd": { + "type": "string", + "minLength": 4, + "maxLength": 8, + "description": "HSN Code" + }, + "Barcde": { + "type": "string", + "minLength": 3, + "maxLength": 30, + "description": "Barcode" + }, + "Qty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999, + "description": "Quantity" + }, + "FreeQty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999, + "description": "Free Quantity" + }, + "Unit": { + "type": "string", + "minLength": 3, + "maxLength": 8, + "description": "UOM" + }, + "UnitPrice": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.999, + "description": "Rate" + }, + "TotAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Gross Amount" + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Discount" + }, + "PreTaxVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Pre tax value" + }, + "AssAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Taxable Value" + }, + "GstRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "GST Rate" + }, + "IgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "IGST Amount" + }, + "CgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "CGST Amount" + }, + "SgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "SGST Amount" + }, + "CesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "Cess Rate" + }, + "CesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Cess Amount (Advalorem)" + }, + "CesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Cess Amount (Non-Advalorem)" + }, + "StateCesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "State CESS Rate" + }, + "StateCesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "State CESS Amount" + }, + "StateCesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "State CESS Amount (Non Advalorem)" + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Other Charges" + }, + "TotItemVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Total Item Value" + }, + "OrdLineRef": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "description": "Order line reference" + }, + "OrgCntry": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Origin Country" + }, + "PrdSlNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Serial number" + }, + "BchDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 20, + "description": "Batch number" + }, + "ExpDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Batch Expiry Date" + }, + "WrDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Warranty Date" + } + }, + "required": ["Nm"] + }, + "AttribDtls": { + "type": "Array", + "Attribute": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Attribute name of the item" + }, + "Val": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Attribute value of the item" + } + } + } + } + }, + "required": [ + "SlNo", + "IsServc", + "HsnCd", + "UnitPrice", + "TotAmt", + "AssAmt", + "GstRt", + "TotItemVal" + ] + }, + "ValDtls": { + "type": "object", + "properties": { + "AssVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total Assessable value of all items" + }, + "CgstVal": { + "type": "number", + "maximum": 99999999999999.99, + "minimum": 0, + "description": "Total CGST value of all items" + }, + "SgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total SGST value of all items" + }, + "IgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total IGST value of all items" + }, + "CesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total CESS value of all items" + }, + "StCesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total State CESS value of all items" + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Invoice Discount" + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Other Charges" + }, + "RndOffAmt": { + "type": "number", + "minimum": -99.99, + "maximum": 99.99, + "description": "Rounded off Amount" + }, + "TotInvVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Final Invoice Value " + }, + "TotInvValFc": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Final Invoice value in Foreign Currency" + } + }, + "required": ["AssVal", "TotInvVal"] + }, + "PayDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Payee Name" + }, + "AccDet": { + "type": "string", + "minLength": 1, + "maxLength": 18, + "description": "Bank Account Number of Payee" + }, + "Mode": { + "type": "string", + "minLength": 1, + "maxLength": 18, + "description": "Mode of Payment" + }, + "FinInsBr": { + "type": "string", + "minLength": 1, + "maxLength": 11, + "description": "Branch or IFSC code" + }, + "PayTerm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Terms of Payment" + }, + "PayInstr": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Payment Instruction" + }, + "CrTrn": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Credit Transfer" + }, + "DirDr": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Direct Debit" + }, + "CrDay": { + "type": "number", + "minimum": 0, + "maximum": 9999, + "description": "Credit Days" + }, + "PaidAmt": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Advance Amount" + }, + "PaymtDue": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Outstanding Amount" + } + } + }, + "RefDtls": { + "type": "object", + "properties": { + "InvRm": { + "type": "string", + "maxLength": 100, + "minLength": 3, + "pattern": "^[0-9A-Za-z/-]{3,100}$", + "description": "Remarks/Note" + }, + "DocPerdDtls": { + "type": "object", + "properties": { + "InvStDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period Start Date" + }, + "InvEndDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period End Date" + } + }, + "required": ["InvStDt ", "InvEndDt "] + }, + "PrecDocDtls": { + "type": "object", + "properties": { + "InvNo": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$", + "description": "Reference of Original Invoice" + }, + "InvDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of Orginal Invoice" + }, + "OthRefNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Other Reference" + } + } + }, + "required": ["InvNo", "InvDt"], + "ContrDtls": { + "type": "object", + "properties": { + "RecAdvRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Receipt Advice No." + }, + "RecAdvDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of receipt advice" + }, + "TendRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Lot/Batch Reference No." + }, + "ContrRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Contract Reference Number" + }, + "ExtRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Any other reference" + }, + "ProjRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Project Reference Number" + }, + "PORefr": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([0-9A-Za-z/-]){1,16}$", + "description": "PO Reference Number" + }, + "PORefDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "PO Reference date" + } + } + } + } + }, + "AddlDocDtls": { + "type": "Array", + "properties": { + "Url": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Supporting document URL" + }, + "Docs": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Supporting document in Base64 Format" + }, + "Info": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Any additional information" + } + } + }, + + "ExpDtls": { + "type": "object", + "properties": { + "ShipBNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Shipping Bill No." + }, + "ShipBDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Shipping Bill Date" + }, + "Port": { + "type": "string", + "minLength": 2, + "maxLength": 10, + "pattern": "^[0-9A-Za-z]{2,10}$", + "description": "Port Code. Refer the master" + }, + "RefClm": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "description": "Claiming Refund. Y/N" + }, + "ForCur": { + "type": "string", + "minLength": 3, + "maxLength": 16, + "description": "Additional Currency Code. Refer the master" + }, + "CntCode": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Country Code. Refer the master" + }, + "ExpDuty": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Export Duty" + } + } + }, + "EwbDtls": { + "type": "object", + "properties": { + "TransId": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "description": "Transporter GSTIN" + }, + "TransName": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Transporter Name" + }, + "TransMode": { + "type": "string", + "maxLength": 1, + "minLength": 1, + "enum": ["1", "2", "3", "4"], + "description": "Mode of Transport" + }, + "Distance": { + "type": "number", + "minimum": 1, + "maximum": 9999, + "description": "Distance" + }, + "TransDocNo": { + "type": "string", + "minLength": 1, + "maxLength": 15, + "pattern": "^([0-9A-Z/-]){1,15}$", + "description": "Tranport Document Number" + }, + "TransDocDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Transport Document Date" + }, + "VehNo": { + "type": "string", + "minLength": 4, + "maxLength": 20, + "description": "Vehicle Number" + }, + "VehType": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["O", "R"], + "description": "Vehicle Type" + } + }, + "required": ["Distance"] + }, + "required": [ + "Version", + "TranDtls", + "DocDtls", + "SellerDtls", + "BuyerDtls", + "ItemList", + "ValDtls" + ] +} diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js new file mode 100644 index 0000000000..9c86cc89f5 --- /dev/null +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -0,0 +1,305 @@ +erpnext.setup_einvoice_actions = (doctype) => { + frappe.ui.form.on(doctype, { + refresh(frm) { + const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); + const supply_type = frm.doc.gst_category; + const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); + const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin; + + if (!einvoicing_enabled || !valid_supply_type || company_transaction) return; + + const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; + + const add_custom_button = (label, action) => { + if (!frm.custom_buttons[label]) { + frm.add_custom_button(label, action, __('E Invoicing')); + } + }; + + if (!irn && !__unsaved) { + const action = () => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.get_einvoice', + args: { doctype, docname: name }, + freeze: true, + callback: (res) => { + const einvoice = res.message; + show_einvoice_preview(frm, einvoice); + } + }); + }; + + add_custom_button(__("Generate IRN"), action); + } + + if (irn && !irn_cancelled && !ewaybill) { + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + const action = () => { + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', + args: { + doctype, + docname: name, + irn: irn, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + add_custom_button(__("Cancel IRN"), action); + } + + if (irn && !irn_cancelled && !ewaybill) { + const action = () => { + const d = new frappe.ui.Dialog({ + title: __('Generate E-Way Bill'), + wide: 1, + fields: get_ewaybill_fields(frm), + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill', + args: { + doctype, + docname: name, + irn, + ...data + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + add_custom_button(__("Generate E-Way Bill"), action); + } + + if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + const action = () => { + const d = new frappe.ui.Dialog({ + title: __('Cancel E-Way Bill'), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { + doctype, + docname: name, + eway_bill: ewaybill, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + add_custom_button(__("Cancel E-Way Bill"), action); + } + } + }); +}; + +const get_ewaybill_fields = (frm) => { + return [ + { + 'fieldname': 'transporter', + 'label': 'Transporter', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'default': frm.doc.transporter + }, + { + 'fieldname': 'gst_transporter_id', + 'label': 'GST Transporter ID', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.gst_transporter_id', + 'default': frm.doc.gst_transporter_id + }, + { + 'fieldname': 'driver', + 'label': 'Driver', + 'fieldtype': 'Link', + 'options': 'Driver', + 'default': frm.doc.driver + }, + { + 'fieldname': 'lr_no', + 'label': 'Transport Receipt No', + 'fieldtype': 'Data', + 'default': frm.doc.lr_no + }, + { + 'fieldname': 'vehicle_no', + 'label': 'Vehicle No', + 'fieldtype': 'Data', + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.vehicle_no + }, + { + 'fieldname': 'distance', + 'label': 'Distance (in km)', + 'fieldtype': 'Float', + 'default': frm.doc.distance + }, + { + 'fieldname': 'transporter_col_break', + 'fieldtype': 'Column Break', + }, + { + 'fieldname': 'transporter_name', + 'label': 'Transporter Name', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.name', + 'read_only': 1, + 'default': frm.doc.transporter_name + }, + { + 'fieldname': 'mode_of_transport', + 'label': 'Mode of Transport', + 'fieldtype': 'Select', + 'options': `\nRoad\nAir\nRail\nShip`, + 'default': frm.doc.mode_of_transport + }, + { + 'fieldname': 'driver_name', + 'label': 'Driver Name', + 'fieldtype': 'Data', + 'fetch_from': 'driver.full_name', + 'read_only': 1, + 'default': frm.doc.driver_name + }, + { + 'fieldname': 'lr_date', + 'label': 'Transport Receipt Date', + 'fieldtype': 'Date', + 'default': frm.doc.lr_date + }, + { + 'fieldname': 'gst_vehicle_type', + 'label': 'GST Vehicle Type', + 'fieldtype': 'Select', + 'options': `Regular\nOver Dimensional Cargo (ODC)`, + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.gst_vehicle_type + } + ]; +}; + +const request_irn_generation = (frm) => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_irn', + args: { doctype: frm.doc.doctype, docname: frm.doc.name }, + freeze: true, + callback: () => frm.reload_doc() + }); +}; + +const get_preview_dialog = (frm, action) => { + const dialog = new frappe.ui.Dialog({ + title: __("Preview"), + wide: 1, + fields: [ + { + "label": "Preview", + "fieldname": "preview_html", + "fieldtype": "HTML" + } + ], + primary_action: () => action(frm) || dialog.hide(), + primary_action_label: __('Generate IRN') + }); + return dialog; +}; + +const show_einvoice_preview = (frm, einvoice) => { + const preview_dialog = get_preview_dialog(frm, request_irn_generation); + + // initialize e-invoice fields + einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate(); + frm.doc.signed_einvoice = JSON.stringify(einvoice); + + // initialize preview wrapper + const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper; + $preview_wrapper.html( + `
+ +
+
` + ); + + frappe.call({ + method: "frappe.www.printview.get_html_and_style", + args: { + doc: frm.doc, + print_format: "GST E-Invoice", + no_letterhead: 1 + }, + callback: function (r) { + if (!r.exc) { + $preview_wrapper.find(".print-format").html(r.message.html); + const style = ` + .print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; } + .print-preview { min-height: 0px; } + .modal-dialog { width: 720px; }`; + + frappe.dom.set_style(style, "custom-print-style"); + preview_dialog.show(); + } + } + }); +}; \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py new file mode 100644 index 0000000000..cb92c42464 --- /dev/null +++ b/erpnext/regional/india/e_invoice/utils.py @@ -0,0 +1,772 @@ +# -*- 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 os +import re +import jwt +import sys +import json +import base64 +import frappe +import traceback +from frappe import _, bold +from pyqrcode import create as qrcreate +from frappe.integrations.utils import make_post_request, make_get_request +from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply +from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date + +def validate_einvoice_fields(doc): + einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) + invalid_doctype = doc.doctype not in ['Sales Invoice'] + invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] + company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') + + if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return + + if doc.docstatus == 0 and doc._action == 'save': + if doc.irn: + frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) + if len(doc.name) > 16: + raise_document_name_too_long_error() + + elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: + frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) + + elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: + frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) + +def raise_document_name_too_long_error(): + title = _('Document ID Too Long') + msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ') + msg += _('document id {} exceed 16 letters. ').format(bold(_('should not'))) + msg += '

' + msg += _('You must {} your {} in order to have document id of {} length 16. ').format( + bold(_('modify')), bold(_('naming series')), bold(_('maximum')) + ) + msg += _('Please account for ammended documents too. ') + frappe.throw(msg, title=title) + +def read_json(name): + file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name)) + with open(file_path, 'r') as f: + return cstr(f.read()) + +def get_transaction_details(invoice): + supply_type = '' + if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' + elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' + elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP' + elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' + + if not supply_type: + rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export') + frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export), + title=_('Invalid Supply Type')) + + return frappe._dict(dict( + tax_scheme='GST', + supply_type=supply_type, + reverse_charge=invoice.reverse_charge + )) + +def get_doc_details(invoice): + invoice_type = 'CRN' if invoice.is_return else 'INV' + + invoice_name = invoice.name + invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') + + return frappe._dict(dict( + invoice_type=invoice_type, + invoice_name=invoice_name, + invoice_date=invoice_date + )) + +def get_party_details(address_name): + address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] + gstin = address.get('gstin') + + gstin_details = get_gstin_details(gstin) + legal_name = gstin_details.get('LegalName') + location = gstin_details.get('AddrLoc') or address.get('city') + state_code = gstin_details.get('StateCode') + pincode = gstin_details.get('AddrPncd') + address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) + address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) + email_id = address.get('email_id') + phone = address.get('phone') + # get last 10 digit + phone = phone.replace(" ", "")[-10:] if phone else '' + + if state_code == 97: + # according to einvoice standard + pincode = 999999 + + return frappe._dict(dict( + gstin=gstin, legal_name=legal_name, location=location, + pincode=pincode, state_code=state_code, address_line1=address_line1, + address_line2=address_line2, email=email_id, phone=phone + )) + +def get_gstin_details(gstin): + if not hasattr(frappe.local, 'gstin_cache'): + frappe.local.gstin_cache = {} + + key = gstin + details = frappe.local.gstin_cache.get(key) + if details: + return details + + details = frappe.cache().hget('gstin_cache', key) + if details: + frappe.local.gstin_cache[key] = details + return details + + if not details: + return GSPConnector.get_gstin_details(gstin) + +def get_overseas_address_details(address_name): + address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( + 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id'] + ) + + return frappe._dict(dict( + gstin='URP', legal_name=address_title, address_line1=address_line1, + address_line2=address_line2, email=email_id, phone=phone, + pincode=999999, state_code=96, place_of_supply=96, location=city + )) + +def get_item_list(invoice): + item_list = [] + + for d in invoice.items: + einvoice_item_schema = read_json('einv_item_template') + item = frappe._dict({}) + item.update(d.as_dict()) + + item.sr_no = d.idx + item.qty = abs(item.qty) + item.description = d.item_name + item.taxable_value = abs(item.base_net_amount) + item.discount_amount = abs(item.discount_amount * item.qty) + item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) + item.gross_amount = abs(item.unit_rate * item.qty) + + item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None + item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None + item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' + + item = update_item_taxes(invoice, item) + + item.total_value = abs( + item.taxable_value + item.igst_amount + item.sgst_amount + + item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges + ) + einv_item = einvoice_item_schema.format(item=item) + item_list.append(einv_item) + + return ', '.join(item_list) + +def update_item_taxes(invoice, item): + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + for attr in [ + 'tax_rate', 'cess_rate', 'cess_nadv_amount', + 'cgst_amount', 'sgst_amount', 'igst_amount', + 'cess_amount', 'cess_nadv_amount', 'other_charges' + ]: + item[attr] = 0 + + for t in invoice.taxes: + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + if t.account_head in gst_accounts_list: + if t.account_head in gst_accounts.cess_account: + if t.charge_type == 'On Item Quantity': + item.cess_nadv_amount += abs(item_tax_detail[1]) + else: + item.cess_rate += item_tax_detail[0] + item.cess_amount += abs(item_tax_detail[1]) + elif t.account_head in gst_accounts.igst_account: + item.tax_rate += item_tax_detail[0] + item.igst_amount += abs(item_tax_detail[1]) + elif t.account_head in gst_accounts.sgst_account: + item.tax_rate += item_tax_detail[0] + item.sgst_amount += abs(item_tax_detail[1]) + elif t.account_head in gst_accounts.cgst_account: + item.tax_rate += item_tax_detail[0] + item.cgst_amount += abs(item_tax_detail[1]) + + return item + +def get_invoice_value_details(invoice): + invoice_value_details = frappe._dict(dict()) + invoice_value_details.base_net_total = abs(invoice.base_net_total) + invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 + # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off + invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) + disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') + invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total) + invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total) + + invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) + + return invoice_value_details + +def update_invoice_taxes(invoice, invoice_value_details): + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + invoice_value_details.total_cgst_amt = 0 + invoice_value_details.total_sgst_amt = 0 + invoice_value_details.total_igst_amt = 0 + invoice_value_details.total_cess_amt = 0 + invoice_value_details.total_other_charges = 0 + for t in invoice.taxes: + if t.account_head in gst_accounts_list: + if t.account_head in gst_accounts.cess_account: + invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) + elif t.account_head in gst_accounts.igst_account: + invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount) + elif t.account_head in gst_accounts.sgst_account: + invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) + elif t.account_head in gst_accounts.cgst_account: + invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount) + else: + invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) + + return invoice_value_details + +def get_payment_details(invoice): + payee_name = invoice.company + mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments]) + paid_amount = invoice.base_paid_amount + outstanding_amount = invoice.outstanding_amount + + return frappe._dict(dict( + payee_name=payee_name, mode_of_payment=mode_of_payment, + paid_amount=paid_amount, outstanding_amount=outstanding_amount + )) + +def get_return_doc_reference(invoice): + invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') + return frappe._dict(dict( + invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') + )) + +def get_eway_bill_details(invoice): + if invoice.is_return: + frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed')) + + mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } + vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } + + return frappe._dict(dict( + gstin=invoice.gst_transporter_id, + name=invoice.transporter_name, + mode_of_transport=mode_of_transport[invoice.mode_of_transport], + distance=invoice.distance or 0, + document_name=invoice.lr_no, + document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'), + vehicle_no=invoice.vehicle_no, + vehicle_type=vehicle_type[invoice.gst_vehicle_type] + )) + +def make_einvoice(invoice): + schema = read_json('einv_template') + + transaction_details = get_transaction_details(invoice) + item_list = get_item_list(invoice) + doc_details = get_doc_details(invoice) + invoice_value_details = get_invoice_value_details(invoice) + seller_details = get_party_details(invoice.company_address) + + if invoice.gst_category == 'Overseas': + buyer_details = get_overseas_address_details(invoice.customer_address) + else: + buyer_details = get_party_details(invoice.customer_address) + place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin + place_of_supply = place_of_supply[:2] + buyer_details.update(dict(place_of_supply=place_of_supply)) + + shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) + if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: + shipping_details = get_party_details(invoice.shipping_address_name) + + if invoice.is_pos and invoice.base_paid_amount: + payment_details = get_payment_details(invoice) + + if invoice.is_return and invoice.return_against: + prev_doc_details = get_return_doc_reference(invoice) + + if invoice.transporter: + eway_bill_details = get_eway_bill_details(invoice) + + # not yet implemented + dispatch_details = period_details = export_details = frappe._dict({}) + + einvoice = schema.format( + transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details, + seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, + item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details, + period_details=period_details, prev_doc_details=prev_doc_details, + export_details=export_details, eway_bill_details=eway_bill_details + ) + einvoice = json.loads(einvoice) + + validations = json.loads(read_json('einv_validation')) + errors = validate_einvoice(validations, einvoice) + if errors: + message = "\n".join([ + "E Invoice: ", json.dumps(einvoice, indent=4), + "-" * 50, + "Errors: ", json.dumps(errors, indent=4) + ]) + frappe.log_error(title="E Invoice Validation Failed", message=message) + frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1) + + return einvoice + +def validate_einvoice(validations, einvoice, errors=[]): + for fieldname, field_validation in validations.items(): + value = einvoice.get(fieldname, None) + if not value or value == "None": + # remove keys with empty values + einvoice.pop(fieldname, None) + continue + + value_type = field_validation.get("type").lower() + if value_type in ['object', 'array']: + child_validations = field_validation.get('properties') + + if isinstance(value, list): + for d in value: + validate_einvoice(child_validations, d, errors) + if not d: + # remove empty dicts + einvoice.pop(fieldname, None) + else: + validate_einvoice(child_validations, value, errors) + if not value: + # remove empty dicts + einvoice.pop(fieldname, None) + continue + + # convert to int or str + if value_type == 'string': + einvoice[fieldname] = str(value) + elif value_type == 'number': + is_integer = '.' not in str(field_validation.get('maximum')) + einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value) + value = einvoice[fieldname] + + max_length = field_validation.get('maxLength') + minimum = flt(field_validation.get('minimum')) + maximum = flt(field_validation.get('maximum')) + pattern_str = field_validation.get('pattern') + pattern = re.compile(pattern_str or '') + + label = field_validation.get('description') or fieldname + + if value_type == 'string' and len(value) > max_length: + errors.append(_('{} should not exceed {} characters').format(label, max_length)) + if value_type == 'number' and (value > maximum or value < minimum): + errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum)) + if pattern_str and not pattern.match(value): + errors.append(field_validation.get('validationMsg')) + + return errors + +class RequestFailed(Exception): pass + +class GSPConnector(): + def __init__(self, doctype=None, docname=None): + self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') + self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None + self.credentials = self.get_credentials() + + self.base_url = 'https://gsp.adaequare.com/' + self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' + self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' + self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' + self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn' + self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel' + self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi' + self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill' + + def get_credentials(self): + if self.invoice: + gstin = self.get_seller_gstin() + credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin) + else: + credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None + return credentials + + def get_seller_gstin(self): + gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin') + if not gstin: + frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.')) + return gstin + + def get_auth_token(self): + if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0: + self.fetch_auth_token() + + return self.e_invoice_settings.auth_token + + def make_request(self, request_type, url, headers=None, data=None): + if request_type == 'post': + res = make_post_request(url, headers=headers, data=data) + else: + res = make_get_request(url, headers=headers, data=data) + + self.log_request(url, headers, data, res) + return res + + def log_request(self, url, headers, data, res): + headers.update({ 'password': self.credentials.password }) + request_log = frappe.get_doc({ + "doctype": "E Invoice Request Log", + "user": frappe.session.user, + "reference_invoice": self.invoice.name if self.invoice else None, + "url": url, + "headers": json.dumps(headers, indent=4) if headers else None, + "data": json.dumps(data, indent=4) if isinstance(data, dict) else data, + "response": json.dumps(res, indent=4) if res else None + }) + request_log.insert(ignore_permissions=True) + frappe.db.commit() + + def fetch_auth_token(self): + headers = { + 'gspappid': frappe.conf.einvoice_client_id, + 'gspappsecret': frappe.conf.einvoice_client_secret + } + res = {} + try: + res = self.make_request('post', self.authenticate_url, headers) + self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token')) + self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in')) + self.e_invoice_settings.save() + + except Exception: + self.log_error(res) + self.raise_error(True) + + def get_headers(self): + return { + 'content-type': 'application/json', + 'user_name': self.credentials.username, + 'password': self.credentials.get_password(), + 'gstin': self.credentials.gstin, + 'authorization': self.get_auth_token(), + 'requestid': str(base64.b64encode(os.urandom(18))), + } + + def fetch_gstin_details(self, gstin): + headers = self.get_headers() + + try: + params = '?gstin={gstin}'.format(gstin=gstin) + res = self.make_request('get', self.gstin_details_url + params, headers) + if res.get('success'): + return res.get('result') + else: + self.log_error(res) + raise RequestFailed + + except RequestFailed: + self.raise_error() + + except Exception: + self.log_error() + self.raise_error(True) + + @staticmethod + def get_gstin_details(gstin): + '''fetch and cache GSTIN details''' + if not hasattr(frappe.local, 'gstin_cache'): + frappe.local.gstin_cache = {} + + key = gstin + gsp_connector = GSPConnector() + details = gsp_connector.fetch_gstin_details(gstin) + + frappe.local.gstin_cache[key] = details + frappe.cache().hset('gstin_cache', key, details) + return details + + def generate_irn(self): + headers = self.get_headers() + einvoice = make_einvoice(self.invoice) + data = json.dumps(einvoice, indent=4) + + try: + res = self.make_request('post', self.generate_irn_url, headers, data) + if res.get('success'): + self.set_einvoice_data(res.get('result')) + + elif '2150' in res.get('message'): + # IRN already generated but not updated in invoice + # Extract the IRN from the response description and fetch irn details + irn = res.get('result')[0].get('Desc').get('Irn') + irn_details = self.get_irn_details(irn) + if irn_details: + self.set_einvoice_data(irn_details) + else: + raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \ + Contact ERPNext support to resolve the issue.') + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + self.log_error(data) + self.raise_error(True) + + def get_irn_details(self, irn): + headers = self.get_headers() + + try: + params = '?irn={irn}'.format(irn=irn) + res = self.make_request('get', self.irn_details_url + params, headers) + if res.get('success'): + return res.get('result') + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + self.log_error() + self.raise_error(True) + + def cancel_irn(self, irn, reason, remark): + headers = self.get_headers() + data = json.dumps({ + 'Irn': irn, + 'Cnlrsn': reason, + 'Cnlrem': remark + }, indent=4) + + try: + res = self.make_request('post', self.cancel_irn_url, headers, data) + if res.get('success'): + self.invoice.irn_cancelled = 1 + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('IRN Cancelled - {}').format(remark) + } + self.update_invoice() + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + self.log_error(data) + self.raise_error(True) + + def generate_eway_bill(self, **kwargs): + args = frappe._dict(kwargs) + + headers = self.get_headers() + eway_bill_details = get_eway_bill_details(args) + data = json.dumps({ + 'Irn': args.irn, + 'Distance': cint(eway_bill_details.distance), + 'TransMode': eway_bill_details.mode_of_transport, + 'TransId': eway_bill_details.gstin, + 'TransName': eway_bill_details.transporter, + 'TrnDocDt': eway_bill_details.document_date, + 'TrnDocNo': eway_bill_details.document_name, + 'VehNo': eway_bill_details.vehicle_no, + 'VehType': eway_bill_details.vehicle_type + }, indent=4) + + try: + res = self.make_request('post', self.generate_ewaybill_url, headers, data) + if res.get('success'): + self.invoice.ewaybill = res.get('result').get('EwbNo') + self.invoice.eway_bill_cancelled = 0 + self.invoice.update(args) + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('E-Way Bill Generated') + } + self.update_invoice() + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + self.log_error(data) + self.raise_error(True) + + def cancel_eway_bill(self, eway_bill, reason, remark): + headers = self.get_headers() + data = json.dumps({ + 'ewbNo': eway_bill, + 'cancelRsnCode': reason, + 'cancelRmrk': remark + }, indent=4) + + try: + res = self.make_request('post', self.cancel_ewaybill_url, headers, data) + if res.get('success'): + self.invoice.ewaybill = '' + self.invoice.eway_bill_cancelled = 1 + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('E-Way Bill Cancelled - {}').format(remark) + } + self.update_invoice() + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + self.log_error(data) + self.raise_error(True) + + def sanitize_error_message(self, message): + ''' + On validation errors, response message looks something like this: + message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, + 3095 : Supplier GSTIN is inactive' + we search for string between ':' to extract the error messages + errors = [ + ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ', + ': Test' + ] + then we trim down the message by looping over errors + ''' + errors = re.findall(': [^:]+', message) + for idx, e in enumerate(errors): + # remove colons + errors[idx] = errors[idx].replace(':', '').strip() + # if not last + if idx != len(errors) - 1: + # remove last 7 chars eg: ', 3095 ' + errors[idx] = errors[idx][:-6] + + return errors + + def log_error(self, data={}): + if not isinstance(data, dict): + data = json.loads(data) + + seperator = "--" * 50 + err_tb = traceback.format_exc() + err_msg = str(sys.exc_info()[1]) + data = json.dumps(data, indent=4) + + message = "\n".join([ + "Error", err_msg, seperator, + "Data:", data, seperator, + "Exception:", err_tb + ]) + frappe.log_error(title=_('E Invoice Request Failed'), message=message) + + def raise_error(self, raise_exception=False, errors=[]): + title = _('E Invoice Request Failed') + if errors: + frappe.throw(errors, title=title, as_list=1) + else: + link_to_error_list = 'Error Log' + frappe.msgprint( + _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list), + title=title, + raise_exception=raise_exception, + indicator='red' + ) + + def set_einvoice_data(self, res): + enc_signed_invoice = res.get('SignedInvoice') + dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data'] + + self.invoice.irn = res.get('Irn') + self.invoice.ewaybill = res.get('EwbNo') + self.invoice.signed_einvoice = dec_signed_invoice + self.invoice.signed_qr_code = res.get('SignedQRCode') + + self.attach_qrcode_image() + + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('IRN Generated') + } + self.update_invoice() + + def attach_qrcode_image(self): + qrcode = self.invoice.signed_qr_code + doctype = self.invoice.doctype + docname = self.invoice.name + + _file = frappe.new_doc('File') + _file.update({ + 'file_name': f'QRCode_{docname}.png', + 'attached_to_doctype': doctype, + 'attached_to_name': docname, + 'content': 'qrcode', + 'is_private': 1 + }) + _file.insert() + frappe.db.commit() + url = qrcreate(qrcode, error='L') + abs_file_path = os.path.abspath(_file.get_full_path()) + url.png(abs_file_path, scale=2, quiet_zone=1) + + self.invoice.qrcode_image = _file.file_url + + def update_invoice(self): + self.invoice.flags.ignore_validate_update_after_submit = True + self.invoice.flags.ignore_validate = True + self.invoice.save() + +@frappe.whitelist() +def get_einvoice(doctype, docname): + invoice = frappe.get_doc(doctype, docname) + return make_einvoice(invoice) + +@frappe.whitelist() +def generate_irn(doctype, docname): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.generate_irn() + +@frappe.whitelist() +def cancel_irn(doctype, docname, irn, reason, remark): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.cancel_irn(irn, reason, remark) + +@frappe.whitelist() +def generate_eway_bill(doctype, docname, **kwargs): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.generate_eway_bill(**kwargs) + +@frappe.whitelist() +def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.cancel_eway_bill(eway_bill, reason, remark) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index cbcd6e3203..5321a9a3b5 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -87,7 +87,7 @@ def add_custom_roles_for_reports(): )).insert() def add_permissions(): - for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'): + for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): add_permission(doctype, role, 0) @@ -103,9 +103,10 @@ def add_permissions(): def add_print_formats(): frappe.reload_doc("regional", "print_format", "gst_tax_invoice") frappe.reload_doc("accounts", "print_format", "gst_pos_invoice") + frappe.reload_doc("accounts", "print_format", "GST E-Invoice") frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where - name in('GST POS Invoice', 'GST Tax Invoice') """) + name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """) def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', @@ -351,7 +352,6 @@ def make_custom_fields(update=True): 'label': 'Mode of Transport', 'fieldtype': 'Select', 'options': '\nRoad\nAir\nRail\nShip', - 'default': 'Road', 'insert_after': 'transporter_name', 'print_hide': 1, 'translatable': 0 @@ -388,13 +388,34 @@ def make_custom_fields(update=True): 'fieldname': 'ewaybill', 'label': 'E-Way Bill No.', 'fieldtype': 'Data', - 'depends_on': 'eval:(doc.docstatus === 1)', + 'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)', 'allow_on_submit': 1, 'insert_after': 'tax_id', 'translatable': 0 } ] + si_einvoice_fields = [ + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) + ] + custom_fields = { 'Address': [ dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data', @@ -407,7 +428,7 @@ def make_custom_fields(update=True): 'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields, 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, - 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields, 'Sales Order': sales_invoice_gst_fields, 'Tax Category': inter_state_gst_field, diff --git a/requirements.txt b/requirements.txt index c4f9171fca..c448ebca2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ taxjar==1.9.0 tweepy==3.8.0 Unidecode==1.1.1 WooCommerce==2.1.1 +pycryptodome==3.9.8 \ No newline at end of file From af1af925ac9e0997553c784f7d5dc5f3485d115b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 26 Dec 2020 11:09:46 +0530 Subject: [PATCH 0228/1264] chore: Added change log (#24213) --- erpnext/change_log/v13/v13_0_0-beta_6.md | 151 +++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0-beta_6.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_6.md b/erpnext/change_log/v13/v13_0_0-beta_6.md new file mode 100644 index 0000000000..4c6d9c28cf --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0-beta_6.md @@ -0,0 +1,151 @@ +### Version 13.0.0 Beta 6 Release Notes + +#### Features and Enhancements + +- GST E-invoicing for India ([#23455](https://github.com/frappe/erpnext/pull/23455)) +- Multi-currency payroll ([#23519](https://github.com/frappe/erpnext/pull/23519)) +- Allow back-dated stock transactions and repost item costing via background job ([#24183](https://github.com/frappe/erpnext/pull/24183)) +- Introduced telephony feature using Twillio ([#24032](https://github.com/frappe/erpnext/pull/24032)) +- Shipment Doctype ([#22914](https://github.com/frappe/erpnext/pull/22914)) +- Leave policy assignment ([#23112](https://github.com/frappe/erpnext/pull/23112)) +- UAE VAT 201 Report ([#23447](https://github.com/frappe/erpnext/pull/23447)) +- Return tracking in PR/DN ([#22859](https://github.com/frappe/erpnext/pull/22859)) +- Close Production Plan ([#23728](https://github.com/frappe/erpnext/pull/23728)) +- Quality Inspection on Job Card ([#23964](https://github.com/frappe/erpnext/pull/23964)) +- Inpatient Medication Orders Script Report ([#23984](https://github.com/frappe/erpnext/pull/23984)) +- Leave type with partial payment ([#23173](https://github.com/frappe/erpnext/pull/23173)) +- Formula based Quality Inspection ([#23916](https://github.com/frappe/erpnext/pull/23916)) +- Link to Material Requests in Tools section for RFQ and Supplier Quotation ([#23429](https://github.com/frappe/erpnext/pull/23429)) +- Hide images & auto add item checkbox ([#24102](https://github.com/frappe/erpnext/pull/24102)) +- In reports get item details from Item instead of the transactions ([#24082](https://github.com/frappe/erpnext/pull/24082)) +- sales order status filter added for production plan ([#23805](https://github.com/frappe/erpnext/pull/23805)) +- Button to create Stock Entry for Drug Shortage ([#24012](https://github.com/frappe/erpnext/pull/24012)) +- Sync old shopify orders ([#23841](https://github.com/frappe/erpnext/pull/23841)) +- Added column cost center in Accounts Receivable report ([#23835](https://github.com/frappe/erpnext/pull/23835)) +- Added jinja templating in Contract Template ([#24046](https://github.com/frappe/erpnext/pull/24046)) +- Consider Holiday List in Student Leave Application and Attendance ([#23388](https://github.com/frappe/erpnext/pull/23388)) +- Make account number length configurable ([#23845](https://github.com/frappe/erpnext/pull/23845)) +- Add communication channel to communication medium ([#23793](https://github.com/frappe/erpnext/pull/23793)) + +#### Fixes + +- Loan disbursement amount validation ([#24000](https://github.com/frappe/erpnext/pull/24000)) +- Making company address read-only in delivery note ([#23890](https://github.com/frappe/erpnext/pull/23890)) +- BOM stock report color showing always red ([#23994](https://github.com/frappe/erpnext/pull/23994)) +- Added filter for customer field in Issue ([#24051](https://github.com/frappe/erpnext/pull/24051)) +- Added project link in timesheet form ([#23764](https://github.com/frappe/erpnext/pull/23764)) +- Update integrations desk page ([#23767](https://github.com/frappe/erpnext/pull/23767)) +- Place of supply change on address change ([#23941](https://github.com/frappe/erpnext/pull/23941)) +- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23672](https://github.com/frappe/erpnext/pull/23672)) +- Auto fetch serial nos with modified conversion factor ([#23854](https://github.com/frappe/erpnext/pull/23854)) +- Default cost center in item master not set in stock entry ([#23877](https://github.com/frappe/erpnext/pull/23877)) +- Incorrect de-link serial no and batch ([#23947](https://github.com/frappe/erpnext/pull/23947)) +- Accounting for internal transfer invoices within same company ([#24021](https://github.com/frappe/erpnext/pull/24021)) +- Multiple pricing rule with margin type as Percentage is not working ([#24205](https://github.com/frappe/erpnext/pull/24205)) +- Added Purchase Order to Global Search ([#24055](https://github.com/frappe/erpnext/pull/24055)) +- Cannot expand row in update items dialog ([#23839](https://github.com/frappe/erpnext/pull/23839)) +- Maintain stock can't be changed it there is product bundle ([#23989](https://github.com/frappe/erpnext/pull/23989)) +- SO to PO Mapping Issue ([#23820](https://github.com/frappe/erpnext/pull/23820)) +- Asset with value zero doesn't show up in fixed asset register ([#24091](https://github.com/frappe/erpnext/pull/24091)) +- Cannot save customer email & phone ([#23797](https://github.com/frappe/erpnext/pull/23797)) +- Incorrect balance value in stock balance report ([#24048](https://github.com/frappe/erpnext/pull/24048)) +- Payment Terms not fetched in Purchase Invoice from Purchase Receipt ([#23735](https://github.com/frappe/erpnext/pull/23735)) +- Fix for LMS Sign Up link ([#23743](https://github.com/frappe/erpnext/pull/23743)) +- Incorrect stock quantity if 'Allow Multiple Material Consumption… ([#24116](https://github.com/frappe/erpnext/pull/24116)) +- Added wrong absent days calculation in salary slip ([#23897](https://github.com/frappe/erpnext/pull/23897)) +- Purchase receipt to purchase invoice bill date mapping ([#23967](https://github.com/frappe/erpnext/pull/23967)) +- Overriding po ([#24022](https://github.com/frappe/erpnext/pull/24022)) +- Do not cancel reference document on Quality Inspection cancellation ([#24198](https://github.com/frappe/erpnext/pull/24198)) +- Get formatted value in 'taxes' print template ([#24035](https://github.com/frappe/erpnext/pull/24035)) +- Don't overrule Item Price via Pricing Rule Rate if 0 ([#23636](https://github.com/frappe/erpnext/pull/23636)) +- Job card error handling for operations field ([#23991](https://github.com/frappe/erpnext/pull/23991)) +- Validation for journal entry with 0 debit and credit values ([#23975](https://github.com/frappe/erpnext/pull/23975)) +- Check if customer exists in product listing ([#24030](https://github.com/frappe/erpnext/pull/24030)) +- Asset finance book posting date fix ([#23778](https://github.com/frappe/erpnext/pull/23778)) +- Same source and target tables in Status Updater's update query ([#24110](https://github.com/frappe/erpnext/pull/24110)) +- Asset finance book depreciation posting date fix ([#23833](https://github.com/frappe/erpnext/pull/23833)) +- Ignore exception during leave ledger creation from patch ([#24005](https://github.com/frappe/erpnext/pull/24005)) +- Added link of bank reconciliation and clearance in accounting desk page ([#23850](https://github.com/frappe/erpnext/pull/23850)) +- Sales invoice add button from sales order dashboard ([#24077](https://github.com/frappe/erpnext/pull/24077)) +- Incorrect calculation for consumed qty for subcontract item ([#23257](https://github.com/frappe/erpnext/pull/23257)) +- Incorrect required_qty in Production Planning Report ([#24074](https://github.com/frappe/erpnext/pull/24074)) +- Email digest user not found ([#23949](https://github.com/frappe/erpnext/pull/23949)) +- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24115](https://github.com/frappe/erpnext/pull/24115)) +- Added TDS Payable account number and an error message ([#24065](https://github.com/frappe/erpnext/pull/24065)) +- Override field_map for job card gantt ([#24155](https://github.com/frappe/erpnext/pull/24155)) +- Old shopify order syncing date ([#23990](https://github.com/frappe/erpnext/pull/23990)) +- Shipping chanrges not sync in erpnext from shopify ([#24114](https://github.com/frappe/erpnext/pull/24114)) +- GSTR B2C report ([#24039](https://github.com/frappe/erpnext/pull/24039)) +- Ignore cancelled entries in stock balance report ([#23757](https://github.com/frappe/erpnext/pull/23757)) +- Stock ageing report not working ([#23923](https://github.com/frappe/erpnext/pull/23923)) +- Incorrect assign to in Maintenance Schedule ([#23831](https://github.com/frappe/erpnext/pull/23831)) +- Improve UX of DATEV report ([#23892](https://github.com/frappe/erpnext/pull/23892)) +- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) +- dDouble exception in payroll ([#24078](https://github.com/frappe/erpnext/pull/24078)) +- Make asset dashboard charts public ([#23751](https://github.com/frappe/erpnext/pull/23751)) +- Don't copy terms and discount from SO to PO ([#23903](https://github.com/frappe/erpnext/pull/23903)) +- Ignore doctypes on company transaction delete ([#23864](https://github.com/frappe/erpnext/pull/23864)) +- Error handling in Upload Attendance ([#23907](https://github.com/frappe/erpnext/pull/23907)) +- Tax template update on customer address change ([#24160](https://github.com/frappe/erpnext/pull/24160)) +- Not able to save bom ([#23910](https://github.com/frappe/erpnext/pull/23910)) +- Enable Allow Auto Repeat for standard doctypes having auto_repeat field ([#23776](https://github.com/frappe/erpnext/pull/23776)) +- Place of Supply fix in Sales Invoices ([#23785](https://github.com/frappe/erpnext/pull/23785)) +- Opening invoices in GSTR-1 report ([#24117](https://github.com/frappe/erpnext/pull/24117)) +- Add check for allowing access to european region ([#23770](https://github.com/frappe/erpnext/pull/23770)) +- Partial serial no return issue ([#24208](https://github.com/frappe/erpnext/pull/24208)) +- Multiple pos issues ([#23347](https://github.com/frappe/erpnext/pull/23347)) +- Import taxjar globally in the taxjar_integration module ([#24027](https://github.com/frappe/erpnext/pull/24027)) +- Payroll attendance error ([#23887](https://github.com/frappe/erpnext/pull/23887)) +- Cannot add items to cart ([#23796](https://github.com/frappe/erpnext/pull/23796)) +- Show tax amount in base currencies ([#24069](https://github.com/frappe/erpnext/pull/24069)) +- Loan application link on creating loan ([#23937](https://github.com/frappe/erpnext/pull/23937)) +- Added shipment link in delivery note dashboard ([#24210](https://github.com/frappe/erpnext/pull/24210)) +- POS item search includes non stock items ([#23914](https://github.com/frappe/erpnext/pull/23914)) +- Paid amount in Sales Invoice POS return resets to 0 ([#24057](https://github.com/frappe/erpnext/pull/24057)) +- Remove check for exempt_from_sales_tax ([#23870](https://github.com/frappe/erpnext/pull/23870)) +- Fiscal year can be shorter than 12 months ([#23838](https://github.com/frappe/erpnext/pull/23838)) +- Loan repayment type option remove ([#23582](https://github.com/frappe/erpnext/pull/23582)) +- Make contract template editable ([#23891](https://github.com/frappe/erpnext/pull/23891)) +- Item wise tax calculation ([#23744](https://github.com/frappe/erpnext/pull/23744)) +- Enabling track changes for stock settings ([#23982](https://github.com/frappe/erpnext/pull/23982)) +- Added link of bank reconciliation and clearance in accounting desk page ([#23809](https://github.com/frappe/erpnext/pull/23809)) +- Location data on Asset to use command(make_demo) ([#23825](https://github.com/frappe/erpnext/pull/23825)) +- Handle Account and Item None not found in Opening Invoice Creation Tool ([#23559](https://github.com/frappe/erpnext/pull/23559)) +- List index out of range on including UOM ([#23814](https://github.com/frappe/erpnext/pull/23814)) +- Showing error for wrong filters. ([#23726](https://github.com/frappe/erpnext/pull/23726)) +- Multiple subcontracting issues ([#23662](https://github.com/frappe/erpnext/pull/23662)) +- Sequence id override with workstation column ([#23810](https://github.com/frappe/erpnext/pull/23810)) +- Replaced formatdate -> format_date ([#23849](https://github.com/frappe/erpnext/pull/23849)) +- Test Payment Based on Leave Application (Travis) ([#24044](https://github.com/frappe/erpnext/pull/24044)) +- Leave policy dashboard fix and roles ([#24170](https://github.com/frappe/erpnext/pull/24170)) +- Budget test cases ([#23801](https://github.com/frappe/erpnext/pull/23801)) +- Handle for custom field IFSC code in Bank remittance report. ([#23905](https://github.com/frappe/erpnext/pull/23905)) +- Scan barcode does not update barcode item field in sales order ([#24090](https://github.com/frappe/erpnext/pull/24090)) +- Item price duplicate checking ([#23408](https://github.com/frappe/erpnext/pull/23408)) +- Tax template update on supplier change for India ([#24060](https://github.com/frappe/erpnext/pull/24060)) +- Consumed qty logic for subcontracted raw materials ([#23314](https://github.com/frappe/erpnext/pull/23314)) +- Finance book not getting added in journal Entry of asset value adjustment ([#24100](https://github.com/frappe/erpnext/pull/24100)) +- Fixed home desk page ([#24075](https://github.com/frappe/erpnext/pull/24075)) +- po_detail field has no value for subcontracted stock entry ([#23777](https://github.com/frappe/erpnext/pull/23777)) +- Set proper state code in ewaybill JSON when GST category is SEZ ([#23953](https://github.com/frappe/erpnext/pull/23953)) +- Copying po no when mapping doc ([#23729](https://github.com/frappe/erpnext/pull/23729)) +- Duplicate items validation for POS Invoice when allow multiple items is disabled ([#23896](https://github.com/frappe/erpnext/pull/23896)) +- POS register shows cancelled documents ([#23747](https://github.com/frappe/erpnext/pull/23747)) +- Subscription test case ([#23763](https://github.com/frappe/erpnext/pull/23763)) +- BOM stock report color issue ([#23980](https://github.com/frappe/erpnext/pull/23980)) +- Handle the "no leave_allocation found" case ([#23922](https://github.com/frappe/erpnext/pull/23922)) +- Filters for tax templates ([#23998](https://github.com/frappe/erpnext/pull/23998)) +- Do not allow Company as accounting dimension ([#23749](https://github.com/frappe/erpnext/pull/23749)) +- Validation for duplicate Tax Category ([#23978](https://github.com/frappe/erpnext/pull/23978)) +- Therapy plan and session fixes ([#23817](https://github.com/frappe/erpnext/pull/23817)) +- Correcting description field in taxes and charges for accounts with account number + account name ([#23836](https://github.com/frappe/erpnext/pull/23836)) +- Pricing rule with transaction not working for additional product ([#24053](https://github.com/frappe/erpnext/pull/24053)) +- Keyerror 'sourced_by_supplier' ([#24038](https://github.com/frappe/erpnext/pull/24038)) +- Validation for membership ([#23934](https://github.com/frappe/erpnext/pull/23934)) +- Inpatient Medication Order and Entry fixes ([#23799](https://github.com/frappe/erpnext/pull/23799)) +- Avoid using SQL query to get fiscal year dates ([#24050](https://github.com/frappe/erpnext/pull/24050)) +- Auto Statewise gst tax template ([#23832](https://github.com/frappe/erpnext/pull/23832)) +- POS profile has no attr 'show_only_available_items' ([#23758](https://github.com/frappe/erpnext/pull/23758)) +- On save sequence id column override with workstation ([#23812](https://github.com/frappe/erpnext/pull/23812)) +- Multiple pricing rules are not working on selling side ([#22711](https://github.com/frappe/erpnext/pull/22711)) +- Salary slip popup error ([#24192](https://github.com/frappe/erpnext/pull/24192)) \ No newline at end of file From d1d030b54a0375580b4139943aa9d61b576ef317 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 26 Dec 2020 15:15:51 +0550 Subject: [PATCH 0229/1264] bumped to version 13.0.0-beta.7 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 14d3563262..c108333e1a 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.6' +__version__ = '13.0.0-beta.7' def get_default_company(user=None): '''Get default company for user''' From a25cb9a563f468e6ea4072bae5ff03d54af6221a Mon Sep 17 00:00:00 2001 From: Karthikeyan S Date: Wed, 30 Dec 2020 19:41:00 +0530 Subject: [PATCH 0230/1264] fix(GST E Invoice): update live URLs for adaequare GSP (#24251) --- erpnext/regional/india/e_invoice/utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index cb92c42464..61fb88a21b 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -386,15 +386,15 @@ class GSPConnector(): self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None self.credentials = self.get_credentials() - self.base_url = 'https://gsp.adaequare.com/' - self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' - self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' - self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' - self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn' - self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel' - self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi' - self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill' - + self.base_url = 'https://gsp.adaequare.com' + self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token' + self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' + self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' + self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' + self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' + self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi' + self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' + def get_credentials(self): if self.invoice: gstin = self.get_seller_gstin() From fc7a11e2aa4ba5d97354e3f6e2e4792e228b5705 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 31 Dec 2020 11:35:46 +0530 Subject: [PATCH 0231/1264] fix (e-invoicing): item & invoice value calculation (#24253) --- .../sales_invoice/test_sales_invoice.py | 63 +++++++++++++----- erpnext/patches.txt | 1 + .../patches/v12_0/setup_einvoice_fields.py | 1 + .../india/e_invoice/einv_template.json | 2 +- erpnext/regional/india/e_invoice/utils.py | 66 +++++++++---------- 5 files changed, 81 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 3c681eeecf..eb223ee42c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1885,8 +1885,8 @@ class TestSalesInvoice(unittest.TestCase): "item_code": "_Test Item", "uom": "Nos", "warehouse": "_Test Warehouse - _TC", - "qty": 2, - "rate": 100, + "qty": 2000, + "rate": 12, "income_account": "Sales - _TC", "expense_account": "Cost of Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", @@ -1895,31 +1895,52 @@ class TestSalesInvoice(unittest.TestCase): "item_code": "_Test Item 2", "uom": "Nos", "warehouse": "_Test Warehouse - _TC", - "qty": 4, - "rate": 150, + "qty": 420, + "rate": 15, "income_account": "Sales - _TC", "expense_account": "Cost of Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", }) + si.discount_amount = 100 si.save() einvoice = make_einvoice(si) - total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']]) - total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']]) - total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']]) - total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']]) - total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']]) + total_item_ass_value = 0 + total_item_cgst_value = 0 + total_item_sgst_value = 0 + total_item_igst_value = 0 + total_item_value = 0 + + for item in einvoice['ItemList']: + total_item_ass_value += item['AssAmt'] + total_item_cgst_value += item['CgstAmt'] + total_item_sgst_value += item['SgstAmt'] + total_item_igst_value += item['IgstAmt'] + total_item_value += item['TotItemVal'] + + self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount']) + self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt']) + + value_details = einvoice['ValDtls'] self.assertEqual(einvoice['Version'], '1.1') - self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value) - self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value) - self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value) - self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value) - self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value) + self.assertEqual(value_details['AssVal'], total_item_ass_value) + self.assertEqual(value_details['CgstVal'], total_item_cgst_value) + self.assertEqual(value_details['SgstVal'], total_item_sgst_value) + self.assertEqual(value_details['IgstVal'], total_item_igst_value) + + self.assertEqual( + value_details['TotInvVal'], + value_details['AssVal'] + value_details['CgstVal'] + + value_details['SgstVal'] + value_details['IgstVal'] + + value_details['OthChrg'] - value_details['Discount'] + ) + + self.assertEqual(value_details['TotInvVal'], si.base_grand_total) self.assertTrue(einvoice['EwbDtls']) -def make_sales_invoice_for_ewaybill(): +def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): address = frappe.get_doc({ "address_line1": "_Test Address Line 1", @@ -1967,7 +1988,8 @@ def make_sales_invoice_for_ewaybill(): }) address.save() - + +def make_test_transporter_for_ewaybill(): if not frappe.db.exists('Supplier', '_Test Transporter'): frappe.get_doc({ "doctype": "Supplier", @@ -1978,12 +2000,17 @@ def make_sales_invoice_for_ewaybill(): "is_transporter": 1 }).insert() +def make_sales_invoice_for_ewaybill(): + make_test_address_for_ewaybill() + make_test_transporter_for_ewaybill() + gst_settings = frappe.get_doc("GST Settings") gst_account = frappe.get_all( "GST Account", fields=["cgst_account", "sgst_account", "igst_account"], - filters = {"company": "_Test Company"}) + filters = {"company": "_Test Company"} + ) if not gst_account: gst_settings.append("gst_accounts", { @@ -1995,7 +2022,7 @@ def make_sales_invoice_for_ewaybill(): gst_settings.save() - si = create_sales_invoice(do_not_save =1, rate = '60000') + si = create_sales_invoice(do_not_save=1, rate='60000') si.distance = 2000 si.company_address = "_Test Address for Eway bill-Billing" diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5b37b38e68..894ee82b4c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -712,6 +712,7 @@ erpnext.patches.v13_0.delete_old_sales_reports 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 +execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings") 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 diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index d0782765de..2474bc3b82 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -8,6 +8,7 @@ def execute(): if not company: return + frappe.reload_doc("custom", "doctype", "custom_field") frappe.reload_doc("regional", "doctype", "e_invoice_settings") custom_fields = { 'Sales Invoice': [ diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json index e5751da561..60f490d616 100644 --- a/erpnext/regional/india/e_invoice/einv_template.json +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -59,7 +59,7 @@ {item_list} ], "ValDtls": {{ - "AssVal": "{invoice_value_details.base_net_total}", + "AssVal": "{invoice_value_details.base_total}", "CgstVal": "{invoice_value_details.total_cgst_amt}", "SgstVal": "{invoice_value_details.total_sgst_amt}", "IgstVal": "{invoice_value_details.total_igst_amt}", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 61fb88a21b..102a2f0f56 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -146,12 +146,12 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.qty = abs(item.qty) - item.description = d.item_name - item.taxable_value = abs(item.base_net_amount) item.discount_amount = abs(item.discount_amount * item.qty) - item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) - item.gross_amount = abs(item.unit_rate * item.qty) + item.description = d.item_name + item.qty = abs(item.qty) + item.unit_rate = abs(item.base_amount / item.qty) + item.gross_amount = abs(item.base_amount) + item.taxable_value = abs(item.base_amount) item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None @@ -180,35 +180,35 @@ def update_item_taxes(invoice, item): item[attr] = 0 for t in invoice.taxes: + # this contains item wise tax rate & tax amount (incl. discount) item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) if t.account_head in gst_accounts_list: + item_tax_rate = item_tax_detail[0] + # item tax amount excluding discount amount + item_tax_amount = (item_tax_rate / 100) * item.base_amount + if t.account_head in gst_accounts.cess_account: + item_tax_amount_after_discount = item_tax_detail[1] if t.charge_type == 'On Item Quantity': - item.cess_nadv_amount += abs(item_tax_detail[1]) + item.cess_nadv_amount += abs(item_tax_amount_after_discount) else: - item.cess_rate += item_tax_detail[0] - item.cess_amount += abs(item_tax_detail[1]) - elif t.account_head in gst_accounts.igst_account: - item.tax_rate += item_tax_detail[0] - item.igst_amount += abs(item_tax_detail[1]) - elif t.account_head in gst_accounts.sgst_account: - item.tax_rate += item_tax_detail[0] - item.sgst_amount += abs(item_tax_detail[1]) - elif t.account_head in gst_accounts.cgst_account: - item.tax_rate += item_tax_detail[0] - item.cgst_amount += abs(item_tax_detail[1]) - + item.cess_rate += item_tax_rate + item.cess_amount += abs(item_tax_amount_after_discount) + + for tax_type in ['igst', 'cgst', 'sgst']: + if t.account_head in gst_accounts[f'{tax_type}_account']: + item.tax_rate += item_tax_rate + item[f'{tax_type}_amount'] += abs(item_tax_amount) + return item def get_invoice_value_details(invoice): invoice_value_details = frappe._dict(dict()) - invoice_value_details.base_net_total = abs(invoice.base_net_total) - invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 - # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off - invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) - disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') - invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total) - invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total) + invoice_value_details.base_total = abs(invoice.base_total) + invoice_value_details.invoice_discount_amt = invoice.discount_amount + invoice_value_details.round_off = invoice.rounding_adjustment + invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) + invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) @@ -226,15 +226,14 @@ def update_invoice_taxes(invoice, invoice_value_details): for t in invoice.taxes: if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: + # using after discount amt since item also uses after discount amt for cess calc invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) - elif t.account_head in gst_accounts.igst_account: - invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount) - elif t.account_head in gst_accounts.sgst_account: - invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) - elif t.account_head in gst_accounts.cgst_account: - invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount) + + for tax_type in ['igst', 'cgst', 'sgst']: + if t.account_head in gst_accounts[f'{tax_type}_account']: + invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount) else: - invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_other_charges += abs(t.base_tax_amount) return invoice_value_details @@ -358,7 +357,8 @@ def validate_einvoice(validations, einvoice, errors=[]): einvoice[fieldname] = str(value) elif value_type == 'number': is_integer = '.' not in str(field_validation.get('maximum')) - einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value) + precision = 3 if '.999' in str(field_validation.get('maximum')) else 2 + einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value) value = einvoice[fieldname] max_length = field_validation.get('maxLength') From 08b89d1ce02894ce87d348eafd4d043740b384e3 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 31 Dec 2020 12:12:58 +0550 Subject: [PATCH 0232/1264] bumped to version 13.0.0-beta.8 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index c108333e1a..6599ee814f 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.7' +__version__ = '13.0.0-beta.8' def get_default_company(user=None): '''Get default company for user''' From f8f015c96152f7f3346847c2c7dc36e23cbb08ce Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 31 Dec 2020 13:28:08 +0530 Subject: [PATCH 0233/1264] fix: cannot submit e-invoice if legal name not found --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 102a2f0f56..02ce6c14c9 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -88,7 +88,7 @@ def get_party_details(address_name): gstin = address.get('gstin') gstin_details = get_gstin_details(gstin) - legal_name = gstin_details.get('LegalName') + legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName') location = gstin_details.get('AddrLoc') or address.get('city') state_code = gstin_details.get('StateCode') pincode = gstin_details.get('AddrPncd') From 16bce49f1856fae6e44650f53bde0c6c26561175 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 31 Dec 2020 17:39:52 +0530 Subject: [PATCH 0234/1264] fix: incorrect valuation rate for finished good --- erpnext/stock/doctype/stock_entry/stock_entry.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 60758b4f8a..6a0a363d2a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -512,7 +512,7 @@ class StockEntry(StockController): bom_items = self.get_bom_raw_materials(finished_item_qty) outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()]) - return flt(outgoing_items_cost - scrap_items_cost) + return flt((outgoing_items_cost - scrap_items_cost) / finished_item_qty) def distribute_additional_costs(self): # If no incoming items, set additional costs blank @@ -699,7 +699,7 @@ class StockEntry(StockController): # SLE for target warehouse self.get_sle_for_target_warehouse(sl_entries, finished_item_row) - + # reverse sl entries if cancel if self.docstatus == 2: sl_entries.reverse() @@ -727,9 +727,9 @@ class StockEntry(StockController): sle.dependant_sle_voucher_detail_no = d.name elif finished_item_row and (finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse): sle.dependant_sle_voucher_detail_no = finished_item_row.name - + sl_entries.append(sle) - + def get_sle_for_target_warehouse(self, sl_entries, finished_item_row): for d in self.get('items'): if cstr(d.t_warehouse): From 0feaab5261c9800454c03dbee50fe45c7533ef85 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 2 Jan 2021 20:19:56 +0550 Subject: [PATCH 0235/1264] bumped to version 13.0.0-beta.9 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6599ee814f..5fbf06d0d8 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.8' +__version__ = '13.0.0-beta.9' def get_default_company(user=None): '''Get default company for user''' From 60460a6d23d8374315358f3eb40bb8597e415e73 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 5 Jan 2021 13:54:04 +0530 Subject: [PATCH 0236/1264] fix(e-invoicing): minor calculation fixes (#24282) --- .../controllers/sales_and_purchase_return.py | 2 + erpnext/public/js/controllers/transaction.js | 1 + erpnext/regional/india/e_invoice/utils.py | 41 +++++++++++++------ erpnext/stock/get_item_details.py | 4 +- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 79792262c0..a048d6e2df 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -328,6 +328,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail target_doc.purchase_invoice_item = source_doc.name + target_doc.price_list_rate = 0 elif doctype == "Delivery Note": returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) @@ -353,6 +354,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account target_doc.sales_invoice_item = source_doc.name + target_doc.price_list_rate = 0 if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3bc20f8733..bed9c14141 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -543,6 +543,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company: me.frm.doc.company, order_type: me.frm.doc.order_type, is_pos: cint(me.frm.doc.is_pos), + is_return: cint(me.frm.doc.is_return), is_subcontracted: me.frm.doc.is_subcontracted, transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date, ignore_pricing_rule: me.frm.doc.ignore_pricing_rule, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 02ce6c14c9..e5f7d2d78c 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -92,21 +92,18 @@ def get_party_details(address_name): location = gstin_details.get('AddrLoc') or address.get('city') state_code = gstin_details.get('StateCode') pincode = gstin_details.get('AddrPncd') - address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) - address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) - email_id = address.get('email_id') - phone = address.get('phone') - # get last 10 digit - phone = phone.replace(" ", "")[-10:] if phone else '' + address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "") + address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "") if state_code == 97: # according to einvoice standard pincode = 999999 return frappe._dict(dict( - gstin=gstin, legal_name=legal_name, location=location, - pincode=pincode, state_code=state_code, address_line1=address_line1, - address_line2=address_line2, email=email_id, phone=phone + gstin=gstin, legal_name=legal_name, + location=location, pincode=pincode, + state_code=state_code, address_line1=address_line1, + address_line2=address_line2 )) def get_gstin_details(gstin): @@ -146,9 +143,10 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.discount_amount = abs(item.discount_amount * item.qty) - item.description = d.item_name + item.description = d.item_name.replace('"', '\\"') + item.qty = abs(item.qty) + item.discount_amount = abs(item.discount_amount * item.qty) item.unit_rate = abs(item.base_amount / item.qty) item.gross_amount = abs(item.base_amount) item.taxable_value = abs(item.base_amount) @@ -156,6 +154,7 @@ def get_item_list(invoice): item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' + item.serial_no = "" item = update_item_taxes(invoice, item) @@ -272,7 +271,25 @@ def get_eway_bill_details(invoice): vehicle_type=vehicle_type[invoice.gst_vehicle_type] )) +def validate_mandatory_fields(invoice): + if not invoice.company_address: + frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields')) + if not invoice.customer_address: + frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields')) + if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), + title=_('Missing Fields') + ) + if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), + title=_('Missing Fields') + ) + def make_einvoice(invoice): + validate_mandatory_fields(invoice) + schema = read_json('einv_template') transaction_details = get_transaction_details(invoice) @@ -351,7 +368,7 @@ def validate_einvoice(validations, einvoice, errors=[]): # remove empty dicts einvoice.pop(fieldname, None) continue - + # convert to int or str if value_type == 'string': einvoice[fieldname] = str(value) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 08f7a83b89..bf45251c9d 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,7 +74,9 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - get_price_list_rate(args, item, out) + if not doc or cint(doc.get('is_return')) == 0: + # get price list rate only if the invoice is not a credit or debit note + get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args)) From 80e0952ba73e3b31981e9917d86b8644cca14f10 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 5 Jan 2021 16:04:25 +0530 Subject: [PATCH 0237/1264] refactor: fetch & validate address from erpnext rather than gst portal --- erpnext/regional/india/e_invoice/utils.py | 54 ++++++++++++++--------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index e5f7d2d78c..abe15043af 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -15,7 +15,7 @@ from frappe import _, bold from pyqrcode import create as qrcreate from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date +from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form def validate_einvoice_fields(doc): einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) @@ -84,26 +84,32 @@ def get_doc_details(invoice): )) def get_party_details(address_name): - address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - gstin = address.get('gstin') + d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - gstin_details = get_gstin_details(gstin) - legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName') - location = gstin_details.get('AddrLoc') or address.get('city') - state_code = gstin_details.get('StateCode') - pincode = gstin_details.get('AddrPncd') - address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "") - address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "") + if (not d.gstin + or not d.city + or not d.pincode + or not d.address_title + or not d.address_line1 + or not d.gst_state_number): - if state_code == 97: + frappe.throw( + msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format( + get_link_to_form('Address', address_name) + ), + title=_('Missing Address Fields') + ) + + if d.gst_state_number == 97: # according to einvoice standard pincode = 999999 return frappe._dict(dict( - gstin=gstin, legal_name=legal_name, - location=location, pincode=pincode, - state_code=state_code, address_line1=address_line1, - address_line2=address_line2 + gstin=d.gstin, legal_name=d.address_title, + location=d.city, pincode=d.pincode, + state_code=d.gst_state_number, + address_line1=d.address_line1, + address_line2=d.address_line2 )) def get_gstin_details(gstin): @@ -124,14 +130,22 @@ def get_gstin_details(gstin): return GSPConnector.get_gstin_details(gstin) def get_overseas_address_details(address_name): - address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( - 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id'] + address_title, address_line1, address_line2, city = frappe.db.get_value( + 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city'] ) + if not address_title or not address_line1 or not city: + frappe.throw( + msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format( + get_link_to_form('Address', address_name) + ), + title=_('Missing Address Fields') + ) + return frappe._dict(dict( - gstin='URP', legal_name=address_title, address_line1=address_line1, - address_line2=address_line2, email=email_id, phone=phone, - pincode=999999, state_code=96, place_of_supply=96, location=city + gstin='URP', legal_name=address_title, location=city, + address_line1=address_line1, address_line2=address_line2, + pincode=999999, state_code=96, place_of_supply=96 )) def get_item_list(invoice): From df9144c198ef4f9ef9ade0d5bf5d5245300f6ff6 Mon Sep 17 00:00:00 2001 From: Leela vadlamudi Date: Tue, 15 Dec 2020 21:23:17 +0530 Subject: [PATCH 0238/1264] feat: Voice Call Settings doctype added (#24126) (cherry picked from commit 29778e2fba4b1f073fdfc048f784f755c57a1eeb) --- erpnext/public/js/telephony.js | 2 +- .../doctype/voice_call_settings/__init__.py | 0 .../test_voice_call_settings.py | 10 ++ .../voice_call_settings.js | 8 ++ .../voice_call_settings.json | 124 ++++++++++++++++++ .../voice_call_settings.py | 10 ++ 6 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 erpnext/telephony/doctype/voice_call_settings/__init__.py create mode 100644 erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py create mode 100644 erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js create mode 100644 erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json create mode 100644 erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py diff --git a/erpnext/public/js/telephony.js b/erpnext/public/js/telephony.js index bd7f890306..f9caadeed7 100644 --- a/erpnext/public/js/telephony.js +++ b/erpnext/public/js/telephony.js @@ -20,4 +20,4 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/telephony/doctype/voice_call_settings/__init__.py b/erpnext/telephony/doctype/voice_call_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py new file mode 100644 index 0000000000..85d6adda09 --- /dev/null +++ b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.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 TestVoiceCallSettings(unittest.TestCase): + pass diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js new file mode 100644 index 0000000000..4a61b612d0 --- /dev/null +++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.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('Voice Call Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json new file mode 100644 index 0000000000..25e55a22dc --- /dev/null +++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json @@ -0,0 +1,124 @@ +{ + "actions": [], + "autoname": "field:user", + "creation": "2020-12-08 16:52:40.590146", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user", + "call_receiving_device", + "column_break_3", + "greeting_message", + "agent_busy_message", + "agent_unavailable_message" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User", + "permlevel": 1, + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "greeting_message", + "fieldtype": "Data", + "label": "Greeting Message" + }, + { + "fieldname": "agent_busy_message", + "fieldtype": "Data", + "label": "Agent Busy Message" + }, + { + "fieldname": "agent_unavailable_message", + "fieldtype": "Data", + "label": "Agent Unavailable Message" + }, + { + "default": "Computer", + "fieldname": "call_receiving_device", + "fieldtype": "Select", + "label": "Call Receiving Device", + "options": "Computer\nPhone" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-12-14 18:49:34.600194", + "modified_by": "Administrator", + "module": "Telephony", + "name": "Voice Call Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 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 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py new file mode 100644 index 0000000000..ad3bbf1784 --- /dev/null +++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.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 VoiceCallSettings(Document): + pass From d43bb4db41d556056b72d73b80f2ea4571a14721 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 11 Jan 2021 12:59:35 +0530 Subject: [PATCH 0239/1264] fix(pos): error while merging pos invoices into sales invoice (#24337) --- .../doctype/pos_closing_entry/pos_closing_entry.py | 6 +++++- .../pos_invoice_merge_log/pos_invoice_merge_log.py | 11 ++++++----- erpnext/controllers/sales_and_purchase_return.py | 1 + erpnext/patches.txt | 1 + .../patches/v13_0/create_uae_pos_invoice_fields.py | 14 ++++++++++++++ 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 erpnext/patches/v13_0/create_uae_pos_invoice_fields.py diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 2b91c74ce6..c26e14ff6f 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -57,7 +57,11 @@ class POSClosingEntry(Document): if not invalid_rows: return - error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows] + error_list = [] + for row in invalid_rows: + for msg in row.get('msg'): + error_list.append(_("Row #{}: {}").format(row.get('idx'), msg)) + frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True) def on_submit(self): 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 index add27e9dff..5e43cfe030 100644 --- 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 @@ -5,10 +5,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate -from frappe.model.document import Document -from frappe.model.mapper import map_doc from frappe.model import default_fields +from frappe.model.document import Document +from frappe.model.mapper import map_doc, map_child_doc +from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate from six import iteritems @@ -83,7 +83,7 @@ class POSInvoiceMergeLog(Document): credit_note.is_consolidated = 1 # TODO: return could be against multiple sales invoice which could also have been consolidated? - credit_note.return_against = self.consolidated_invoice + # credit_note.return_against = self.consolidated_invoice credit_note.save() credit_note.submit() self.consolidated_credit_note = credit_note.name @@ -111,7 +111,8 @@ class POSInvoiceMergeLog(Document): i.qty = i.qty + item.qty if not found: item.rate = item.net_rate - items.append(item) + si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"}) + items.append(si_item) for tax in doc.get('taxes'): found = False diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 79792262c0..85af0eaedf 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -262,6 +262,7 @@ def make_return_doc(doctype, source_name, target_doc=None): if doc.get("is_return"): if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice': + doc.consolidated_invoice = "" doc.set('payments', []) for data in source.payments: paid_amount = 0.00 diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 894ee82b4c..ca8e01c515 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -743,3 +743,4 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.update_returned_qty_in_pr_dn +erpnext.patches.v13_0.create_uae_pos_invoice_fields \ No newline at end of file diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py new file mode 100644 index 0000000000..48d5cb4cc8 --- /dev/null +++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py @@ -0,0 +1,14 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from erpnext.regional.united_arab_emirates.setup import make_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': ['in', ['Saudi Arabia', 'United Arab Emirates']]}) + if not company: + return + + make_custom_fields() \ No newline at end of file From aeba3d311c6278dc62d46dc8dd44f3efd5c554f7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 14 Jan 2021 11:47:57 +0530 Subject: [PATCH 0240/1264] fix: extra transferred qty has not consumed against work order --- .../doctype/work_order/test_work_order.py | 42 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.py | 8 +++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index a77bd159af..e562d6c4bd 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -550,6 +550,48 @@ class TestWorkOrder(unittest.TestCase): frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) + def test_extra_material_transfer(self): + frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) + frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", + "Material Transferred for Manufacture") + + wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4) + + ste_cancel_list = [] + ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item", + target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0) + ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0) + + ste_cancel_list.extend([ste1, ste2]) + + itemwise_qty = {} + s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4)) + for row in s.items: + row.qty = row.qty + 2 + itemwise_qty.setdefault(row.item_code, row.qty) + + s.submit() + ste_cancel_list.append(s) + + ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) + for ste_row in ste3.items: + if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: + self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + + ste3.submit() + ste_cancel_list.append(ste3) + + ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) + for ste_row in ste2.items: + if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: + self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + + for ste_doc in ste_cancel_list: + ste_doc.cancel() + + frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM") + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d623d5c758..d77b70ff14 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1200,7 +1200,10 @@ class StockEntry(StockController): else: qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty else: - qty = req_qty_each * flt(self.fg_completed_qty) + if self.flags.backflush_based_on == "Material Transferred for Manufacture": + qty = (item.qty/trans_qty) * flt(self.fg_completed_qty) + else: + qty = req_qty_each * flt(self.fg_completed_qty) elif backflushed_materials.get(item.item_code): for d in backflushed_materials.get(item.item_code): @@ -1208,7 +1211,8 @@ class StockEntry(StockController): if (qty > req_qty): qty = (qty/trans_qty) * flt(self.fg_completed_qty) - if consumed_qty: + if consumed_qty and frappe.db.get_single_value("Manufacturing Settings", + "material_consumption"): qty -= consumed_qty if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')): From fd91973ac1c8efd99c53a47750aa37193eb194a7 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Fri, 29 Jan 2021 13:18:39 +0530 Subject: [PATCH 0241/1264] fix: template task parent child deadlock --- erpnext/projects/doctype/task/task.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index a2095c95d5..a99603329b 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -77,14 +77,7 @@ class Task(NestedSet): def validate_dependencies_for_template_task(self): if self.is_template: - self.validate_parent_template_task() self.validate_depends_on_tasks() - - def validate_parent_template_task(self): - if self.parent_task: - if not frappe.db.get_value("Task", self.parent_task, "is_template"): - parent_task_format = """{0}""".format(self.parent_task) - frappe.throw(_("Parent Task {0} is not a Template Task").format(parent_task_format)) def validate_depends_on_tasks(self): if self.depends_on: From 2fa840e5d4bf298e38212301a5e84be5439db090 Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 1 Feb 2021 20:07:00 +0530 Subject: [PATCH 0242/1264] chore: Use sql to set naming series in older projects (#24513) * chore: Use sql to set naming series in older projects * fix: Remove unncessary db.commit() --- erpnext/patches.txt | 2 +- .../add_naming_series_to_old_projects.py | 23 ++++--------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f3660b3c51..9e022da807 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -747,4 +747,4 @@ erpnext.patches.v13_0.update_project_template_tasks erpnext.patches.v13_0.set_company_in_leave_ledger_entry erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes -erpnext.patches.v13_0.add_naming_series_to_old_projects +erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 diff --git a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py index 79b67533ed..5ed9040f1e 100644 --- a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py +++ b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py @@ -4,23 +4,10 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_ def execute(): frappe.reload_doc("projects", "doctype", "project") - projects = frappe.db.get_all("Project", - fields=["name", "naming_series", "modified"], - filters={ - "naming_series": ["is", "not set"] - }, - order_by="timestamp(modified) asc") - # disable set only once as the old docs must be saved - # (to bypass 'Cant change naming series' validation on save) - make_property_setter("Project", "naming_series", "set_only_once", 0, "Check") + frappe.db.sql("""UPDATE `tabProject` + SET + naming_series = 'PROJ-.####' + WHERE + naming_series is NULL""") - for entry in projects: - # need to save the doc so that users can edit old projects - doc = frappe.get_doc("Project", entry.name) - if not doc.naming_series: - doc.naming_series = "PROJ-.####" - doc.save() - - delete_property_setter("Project", "set_only_once", "naming_series") - frappe.db.commit() From 081f5069928f8046903f927803f0f8986cd42391 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 1 Feb 2021 20:18:44 +0530 Subject: [PATCH 0243/1264] fix: consider select perm while setting party details (#24514) --- erpnext/accounts/party.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 64268b8064..38b228477f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -39,7 +39,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)) party = party_details[party_type.lower()] - if not ignore_permissions and not frappe.has_permission(party_type, "read", party): + if not ignore_permissions and not (frappe.has_permission(party_type, "read", party) or frappe.has_permission(party_type, "select", party)): frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError) party = frappe.get_doc(party_type, party) From 68edf749ec8781d4b784403d4f37fb011381ef24 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 Feb 2021 20:22:02 +0530 Subject: [PATCH 0244/1264] fix: multiple pricing rule issue (#24515) --- .../accounts/doctype/pricing_rule/utils.py | 41 ++++++++----------- erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/taxes_and_totals.py | 5 +-- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index fb1fbe484e..bd9d0b3815 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -41,10 +41,11 @@ def get_pricing_rules(args, doc=None): if not pricing_rules: return [] if apply_multiple_pricing_rules(pricing_rules): - pricing_rules = sorted_by_priority(pricing_rules) + pricing_rules = sorted_by_priority(pricing_rules, args, doc) for pricing_rule in pricing_rules: - pricing_rule = filter_pricing_rules(args, pricing_rule, doc) - if pricing_rule: + if isinstance(pricing_rule, list): + rules.extend(pricing_rule) + else: rules.append(pricing_rule) else: pricing_rule = filter_pricing_rules(args, pricing_rules, doc) @@ -53,17 +54,23 @@ def get_pricing_rules(args, doc=None): return rules -def sorted_by_priority(pricing_rules): +def sorted_by_priority(pricing_rules, args, doc=None): # If more than one pricing rules, then sort by priority pricing_rules_list = [] pricing_rule_dict = {} + + priority = [] for pricing_rule in pricing_rules: - if not pricing_rule.get("priority"): continue + pricing_rule = filter_pricing_rules(args, pricing_rule, doc) + if pricing_rule: + if not pricing_rule.get('priority'): + pricing_rules_list.append(pricing_rule) + else: + priority.append(cint(pricing_rule.get('priority'))) + pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule) - pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule) - - for key in sorted(pricing_rule_dict): - pricing_rules_list.append(pricing_rule_dict.get(key)) + if priority: + pricing_rules_list.extend(pricing_rule_dict.get(min(priority))) return pricing_rules_list or pricing_rules @@ -144,9 +151,7 @@ def apply_multiple_pricing_rules(pricing_rules): if not apply_multiple_rule: return False - if (apply_multiple_rule - and len(apply_multiple_rule) == len(pricing_rules)): - return True + return True def _get_tree_conditions(args, parenttype, table, allow_blank=True): field = frappe.scrub(parenttype) @@ -264,18 +269,6 @@ def filter_pricing_rules(args, pricing_rules, doc=None): if max_priority: pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules)) - # apply internal priority - all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory", - "supplier", "supplier_group", "campaign", "sales_partner", "variant_of"] - - if len(pricing_rules) > 1: - for field_set in [["item_code", "variant_of", "item_group", "brand"], - ["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]: - remaining_fields = list(set(all_fields) - set(field_set)) - if if_all_rules_same(pricing_rules, remaining_fields): - pricing_rules = apply_internal_priority(pricing_rules, field_set, args) - break - if pricing_rules and not isinstance(pricing_rules, list): pricing_rules = list(pricing_rules) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9d9d1b363a..35c6cd33f9 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -121,7 +121,7 @@ class AccountsController(TransactionBase): def before_cancel(self): validate_einvoice_fields(self) - + def on_trash(self): # delete sl and gl entries on deletion of transaction if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'): diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 76309f8799..452246e89c 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -616,7 +616,6 @@ class calculate_taxes_and_totals(object): self.doc.precision("base_write_off_amount")) def calculate_margin(self, item): - rate_with_margin = 0.0 base_rate_with_margin = 0.0 if item.price_list_rate: @@ -625,8 +624,8 @@ class calculate_taxes_and_totals(object): for d in get_applied_pricing_rules(item.pricing_rules): pricing_rule = frappe.get_cached_doc('Pricing Rule', d) - if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\ - or (pricing_rule.margin_type == 'Percentage'): + if (pricing_rule.margin_rate_or_amount and pricing_rule.currency == self.doc.currency and + pricing_rule.margin_type in ['Amount', 'Percentage']): item.margin_type = pricing_rule.margin_type item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount has_margin = True From 003ae90e12413d534878acef29aedf50c3b00db2 Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 1 Feb 2021 20:38:32 +0530 Subject: [PATCH 0245/1264] fix: Numeric/Non-numeric QI UX (#24517) * chore: Show 1 field each of both types of Insoections in grid view * fix: Make QI check Numeric by default and make checkbox "Numeric" - Reducing cognitive load --- .../item_quality_inspection_parameter.json | 16 ++++----- .../quality_inspection/quality_inspection.py | 4 +-- .../test_quality_inspection.py | 4 +-- .../quality_inspection_reading.json | 34 +++++++++---------- .../quality_inspection_template.py | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index 3e81619cfd..471e6853b5 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -8,7 +8,7 @@ "field_order": [ "specification", "value", - "non_numeric", + "numeric", "column_break_3", "min_value", "max_value", @@ -29,7 +29,7 @@ "width": "100px" }, { - "depends_on": "eval:(!doc.formula_based_criteria && doc.non_numeric)", + "depends_on": "eval:(!doc.formula_based_criteria && !doc.numeric)", "fieldname": "value", "fieldtype": "Data", "in_list_view": 1, @@ -55,32 +55,32 @@ "label": "Formula Based Criteria" }, { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", + "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)", "fieldname": "min_value", "fieldtype": "Float", "in_list_view": 1, "label": "Minimum Value" }, { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", + "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)", "fieldname": "max_value", "fieldtype": "Float", "in_list_view": 1, "label": "Maximum Value" }, { - "default": "0", - "fieldname": "non_numeric", + "default": "1", + "fieldname": "numeric", "fieldtype": "Check", "in_list_view": 1, - "label": "Non-Numeric", + "label": "Numeric", "width": "80px" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-07 21:32:49.866439", + "modified": "2021-02-01 19:18:46.924399", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index b3acbc5ba0..58b1eca2d3 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -97,7 +97,7 @@ class QualityInspection(Document): self.set_status_based_on_acceptance_values(reading) def set_status_based_on_acceptance_values(self, reading): - if cint(reading.non_numeric): + if not cint(reading.numeric): result = reading.get("reading_value") == reading.get("value") else: # numeric readings @@ -136,7 +136,7 @@ class QualityInspection(Document): def get_formula_evaluation_data(self, reading): data = {} - if cint(reading.non_numeric): + if not cint(reading.numeric): data = {"reading_value": reading.get("reading_value")} else: # numeric readings diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 8c5a04b3f0..a7dfc9ee28 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -55,7 +55,7 @@ class TestQualityInspection(unittest.TestCase): }, { "specification": "Particle Inspection Needed", # non-numeric reading - "non_numeric": 1, + "numeric": 0, "value": "Yes", "reading_value": "Yes" }] @@ -96,7 +96,7 @@ class TestQualityInspection(unittest.TestCase): { "specification": "Calcium Content", # non-numeric reading "formula_based_criteria": 1, - "non_numeric": 1, + "numeric": 0, "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", "reading_value": "Grade B" }] diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index 30ff1fea3a..35d58eff58 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -9,7 +9,7 @@ "specification", "status", "value", - "non_numeric", + "numeric", "manual_inspection", "column_break_4", "min_value", @@ -46,7 +46,7 @@ }, { "columns": 2, - "depends_on": "eval:(!doc.formula_based_criteria && doc.non_numeric)", + "depends_on": "eval:(!doc.formula_based_criteria && !doc.numeric)", "fieldname": "value", "fieldtype": "Data", "label": "Acceptance Criteria Value", @@ -54,7 +54,7 @@ "oldfieldtype": "Data" }, { - "columns": 1, + "columns": 2, "fieldname": "reading_1", "fieldtype": "Data", "in_list_view": 1, @@ -66,7 +66,6 @@ "columns": 1, "fieldname": "reading_2", "fieldtype": "Data", - "in_list_view": 1, "label": "Reading 2", "oldfieldname": "reading_2", "oldfieldtype": "Data" @@ -140,7 +139,7 @@ "options": "\nAccepted\nRejected" }, { - "depends_on": "non_numeric", + "depends_on": "eval:!doc.numeric", "fieldname": "section_break_3", "fieldtype": "Section Break", "label": "Value Based Inspection" @@ -171,51 +170,52 @@ "label": "Formula Based Criteria" }, { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", + "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)", "description": "Applied on each reading.", "fieldname": "min_value", "fieldtype": "Float", "label": "Minimum Value" }, { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", + "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)", "description": "Applied on each reading.", "fieldname": "max_value", "fieldtype": "Float", "label": "Maximum Value" }, { - "depends_on": "non_numeric", + "columns": 2, + "depends_on": "eval:!doc.numeric", "fieldname": "reading_value", "fieldtype": "Data", "in_list_view": 1, "label": "Reading Value" }, { - "depends_on": "eval:!doc.non_numeric", + "depends_on": "numeric", "fieldname": "section_break_14", "fieldtype": "Section Break", "label": "Numeric Inspection" }, - { - "default": "0", - "fieldname": "non_numeric", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Non-Numeric" - }, { "default": "0", "description": "Set the status manually.", "fieldname": "manual_inspection", "fieldtype": "Check", "label": "Manual Inspection" + }, + { + "default": "1", + "fieldname": "numeric", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Numeric" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-07 22:16:53.978410", + "modified": "2021-02-01 19:46:22.138018", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py index c5a7974a73..01d2031b3a 100644 --- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py +++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py @@ -14,6 +14,6 @@ def get_template_details(template): return frappe.get_all('Item Quality Inspection Parameter', fields=["specification", "value", "acceptance_formula", - "non_numeric", "formula_based_criteria", "min_value", "max_value"], + "numeric", "formula_based_criteria", "min_value", "max_value"], filters={'parenttype': 'Quality Inspection Template', 'parent': template}, order_by="idx") \ No newline at end of file From 5c28416daa8ac2b3b8968d750fa12fc214261088 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 Feb 2021 22:58:22 +0530 Subject: [PATCH 0246/1264] fix: test cases for pricing rule --- .../doctype/pricing_rule/test_pricing_rule.py | 3 +++ erpnext/accounts/doctype/pricing_rule/utils.py | 11 +++++------ erpnext/controllers/taxes_and_totals.py | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index af8d21d9ce..f28cee7c5a 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -56,6 +56,7 @@ class TestPricingRule(unittest.TestCase): self.assertEqual(details.get("discount_percentage"), 10) prule = frappe.get_doc(test_record.copy()) + prule.priority = 1 prule.applicable_for = "Customer" prule.title = "_Test Pricing Rule for Customer" self.assertRaises(MandatoryError, prule.insert) @@ -261,6 +262,7 @@ class TestPricingRule(unittest.TestCase): "rate_or_discount": "Discount Percentage", "rate": 0, "discount_percentage": 17.5, + "priority": 1, "company": "_Test Company" }).insert() @@ -557,6 +559,7 @@ def make_pricing_rule(**args): "rate": args.rate or 0.0, "margin_rate_or_amount": args.margin_rate_or_amount or 0.0, "condition": args.condition or '', + "priority": 1, "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 }) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index bd9d0b3815..d163335996 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -59,18 +59,17 @@ def sorted_by_priority(pricing_rules, args, doc=None): pricing_rules_list = [] pricing_rule_dict = {} - priority = [] for pricing_rule in pricing_rules: pricing_rule = filter_pricing_rules(args, pricing_rule, doc) if pricing_rule: if not pricing_rule.get('priority'): - pricing_rules_list.append(pricing_rule) - else: - priority.append(cint(pricing_rule.get('priority'))) + pricing_rule['priority'] = 1 + + if pricing_rule.get('apply_multiple_pricing_rules'): pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule) - if priority: - pricing_rules_list.extend(pricing_rule_dict.get(min(priority))) + for key in sorted(pricing_rule_dict): + pricing_rules_list.extend(pricing_rule_dict.get(key)) return pricing_rules_list or pricing_rules diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 452246e89c..1f50e9c14d 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -624,8 +624,8 @@ class calculate_taxes_and_totals(object): for d in get_applied_pricing_rules(item.pricing_rules): pricing_rule = frappe.get_cached_doc('Pricing Rule', d) - if (pricing_rule.margin_rate_or_amount and pricing_rule.currency == self.doc.currency and - pricing_rule.margin_type in ['Amount', 'Percentage']): + if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and + pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'): item.margin_type = pricing_rule.margin_type item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount has_margin = True From bf97eb2d3bf7e3074c2fd84c7079bac4915b390d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Feb 2021 10:57:55 +0530 Subject: [PATCH 0247/1264] core: Added change log (#24526) --- erpnext/change_log/v13/v13_0_0-beta_11.md | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0-beta_11.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_11.md b/erpnext/change_log/v13/v13_0_0-beta_11.md new file mode 100644 index 0000000000..5c40ffbf73 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0-beta_11.md @@ -0,0 +1,77 @@ +### Version 13.0.0 Beta 11 Release Notes + +#### Features and Enhancements + +- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398)) +- Multi currency in landed cost voucher ([#24127](https://github.com/frappe/erpnext/pull/24127)) +- Putaway ([#23969](https://github.com/frappe/erpnext/pull/23969)) +- Item valuation for internal stock transfers ([#24200](https://github.com/frappe/erpnext/pull/24200)) +- Batch wise item pricing ([#24470](https://github.com/frappe/erpnext/pull/24470)) +- Project template with dependent tasks ([#24092](https://github.com/frappe/erpnext/pull/24092)) +- Patient History Enhancements ([#24033](https://github.com/frappe/erpnext/pull/24033)) +- Compute Year to Date for Salary Slip components ([#24362](https://github.com/frappe/erpnext/pull/24362)) +- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912)) +- Issue Summary Script Report ([#23603](https://github.com/frappe/erpnext/pull/23603)) +- Issue Analytics Script Report ([#23604](https://github.com/frappe/erpnext/pull/23604)) +- Loan report and enhancements ([#24370](https://github.com/frappe/erpnext/pull/24370)) +- Enhancements to erpnext membership ([#23865](https://github.com/frappe/erpnext/pull/23865)) +- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281)) +- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201)) +- Allow selecting admission service unit in Patient Appointment for inpatients ([#24410](https://github.com/frappe/erpnext/pull/24410)) +- Separate equity tree in CoA SKR04 ([#24095](https://github.com/frappe/erpnext/pull/24095)) +- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355)) +- Value Based and Numeric Quality Inspection ([#24181](https://github.com/frappe/erpnext/pull/24181)) +- Deleting account & stock entries on deletion of transaction ([#24298](https://github.com/frappe/erpnext/pull/24298)) +- Remove german sales invoice validation ([#24441](https://github.com/frappe/erpnext/pull/24441)) +- Voice Call Settings doctype added ([#24126](https://github.com/frappe/erpnext/pull/24126)) +- Shopping portal changes ([#24445](https://github.com/frappe/erpnext/pull/24445)) +- Add "Sync Now" to Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602)) + +#### Fixes + +- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204)) +- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424)) +- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119)) +- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339)) +- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191)) +- Cancelling of asset value adjustement ([#24193](https://github.com/frappe/erpnext/pull/24193)) +- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338)) +- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346)) +- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019)) +- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358)) +- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324)) +- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437)) +- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252)) +- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226)) +- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329)) +- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130)) +- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474)) +- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146)) +- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164)) +- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375)) +- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207)) +- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376)) +- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349)) +- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296)) +- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265)) +- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397)) +- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272)) +- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402)) +- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517)) +- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220)) +- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354)) +- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294)) +- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063)) +- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356)) +- Allow addition and removal of employee in payroll Entry ([#24169](https://github.com/frappe/erpnext/pull/24169)) +- Filter Therapy Types and Therapy Plan in Patient Appointment ([#24152](https://github.com/frappe/erpnext/pull/24152)) +- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378)) +- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072)) +- E-invoicing qrcode image generation ([#24395](https://github.com/frappe/erpnext/pull/24395)) +- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332)) +- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515)) +- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322)) +- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197)) +- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297)) +- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944)) +- Extra transferred qty has not consumed against work order ([#24495](https://github.com/frappe/erpnext/pull/24495)) \ No newline at end of file From 8676089794abb8cd904d1aa7fdf6fd7de1b903bb Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 2 Feb 2021 11:02:22 +0530 Subject: [PATCH 0248/1264] fix: emp disappear (#24524) * fix: emp disappear * fix: renamed set_totals_call to set_totals Co-authored-by: Nabin Hait --- .../payroll/doctype/payroll_entry/payroll_entry.js | 1 - erpnext/payroll/doctype/salary_slip/salary_slip.js | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 45f9aa170f..395e56fa92 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -348,7 +348,6 @@ let render_employee_attendance = function (frm, data) { frappe.ui.form.on('Payroll Employee Detail', { employee: function(frm) { - frm.events.clear_employee_table(frm); if (!frm.doc.payroll_frequency) { frappe.throw(__("Please set a Payroll Frequency")); } diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index b50c774fbe..7460c75227 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -116,7 +116,7 @@ frappe.ui.form.on("Salary Slip", { }, exchange_rate: function(frm) { - calculate_totals(frm); + set_totals(frm); }, hide_loan_section: function(frm) { @@ -205,14 +205,14 @@ frappe.ui.form.on("Salary Slip", { frappe.ui.form.on('Salary Slip Timesheet', { time_sheet: function(frm) { - calculate_totals(frm); + set_totals(frm); }, timesheets_remove: function(frm) { - calculate_totals(frm); + set_totals(frm); } }); -var calculate_totals = function(frm) { +var set_totals = function(frm) { if (frm.doc.docstatus === 0) { if (frm.doc.earnings || frm.doc.deductions) { frappe.call({ @@ -228,15 +228,15 @@ var calculate_totals = function(frm) { frappe.ui.form.on('Salary Detail', { amount: function(frm) { - calculate_totals(frm); + set_totals(frm); }, earnings_remove: function(frm) { - calculate_totals(frm); + set_totals(frm); }, deductions_remove: function(frm) { - calculate_totals(frm); + set_totals(frm); }, salary_component: function(frm, cdt, cdn) { From 10b00f9d0d827cf6664fbe134a94d09b7c14c1f8 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 2 Feb 2021 16:24:01 +0530 Subject: [PATCH 0249/1264] fix: separation of salary creation from payroll (#24528) * fix: separation of salary creation from payroll * fix: removed unnecessary veriable * fix: check existing sal slips * fix: existing checks * style: refactoring query --- .../doctype/payroll_entry/payroll_entry.js | 11 ++++-- .../doctype/payroll_entry/payroll_entry.py | 35 +++++++------------ 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 395e56fa92..94f3f3098e 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -138,7 +138,6 @@ frappe.ui.form.on('Payroll Entry', { payroll_frequency: function (frm) { frm.trigger("set_start_end_dates").then( ()=> { frm.events.clear_employee_table(frm); - frm.events.get_employee_with_salary_slip_and_set_query(frm); }); }, @@ -146,13 +145,17 @@ frappe.ui.form.on('Payroll Entry', { frm.set_query('employee', 'employees', () => { return { filters: { - name: ["not in", emp_list] + name: ["not in", emp_list], + company: frm.doc.company } }; }); }, get_employee_with_salary_slip_and_set_query: function (frm) { + if (!frm.doc.company) { + frappe.throw(__("Please set a Company")); + } frappe.db.get_list('Salary Slip', { filters: { start_date: frm.doc.start_date, @@ -171,6 +174,7 @@ frappe.ui.form.on('Payroll Entry', { company: function (frm) { frm.events.clear_employee_table(frm); + frm.events.get_employee_with_salary_slip_and_set_query(frm); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, @@ -348,6 +352,9 @@ let render_employee_attendance = function (frm, data) { frappe.ui.form.on('Payroll Employee Detail', { employee: function(frm) { + if (!frm.doc.company) { + frappe.throw(__("Please set a Company")); + } if (!frm.doc.payroll_frequency) { frappe.throw(__("Please set a Payroll Frequency")); } diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 6bcd4e0c00..1ed3332d0f 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -14,7 +14,7 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class PayrollEntry(Document): def onload(self): if not self.docstatus==1 or self.salary_slips_submitted: - return + return # check if salary slips were manually submitted entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name']) @@ -174,13 +174,12 @@ class PayrollEntry(Document): """ Returns list of salary slips based on selected criteria """ - cond = self.get_filter_condition() ss_list = frappe.db.sql(""" select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1 - where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s - and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s - """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict) + where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s + and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s + """, (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict) return ss_list def submit_salary_slips(self): @@ -332,10 +331,9 @@ class PayrollEntry(Document): def make_payment_entry(self): self.check_permission('write') - cond = self.get_filter_condition() salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1 - where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s - """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True) + where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s + """, (self.start_date, self.end_date, self.name), as_list = True) if salary_slip_name_list and len(salary_slip_name_list) > 0: salary_slip_total = 0 @@ -550,6 +548,7 @@ def payroll_entry_has_bank_entries(name): def create_salary_slips_for_employees(employees, args, publish_progress=True): salary_slips_exists_for = get_existing_salary_slips(employees, args) count=0 + salary_slips_not_created = [] for emp in employees: if emp not in salary_slips_exists_for: args.update({ @@ -562,26 +561,18 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True): if publish_progress: frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)), title = _("Creating Salary Slips...")) - else: - salary_slip_name = frappe.db.sql( - '''SELECT - name - FROM `tabSalary Slip` - WHERE company=%s - AND start_date >= %s - AND end_date <= %s - AND employee = %s - ''', (args.company, args.start_date, args.end_date, emp), as_dict=True) - salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name) - salary_slip_doc.exchange_rate = args.exchange_rate - salary_slip_doc.set_totals() - salary_slip_doc.db_update() + else: + salary_slips_not_created.append(emp) payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) payroll_entry.db_set("salary_slips_created", 1) payroll_entry.notify_update() + if salary_slips_not_created: + frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.") + .format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange") + def get_existing_salary_slips(employees, args): return frappe.db.sql_list(""" select distinct employee from `tabSalary Slip` From 243d59b0c3ab030c10bca210376a8c199a97ec8d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Feb 2021 16:55:13 +0530 Subject: [PATCH 0250/1264] Reposting logic fixes (#24520) * fix: Dependant sle logic fixes * fix: negative qty validation * fix: Travis fixes * fix: test fix --- .../doctype/work_order/test_work_order.py | 4 +- .../united_states/test_united_states.py | 1 - erpnext/stock/doctype/bin/bin.py | 6 +-- .../purchase_receipt/test_purchase_receipt.py | 3 -- .../repost_item_valuation.js | 2 +- erpnext/stock/stock_ledger.py | 48 +++++++------------ .../report/issue_analytics/issue_analytics.py | 3 +- .../issue_analytics/test_issue_analytics.py | 7 ++- 8 files changed, 28 insertions(+), 46 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index e562d6c4bd..06a8e1987d 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -544,7 +544,7 @@ class TestWorkOrder(unittest.TestCase): expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4} for row in ste3.items: self.assertEquals(row.qty, expected_qty.get(row.item_code)) - + ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() @@ -586,7 +586,7 @@ class TestWorkOrder(unittest.TestCase): for ste_row in ste2.items: if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) - + ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() diff --git a/erpnext/regional/united_states/test_united_states.py b/erpnext/regional/united_states/test_united_states.py index ad95010a9a..513570ed6d 100644 --- a/erpnext/regional/united_states/test_united_states.py +++ b/erpnext/regional/united_states/test_united_states.py @@ -26,7 +26,6 @@ class TestUnitedStates(unittest.TestCase): make_payment_entry_to_irs_1099_supplier() filters = frappe._dict({"fiscal_year": "_Test Fiscal Year 2016", "company": "_Test Company 1"}) columns, data = execute_1099_report(filters) - print(columns, data) expected_row = {'supplier': '_US 1099 Test Supplier', 'supplier_group': 'Services', 'payments': 100.0, diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index ab19b77ad8..1088b4127d 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -17,7 +17,7 @@ class Bin(Document): '''Called from erpnext.stock.utils.update_bin''' self.update_qty(args) if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": - from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle + from erpnext.stock.stock_ledger import update_entries_after, validate_negative_qty_in_future_sle if not args.get("posting_date"): args["posting_date"] = nowdate() @@ -37,8 +37,8 @@ class Bin(Document): "sle_id": args.name }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - # Update qty_after_transaction in future SLEs of this item and warehouse - update_qty_in_future_sle(args) + # Validate negative qty in future transactions + validate_negative_qty_in_future_sle(args) def update_qty(self, args): # update the stock values (for current quantities) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 37e0c7f344..ca58ab2823 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -580,9 +580,6 @@ class TestPurchaseReceipt(unittest.TestCase): dn.cancel() pr1.cancel() - dn.cancel() - pr1.cancel() - def test_auto_asset_creation(self): asset_item = "Test Asset Item" diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index e429cd5e30..b3e4286bcc 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -31,7 +31,7 @@ frappe.ui.form.on('Repost Item Valuation', { } }, refresh: function(frm) { - if (frm.doc.status == "Failed") { + if (frm.doc.status == "Failed" && frm.doc.docstatus==1) { frm.add_custom_button(__('Restart'), function () { frm.trigger("restart_reposting"); }).addClass("btn-primary"); diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 2b2a7a202d..46919c8c8c 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -62,7 +62,7 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): sle.submit() return sle -def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=False, via_landed_cost_voucher=False): +def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False): if not args and voucher_type and voucher_no: args = get_args_for_voucher(voucher_type, voucher_no) @@ -181,7 +181,7 @@ class update_entries_after(object): self.process_sle(sle) if sle.dependant_sle_voucher_detail_no: - self.get_dependent_entries_to_fix(entries_to_fix, sle) + entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle) if self.exceptions: self.raise_exceptions() @@ -221,13 +221,15 @@ class update_entries_after(object): excluded_sle=sle.name) if not dependant_sle: - return + return entries_to_fix elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse: - return - elif dependant_sle.item_code != self.item_code \ - and (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items: - self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle - return + return entries_to_fix + elif dependant_sle.item_code != self.item_code: + if (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items: + self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle + return entries_to_fix + elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data: + return entries_to_fix self.initialize_previous_data(dependant_sle) @@ -236,7 +238,7 @@ class update_entries_after(object): future_sle_for_dependant = list(self.get_sle_after_datetime(args)) entries_to_fix.extend(future_sle_for_dependant) - entries_to_fix = sorted(entries_to_fix, key=lambda k: k['timestamp']) + return sorted(entries_to_fix, key=lambda k: k['timestamp']) def process_sle(self, sle): # previous sle data for this warehouse @@ -612,11 +614,11 @@ class update_entries_after(object): frappe.local.flags.currently_saving): msg = _("{0} units of {1} needed in {2} to complete this transaction.").format( - abs(deficiency), frappe.get_desk_link('Item', self.item_code), + abs(deficiency), frappe.get_desk_link('Item', exceptions[0]["item_code"]), frappe.get_desk_link('Warehouse', warehouse)) else: msg = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format( - abs(deficiency), frappe.get_desk_link('Item', self.item_code), + abs(deficiency), frappe.get_desk_link('Item', exceptions[0]["item_code"]), frappe.get_desk_link('Warehouse', warehouse), exceptions[0]["posting_date"], exceptions[0]["posting_time"], frappe.get_desk_link(exceptions[0]["voucher_type"], exceptions[0]["voucher_no"])) @@ -761,25 +763,6 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, return valuation_rate -def update_qty_in_future_sle(args, allow_negative_stock=None): - frappe.db.sql(""" - update `tabStock Ledger Entry` - set qty_after_transaction = qty_after_transaction + {qty} - where - item_code = %(item_code)s - and warehouse = %(warehouse)s - and voucher_no != %(voucher_no)s - and is_cancelled = 0 - and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) - or ( - timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) - and creation > %(creation)s - ) - ) - """.format(qty=args.actual_qty), args) - - validate_negative_qty_in_future_sle(args, allow_negative_stock) - def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): allow_negative_stock = allow_negative_stock \ or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) @@ -808,6 +791,7 @@ def get_future_sle_with_negative_qty(args): and voucher_no != %(voucher_no)s and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) and is_cancelled = 0 - and qty_after_transaction < 0 + and qty_after_transaction + {0} < 0 + order by timestamp(posting_date, posting_time) asc limit 1 - """, args, as_dict=1) \ No newline at end of file + """.format(args.actual_qty), args, as_dict=1) \ No newline at end of file diff --git a/erpnext/support/report/issue_analytics/issue_analytics.py b/erpnext/support/report/issue_analytics/issue_analytics.py index 0b629151a6..3fdb10ddf3 100644 --- a/erpnext/support/report/issue_analytics/issue_analytics.py +++ b/erpnext/support/report/issue_analytics/issue_analytics.py @@ -147,8 +147,7 @@ class IssueAnalytics(object): self.entries = frappe.db.get_all('Issue', fields=[self.field_map.get(self.filters.based_on), 'name', 'opening_date'], - filters=filters, - debug=1 + filters=filters ) def get_common_filters(self): diff --git a/erpnext/support/report/issue_analytics/test_issue_analytics.py b/erpnext/support/report/issue_analytics/test_issue_analytics.py index 432906db9b..fc6bb587be 100644 --- a/erpnext/support/report/issue_analytics/test_issue_analytics.py +++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py @@ -17,8 +17,11 @@ class TestIssueAnalytics(unittest.TestCase): current_month_date = getdate() last_month_date = add_months(current_month_date, -1) - self.current_month = str(months[current_month_date.month - 1]).lower() + '_' + str(current_month_date.year) - self.last_month = str(months[last_month_date.month - 1]).lower() + '_' + str(last_month_date.year) + self.current_month = str(months[current_month_date.month - 1]).lower() + self.last_month = str(months[last_month_date.month - 1]).lower() + if current_month_date.year != last_month_date.year: + self.current_month += '_' + str(current_month_date.year) + self.last_month += '_' + str(last_month_date.year) def test_issue_analytics(self): create_service_level_agreements_for_issues() From 17433d2c908f77b8cc5c2586e437150fd0480c08 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 2 Feb 2021 17:40:11 +0530 Subject: [PATCH 0251/1264] fix: version number --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 8b90a51542..ad1167b0d8 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.9' +__version__ = '13.0.0-beta.10' def get_default_company(user=None): '''Get default company for user''' From c0f8f3280110f45a897f8002f46a05fa13b61851 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 2 Feb 2021 20:27:23 +0530 Subject: [PATCH 0252/1264] fix: fetch query for employee (#24529) --- .../doctype/payroll_entry/payroll_entry.js | 68 +++++++------------ .../doctype/payroll_entry/payroll_entry.py | 55 +++++++++++++++ 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 94f3f3098e..0dcea88c89 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -133,6 +133,31 @@ frappe.ui.form.on('Payroll Entry', { } }; }); + + frm.set_query('employee', 'employees', () => { + if (!frm.doc.company) { + frappe.msgprint(__("Please set a Company")); + return [] + } + let filters = {}; + filters['company'] = frm.doc.company; + filters['start_date'] = frm.doc.start_date; + filters['end_date'] = frm.doc.end_date; + + if (frm.doc.department) { + filters['department'] = frm.doc.department; + } + if (frm.doc.branch) { + filters['branch'] = frm.doc.branch; + } + if (frm.doc.designation) { + filters['designation'] = frm.doc.designation; + } + return { + query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query", + filters: filters + } + }); }, payroll_frequency: function (frm) { @@ -141,40 +166,8 @@ frappe.ui.form.on('Payroll Entry', { }); }, - employee_filters: function (frm, emp_list) { - frm.set_query('employee', 'employees', () => { - return { - filters: { - name: ["not in", emp_list], - company: frm.doc.company - } - }; - }); - }, - - get_employee_with_salary_slip_and_set_query: function (frm) { - if (!frm.doc.company) { - frappe.throw(__("Please set a Company")); - } - frappe.db.get_list('Salary Slip', { - filters: { - start_date: frm.doc.start_date, - end_date: frm.doc.end_date, - docstatus: 1, - }, - fields: ['employee'] - }).then((emp) => { - var emp_list = []; - emp.forEach((employee_data) => { - emp_list.push(Object.values(employee_data)[0]); - }); - frm.events.employee_filters(frm, emp_list); - }); - }, - company: function (frm) { frm.events.clear_employee_table(frm); - frm.events.get_employee_with_salary_slip_and_set_query(frm); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, @@ -349,14 +342,3 @@ let render_employee_attendance = function (frm, data) { }) ); }; - -frappe.ui.form.on('Payroll Employee Detail', { - employee: function(frm) { - if (!frm.doc.company) { - frappe.throw(__("Please set a Company")); - } - if (!frm.doc.payroll_frequency) { - frappe.throw(__("Please set a Payroll Frequency")); - } - } -}); diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 1ed3332d0f..b520cdabdc 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -10,6 +10,7 @@ from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, from frappe import _ from erpnext.accounts.utils import get_fiscal_year from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from frappe.desk.reportview import get_match_cond, get_filters_cond class PayrollEntry(Document): def onload(self): @@ -632,3 +633,57 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte 'txt': "%%%s%%" % frappe.db.escape(txt), 'start': start, 'page_len': page_len }) + +def get_employee_with_existing_salary_slip(start_date, end_date): + + return frappe.db.sql_list(""" + select employee from `tabSalary Slip` + where + (start_date between %(start_date)s and %(end_date)s + or + end_date between %(start_date)s and %(end_date)s + or + %(start_date)s between start_date and end_date) + and docstatus = 1 + """, {'start_date': start_date, 'end_date': end_date}) + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def employee_query(doctype, txt, searchfield, start, page_len, filters): + filters = frappe._dict(filters) + conditions = [] + emp_cond = '' + if filters.start_date and filters.end_date: + employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date) + filters.pop('start_date') + filters.pop('end_date') + if employee_list: + emp_cond += 'and employee not in %(employee_list)s' + else: + employee_list = [] + + + return frappe.db.sql("""select name, employee_name from `tabEmployee` + where status = 'Active' + and docstatus < 2 + and ({key} like %(txt)s + or employee_name like %(txt)s) + {emp_cond} + {fcond} {mcond} + order by + if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), + if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999), + idx desc, + name, employee_name + limit %(start)s, %(page_len)s""".format(**{ + 'key': searchfield, + 'fcond': get_filters_cond(doctype, filters, conditions), + 'mcond': get_match_cond(doctype), + 'emp_cond': emp_cond + }), { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), + 'start': start, + 'page_len': page_len, + 'employee_list': employee_list + }) From 7645e5319d53401e454117f35f5abe22f02217f0 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 2 Feb 2021 21:15:21 +0550 Subject: [PATCH 0253/1264] bumped to version 13.0.0-beta.11 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ad1167b0d8..a754e1323b 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.10' +__version__ = '13.0.0-beta.11' def get_default_company(user=None): '''Get default company for user''' From 6485eeb302f87965e2179f226dafcf2cef1c573b Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 11 Feb 2021 11:12:13 +0530 Subject: [PATCH 0254/1264] fix: portal permission issue (#24577) --- .../setup/doctype/customer_group/customer_group.json | 11 ++++++++++- erpnext/setup/doctype/item_group/item_group.json | 9 +++++++++ erpnext/setup/doctype/territory/territory.json | 11 ++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json index 10f9bd0030..0e2ed9efcf 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.json +++ b/erpnext/setup/doctype/customer_group/customer_group.json @@ -139,7 +139,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 18:10:13.048492", + "modified": "2021-02-08 17:01:52.162202", "modified_by": "Administrator", "module": "Setup", "name": "Customer Group", @@ -189,6 +189,15 @@ "permlevel": 1, "read": 1, "role": "Sales Manager" + }, + { + "email": 1, + "export": 1, + "print": 1, + "report": 1, + "role": "Customer", + "select": 1, + "share": 1 } ], "search_fields": "parent_customer_group", diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json index 004421d2bc..fc464b237c 100644 --- a/erpnext/setup/doctype/item_group/item_group.json +++ b/erpnext/setup/doctype/item_group/item_group.json @@ -245,6 +245,15 @@ "read": 1, "report": 1, "role": "Accounts User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "report": 1, + "role": "Customer", + "select": 1, + "share": 1 } ], "search_fields": "parent_item_group", diff --git a/erpnext/setup/doctype/territory/territory.json b/erpnext/setup/doctype/territory/territory.json index aa8e0486f5..a25bda054b 100644 --- a/erpnext/setup/doctype/territory/territory.json +++ b/erpnext/setup/doctype/territory/territory.json @@ -123,7 +123,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 18:11:36.623555", + "modified": "2021-02-08 17:10:03.767426", "modified_by": "Administrator", "module": "Setup", "name": "Territory", @@ -166,6 +166,15 @@ { "read": 1, "role": "Maintenance User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "report": 1, + "role": "Customer", + "select": 1, + "share": 1 } ], "search_fields": "parent_territory,territory_manager", From 73c2d23a9a73057edddc70c247a408a2580ab614 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 18 Feb 2021 16:41:10 +0530 Subject: [PATCH 0255/1264] feat: Department Wise Appointment Type charges (#24572) * feat: Appointment Type Service Items Co-Authored-By: muhammad * fix: set practitioner service item charges mandatory on item selection Co-Authored-By: muhammad * feat: use charges from appointment type during billing * feat: handle appointment charges priority for invoice automation * test: patient appointment auto invoicing scenarios * fix: sider * fix: minor fixes Co-authored-by: muhammad Co-authored-by: Nabin Hait --- .../appointment_type/appointment_type.js | 78 ++++++++++++ .../appointment_type/appointment_type.json | 24 +++- .../appointment_type/appointment_type.py | 49 +++++++- .../appointment_type_service_item/__init__.py | 0 .../appointment_type_service_item.json | 67 +++++++++++ .../appointment_type_service_item.py | 10 ++ .../healthcare_practitioner.json | 6 +- .../patient_appointment.js | 112 +++++++++--------- .../patient_appointment.json | 26 ++-- .../patient_appointment.py | 67 +++++++---- .../test_patient_appointment.py | 84 ++++++++++++- erpnext/healthcare/utils.py | 85 ++++++++++--- 12 files changed, 485 insertions(+), 123 deletions(-) create mode 100644 erpnext/healthcare/doctype/appointment_type_service_item/__init__.py create mode 100644 erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json create mode 100644 erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.js b/erpnext/healthcare/doctype/appointment_type/appointment_type.js index 15916a5134..861675acea 100644 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type.js +++ b/erpnext/healthcare/doctype/appointment_type/appointment_type.js @@ -2,4 +2,82 @@ // For license information, please see license.txt frappe.ui.form.on('Appointment Type', { + refresh: function(frm) { + frm.set_query('price_list', function() { + return { + filters: {'selling': 1} + }; + }); + + frm.set_query('medical_department', 'items', function(doc) { + let item_list = doc.items.map(({medical_department}) => medical_department); + return { + filters: [ + ['Medical Department', 'name', 'not in', item_list] + ] + }; + }); + + frm.set_query('op_consulting_charge_item', 'items', function() { + return { + filters: { + is_stock_item: 0 + } + }; + }); + + frm.set_query('inpatient_visit_charge_item', 'items', function() { + return { + filters: { + is_stock_item: 0 + } + }; + }); + } }); + +frappe.ui.form.on('Appointment Type Service Item', { + op_consulting_charge_item: function(frm, cdt, cdn) { + let d = locals[cdt][cdn]; + if (frm.doc.price_list && d.op_consulting_charge_item) { + frappe.call({ + 'method': 'frappe.client.get_value', + args: { + 'doctype': 'Item Price', + 'filters': { + 'item_code': d.op_consulting_charge_item, + 'price_list': frm.doc.price_list + }, + 'fieldname': ['price_list_rate'] + }, + callback: function(data) { + if (data.message.price_list_rate) { + frappe.model.set_value(cdt, cdn, 'op_consulting_charge', data.message.price_list_rate); + } + } + }); + } + }, + + inpatient_visit_charge_item: function(frm, cdt, cdn) { + let d = locals[cdt][cdn]; + if (frm.doc.price_list && d.inpatient_visit_charge_item) { + frappe.call({ + 'method': 'frappe.client.get_value', + args: { + 'doctype': 'Item Price', + 'filters': { + 'item_code': d.inpatient_visit_charge_item, + 'price_list': frm.doc.price_list + }, + 'fieldname': ['price_list_rate'] + }, + callback: function (data) { + if (data.message.price_list_rate) { + frappe.model.set_value(cdt, cdn, 'inpatient_visit_charge', data.message.price_list_rate); + } + } + }); + } + } +}); \ No newline at end of file diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.json b/erpnext/healthcare/doctype/appointment_type/appointment_type.json index 58753bb4f0..3872318287 100644 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type.json +++ b/erpnext/healthcare/doctype/appointment_type/appointment_type.json @@ -12,7 +12,10 @@ "appointment_type", "ip", "default_duration", - "color" + "color", + "billing_section", + "price_list", + "items" ], "fields": [ { @@ -52,10 +55,27 @@ "label": "Color", "no_copy": 1, "report_hide": 1 + }, + { + "fieldname": "billing_section", + "fieldtype": "Section Break", + "label": "Billing" + }, + { + "fieldname": "price_list", + "fieldtype": "Link", + "label": "Price List", + "options": "Price List" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Appointment Type Service Items", + "options": "Appointment Type Service Item" } ], "links": [], - "modified": "2020-02-03 21:06:05.833050", + "modified": "2021-01-22 09:41:05.010524", "modified_by": "Administrator", "module": "Healthcare", "name": "Appointment Type", diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.py b/erpnext/healthcare/doctype/appointment_type/appointment_type.py index 1dacffab35..67a24f31e0 100644 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type.py +++ b/erpnext/healthcare/doctype/appointment_type/appointment_type.py @@ -4,6 +4,53 @@ from __future__ import unicode_literals from frappe.model.document import Document +import frappe class AppointmentType(Document): - pass + def validate(self): + if self.items and self.price_list: + for item in self.items: + existing_op_item_price = frappe.db.exists('Item Price', { + 'item_code': item.op_consulting_charge_item, + 'price_list': self.price_list + }) + + if not existing_op_item_price and item.op_consulting_charge_item and item.op_consulting_charge: + make_item_price(self.price_list, item.op_consulting_charge_item, item.op_consulting_charge) + + existing_ip_item_price = frappe.db.exists('Item Price', { + 'item_code': item.inpatient_visit_charge_item, + 'price_list': self.price_list + }) + + if not existing_ip_item_price and item.inpatient_visit_charge_item and item.inpatient_visit_charge: + make_item_price(self.price_list, item.inpatient_visit_charge_item, item.inpatient_visit_charge) + +@frappe.whitelist() +def get_service_item_based_on_department(appointment_type, department): + item_list = frappe.db.get_value('Appointment Type Service Item', + filters = {'medical_department': department, 'parent': appointment_type}, + fieldname = ['op_consulting_charge_item', + 'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'], + as_dict = 1 + ) + + # if department wise items are not set up + # use the generic items + if not item_list: + item_list = frappe.db.get_value('Appointment Type Service Item', + filters = {'parent': appointment_type}, + fieldname = ['op_consulting_charge_item', + 'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'], + as_dict = 1 + ) + + return item_list + +def make_item_price(price_list, item, item_price): + frappe.get_doc({ + 'doctype': 'Item Price', + 'price_list': price_list, + 'item_code': item, + 'price_list_rate': item_price + }).insert(ignore_permissions=True, ignore_mandatory=True) diff --git a/erpnext/healthcare/doctype/appointment_type_service_item/__init__.py b/erpnext/healthcare/doctype/appointment_type_service_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json new file mode 100644 index 0000000000..5ff68cd682 --- /dev/null +++ b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json @@ -0,0 +1,67 @@ +{ + "actions": [], + "creation": "2021-01-22 09:34:53.373105", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "medical_department", + "op_consulting_charge_item", + "op_consulting_charge", + "column_break_4", + "inpatient_visit_charge_item", + "inpatient_visit_charge" + ], + "fields": [ + { + "fieldname": "medical_department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "op_consulting_charge_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Out Patient Consulting Charge Item", + "options": "Item" + }, + { + "fieldname": "op_consulting_charge", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Out Patient Consulting Charge" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "inpatient_visit_charge_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Inpatient Visit Charge Item", + "options": "Item" + }, + { + "fieldname": "inpatient_visit_charge", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Inpatient Visit Charge Item" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-01-22 09:35:26.503443", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Appointment Type Service Item", + "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/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py new file mode 100644 index 0000000000..b2e0e82bad --- /dev/null +++ b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 AppointmentTypeServiceItem(Document): + pass diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json index cb747f95ef..8162f03f6d 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json @@ -159,6 +159,7 @@ "fieldname": "op_consulting_charge", "fieldtype": "Currency", "label": "Out Patient Consulting Charge", + "mandatory_depends_on": "op_consulting_charge_item", "options": "Currency" }, { @@ -174,7 +175,8 @@ { "fieldname": "inpatient_visit_charge", "fieldtype": "Currency", - "label": "Inpatient Visit Charge" + "label": "Inpatient Visit Charge", + "mandatory_depends_on": "inpatient_visit_charge_item" }, { "depends_on": "eval: !doc.__islocal", @@ -280,7 +282,7 @@ ], "image_field": "image", "links": [], - "modified": "2020-04-06 13:44:24.759623", + "modified": "2021-01-22 10:14:43.187675", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Practitioner", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 3d5073b13e..0354733dfb 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -24,11 +24,13 @@ frappe.ui.form.on('Patient Appointment', { }); frm.set_query('practitioner', function() { - return { - filters: { - 'department': frm.doc.department - } - }; + if (frm.doc.department) { + return { + filters: { + 'department': frm.doc.department + } + }; + } }); frm.set_query('service_unit', function() { @@ -140,6 +142,20 @@ frappe.ui.form.on('Patient Appointment', { patient: function(frm) { if (frm.doc.patient) { frm.trigger('toggle_payment_fields'); + frappe.call({ + method: 'frappe.client.get', + args: { + doctype: 'Patient', + name: frm.doc.patient + }, + callback: function (data) { + let age = null; + if (data.message.dob) { + age = calculate_age(data.message.dob); + } + frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age); + } + }); } else { frm.set_value('patient_name', ''); frm.set_value('patient_sex', ''); @@ -148,6 +164,37 @@ frappe.ui.form.on('Patient Appointment', { } }, + practitioner: function(frm) { + if (frm.doc.practitioner ) { + frm.events.set_payment_details(frm); + } + }, + + appointment_type: function(frm) { + if (frm.doc.appointment_type) { + frm.events.set_payment_details(frm); + } + }, + + set_payment_details: function(frm) { + frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing').then(val => { + if (val) { + frappe.call({ + method: 'erpnext.healthcare.utils.get_service_item_and_practitioner_charge', + args: { + doc: frm.doc + }, + callback: function(data) { + if (data.message) { + frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.practitioner_charge); + frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.service_item); + } + } + }); + } + }); + }, + therapy_plan: function(frm) { frm.trigger('set_therapy_type_filter'); }, @@ -190,14 +237,18 @@ frappe.ui.form.on('Patient Appointment', { // show payment fields as non-mandatory frm.toggle_display('mode_of_payment', 0); frm.toggle_display('paid_amount', 0); + frm.toggle_display('billing_item', 0); frm.toggle_reqd('mode_of_payment', 0); frm.toggle_reqd('paid_amount', 0); + frm.toggle_reqd('billing_item', 0); } else { // if automated appointment invoicing is disabled, hide fields frm.toggle_display('mode_of_payment', data.message ? 1 : 0); frm.toggle_display('paid_amount', data.message ? 1 : 0); + frm.toggle_display('billing_item', data.message ? 1 : 0); frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0); frm.toggle_reqd('paid_amount', data.message ? 1 :0); + frm.toggle_reqd('billing_item', data.message ? 1 : 0); } } }); @@ -540,57 +591,6 @@ let update_status = function(frm, status){ ); }; -frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) { - if (frm.doc.practitioner) { - frappe.call({ - method: 'frappe.client.get', - args: { - doctype: 'Healthcare Practitioner', - name: frm.doc.practitioner - }, - callback: function (data) { - frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department); - frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge); - frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item); - } - }); - } -}); - -frappe.ui.form.on('Patient Appointment', 'patient', function(frm) { - if (frm.doc.patient) { - frappe.call({ - method: 'frappe.client.get', - args: { - doctype: 'Patient', - name: frm.doc.patient - }, - callback: function (data) { - let age = null; - if (data.message.dob) { - age = calculate_age(data.message.dob); - } - frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age); - } - }); - } -}); - -frappe.ui.form.on('Patient Appointment', 'appointment_type', function(frm) { - if (frm.doc.appointment_type) { - frappe.call({ - method: 'frappe.client.get', - args: { - doctype: 'Appointment Type', - name: frm.doc.appointment_type - }, - callback: function(data) { - frappe.model.set_value(frm.doctype,frm.docname, 'duration',data.message.default_duration); - } - }); - } -}); - let calculate_age = function(birth) { let ageMS = Date.parse(Date()) - Date.parse(birth); let age = new Date(); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 35600e4809..83c92af36a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -19,19 +19,19 @@ "inpatient_record", "column_break_1", "company", + "practitioner", + "practitioner_name", + "department", "service_unit", + "section_break_12", + "appointment_type", + "duration", "procedure_template", "get_procedure_from_encounter", "procedure_prescription", "therapy_plan", "therapy_type", "get_prescribed_therapies", - "practitioner", - "practitioner_name", - "department", - "section_break_12", - "appointment_type", - "duration", "column_break_17", "appointment_date", "appointment_time", @@ -79,6 +79,7 @@ "set_only_once": 1 }, { + "fetch_from": "appointment_type.default_duration", "fieldname": "duration", "fieldtype": "Int", "in_filter": 1, @@ -144,7 +145,6 @@ "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner", - "read_only": 1, "reqd": 1, "search_index": 1, "set_only_once": 1 @@ -158,7 +158,6 @@ "in_standard_filter": 1, "label": "Department", "options": "Medical Department", - "read_only": 1, "search_index": 1, "set_only_once": 1 }, @@ -227,12 +226,14 @@ "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", - "options": "Mode of Payment" + "options": "Mode of Payment", + "read_only_depends_on": "invoiced" }, { "fieldname": "paid_amount", "fieldtype": "Currency", - "label": "Paid Amount" + "label": "Paid Amount", + "read_only_depends_on": "invoiced" }, { "fieldname": "column_break_2", @@ -302,7 +303,8 @@ "fieldname": "therapy_plan", "fieldtype": "Link", "label": "Therapy Plan", - "options": "Therapy Plan" + "options": "Therapy Plan", + "set_only_once": 1 }, { "fieldname": "ref_sales_invoice", @@ -347,7 +349,7 @@ } ], "links": [], - "modified": "2020-12-16 13:16:58.578503", + "modified": "2021-02-08 13:13:15.116833", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index b05c673d84..649f16dbf1 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -26,6 +26,7 @@ class PatientAppointment(Document): def after_insert(self): self.update_prescription_details() + self.set_payment_details() invoice_appointment(self) self.update_fee_validity() send_confirmation_msg(self) @@ -85,6 +86,13 @@ class PatientAppointment(Document): def set_appointment_datetime(self): self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00") + def set_payment_details(self): + if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'): + details = get_service_item_and_practitioner_charge(self) + self.db_set('billing_item', details.get('service_item')) + if not self.paid_amount: + self.db_set('paid_amount', details.get('practitioner_charge')) + def validate_customer_created(self): if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'): if not frappe.db.get_value('Patient', self.patient, 'customer'): @@ -148,31 +156,37 @@ def invoice_appointment(appointment_doc): fee_validity = None if automate_invoicing and not appointment_invoiced and not fee_validity: - sales_invoice = frappe.new_doc('Sales Invoice') - sales_invoice.patient = appointment_doc.patient - sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') - sales_invoice.appointment = appointment_doc.name - sales_invoice.due_date = getdate() - sales_invoice.company = appointment_doc.company - sales_invoice.debit_to = get_receivable_account(appointment_doc.company) + create_sales_invoice(appointment_doc) - item = sales_invoice.append('items', {}) - item = get_appointment_item(appointment_doc, item) - # Add payments if payment details are supplied else proceed to create invoice as Unpaid - if appointment_doc.mode_of_payment and appointment_doc.paid_amount: - sales_invoice.is_pos = 1 - payment = sales_invoice.append('payments', {}) - payment.mode_of_payment = appointment_doc.mode_of_payment - payment.amount = appointment_doc.paid_amount +def create_sales_invoice(appointment_doc): + sales_invoice = frappe.new_doc('Sales Invoice') + sales_invoice.patient = appointment_doc.patient + sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') + sales_invoice.appointment = appointment_doc.name + sales_invoice.due_date = getdate() + sales_invoice.company = appointment_doc.company + sales_invoice.debit_to = get_receivable_account(appointment_doc.company) - sales_invoice.set_missing_values(for_validate=True) - sales_invoice.flags.ignore_mandatory = True - sales_invoice.save(ignore_permissions=True) - sales_invoice.submit() - frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True) - frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1) - frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name) + item = sales_invoice.append('items', {}) + item = get_appointment_item(appointment_doc, item) + + # Add payments if payment details are supplied else proceed to create invoice as Unpaid + if appointment_doc.mode_of_payment and appointment_doc.paid_amount: + sales_invoice.is_pos = 1 + payment = sales_invoice.append('payments', {}) + payment.mode_of_payment = appointment_doc.mode_of_payment + payment.amount = appointment_doc.paid_amount + + sales_invoice.set_missing_values(for_validate=True) + sales_invoice.flags.ignore_mandatory = True + sales_invoice.save(ignore_permissions=True) + sales_invoice.submit() + frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True) + frappe.db.set_value('Patient Appointment', appointment_doc.name, { + 'invoiced': 1, + 'ref_sales_invoice': sales_invoice.name + }) def check_is_new_patient(patient, name=None): @@ -187,13 +201,14 @@ def check_is_new_patient(patient, name=None): def get_appointment_item(appointment_doc, item): - service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment_doc) - item.item_code = service_item + details = get_service_item_and_practitioner_charge(appointment_doc) + charge = appointment_doc.paid_amount or details.get('practitioner_charge') + item.item_code = details.get('service_item') item.description = _('Consulting Charges: {0}').format(appointment_doc.practitioner) item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company) item.cost_center = frappe.get_cached_value('Company', appointment_doc.company, 'cost_center') - item.rate = practitioner_charge - item.amount = practitioner_charge + item.rate = charge + item.amount = charge item.qty = 1 item.reference_dt = 'Patient Appointment' item.reference_dn = appointment_doc.name diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index f7ec6f58fc..2bb8a53c45 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -32,7 +32,8 @@ class TestPatientAppointment(unittest.TestCase): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1) - self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1) + appointment.reload() + self.assertEqual(appointment.invoiced, 1) encounter = make_encounter(appointment.name) self.assertTrue(encounter) self.assertEqual(encounter.company, appointment.company) @@ -41,7 +42,7 @@ class TestPatientAppointment(unittest.TestCase): # invoiced flag mapped from appointment self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced')) - def test_invoicing(self): + def test_auto_invoicing(self): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) @@ -57,6 +58,50 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient) self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) + def test_auto_invoicing_based_on_department(self): + patient, medical_department, practitioner = create_healthcare_docs() + frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) + frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) + appointment_type = create_appointment_type() + + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), + invoice=1, appointment_type=appointment_type.name, department='_Test Medical Department') + appointment.reload() + + self.assertEqual(appointment.invoiced, 1) + self.assertEqual(appointment.billing_item, 'HLC-SI-001') + self.assertEqual(appointment.paid_amount, 200) + + sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') + self.assertTrue(sales_invoice_name) + self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) + + def test_auto_invoicing_according_to_appointment_type_charge(self): + patient, medical_department, practitioner = create_healthcare_docs() + frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) + frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) + + item = create_healthcare_service_items() + items = [{ + 'op_consulting_charge_item': item, + 'op_consulting_charge': 300 + }] + appointment_type = create_appointment_type(args={ + 'name': 'Generic Appointment Type charge', + 'items': items + }) + + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), + invoice=1, appointment_type=appointment_type.name) + appointment.reload() + + self.assertEqual(appointment.invoiced, 1) + self.assertEqual(appointment.billing_item, item) + self.assertEqual(appointment.paid_amount, 300) + + sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') + self.assertTrue(sales_invoice_name) + def test_appointment_cancel(self): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1) @@ -178,14 +223,15 @@ def create_encounter(appointment): encounter.submit() return encounter -def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1): +def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, + service_unit=None, appointment_type=None, save=1, department=None): item = create_healthcare_service_items() frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item) frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item) appointment = frappe.new_doc('Patient Appointment') appointment.patient = patient appointment.practitioner = practitioner - appointment.department = '_Test Medical Department' + appointment.department = department or '_Test Medical Department' appointment.appointment_date = appointment_date appointment.company = '_Test Company' appointment.duration = 15 @@ -193,7 +239,8 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce appointment.service_unit = service_unit if invoice: appointment.mode_of_payment = 'Cash' - appointment.paid_amount = 500 + if appointment_type: + appointment.appointment_type = appointment_type if procedure_template: appointment.procedure_template = create_clinical_procedure_template().get('name') if save: @@ -223,4 +270,29 @@ def create_clinical_procedure_template(): template.description = 'Knee Surgery and Rehab' template.rate = 50000 template.save() - return template \ No newline at end of file + return template + +def create_appointment_type(args=None): + if not args: + args = frappe.local.form_dict + + name = args.get('name') or 'Test Appointment Type wise Charge' + + if frappe.db.exists('Appointment Type', name): + return frappe.get_doc('Appointment Type', name) + + else: + item = create_healthcare_service_items() + items = [{ + 'medical_department': '_Test Medical Department', + 'op_consulting_charge_item': item, + 'op_consulting_charge': 200 + }] + return frappe.get_doc({ + 'doctype': 'Appointment Type', + 'appointment_type': args.get('name') or 'Test Appointment Type wise Charge', + 'default_duration': args.get('default_duration') or 20, + 'color': args.get('color') or '#7575ff', + 'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}), + 'items': args.get('items') or items + }).insert() \ No newline at end of file diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 40f7f9cabd..510ac9e475 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -5,9 +5,11 @@ from __future__ import unicode_literals import math import frappe +import json from frappe import _ from frappe.utils.formatters import format_value from frappe.utils import time_diff_in_hours, rounded +from six import string_types from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple @@ -64,7 +66,9 @@ def get_appointments_to_invoice(patient, company): income_account = None service_item = None if appointment.practitioner: - service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) + details = get_service_item_and_practitioner_charge(appointment) + service_item = details.get('service_item') + practitioner_charge = details.get('practitioner_charge') income_account = get_income_account(appointment.practitioner, appointment.company) appointments_to_invoice.append({ 'reference_type': 'Patient Appointment', @@ -97,7 +101,9 @@ def get_encounters_to_invoice(patient, company): frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'): continue - service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter) + details = get_service_item_and_practitioner_charge(encounter) + service_item = details.get('service_item') + practitioner_charge = details.get('practitioner_charge') income_account = get_income_account(encounter.practitioner, encounter.company) encounters_to_invoice.append({ @@ -173,7 +179,7 @@ def get_clinical_procedures_to_invoice(patient, company): if procedure.invoice_separately_as_consumables and procedure.consume_stock \ and procedure.status == 'Completed' and not procedure.consumption_invoiced: - service_item = get_healthcare_service_item('clinical_procedure_consumable_item') + service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') if not service_item: msg = _('Please Configure Clinical Procedure Consumable Item in ') msg += '''Healthcare Settings''' @@ -304,24 +310,50 @@ def get_therapy_sessions_to_invoice(patient, company): return therapy_sessions_to_invoice - +@frappe.whitelist() def get_service_item_and_practitioner_charge(doc): + if isinstance(doc, string_types): + doc = json.loads(doc) + doc = frappe.get_doc(doc) + + service_item = None + practitioner_charge = None + department = doc.medical_department if doc.doctype == 'Patient Encounter' else doc.department + is_inpatient = doc.inpatient_record - if is_inpatient: - service_item = get_practitioner_service_item(doc.practitioner, 'inpatient_visit_charge_item') + + if doc.get('appointment_type'): + service_item, practitioner_charge = get_appointment_type_service_item(doc.appointment_type, department, is_inpatient) + + if not service_item and not practitioner_charge: + service_item, practitioner_charge = get_practitioner_service_item(doc.practitioner, is_inpatient) if not service_item: - service_item = get_healthcare_service_item('inpatient_visit_charge_item') - else: - service_item = get_practitioner_service_item(doc.practitioner, 'op_consulting_charge_item') - if not service_item: - service_item = get_healthcare_service_item('op_consulting_charge_item') + service_item = get_healthcare_service_item(is_inpatient) + if not service_item: throw_config_service_item(is_inpatient) - practitioner_charge = get_practitioner_charge(doc.practitioner, is_inpatient) if not practitioner_charge: throw_config_practitioner_charge(is_inpatient, doc.practitioner) + return {'service_item': service_item, 'practitioner_charge': practitioner_charge} + + +def get_appointment_type_service_item(appointment_type, department, is_inpatient): + from erpnext.healthcare.doctype.appointment_type.appointment_type import get_service_item_based_on_department + + item_list = get_service_item_based_on_department(appointment_type, department) + service_item = None + practitioner_charge = None + + if item_list: + if is_inpatient: + service_item = item_list.get('inpatient_visit_charge_item') + practitioner_charge = item_list.get('inpatient_visit_charge') + else: + service_item = item_list.get('op_consulting_charge_item') + practitioner_charge = item_list.get('op_consulting_charge') + return service_item, practitioner_charge @@ -345,12 +377,27 @@ def throw_config_practitioner_charge(is_inpatient, practitioner): frappe.throw(msg, title=_('Missing Configuration')) -def get_practitioner_service_item(practitioner, service_item_field): - return frappe.db.get_value('Healthcare Practitioner', practitioner, service_item_field) +def get_practitioner_service_item(practitioner, is_inpatient): + service_item = None + practitioner_charge = None + + if is_inpatient: + service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['inpatient_visit_charge_item', 'inpatient_visit_charge']) + else: + service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['op_consulting_charge_item', 'op_consulting_charge']) + + return service_item, practitioner_charge -def get_healthcare_service_item(service_item_field): - return frappe.db.get_single_value('Healthcare Settings', service_item_field) +def get_healthcare_service_item(is_inpatient): + service_item = None + + if is_inpatient: + service_item = frappe.db.get_single_value('Healthcare Settings', 'inpatient_visit_charge_item') + else: + service_item = frappe.db.get_single_value('Healthcare Settings', 'op_consulting_charge_item') + + return service_item def get_practitioner_charge(practitioner, is_inpatient): @@ -381,7 +428,8 @@ def set_invoiced(item, method, ref_invoice=None): invoiced = True if item.reference_dt == 'Clinical Procedure': - if get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code: + service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') + if service_item == item.item_code: frappe.db.set_value(item.reference_dt, item.reference_dn, 'consumption_invoiced', invoiced) else: frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced) @@ -403,7 +451,8 @@ def set_invoiced(item, method, ref_invoice=None): def validate_invoiced_on_submit(item): - if item.reference_dt == 'Clinical Procedure' and get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code: + if item.reference_dt == 'Clinical Procedure' and \ + frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') == item.item_code: is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'consumption_invoiced') else: is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced') From 4dad1f01ba397aae5eddcc39366543ee7c5b85e7 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 2 Feb 2021 12:43:13 +0530 Subject: [PATCH 0256/1264] fix: contact permmission issue (#24503) --- erpnext/crm/doctype/lead/lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 1439adb015..938cbfdc85 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -176,7 +176,7 @@ class Lead(SellingController): "phone": self.mobile_no }) - contact.insert() + contact.insert(ignore_permissions=True) return contact From 57c2e07c45c24007ef419aef7daa231224b4365d Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 11 Feb 2021 17:50:57 +0530 Subject: [PATCH 0257/1264] fix: validate cancellation only if irn generated (#24608) --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 0f604efa1f..2df49a6655 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -35,7 +35,7 @@ def validate_einvoice_fields(doc): elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) - elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: + elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) def raise_document_name_too_long_error(): From a439d19917f9c06958a1f62f215571355e0352f7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 11 Feb 2021 14:06:15 +0530 Subject: [PATCH 0258/1264] feat(pos): mpesa related fixes & additions (#24306) * fix: switching of mode of payments * feat: transaction limit for mpesa integration * feat: resend payment request if one fails * feat: make new request only for failed ones * fix: invalid amount for mpesa request * fix: payment successful message not shown * fix: url and business shortcode for live env * fix: duplicate items validation for pos invoices * fix: pos closing entry queued status * fix: peroid end date for amended pos closing --- .../payment_request/payment_request.py | 50 +++-- .../payment_request/test_payment_request.py | 12 +- .../pos_closing_entry/pos_closing_entry.js | 2 +- .../doctype/pos_invoice/pos_invoice.js | 41 +++- .../doctype/pos_invoice/pos_invoice.py | 56 ++++-- erpnext/controllers/selling_controller.py | 6 +- .../doctype/mpesa_settings/mpesa_connector.py | 8 +- .../mpesa_settings/mpesa_settings.json | 18 +- .../doctype/mpesa_settings/mpesa_settings.py | 115 ++++++++--- .../mpesa_settings/test_mpesa_settings.py | 182 ++++++++++++++---- .../page/point_of_sale/pos_item_cart.js | 8 +- .../selling/page/point_of_sale/pos_payment.js | 75 +++++--- 12 files changed, 441 insertions(+), 132 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 1b97050eb1..53ac996290 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -3,6 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals +import json import frappe from frappe import _ from frappe.model.document import Document @@ -82,18 +83,37 @@ class PaymentRequest(Document): self.make_communication_entry() elif self.payment_channel == "Phone": - controller = get_payment_gateway_controller(self.payment_gateway) - payment_record = dict( - reference_doctype="Payment Request", - reference_docname=self.name, - payment_reference=self.reference_name, - grand_total=self.grand_total, - sender=self.email_to, - currency=self.currency, - payment_gateway=self.payment_gateway - ) - controller.validate_transaction_currency(self.currency) - controller.request_for_payment(**payment_record) + self.request_phone_payment() + + def request_phone_payment(self): + controller = get_payment_gateway_controller(self.payment_gateway) + request_amount = self.get_request_amount() + + payment_record = dict( + reference_doctype="Payment Request", + reference_docname=self.name, + payment_reference=self.reference_name, + request_amount=request_amount, + sender=self.email_to, + currency=self.currency, + payment_gateway=self.payment_gateway + ) + + controller.validate_transaction_currency(self.currency) + controller.request_for_payment(**payment_record) + + def get_request_amount(self): + data_of_completed_requests = frappe.get_all("Integration Request", filters={ + 'reference_doctype': self.doctype, + 'reference_docname': self.name, + 'status': 'Completed' + }, pluck="data") + + if not data_of_completed_requests: + return self.grand_total + + request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests]) + return request_amounts def on_cancel(self): self.check_if_payment_entry_exists() @@ -351,8 +371,8 @@ def make_payment_request(**args): if args.order_type == "Shopping Cart" or args.mute_email: pr.flags.mute_email = True + pr.insert(ignore_permissions=True) if args.submit_doc: - pr.insert(ignore_permissions=True) pr.submit() if args.order_type == "Shopping Cart": @@ -412,8 +432,8 @@ def get_existing_payment_request_amount(ref_dt, ref_dn): def get_gateway_details(args): """return gateway and payment account of default payment gateway""" - if args.get("payment_gateway"): - return get_payment_gateway_account(args.get("payment_gateway")) + if args.get("payment_gateway_account"): + return get_payment_gateway_account(args.get("payment_gateway_account")) if args.order_type == "Shopping Cart": payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 8a10e2cbd9..5eba62c0b3 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -45,7 +45,8 @@ class TestPaymentRequest(unittest.TestCase): def test_payment_request_linkings(self): so_inr = make_sales_order(currency="INR") - pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com") + pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com", + payment_gateway_account="_Test Gateway - INR") self.assertEqual(pr.reference_doctype, "Sales Order") self.assertEqual(pr.reference_name, so_inr.name) @@ -54,7 +55,8 @@ class TestPaymentRequest(unittest.TestCase): conversion_rate = get_exchange_rate("USD", "INR") si_usd = create_sales_invoice(currency="USD", conversion_rate=conversion_rate) - pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com") + pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com", + payment_gateway_account="_Test Gateway - USD") self.assertEqual(pr.reference_doctype, "Sales Invoice") self.assertEqual(pr.reference_name, si_usd.name) @@ -68,7 +70,7 @@ class TestPaymentRequest(unittest.TestCase): so_inr = make_sales_order(currency="INR") pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com", - mute_email=1, submit_doc=1, return_doc=1) + mute_email=1, payment_gateway_account="_Test Gateway - INR", submit_doc=1, return_doc=1) pe = pr.set_as_paid() so_inr = frappe.get_doc("Sales Order", so_inr.name) @@ -79,7 +81,7 @@ class TestPaymentRequest(unittest.TestCase): currency="USD", conversion_rate=50) pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com", - mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1) + mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1) pe = pr.set_as_paid() @@ -106,7 +108,7 @@ class TestPaymentRequest(unittest.TestCase): currency="USD", conversion_rate=50) pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com", - mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1) + mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1) pe = pr.create_payment_entry() pr.load_from_db() diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 57baac7681..e79eb421a0 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -20,7 +20,7 @@ frappe.ui.form.on('POS Closing Entry', { 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 === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime()); if (frm.doc.docstatus === 1) set_html_data(frm); }, diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index 86062d1e7c..6a7c4be890 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -187,18 +187,43 @@ frappe.ui.form.on('POS Invoice', { }, request_for_payment: function (frm) { + if (!frm.doc.contact_mobile) { + frappe.throw(__('Please enter mobile number first.')); + } + frm.dirty(); frm.save().then(() => { - frappe.dom.freeze(); - frappe.call({ - method: 'create_payment_request', - doc: frm.doc, - }) + frappe.dom.freeze(__('Waiting for payment...')); + frappe + .call({ + method: 'create_payment_request', + doc: frm.doc + }) .fail(() => { frappe.dom.unfreeze(); - frappe.msgprint('Payment request failed'); + frappe.msgprint(__('Payment request failed')); }) - .then(() => { - frappe.msgprint('Payment request sent successfully'); + .then(({ message }) => { + const payment_request_name = message.name; + setTimeout(() => { + frappe.db.get_value('Payment Request', payment_request_name, ['status', 'grand_total']).then(({ message }) => { + if (message.status != 'Paid') { + frappe.dom.unfreeze(); + frappe.msgprint({ + message: __('Payment Request took too long to respond. Please try requesting for payment again.'), + title: __('Request Timeout') + }); + } else if (frappe.dom.freeze_count != 0) { + frappe.dom.unfreeze(); + cur_frm.reload_doc(); + cur_pos.payment.events.submit_invoice(); + + frappe.show_alert({ + message: __("Payment of {0} received successfully.", [format_currency(message.grand_total, frm.doc.currency, 0)]), + indicator: 'green' + }); + } + }); + }, 60000); }); }); } diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index bd664c59f2..f67cae308a 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -363,22 +363,48 @@ class POSInvoice(SalesInvoice): if not self.contact_mobile: frappe.throw(_("Please enter the phone number first")) - payment_gateway = frappe.db.get_value("Payment Gateway Account", { - "payment_account": pay.account, - }) - record = { - "payment_gateway": payment_gateway, - "dt": "POS Invoice", - "dn": self.name, - "payment_request_type": "Inward", - "party_type": "Customer", - "party": self.customer, - "mode_of_payment": pay.mode_of_payment, - "recipient_id": self.contact_mobile, - "submit_doc": True - } + pay_req = self.get_existing_payment_request(pay) + if not pay_req: + pay_req = self.get_new_payment_request(pay) + pay_req.submit() + else: + pay_req.request_phone_payment() - return make_payment_request(**record) + return pay_req + + def get_new_payment_request(self, mop): + payment_gateway_account = frappe.db.get_value("Payment Gateway Account", { + "payment_account": mop.account, + }, ["name"]) + + args = { + "dt": "POS Invoice", + "dn": self.name, + "recipient_id": self.contact_mobile, + "mode_of_payment": mop.mode_of_payment, + "payment_gateway_account": payment_gateway_account, + "payment_request_type": "Inward", + "party_type": "Customer", + "party": self.customer, + "return_doc": True + } + return make_payment_request(**args) + + def get_existing_payment_request(self, pay): + payment_gateway_account = frappe.db.get_value("Payment Gateway Account", { + "payment_account": pay.account, + }, ["name"]) + + args = { + 'doctype': 'Payment Request', + 'reference_doctype': 'POS Invoice', + 'reference_name': self.name, + 'payment_gateway_account': payment_gateway_account, + 'email_to': self.contact_mobile + } + pr = frappe.db.exists(args) + if pr: + return frappe.get_doc('Payment Request', pr[0][0]) def add_return_modes(doc, pos_profile): def append_payment(payment_mode): diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index e085048f99..aa7b27adf4 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -456,9 +456,13 @@ class SellingController(StockController): check_list, chk_dupl_itm = [], [] if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")): return + if self.doctype == "Sales Invoice" and self.is_consolidated: + return + if self.doctype == "POS Invoice": + return for d in self.get('items'): - if self.doctype in ["POS Invoice","Sales Invoice"]: + if self.doctype == "Sales Invoice": stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or ''] non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note] elif self.doctype == "Delivery Note": diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py index d33b0a7089..554c6b0eb0 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py @@ -5,7 +5,7 @@ import datetime class MpesaConnector(): def __init__(self, env="sandbox", app_key=None, app_secret=None, sandbox_url="https://sandbox.safaricom.co.ke", - live_url="https://safaricom.co.ke"): + live_url="https://api.safaricom.co.ke"): """Setup configuration for Mpesa connector and generate new access token.""" self.env = env self.app_key = app_key @@ -102,14 +102,14 @@ class MpesaConnector(): "BusinessShortCode": business_shortcode, "Password": encoded.decode("utf-8"), "Timestamp": time, - "TransactionType": "CustomerPayBillOnline", "Amount": amount, "PartyA": int(phone_number), - "PartyB": business_shortcode, + "PartyB": reference_code, "PhoneNumber": int(phone_number), "CallBackURL": callback_url, "AccountReference": reference_code, - "TransactionDesc": description + "TransactionDesc": description, + "TransactionType": "CustomerPayBillOnline" if self.env == "sandbox" else "CustomerBuyGoodsOnline" } headers = {'Authorization': 'Bearer {0}'.format(self.authentication_token), 'Content-Type': "application/json"} diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json index fc7b310c08..407f82616f 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json @@ -11,8 +11,10 @@ "consumer_secret", "initiator_name", "till_number", + "transaction_limit", "sandbox", "column_break_4", + "business_shortcode", "online_passkey", "security_credential", "get_account_balance", @@ -84,10 +86,24 @@ "fieldname": "get_account_balance", "fieldtype": "Button", "label": "Get Account Balance" + }, + { + "depends_on": "eval:(doc.sandbox==0)", + "fieldname": "business_shortcode", + "fieldtype": "Data", + "label": "Business Shortcode", + "mandatory_depends_on": "eval:(doc.sandbox==0)" + }, + { + "default": "150000", + "fieldname": "transaction_limit", + "fieldtype": "Float", + "label": "Transaction Limit", + "non_negative": 1 } ], "links": [], - "modified": "2020-09-25 20:21:38.215494", + "modified": "2021-01-29 12:02:16.106942", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Mpesa Settings", diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py index 1cad84dcde..b5718026c1 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py @@ -33,13 +33,34 @@ class MpesaSettings(Document): create_mode_of_payment('Mpesa-' + self.payment_gateway_name, payment_type="Phone") def request_for_payment(self, **kwargs): - if frappe.flags.in_test: - from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import get_payment_request_response_payload - response = frappe._dict(get_payment_request_response_payload()) - else: - response = frappe._dict(generate_stk_push(**kwargs)) + args = frappe._dict(kwargs) + request_amounts = self.split_request_amount_according_to_transaction_limit(args) - self.handle_api_response("CheckoutRequestID", kwargs, response) + for i, amount in enumerate(request_amounts): + args.request_amount = amount + if frappe.flags.in_test: + from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import get_payment_request_response_payload + response = frappe._dict(get_payment_request_response_payload(amount)) + else: + response = frappe._dict(generate_stk_push(**args)) + + self.handle_api_response("CheckoutRequestID", args, response) + + def split_request_amount_according_to_transaction_limit(self, args): + request_amount = args.request_amount + if request_amount > self.transaction_limit: + # make multiple requests + request_amounts = [] + requests_to_be_made = frappe.utils.ceil(request_amount / self.transaction_limit) # 480/150 = ceil(3.2) = 4 + for i in range(requests_to_be_made): + amount = self.transaction_limit + if i == requests_to_be_made - 1: + amount = request_amount - (self.transaction_limit * i) # for 4th request, 480 - (150 * 3) = 30 + request_amounts.append(amount) + else: + request_amounts = [request_amount] + + return request_amounts def get_account_balance_info(self): payload = dict( @@ -67,7 +88,8 @@ class MpesaSettings(Document): req_name = getattr(response, global_id) error = None - create_request_log(request_dict, "Host", "Mpesa", req_name, error) + if not frappe.db.exists('Integration Request', req_name): + create_request_log(request_dict, "Host", "Mpesa", req_name, error) if error: frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error")) @@ -80,6 +102,8 @@ def generate_stk_push(**kwargs): mpesa_settings = frappe.get_doc("Mpesa Settings", args.payment_gateway[6:]) env = "production" if not mpesa_settings.sandbox else "sandbox" + # for sandbox, business shortcode is same as till number + business_shortcode = mpesa_settings.business_shortcode if env == "production" else mpesa_settings.till_number connector = MpesaConnector(env=env, app_key=mpesa_settings.consumer_key, @@ -87,10 +111,12 @@ def generate_stk_push(**kwargs): mobile_number = sanitize_mobile_number(args.sender) - response = connector.stk_push(business_shortcode=mpesa_settings.till_number, - passcode=mpesa_settings.get_password("online_passkey"), amount=args.grand_total, + response = connector.stk_push( + business_shortcode=business_shortcode, amount=args.request_amount, + passcode=mpesa_settings.get_password("online_passkey"), callback_url=callback_url, reference_code=mpesa_settings.till_number, - phone_number=mobile_number, description="POS Payment") + phone_number=mobile_number, description="POS Payment" + ) return response @@ -108,29 +134,72 @@ def verify_transaction(**kwargs): transaction_response = frappe._dict(kwargs["Body"]["stkCallback"]) checkout_id = getattr(transaction_response, "CheckoutRequestID", "") - request = frappe.get_doc("Integration Request", checkout_id) - transaction_data = frappe._dict(loads(request.data)) + integration_request = frappe.get_doc("Integration Request", checkout_id) + transaction_data = frappe._dict(loads(integration_request.data)) + total_paid = 0 # for multiple integration request made against a pos invoice + success = False # for reporting successfull callback to point of sale ui if transaction_response['ResultCode'] == 0: - if request.reference_doctype and request.reference_docname: + if integration_request.reference_doctype and integration_request.reference_docname: try: - doc = frappe.get_doc(request.reference_doctype, - request.reference_docname) - doc.run_method("on_payment_authorized", 'Completed') - item_response = transaction_response["CallbackMetadata"]["Item"] + amount = fetch_param_value(item_response, "Amount", "Name") mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name") - frappe.db.set_value("POS Invoice", doc.reference_name, "mpesa_receipt_number", mpesa_receipt) - request.handle_success(transaction_response) + pr = frappe.get_doc(integration_request.reference_doctype, integration_request.reference_docname) + + mpesa_receipts, completed_payments = get_completed_integration_requests_info( + integration_request.reference_doctype, + integration_request.reference_docname, + checkout_id + ) + + total_paid = amount + sum(completed_payments) + mpesa_receipts = ', '.join(mpesa_receipts + [mpesa_receipt]) + + if total_paid >= pr.grand_total: + pr.run_method("on_payment_authorized", 'Completed') + success = True + + frappe.db.set_value("POS Invoice", pr.reference_name, "mpesa_receipt_number", mpesa_receipts) + integration_request.handle_success(transaction_response) except Exception: - request.handle_failure(transaction_response) + integration_request.handle_failure(transaction_response) frappe.log_error(frappe.get_traceback()) else: - request.handle_failure(transaction_response) + integration_request.handle_failure(transaction_response) - frappe.publish_realtime('process_phone_payment', doctype="POS Invoice", - docname=transaction_data.payment_reference, user=request.owner, message=transaction_response) + frappe.publish_realtime( + event='process_phone_payment', + doctype="POS Invoice", + docname=transaction_data.payment_reference, + user=integration_request.owner, + message={ + 'amount': total_paid, + 'success': success, + 'failure_message': transaction_response["ResultDesc"] if transaction_response['ResultCode'] != 0 else '' + }, + ) + +def get_completed_integration_requests_info(reference_doctype, reference_docname, checkout_id): + output_of_other_completed_requests = frappe.get_all("Integration Request", filters={ + 'name': ['!=', checkout_id], + 'reference_doctype': reference_doctype, + 'reference_docname': reference_docname, + 'status': 'Completed' + }, pluck="output") + + mpesa_receipts, completed_payments = [], [] + + for out in output_of_other_completed_requests: + out = frappe._dict(loads(out)) + item_response = out["CallbackMetadata"]["Item"] + completed_amount = fetch_param_value(item_response, "Amount", "Name") + completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name") + completed_payments.append(completed_amount) + mpesa_receipts.append(completed_mpesa_receipt) + + return mpesa_receipts, completed_payments def get_account_balance(request_payload): """Call account balance API to send the request to the Mpesa Servers.""" diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 4e86d365e3..18d2732313 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -9,6 +9,10 @@ from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import p from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice class TestMpesaSettings(unittest.TestCase): + def tearDown(self): + frappe.db.sql('delete from `tabMpesa Settings`') + frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"') + def test_creation_of_payment_gateway(self): create_mpesa_settings(payment_gateway_name="_Test") @@ -40,6 +44,8 @@ class TestMpesaSettings(unittest.TestCase): } })) + integration_request.delete() + def test_processing_of_callback_payload(self): create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") @@ -55,10 +61,16 @@ class TestMpesaSettings(unittest.TestCase): # test payment request creation self.assertEquals(pr.payment_gateway, "Mpesa-Payment") - callback_response = get_payment_callback_payload() + # submitting payment request creates integration requests with random id + integration_req_ids = frappe.get_all("Integration Request", filters={ + 'reference_doctype': pr.doctype, + 'reference_docname': pr.name, + }, pluck="name") + + callback_response = get_payment_callback_payload(Amount=500, CheckoutRequestID=integration_req_ids[0]) verify_transaction(**callback_response) # test creation of integration request - integration_request = frappe.get_doc("Integration Request", "ws_CO_061020201133231972") + integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) # test integration request creation and successful update of the status on receiving callback response self.assertTrue(integration_request) @@ -68,6 +80,120 @@ class TestMpesaSettings(unittest.TestCase): integration_request.reload() self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") self.assertEquals(integration_request.status, "Completed") + + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") + integration_request.delete() + pr.reload() + pr.cancel() + pr.delete() + pos_invoice.delete() + + def test_processing_of_multiple_callback_payload(self): + create_mpesa_settings(payment_gateway_name="Payment") + mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") + frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") + frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") + + pos_invoice = create_pos_invoice(do_not_submit=1) + pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 1000}) + pos_invoice.contact_mobile = "093456543894" + pos_invoice.currency = "KES" + pos_invoice.save() + + pr = pos_invoice.create_payment_request() + # test payment request creation + self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + + # submitting payment request creates integration requests with random id + integration_req_ids = frappe.get_all("Integration Request", filters={ + 'reference_doctype': pr.doctype, + 'reference_docname': pr.name, + }, pluck="name") + + # create random receipt nos and send it as response to callback handler + mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids] + + integration_requests = [] + for i in range(len(integration_req_ids)): + callback_response = get_payment_callback_payload( + Amount=500, + CheckoutRequestID=integration_req_ids[i], + MpesaReceiptNumber=mpesa_receipt_numbers[i] + ) + # handle response manually + verify_transaction(**callback_response) + # test completion of integration request + integration_request = frappe.get_doc("Integration Request", integration_req_ids[i]) + self.assertEquals(integration_request.status, "Completed") + integration_requests.append(integration_request) + + # check receipt number once all the integration requests are completed + pos_invoice.reload() + self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers)) + + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") + [d.delete() for d in integration_requests] + pr.reload() + pr.cancel() + pr.delete() + pos_invoice.delete() + + def test_processing_of_only_one_succes_callback_payload(self): + create_mpesa_settings(payment_gateway_name="Payment") + mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") + frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") + frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") + + pos_invoice = create_pos_invoice(do_not_submit=1) + pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 1000}) + pos_invoice.contact_mobile = "093456543894" + pos_invoice.currency = "KES" + pos_invoice.save() + + pr = pos_invoice.create_payment_request() + # test payment request creation + self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + + # submitting payment request creates integration requests with random id + integration_req_ids = frappe.get_all("Integration Request", filters={ + 'reference_doctype': pr.doctype, + 'reference_docname': pr.name, + }, pluck="name") + + # create random receipt nos and send it as response to callback handler + mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids] + + callback_response = get_payment_callback_payload( + Amount=500, + CheckoutRequestID=integration_req_ids[0], + MpesaReceiptNumber=mpesa_receipt_numbers[0] + ) + # handle response manually + verify_transaction(**callback_response) + # test completion of integration request + integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) + self.assertEquals(integration_request.status, "Completed") + + # now one request is completed + # second integration request fails + # now retrying payment request should make only one integration request again + pr = pos_invoice.create_payment_request() + new_integration_req_ids = frappe.get_all("Integration Request", filters={ + 'reference_doctype': pr.doctype, + 'reference_docname': pr.name, + 'name': ['not in', integration_req_ids] + }, pluck="name") + + self.assertEquals(len(new_integration_req_ids), 1) + + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") + frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'") + pr.reload() + pr.cancel() + pr.delete() + pos_invoice.delete() def create_mpesa_settings(payment_gateway_name="Express"): if frappe.db.exists("Mpesa Settings", payment_gateway_name): @@ -157,16 +283,19 @@ def get_test_account_balance_response(): } } -def get_payment_request_response_payload(): +def get_payment_request_response_payload(Amount=500): """Response received after successfully calling the stk push process request API.""" + + CheckoutRequestID = frappe.utils.random_string(10) + return { "MerchantRequestID": "8071-27184008-1", - "CheckoutRequestID": "ws_CO_061020201133231972", + "CheckoutRequestID": CheckoutRequestID, "ResultCode": 0, "ResultDesc": "The service request is processed successfully.", "CallbackMetadata": { "Item": [ - { "Name": "Amount", "Value": 500.0 }, + { "Name": "Amount", "Value": Amount }, { "Name": "MpesaReceiptNumber", "Value": "LGR7OWQX0R" }, { "Name": "TransactionDate", "Value": 20201006113336 }, { "Name": "PhoneNumber", "Value": 254723575670 } @@ -174,41 +303,26 @@ def get_payment_request_response_payload(): } } - -def get_payment_callback_payload(): +def get_payment_callback_payload(Amount=500, CheckoutRequestID="ws_CO_061020201133231972", MpesaReceiptNumber="LGR7OWQX0R"): """Response received from the server as callback after calling the stkpush process request API.""" return { "Body":{ - "stkCallback":{ - "MerchantRequestID":"19465-780693-1", - "CheckoutRequestID":"ws_CO_061020201133231972", - "ResultCode":0, - "ResultDesc":"The service request is processed successfully.", - "CallbackMetadata":{ - "Item":[ - { - "Name":"Amount", - "Value":500 - }, - { - "Name":"MpesaReceiptNumber", - "Value":"LGR7OWQX0R" - }, - { - "Name":"Balance" - }, - { - "Name":"TransactionDate", - "Value":20170727154800 - }, - { - "Name":"PhoneNumber", - "Value":254721566839 + "stkCallback":{ + "MerchantRequestID":"19465-780693-1", + "CheckoutRequestID":CheckoutRequestID, + "ResultCode":0, + "ResultDesc":"The service request is processed successfully.", + "CallbackMetadata":{ + "Item":[ + { "Name":"Amount", "Value":Amount }, + { "Name":"MpesaReceiptNumber", "Value":MpesaReceiptNumber }, + { "Name":"Balance" }, + { "Name":"TransactionDate", "Value":20170727154800 }, + { "Name":"PhoneNumber", "Value":254721566839 } + ] } - ] } } - } } def get_account_balance_callback_payload(): diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 3938300a2a..03d99c6bfa 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -482,8 +482,12 @@ erpnext.PointOfSale.ItemCart = class { 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); + const taxes = frm.doc.taxes.map(t => { + return { + description: t.description, rate: t.rate + }; + }); + this.render_taxes(frm.doc.total_taxes_and_charges, taxes); } render_net_total(value) { diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index e4d8965ac2..e150271fd0 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -168,30 +168,22 @@ erpnext.PointOfSale.Payment = class { me.toggle_numpad(true); me.selected_mode = me[`${mode}_control`]; - const doc = me.events.get_frm().doc; - me.selected_mode?.$input?.get(0).focus(); - const current_value = me.selected_mode?.get_value() - !current_value && doc.grand_total > doc.paid_amount ? me.selected_mode?.set_value(doc.grand_total - doc.paid_amount) : ''; + me.selected_mode && me.selected_mode.$input.get(0).focus(); + me.auto_set_remaining_amount(); } }) - frappe.realtime.on("process_phone_payment", function(data) { - frappe.dom.unfreeze(); - cur_frm.reload_doc(); - let message = data["ResultDesc"]; - let title = __("Payment Failed"); + frappe.ui.form.on('POS Invoice', 'contact_mobile', (frm) => { + const contact = frm.doc.contact_mobile; + const request_button = $(this.request_for_payment_field.$input[0]); + if (contact) { + request_button.removeClass('btn-default').addClass('btn-primary'); + } else { + request_button.removeClass('btn-primary').addClass('btn-default'); + } + }); - if (data["ResultCode"] == 0) { - title = __("Payment Received"); - $('.btn.btn-xs.btn-default[data-fieldname=request_for_payment]').html(`Payment Received`) - me.events.submit_invoice(); - } - - frappe.msgprint({ - "message": message, - "title": title - }); - }); + this.setup_listener_for_payments(); this.$payment_modes.on('click', '.shortcut', function(e) { const value = $(this).attr('data-value'); @@ -250,6 +242,41 @@ erpnext.PointOfSale.Payment = class { }) } + setup_listener_for_payments() { + frappe.realtime.on("process_phone_payment", (data) => { + const doc = this.events.get_frm().doc; + const { response, amount, success, failure_message } = data; + let message, title; + + if (success) { + title = __("Payment Received"); + if (amount >= doc.grand_total) { + frappe.dom.unfreeze(); + message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]); + this.events.submit_invoice(); + cur_frm.reload_doc(); + + } else { + message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]); + } + } else if (failure_message) { + message = failure_message; + title = __("Payment Failed"); + } + + frappe.msgprint({ "message": message, "title": title }); + }); + } + + auto_set_remaining_amount() { + const doc = this.events.get_frm().doc; + const remaining_amount = doc.grand_total - doc.paid_amount; + const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined; + if (!current_value && remaining_amount > 0 && this.selected_mode) { + this.selected_mode.set_value(remaining_amount); + } + } + attach_shortcuts() { const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; this.$component.find('.submit-order').attr("title", `${ctrl_label}+Enter`); @@ -370,9 +397,11 @@ erpnext.PointOfSale.Payment = class { fieldtype: 'Currency', placeholder: __('Enter {0} amount.', [p.mode_of_payment]), 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 current_value = frappe.model.get_value(p.doctype, p.name, 'amount'); + if (current_value != this.value) { + 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); From 4fb547179dbb1592ffd13be8972a74392f7749d6 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 11 Feb 2021 13:10:52 +0530 Subject: [PATCH 0259/1264] fix: customer_currency referenced before assignment (#24607) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index f67cae308a..7312248456 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -299,6 +299,9 @@ class POSInvoice(SalesInvoice): 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 profile.get('selling_price_list') + if customer_currency != profile.get('currency'): + self.set('currency', customer_currency) + else: selling_price_list = profile.get('selling_price_list') From 18c7c45cfe57611072be555d1f69840907a49cf5 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 10 Feb 2021 17:21:12 +0530 Subject: [PATCH 0260/1264] fix: NoneType has no len() (#24600) --- erpnext/regional/india/e_invoice/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 2df49a6655..4460d82da5 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -23,6 +23,10 @@ def validate_einvoice_fields(doc): invalid_doctype = doc.doctype not in ['Sales Invoice'] invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') +<<<<<<< HEAD +======= + no_taxes_applied = len(doc.get('taxes', [])) == 0 +>>>>>>> 7b2afaf349... fix: NoneType has no len() (#24600) if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return From ade96589b46970bc9a80e33a4dff915450437fe7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 8 Feb 2021 11:40:56 +0530 Subject: [PATCH 0261/1264] fix(e-invoice): skip e-invoice generation for non-taxable invoices (#24568) --- erpnext/regional/india/e_invoice/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 4460d82da5..876016fc19 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -20,15 +20,13 @@ from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds def validate_einvoice_fields(doc): einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) - invalid_doctype = doc.doctype not in ['Sales Invoice'] + invalid_doctype = doc.doctype != 'Sales Invoice' invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') -<<<<<<< HEAD -======= - no_taxes_applied = len(doc.get('taxes', [])) == 0 ->>>>>>> 7b2afaf349... fix: NoneType has no len() (#24600) - - if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return + no_taxes_applied = not doc.get('taxes') + + if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied: + return if doc.docstatus == 0 and doc._action == 'save': if doc.irn: From fd4bed1a3866064cba77b49b53aa4a39800cb998 Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 6 Feb 2021 17:55:20 +0530 Subject: [PATCH 0262/1264] fix(e-invoice): do not validate gstin for exports (#24561) --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 876016fc19..4cf0b18f8c 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -304,7 +304,7 @@ def validate_mandatory_fields(invoice): _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), title=_('Missing Fields') ) - if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): + if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): frappe.throw( _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), title=_('Missing Fields') From e965d0ff900714eece1028c888d5d4dbabdf9b28 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 29 Jan 2021 14:24:08 +0530 Subject: [PATCH 0263/1264] fix: discount amount calculation on net total (#24497) --- erpnext/regional/india/e_invoice/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 4cf0b18f8c..4b6bc251ad 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -163,7 +163,7 @@ def get_item_list(invoice): item.description = d.item_name.replace('"', '\\"') item.qty = abs(item.qty) - item.discount_amount = abs(item.discount_amount * item.qty) + item.discount_amount = 0 item.unit_rate = abs(item.base_net_amount / item.qty) item.gross_amount = abs(item.base_net_amount) item.taxable_value = abs(item.base_net_amount) @@ -223,11 +223,12 @@ def get_invoice_value_details(invoice): if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: invoice_value_details.base_total = abs(invoice.base_total) + invoice_value_details.invoice_discount_amt = invoice.base_discount_amount else: invoice_value_details.base_total = abs(invoice.base_net_total) + # since tax already considers discount amount + invoice_value_details.invoice_discount_amt = 0 - # since tax already considers discount amount - invoice_value_details.invoice_discount_amt = 0 # invoice.base_discount_amount invoice_value_details.round_off = invoice.base_rounding_adjustment invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) From 90ff48baa35a240a29f241031e25bb5f246cfe21 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 28 Jan 2021 18:42:43 +0530 Subject: [PATCH 0264/1264] refactor: POS Invoice merging and cancellation (#24351) * feat: pos invoice merging with background jobs * fix: invoice threshold for queueing job * refactor: cancellation flow of point of sale * feat: tests for cancellation of pos closing Co-authored-by: Marica --- .../pos_closing_entry/pos_closing_entry.js | 1 + .../pos_closing_entry/pos_closing_entry.json | 21 +++- .../pos_closing_entry/pos_closing_entry.py | 28 +++-- .../pos_closing_entry_list.js | 16 +++ .../test_pos_closing_entry.py | 44 ++++++- .../doctype/pos_invoice/pos_invoice.js | 1 + .../doctype/pos_invoice/pos_invoice.py | 19 ++- .../doctype/pos_invoice/test_pos_invoice.py | 12 +- .../pos_invoice_merge_log.json | 15 ++- .../pos_invoice_merge_log.py | 113 +++++++++++++++--- .../test_pos_invoice_merge_log.py | 6 +- .../pos_opening_entry/pos_opening_entry.py | 1 - .../pos_opening_entry_list.js | 2 +- .../doctype/sales_invoice/sales_invoice.js | 1 + .../doctype/sales_invoice/sales_invoice.json | 11 +- .../doctype/sales_invoice/sales_invoice.py | 18 +++ erpnext/controllers/status_updater.py | 6 + erpnext/patches.txt | 1 + .../update_pos_closing_entry_in_merge_log.py | 25 ++++ 19 files changed, 291 insertions(+), 50 deletions(-) create mode 100644 erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js create mode 100644 erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index e79eb421a0..9ea616f8e7 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -3,6 +3,7 @@ frappe.ui.form.on('POS Closing Entry', { onload: function(frm) { + frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log']; frm.set_query("pos_profile", function(doc) { return { filters: { 'user': doc.user } diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json index 32bca3b840..18d430f59f 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -11,6 +11,7 @@ "column_break_3", "posting_date", "pos_opening_entry", + "status", "section_break_5", "company", "column_break_7", @@ -184,11 +185,27 @@ "label": "POS Opening Entry", "options": "POS Opening Entry", "reqd": 1 + }, + { + "allow_on_submit": 1, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "label": "Status", + "options": "Draft\nSubmitted\nQueued\nCancelled", + "print_hide": 1, + "read_only": 1 } ], "is_submittable": 1, - "links": [], - "modified": "2020-05-29 15:03:22.226113", + "links": [ + { + "link_doctype": "POS Invoice Merge Log", + "link_fieldname": "pos_closing_entry" + } + ], + "modified": "2021-01-12 12:21:05.388650", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index c26e14ff6f..edf3d5a574 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -6,13 +6,12 @@ from __future__ import unicode_literals import frappe import json from frappe import _ -from frappe.model.document import Document -from frappe.utils import getdate, get_datetime, flt -from collections import defaultdict +from frappe.utils import get_datetime, flt +from erpnext.controllers.status_updater import StatusUpdater from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data -from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices +from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices, unconsolidate_pos_invoices -class POSClosingEntry(Document): +class POSClosingEntry(StatusUpdater): def validate(self): if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) @@ -64,17 +63,22 @@ class POSClosingEntry(Document): frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True) - 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}) + + def on_submit(self): + consolidate_pos_invoices(closing_entry=self) + + def on_cancel(self): + unconsolidate_pos_invoices(closing_entry=self) + + def update_opening_entry(self, for_cancel=False): + opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry) + opening_entry.pos_closing_entry = self.name if not for_cancel else None + opening_entry.set_status() + opening_entry.save() @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js new file mode 100644 index 0000000000..20fd610899 --- /dev/null +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_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 Closing Entry'] = { + get_indicator: function(doc) { + var status_color = { + "Draft": "red", + "Submitted": "blue", + "Queued": "orange", + "Cancelled": "red" + + }; + return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; + } +}; 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 index 8de54d5bde..40db09ec3b 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -13,7 +13,6 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi class TestPOSClosingEntry(unittest.TestCase): def test_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) @@ -45,6 +44,49 @@ class TestPOSClosingEntry(unittest.TestCase): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") + def test_cancelling_of_pos_closing_entry(self): + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile, test_user.name) + + pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) + pos_inv1.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500 + }) + pos_inv1.submit() + + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 + }) + pos_inv2.submit() + + pcv_doc = make_closing_entry_from_opening(opening_entry) + payment = pcv_doc.payment_reconciliation[0] + + self.assertEqual(payment.mode_of_payment, 'Cash') + + for d in pcv_doc.payment_reconciliation: + if d.mode_of_payment == 'Cash': + d.closing_amount = 6700 + + pcv_doc.submit() + + pos_inv1.load_from_db() + self.assertRaises(frappe.ValidationError, pos_inv1.cancel) + + si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice) + self.assertRaises(frappe.ValidationError, si_doc.cancel) + + pcv_doc.load_from_db() + pcv_doc.cancel() + si_doc.load_from_db() + pos_inv1.load_from_db() + self.assertEqual(si_doc.docstatus, 2) + self.assertEqual(pos_inv1.status, 'Paid') + + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + def init_user_and_profile(**args): user = 'test@example.com' test_user = frappe.get_doc('User', user) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index 6a7c4be890..465f0d31c4 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -11,6 +11,7 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend( onload(doc) { this._super(); + this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log']; if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') { this.frm.script_manager.trigger("is_pos"); this.frm.refresh_fields(); diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 7312248456..9d8f848b02 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -6,10 +6,9 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from erpnext.controllers.selling_controller import SellingController -from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate from erpnext.accounts.utils import get_account_currency from erpnext.accounts.party import get_party_account, get_due_date +from frappe.utils import cint, flt, getdate, nowdate, get_link_to_form from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos @@ -58,6 +57,22 @@ class POSInvoice(SalesInvoice): self.apply_loyalty_points() self.check_phone_payments() self.set_status(update=True) + + def before_cancel(self): + if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1: + pos_closing_entry = frappe.get_all( + "POS Invoice Reference", + ignore_permissions=True, + filters={ 'pos_invoice': self.name }, + pluck="parent", + limit=1 + ) + frappe.throw( + _('You need to cancel POS Closing Entry {} to be able to cancel this document.').format( + get_link_to_form("POS Closing Entry", pos_closing_entry[0]) + ), + title=_('Not Allowed') + ) def on_cancel(self): # run on cancel method of selling controller diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index c179360b01..57a23af8af 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -290,7 +290,7 @@ class TestPOSInvoice(unittest.TestCase): def test_merging_into_sales_invoice_with_discount(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices frappe.db.sql("delete from `tabPOS Invoice`") test_user, pos_profile = init_user_and_profile() @@ -306,7 +306,7 @@ class TestPOSInvoice(unittest.TestCase): }) pos_inv2.submit() - merge_pos_invoices() + consolidate_pos_invoices() pos_inv.load_from_db() rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") @@ -315,7 +315,7 @@ class TestPOSInvoice(unittest.TestCase): def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices frappe.db.sql("delete from `tabPOS Invoice`") test_user, pos_profile = init_user_and_profile() @@ -348,7 +348,7 @@ class TestPOSInvoice(unittest.TestCase): }) pos_inv2.submit() - merge_pos_invoices() + consolidate_pos_invoices() pos_inv.load_from_db() rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") @@ -357,7 +357,7 @@ class TestPOSInvoice(unittest.TestCase): def test_merging_with_validate_selling_price(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1) @@ -393,7 +393,7 @@ class TestPOSInvoice(unittest.TestCase): }) pos_inv2.submit() - merge_pos_invoices() + consolidate_pos_invoices() pos_inv2.load_from_db() rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total") 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 index 8f97639bbc..da2984f05a 100644 --- 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 @@ -7,6 +7,8 @@ "field_order": [ "posting_date", "customer", + "column_break_3", + "pos_closing_entry", "section_break_3", "pos_invoices", "references_section", @@ -76,11 +78,22 @@ "label": "Consolidated Credit Note", "options": "Sales Invoice", "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "pos_closing_entry", + "fieldtype": "Link", + "label": "POS Closing Entry", + "options": "POS Closing Entry" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-05-29 15:08:41.317100", + "modified": "2020-12-01 11:53:57.267579", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Merge Log", 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 index 5e43cfe030..5496804474 100644 --- 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 @@ -7,8 +7,11 @@ import frappe from frappe import _ from frappe.model import default_fields from frappe.model.document import Document +from frappe.utils import flt, getdate, nowdate +from frappe.utils.background_jobs import enqueue from frappe.model.mapper import map_doc, map_child_doc -from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate +from frappe.utils.scheduler import is_scheduler_inactive +from frappe.core.page.background_jobs.background_jobs import get_info from six import iteritems @@ -61,7 +64,13 @@ class POSInvoiceMergeLog(Document): self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log - self.update_pos_invoices(sales_invoice, credit_note) + self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) + + def on_cancel(self): + pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices] + + self.update_pos_invoices(pos_invoice_docs) + self.cancel_linked_invoices() def process_merging_into_sales_invoice(self, data): sales_invoice = self.get_new_sales_invoice() @@ -160,17 +169,21 @@ class POSInvoiceMergeLog(Document): return sales_invoice - def update_pos_invoices(self, sales_invoice, credit_note): - for d in self.pos_invoices: - doc = frappe.get_doc('POS Invoice', d.pos_invoice) - if not doc.is_return: - doc.update({'consolidated_invoice': sales_invoice}) - else: - doc.update({'consolidated_invoice': credit_note}) + def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''): + for doc in invoice_docs: + doc.load_from_db() + doc.update({ 'consolidated_invoice': None if self.docstatus==2 else (credit_note if doc.is_return else sales_invoice) }) doc.set_status(update=True) doc.save() -def get_all_invoices(): + def cancel_linked_invoices(self): + for si_name in [self.consolidated_invoice, self.consolidated_credit_note]: + if not si_name: continue + si = frappe.get_doc('Sales Invoice', si_name) + si.flags.ignore_validate = True + si.cancel() + +def get_all_unconsolidated_invoices(): filters = { 'consolidated_invoice': [ 'in', [ '', None ]], 'status': ['not in', ['Consolidated']], @@ -181,7 +194,7 @@ def get_all_invoices(): return pos_invoices -def get_invoices_customer_map(pos_invoices): +def get_invoice_customer_map(pos_invoices): # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] } pos_invoice_customer_map = {} for invoice in pos_invoices: @@ -191,20 +204,82 @@ def get_invoices_customer_map(pos_invoices): return pos_invoice_customer_map -def merge_pos_invoices(pos_invoices=[]): - if not pos_invoices: - pos_invoices = get_all_invoices() - - pos_invoice_map = get_invoices_customer_map(pos_invoices) - create_merge_logs(pos_invoice_map) +def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): + invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices() + invoice_by_customer = get_invoice_customer_map(invoices) -def create_merge_logs(pos_invoice_customer_map): - for customer, invoices in iteritems(pos_invoice_customer_map): + if len(invoices) >= 5 and closing_entry: + enqueue_job(create_merge_logs, invoice_by_customer, closing_entry) + closing_entry.set_status(update=True, status='Queued') + else: + create_merge_logs(invoice_by_customer, closing_entry) + +def unconsolidate_pos_invoices(closing_entry): + merge_logs = frappe.get_all( + 'POS Invoice Merge Log', + filters={ 'pos_closing_entry': closing_entry.name }, + pluck='name' + ) + + if len(merge_logs) >= 5: + enqueue_job(cancel_merge_logs, merge_logs, closing_entry) + closing_entry.set_status(update=True, status='Queued') + else: + cancel_merge_logs(merge_logs, closing_entry) + +def create_merge_logs(invoice_by_customer, closing_entry={}): + for customer, invoices in iteritems(invoice_by_customer): merge_log = frappe.new_doc('POS Invoice Merge Log') merge_log.posting_date = getdate(nowdate()) merge_log.customer = customer + merge_log.pos_closing_entry = closing_entry.get('name', None) merge_log.set('pos_invoices', invoices) merge_log.save(ignore_permissions=True) merge_log.submit() + + if closing_entry: + closing_entry.set_status(update=True, status='Submitted') + closing_entry.update_opening_entry() +def cancel_merge_logs(merge_logs, closing_entry={}): + for log in merge_logs: + merge_log = frappe.get_doc('POS Invoice Merge Log', log) + merge_log.flags.ignore_permissions = True + merge_log.cancel() + + if closing_entry: + closing_entry.set_status(update=True, status='Cancelled') + closing_entry.update_opening_entry(for_cancel=True) + +def enqueue_job(job, invoice_by_customer, closing_entry): + check_scheduler_status() + + job_name = closing_entry.get("name") + if not job_already_enqueued(job_name): + enqueue( + job, + queue="long", + timeout=10000, + event="processing_merge_logs", + job_name=job_name, + closing_entry=closing_entry, + invoice_by_customer=invoice_by_customer, + now=frappe.conf.developer_mode or frappe.flags.in_test + ) + + if job == create_merge_logs: + msg = _('POS Invoices will be consolidated in a background process') + else: + msg = _('POS Invoices will be unconsolidated in a background process') + + frappe.msgprint(msg, alert=1) + +def check_scheduler_status(): + if is_scheduler_inactive() and not frappe.flags.in_test: + frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive")) + +def job_already_enqueued(job_name): + enqueued_jobs = [d.get("job_name") for d in get_info()] + if job_name in enqueued_jobs: + return True \ No newline at end of file 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 index 0f34272eb4..db046c9800 100644 --- 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 @@ -7,7 +7,7 @@ import frappe import unittest from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return -from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices +from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile class TestPOSInvoiceMergeLog(unittest.TestCase): @@ -34,7 +34,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): }) pos_inv3.submit() - merge_pos_invoices() + consolidate_pos_invoices() pos_inv.load_from_db() self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) @@ -79,7 +79,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): pos_inv_cn.paid_amount = -300 pos_inv_cn.submit() - merge_pos_invoices() + consolidate_pos_invoices() pos_inv.load_from_db() self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py index acac1c4072..cb5b3a58fe 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, get_link_to_form -from frappe.model.document import Document from erpnext.controllers.status_updater import StatusUpdater class POSOpeningEntry(StatusUpdater): 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 index 6c26dedc54..1ad3c919b7 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js @@ -5,7 +5,7 @@ frappe.listview_settings['POS Opening Entry'] = { get_indicator: function(doc) { var status_color = { - "Draft": "grey", + "Draft": "red", "Open": "orange", "Closed": "green", "Cancelled": "red" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index f2a62cdacd..5bef9e242d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -20,6 +20,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte var me = this; this._super(); + this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice']; if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { // show debit_to in print format this.frm.set_df_property("debit_to", "print_hide", 0); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 447cee42a7..018bc7e641 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1987,8 +1987,15 @@ "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, - "links": [], - "modified": "2020-12-25 22:57:32.555067", + "links": [ + { + "custom": 1, + "group": "Reference", + "link_doctype": "POS Invoice", + "link_fieldname": "consolidated_invoice" + } + ], + "modified": "2021-01-12 12:16:15.192520", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f45e076ce1..c39bd3954c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -236,7 +236,25 @@ class SalesInvoice(SellingController): if len(self.payments) == 0 and self.is_pos: frappe.throw(_("At least one mode of payment is required for POS invoice.")) + def check_if_consolidated_invoice(self): + # since POS Invoice extends Sales Invoice, we explicitly check if doctype is Sales Invoice + if self.doctype == "Sales Invoice" and self.is_consolidated: + invoice_or_credit_note = "consolidated_credit_note" if self.is_return else "consolidated_invoice" + pos_closing_entry = frappe.get_all( + "POS Invoice Merge Log", + filters={ invoice_or_credit_note: self.name }, + pluck="pos_closing_entry" + ) + if pos_closing_entry: + msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format( + frappe.bold("Consolidated Sales Invoice"), + get_link_to_form("POS Closing Entry", pos_closing_entry[0]) + ) + frappe.throw(msg, title=_("Not Allowed")) + def before_cancel(self): + self.check_if_consolidated_invoice() + super(SalesInvoice, self).before_cancel() self.update_time_sheet(None) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 8c05134ae4..0987d0985e 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -93,6 +93,12 @@ status_map = { ["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"], ["Closed", "eval:self.docstatus == 1 and self.pos_closing_entry"], ["Cancelled", "eval:self.docstatus == 2"], + ], + "POS Closing Entry": [ + ["Draft", None], + ["Submitted", "eval:self.docstatus == 1"], + ["Queued", "eval:self.status == 'Queued'"], + ["Cancelled", "eval:self.docstatus == 2"], ] } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6e28865a54..0c0b307c75 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -741,6 +741,7 @@ erpnext.patches.v13_0.update_member_email_address erpnext.patches.v13_0.update_custom_fields_for_shopify erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy +erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.update_returned_qty_in_pr_dn erpnext.patches.v13_0.create_uae_pos_invoice_fields diff --git a/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py new file mode 100644 index 0000000000..42bca7c53a --- /dev/null +++ b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py @@ -0,0 +1,25 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("accounts", "doctype", "POS Invoice Merge Log") + frappe.reload_doc("accounts", "doctype", "POS Closing Entry") + if frappe.db.count('POS Invoice Merge Log'): + frappe.db.sql(''' + UPDATE + `tabPOS Invoice Merge Log` log, `tabPOS Invoice Reference` log_ref + SET + log.pos_closing_entry = ( + SELECT clo_ref.parent FROM `tabPOS Invoice Reference` clo_ref + WHERE clo_ref.pos_invoice = log_ref.pos_invoice + AND clo_ref.parenttype = 'POS Closing Entry' + ) + WHERE + log_ref.parent = log.name + ''') + + frappe.db.sql('''UPDATE `tabPOS Closing Entry` SET status = 'Submitted' where docstatus = 1''') + frappe.db.sql('''UPDATE `tabPOS Closing Entry` SET status = 'Cancelled' where docstatus = 2''') From 83768b68c1e856e958e888db629a46dbbadf4644 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 2 Feb 2021 11:04:25 +0530 Subject: [PATCH 0265/1264] fix: emp disappear (#24525) * fix: emp disappear * fix: renamed set_totals_call to set_totals Co-authored-by: Nabin Hait --- erpnext/payroll/doctype/payroll_entry/payroll_entry.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 0dcea88c89..7ead0b3882 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -342,3 +342,11 @@ let render_employee_attendance = function (frm, data) { }) ); }; + +frappe.ui.form.on('Payroll Employee Detail', { + employee: function(frm) { + if (!frm.doc.payroll_frequency) { + frappe.throw(__("Please set a Payroll Frequency")); + } + } +}); From d60a40ae822e3dc0d715d234d30ee416d502ad41 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 3 Feb 2021 12:06:36 +0530 Subject: [PATCH 0266/1264] fix: Add accounts user role permission for accounting dimension filter --- .../accounting_dimension_filter.json | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json index c0327ad0ad..0f3fbc0b8d 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json @@ -108,7 +108,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-16 15:27:23.659285", + "modified": "2021-02-03 12:04:58.678402", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension Filter", @@ -125,6 +125,30 @@ "role": "System 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 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 } ], "quick_entry": 1, From 33f4419a7833ba0d1c0c36a635cec18380c79463 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 8 Feb 2021 12:32:42 +0530 Subject: [PATCH 0267/1264] fix: StopIteration error when e-invoice not enabled (#24548) * fix: StopIteration error when e-invoice not enabled * chore: update message Co-authored-by: Saqib --- erpnext/regional/india/e_invoice/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 4b6bc251ad..410c09396d 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -446,6 +446,8 @@ class GSPConnector(): def get_credentials(self): if self.invoice: gstin = self.get_seller_gstin() + if not self.e_invoice_settings.enable: + frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin) else: credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None @@ -816,4 +818,4 @@ def generate_eway_bill(doctype, docname, **kwargs): @frappe.whitelist() def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): gsp_connector = GSPConnector(doctype, docname) - gsp_connector.cancel_eway_bill(eway_bill, reason, remark) \ No newline at end of file + gsp_connector.cancel_eway_bill(eway_bill, reason, remark) From 56e6ca628545604011995a04eb0b53350f4a8bbc Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 5 Feb 2021 10:04:18 -0800 Subject: [PATCH 0268/1264] fix: couple of travis fixes (#24554) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../doctype/inpatient_record/test_inpatient_record.py | 1 + erpnext/hr/doctype/employee/test_employee.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index 8a918b0275..ea0d1e982d 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -139,6 +139,7 @@ def create_inpatient(patient): inpatient_record.phone = patient_obj.phone inpatient_record.inpatient = "Scheduled" inpatient_record.scheduled_date = today() + inpatient_record.company = "_Test Company" return inpatient_record diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index f4b214adc3..c0e614ac08 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -16,11 +16,13 @@ class TestEmployee(unittest.TestCase): employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:] employee.company_email = "test@example.com" + employee.company = "_Test Company" employee.save() from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders - self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()]) + employees_born_today = get_employees_who_are_born_today() + self.assertTrue(employees_born_today.get("_Test Company")) frappe.db.sql("delete from `tabEmail Queue`") From 83792ec0091b0fffbcf340ba415071032e7e8a71 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 11 Feb 2021 13:23:01 +0530 Subject: [PATCH 0269/1264] fix: reposting issue with same posting date and posting time (#24570) * fix: reposting issue with same posting date and posting time * Update erpnext/stock/stock_ledger.py Co-authored-by: Nabin Hait --- erpnext/stock/stock_ledger.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 46919c8c8c..95f8c438b3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -202,8 +202,7 @@ class update_entries_after(object): where item_code = %(item_code)s and warehouse = %(warehouse)s - and voucher_type = %(voucher_type)s - and voucher_no = %(voucher_no)s + and timestamp(posting_date, time_format(posting_time, '%H:%i:%s')) = timestamp(%(posting_date)s, time_format(%(posting_time)s, '%H:%i:%s')) order by creation ASC for update @@ -794,4 +793,4 @@ def get_future_sle_with_negative_qty(args): and qty_after_transaction + {0} < 0 order by timestamp(posting_date, posting_time) asc limit 1 - """.format(args.actual_qty), args, as_dict=1) \ No newline at end of file + """.format(args.actual_qty), args, as_dict=1) From 4b2cbdc2dd1a3391f1338b95008ba73b2d5473bc Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 11 Feb 2021 11:04:39 +0530 Subject: [PATCH 0270/1264] fix: update total in words after updating items (#24602) * fix: Update total in words after Updating items Update total in words after Updating items in sales/purchase orders. Port of #24592 Closes ISS-20-21-09425 * test: Add test for total & words after update item Add test for total & words after updating items in sales order. --- erpnext/controllers/accounts_controller.py | 1 + erpnext/selling/doctype/sales_order/test_sales_order.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 35c6cd33f9..a838259862 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1503,6 +1503,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.flags.ignore_validate_update_after_submit = True parent.set_qty_as_per_stock_uom() parent.calculate_taxes_and_totals() + parent.set_total_in_words() if parent_doctype == "Sales Order": make_packing_list(parent) parent.set_gross_profit() diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index e259367e58..cbfab8204f 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -325,6 +325,9 @@ class TestSalesOrder(unittest.TestCase): create_dn_against_so(so.name, 4) make_sales_invoice(so.name) + prev_total = so.get("base_total") + prev_total_in_words = so.get("base_in_words") + first_item_of_so = so.get("items")[0] trans_item = json.dumps([ {'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \ @@ -340,6 +343,12 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.get("items")[-1].amount, 1400) self.assertEqual(so.status, 'To Deliver and Bill') + updated_total = so.get("base_total") + updated_total_in_words = so.get("base_in_words") + + self.assertEqual(updated_total, prev_total+1400) + self.assertNotEqual(updated_total_in_words, prev_total_in_words) + def test_update_child_removing_item(self): so = make_sales_order(**{ "item_list": [{ From 76f616565eedb96dfe9db871fa0d31a6d1563d53 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 11 Feb 2021 11:07:59 +0530 Subject: [PATCH 0271/1264] fix: Consolidated Financial Statement report not works if child company account not present in parent company (#24580) Fixed conflicts --- .../consolidated_financial_statement.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index d0116890b6..76f3c50578 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -222,7 +222,7 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i set_gl_entries_by_account(start_date, end_date, root.lft, root.rgt, filters, - gl_entries_by_account, accounts_by_name, ignore_closing_entries=False) + gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False) calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters) accumulate_values_into_parents(accounts, accounts_by_name, companies) @@ -339,7 +339,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com return data def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account, - accounts_by_name, ignore_closing_entries=False): + accounts_by_name, accounts, ignore_closing_entries=False): """Returns a dict like { "account": [gl entries], ... }""" company_lft, company_rgt = frappe.get_cached_value('Company', @@ -382,15 +382,31 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g for entry in gl_entries: key = entry.account_number or entry.account_name - validate_entries(key, entry, accounts_by_name) + validate_entries(key, entry, accounts_by_name, accounts) gl_entries_by_account.setdefault(key, []).append(entry) return gl_entries_by_account -def validate_entries(key, entry, accounts_by_name): +def get_account_details(account): + return frappe.get_cached_value('Account', account, ['name', 'report_type', 'root_type', 'company', + 'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1) + +def validate_entries(key, entry, accounts_by_name, accounts): if key not in accounts_by_name: - field = "Account number" if entry.account_number else "Account name" - frappe.throw(_("{0} {1} is not present in the parent company").format(field, key)) + args = get_account_details(entry.account) + + if args.parent_account: + parent_args = get_account_details(args.parent_account) + + args.update({ + 'lft': parent_args.lft + 1, + 'rgt': parent_args.rgt - 1, + 'root_type': parent_args.root_type, + 'report_type': parent_args.report_type + }) + + accounts_by_name.setdefault(key, args) + accounts.append(args) def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions = [] From f2be0805f7463a8983be4febdd2abb68af73455c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 15 Feb 2021 19:27:49 +0530 Subject: [PATCH 0272/1264] fix: formatting query args (#24627) --- erpnext/stock/stock_ledger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 95f8c438b3..8c9c172ebd 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -194,6 +194,8 @@ class update_entries_after(object): self.process_sle(sle) def get_sle_against_current_voucher(self): + self.args['time_format'] = '%H:%i:%s' + return frappe.db.sql(""" select *, timestamp(posting_date, posting_time) as "timestamp" @@ -202,7 +204,7 @@ class update_entries_after(object): where item_code = %(item_code)s and warehouse = %(warehouse)s - and timestamp(posting_date, time_format(posting_time, '%H:%i:%s')) = timestamp(%(posting_date)s, time_format(%(posting_time)s, '%H:%i:%s')) + and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) order by creation ASC for update From 7797e9d3ac027135b94115fa7f6f11d97dec2945 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 16 Feb 2021 09:12:27 +0530 Subject: [PATCH 0273/1264] fix: added patch to fix incorrect stock qty and account value (#24628) --- erpnext/accounts/utils.py | 13 ++++++--- erpnext/patches.txt | 1 + .../item_reposting_for_incorrect_sl_and_gl.py | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 67c7fd2d22..80319056dd 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -82,7 +82,7 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date)) if company: error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company)) - + if verbose==1: frappe.msgprint(error_msg) raise FiscalYearError(error_msg) @@ -895,7 +895,8 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for if not warehouse_account: warehouse_account = get_warehouse_account_map(company) - future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items) + future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, + for_warehouses, for_items, company) gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) for voucher_type, voucher_no in future_stock_vouchers: @@ -909,7 +910,7 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for else: _delete_gl_entries(voucher_type, voucher_no) -def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None): +def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None): future_stock_vouchers = [] values = [] @@ -922,6 +923,10 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses))) values += for_warehouses + if company: + condition += " and company = %s " + values.append(company) + for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no from `tabStock Ledger Entry` sle where @@ -982,7 +987,7 @@ def check_if_stock_and_account_balance_synced(posting_date, company, voucher_typ error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format( stock_bal, account_bal, frappe.bold(account), posting_date) error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\ - .format(frappe.bold(diff), frappe.bold(posting_date)) + .format(frappe.bold(diff), frappe.bold(posting_date)) frappe.msgprint( msg="""{0}

{1}

""".format(error_reason, error_resolution), diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0c0b307c75..25ebf67dc1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -750,3 +750,4 @@ erpnext.patches.v13_0.set_company_in_leave_ledger_entry erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 +erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py new file mode 100644 index 0000000000..7c4835a813 --- /dev/null +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -0,0 +1,27 @@ +import frappe +from frappe import _ +from erpnext.stock.stock_ledger import update_entries_after +from erpnext.accounts.utils import update_gl_entries_after + + +data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time + from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0 + order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1) + +for index, d in enumerate(data): + update_entries_after({ + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": d.posting_date, + "posting_time": d.posting_time, + "voucher_type": d.voucher_type, + "voucher_no": d.voucher_no, + "sle_id": d.name + }, allow_negative_stock=True) + +frappe.db.auto_commit_on_many_writes = 1 + +for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): + update_gl_entries_after('2020-12-25', '01:58:55', company=row.name) + +frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file From 83f5468ca1d712b69714a81cc855ffe6607eed27 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 16 Feb 2021 10:35:10 +0530 Subject: [PATCH 0274/1264] fix: patch --- .../item_reposting_for_incorrect_sl_and_gl.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index 7c4835a813..eff0ae4694 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -4,24 +4,25 @@ from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after -data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time - from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0 - order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1) +def execute(): + data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time + from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0 + order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1) -for index, d in enumerate(data): - update_entries_after({ - "item_code": d.item_code, - "warehouse": d.warehouse, - "posting_date": d.posting_date, - "posting_time": d.posting_time, - "voucher_type": d.voucher_type, - "voucher_no": d.voucher_no, - "sle_id": d.name - }, allow_negative_stock=True) + for index, d in enumerate(data): + update_entries_after({ + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": d.posting_date, + "posting_time": d.posting_time, + "voucher_type": d.voucher_type, + "voucher_no": d.voucher_no, + "sle_id": d.name + }, allow_negative_stock=True) -frappe.db.auto_commit_on_many_writes = 1 + frappe.db.auto_commit_on_many_writes = 1 -for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): - update_gl_entries_after('2020-12-25', '01:58:55', company=row.name) + for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): + update_gl_entries_after('2020-12-25', '01:58:55', company=row.name) -frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file + frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file From deddcc513d043f8198a114b2b585818150c91ab8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 16 Feb 2021 14:57:00 +0530 Subject: [PATCH 0275/1264] fix: stock and account balance syncing (#24644) * fix: stock and account balance syncing * fix: stock and account balance syncing * fix: stock and account balance syncing * fix: minor fix --- erpnext/accounts/utils.py | 14 +++++++++----- erpnext/controllers/stock_controller.py | 3 +-- .../clinical_procedure/clinical_procedure.py | 1 + .../clinical_procedure/test_clinical_procedure.py | 5 +++-- .../item_reposting_for_incorrect_sl_and_gl.py | 1 - erpnext/stock/__init__.py | 2 +- erpnext/stock/doctype/batch/test_batch.py | 6 +++--- .../test_landed_cost_voucher.py | 5 ++++- .../repost_item_valuation/repost_item_valuation.py | 2 +- erpnext/stock/doctype/shipment/test_shipment.py | 1 + erpnext/stock/stock_ledger.py | 3 ++- 11 files changed, 26 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 80319056dd..60d1e20fea 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -888,18 +888,22 @@ def get_coa(doctype, parent, is_root, chart=None): def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None, company=None): + stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items, company) + repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company, warehouse_account) + + +def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, warehouse_account=None): def _delete_gl_entries(voucher_type, voucher_no): frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) + if not warehouse_account: warehouse_account = get_warehouse_account_map(company) - future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, - for_warehouses, for_items, company) - gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) + gle = get_voucherwise_gl_entries(stock_vouchers, posting_date) - for voucher_type, voucher_no in future_stock_vouchers: + for voucher_type, voucher_no in stock_vouchers: existing_gle = gle.get((voucher_type, voucher_no), []) voucher_obj = frappe.get_doc(voucher_type, voucher_no) expected_gle = voucher_obj.get_gl_entries(warehouse_account) @@ -924,7 +928,7 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f values += for_warehouses if company: - condition += " and company = %s " + condition += " and company = %s" values.append(company) for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4b5e347970..94735733b2 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -481,13 +481,12 @@ class StockController(AccountsController): "voucher_no": self.name, "company": self.company }) - if check_if_future_sle_exists(args): create_repost_item_valuation_entry(args) elif not is_reposting_pending(): check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) - + def is_reposting_pending(): return frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index c324228467..325c2094fb 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -121,6 +121,7 @@ class ClinicalProcedure(Document): stock_entry.stock_entry_type = 'Material Receipt' stock_entry.to_warehouse = self.warehouse + stock_entry.company = self.company expense_account = get_account(None, 'expense_account', 'Healthcare Settings', self.company) for item in self.items: if item.qty > item.actual_qty: diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index 4ee5f6bad3..fb72073a07 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- + # -*- coding: utf-8 -*- # Copyright (c) 2017, ESS LLP and Contributors # See license.txt from __future__ import unicode_literals @@ -60,6 +60,7 @@ def create_procedure(procedure_template, patient, practitioner): procedure.practitioner = practitioner procedure.consume_stock = procedure_template.allow_stock_consumption procedure.items = procedure_template.items - procedure.warehouse = frappe.db.get_single_value('Stock Settings', 'default_warehouse') + procedure.company = "_Test Company" + procedure.warehouse = "_Test Warehouse - _TC" procedure.submit() return procedure \ No newline at end of file diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index eff0ae4694..f60e0d3036 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -3,7 +3,6 @@ from frappe import _ from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after - def execute(): data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0 diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index 8d64efe41d..b3ae804b1c 100644 --- a/erpnext/stock/__init__.py +++ b/erpnext/stock/__init__.py @@ -64,7 +64,7 @@ def get_warehouse_account(warehouse, warehouse_account=None): if not account and warehouse.company: account = get_company_default_inventory_account(warehouse.company) - if not account and warehouse.company: + if not account and warehouse.company and not warehouse.is_group: frappe.throw(_("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}") .format(warehouse.name, warehouse.company)) return account diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 97f85bafd9..cbd272df4b 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -298,9 +298,9 @@ class TestBatch(unittest.TestCase): self.assertEqual(details.get('price_list_rate'), 400) def create_batch(item_code, rate, create_item_price_for_batch): - pi = make_purchase_invoice(company="_Test Company with perpetual inventory", - warehouse= "Stores - TCP1", cost_center = "Main - TCP1", update_stock=1, - expense_account ="_Test Account Cost for Goods Sold - TCP1", item_code=item_code) + pi = make_purchase_invoice(company="_Test Company", + warehouse= "Stores - _TC", cost_center = "Main - _TC", update_stock=1, + expense_account ="_Test Account Cost for Goods Sold - _TC", item_code=item_code) batch = frappe.db.get_value('Batch', {'item': item_code, 'reference_name': pi.name}) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 144101c67d..984ae46c66 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -148,7 +148,6 @@ class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher_for_odd_numbers (self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", do_not_save=True) pr.items[0].cost_center = "Main - TCP1" for x in range(2): @@ -208,6 +207,10 @@ class TestLandedCostVoucher(unittest.TestCase): self.assertEqual(pr.items[1].landed_cost_voucher_amount, 100) def test_multi_currency_lcv(self): + from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records + + save_new_records(test_records) + ## Create USD Shipping charges_account usd_shipping = create_account(account_name="Shipping Charges USD", parent_account="Duties and Taxes - TCP1", company="_Test Company with perpetual inventory", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index ba2c2c6f44..f22c6018d4 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -64,7 +64,7 @@ def repost(doc): message += "
" + "Traceback:
" + traceback frappe.db.set_value(doc.doctype, doc.name, 'error_log', message) - notify_error_to_stock_managers(doc) + notify_error_to_stock_managers(doc, message) doc.set_status('Failed') raise finally: diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index e1fa207a21..9c3e22f023 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -190,6 +190,7 @@ def create_shipment_company(company_name, abbr): company.abbr = abbr company.default_currency = 'EUR' company.country = 'Germany' + company.enable_perpetual_inventory = 0 company.insert() return company diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 8c9c172ebd..21860b6863 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -204,7 +204,8 @@ class update_entries_after(object): where item_code = %(item_code)s and warehouse = %(warehouse)s - and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) + and voucher_type = %(voucher_type)s + and voucher_no = %(voucher_no)s order by creation ASC for update From 186a045e28ae10b17e0aeeb83b847372a6b32354 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 18 Feb 2021 14:14:21 +0530 Subject: [PATCH 0276/1264] fix: update qty in future sle (#24649) * fix: update qty in future sle * fix: validate cancellation due to ongoing reposting * fix: process sle against current timestamp --- .../doctype/work_order/test_work_order.py | 10 +-- erpnext/stock/__init__.py | 2 +- erpnext/stock/doctype/bin/bin.py | 13 +-- .../delivery_note/test_delivery_note.py | 5 +- .../purchase_receipt/test_purchase_receipt.py | 9 +- .../repost_item_valuation.py | 3 + .../stock_ledger_entry/stock_ledger_entry.py | 1 + erpnext/stock/stock_ledger.py | 87 ++++++++++++++++--- 8 files changed, 102 insertions(+), 28 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 06a8e1987d..00e8c5418a 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -94,11 +94,11 @@ class TestWorkOrder(unittest.TestCase): wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2, source_warehouse=warehouse, skip_transfer=1) - bin1_on_submit = get_bin(item, warehouse) + reserved_qty_on_submission = cint(get_bin(item, warehouse).reserved_qty_for_production) # reserved qty for production is updated - self.assertEqual(cint(bin1_at_start.reserved_qty_for_production) + 2, - cint(bin1_on_submit.reserved_qty_for_production)) + self.assertEqual(cint(bin1_at_start.reserved_qty_for_production) + 2, reserved_qty_on_submission) + test_stock_entry.make_stock_entry(item_code="_Test Item", target=warehouse, qty=100, basic_rate=100) @@ -109,9 +109,9 @@ class TestWorkOrder(unittest.TestCase): s.submit() bin1_at_completion = get_bin(item, warehouse) - + self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production), - cint(bin1_on_submit.reserved_qty_for_production) - 1) + reserved_qty_on_submission - 1) def test_production_item(self): wo_order = make_wo_order_test_record(item="_Test FG Item", qty=1, do_not_save=True) diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index b3ae804b1c..9e240cc2b3 100644 --- a/erpnext/stock/__init__.py +++ b/erpnext/stock/__init__.py @@ -70,4 +70,4 @@ def get_warehouse_account(warehouse, warehouse_account=None): return account def get_company_default_inventory_account(company): - return frappe.get_cached_value('Company', company, 'default_inventory_account') + return frappe.get_cached_value('Company', company, 'default_inventory_account') diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 1088b4127d..0514bd2394 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -16,8 +16,9 @@ class Bin(Document): def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False): '''Called from erpnext.stock.utils.update_bin''' self.update_qty(args) + if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": - from erpnext.stock.stock_ledger import update_entries_after, validate_negative_qty_in_future_sle + from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle if not args.get("posting_date"): args["posting_date"] = nowdate() @@ -34,11 +35,13 @@ class Bin(Document): "posting_time": args.get("posting_time"), "voucher_type": args.get("voucher_type"), "voucher_no": args.get("voucher_no"), - "sle_id": args.name + "sle_id": args.name, + "creation": args.creation }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - # Validate negative qty in future transactions - validate_negative_qty_in_future_sle(args) + # update qty in future ale and Validate negative qty + update_qty_in_future_sle(args, allow_negative_stock) + def update_qty(self, args): # update the stock values (for current quantities) @@ -51,7 +54,7 @@ class Bin(Document): self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty")) self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty")) self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty")) - + self.set_projected_qty() self.db_update() diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 559f8be0de..d39b22965e 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -489,7 +489,10 @@ class TestDeliveryNote(unittest.TestCase): def test_closed_delivery_note(self): from erpnext.stock.doctype.delivery_note.delivery_note import update_delivery_note_status - 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=True) + make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) + + 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=True) dn.submit() diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index ca58ab2823..7741ee7f60 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -94,10 +94,15 @@ class TestPurchaseReceipt(unittest.TestCase): frappe.get_doc('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice').delete() def test_purchase_receipt_no_gl_entry(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - existing_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC"}, "stock_value") + existing_bin_qty, existing_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC"}, ["actual_qty", "stock_value"]) + + if existing_bin_qty < 0: + make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=abs(existing_bin_qty)) pr = make_purchase_receipt() diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index f22c6018d4..8436acbed2 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -46,6 +46,9 @@ class RepostItemValuation(Document): def repost(doc): try: + if not frappe.db.exists("Repost Item Valuation", doc.name): + return + doc.set_status('In Progress') frappe.db.commit() diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index a5c303ccb4..78457e4809 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -37,6 +37,7 @@ class StockLedgerEntry(Document): self.block_transactions_against_group_warehouse() self.validate_with_last_transaction_posting_time() + def on_submit(self): self.check_stock_frozen_date() self.actual_amt_check() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 21860b6863..e4f5725c68 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -23,6 +23,7 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc cancel = sl_entries[0].get("is_cancelled") if cancel: + validate_cancellation(sl_entries) set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) for sle in sl_entries: @@ -45,6 +46,20 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc args = sle_doc.as_dict() update_bin(args, allow_negative_stock, via_landed_cost_voucher) +def validate_cancellation(args): + if args[0].get("is_cancelled"): + repost_entry = frappe.db.get_value("Repost Item Valuation", { + 'voucher_type': args[0].voucher_type, + 'voucher_no': args[0].voucher_no, + 'docstatus': 1 + }, ['name', 'status'], as_dict=1) + + if repost_entry: + if repost_entry.status == 'In Progress': + frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet.")) + if repost_entry.status == 'Queued': + frappe.delete_doc("Repost Item Valuation", repost_entry.name) + def set_as_cancel(voucher_type, voucher_no): frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1, @@ -74,7 +89,8 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat "item_code": args[i].item_code, "warehouse": args[i].warehouse, "posting_date": args[i].posting_date, - "posting_time": args[i].posting_time + "posting_time": args[i].posting_time, + "creation": args[i].get("creation") }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) for item_wh, new_sle in iteritems(obj.new_items): @@ -86,7 +102,7 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat def get_args_for_voucher(voucher_type, voucher_no): return frappe.db.get_all("Stock Ledger Entry", filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, - fields=["item_code", "warehouse", "posting_date", "posting_time"], + fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"], order_by="creation asc", group_by="item_code, warehouse" ) @@ -117,7 +133,7 @@ class update_entries_after(object): self.item_code = args.get("item_code") if self.args.sle_id: self.args['name'] = self.args.sle_id - + self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") self.get_precision() self.valuation_method = get_valuation_method(self.item_code) @@ -155,7 +171,7 @@ class update_entries_after(object): """ self.data.setdefault(args.warehouse, frappe._dict()) warehouse_dict = self.data[args.warehouse] - previous_sle = self.get_sle_before_datetime(args) + previous_sle = self.get_previous_sle_of_current_voucher(args) warehouse_dict.previous_sle = previous_sle for key in ("qty_after_transaction", "valuation_rate", "stock_value"): @@ -167,9 +183,35 @@ class update_entries_after(object): "stock_value_difference": 0.0 }) + def get_previous_sle_of_current_voucher(self, args): + """get stock ledger entries filtered by specific posting datetime conditions""" + + args['time_format'] = '%H:%i:%s' + if not args.get("posting_date"): + args["posting_date"] = "1900-01-01" + if not args.get("posting_time"): + args["posting_time"] = "00:00" + + sle = frappe.db.sql(""" + select *, timestamp(posting_date, posting_time) as "timestamp" + from `tabStock Ledger Entry` + where item_code = %(item_code)s + and warehouse = %(warehouse)s + and is_cancelled = 0 + and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) + order by timestamp(posting_date, posting_time) desc, creation desc + limit 1""", args, as_dict=1) + + return sle[0] if sle else frappe._dict() + + def build(self): + from erpnext.controllers.stock_controller import check_if_future_sle_exists + if self.args.get("sle_id"): - self.process_sle_against_current_voucher() + self.process_sle_against_current_timestamp() + if not check_if_future_sle_exists(self.args): + self.update_bin() else: entries_to_fix = self.get_future_entries_to_fix() @@ -182,13 +224,13 @@ class update_entries_after(object): if sle.dependant_sle_voucher_detail_no: entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle) + + self.update_bin() if self.exceptions: self.raise_exceptions() - self.update_bin() - - def process_sle_against_current_voucher(self): + def process_sle_against_current_timestamp(self): sl_entries = self.get_sle_against_current_voucher() for sle in sl_entries: self.process_sle(sle) @@ -204,8 +246,8 @@ class update_entries_after(object): where item_code = %(item_code)s and warehouse = %(warehouse)s - and voucher_type = %(voucher_type)s - and voucher_no = %(voucher_no)s + and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) + order by creation ASC for update @@ -232,7 +274,6 @@ class update_entries_after(object): return entries_to_fix elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data: return entries_to_fix - self.initialize_previous_data(dependant_sle) args = self.data[dependant_sle.warehouse].previous_sle \ @@ -639,7 +680,6 @@ class update_entries_after(object): # update bin for each warehouse for warehouse, data in iteritems(self.data): bin_doc = get_bin(self.item_code, warehouse) - bin_doc.update({ "valuation_rate": data.valuation_rate, "actual_qty": data.qty_after_transaction, @@ -765,6 +805,25 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, return valuation_rate +def update_qty_in_future_sle(args, allow_negative_stock=None): + frappe.db.sql(""" + update `tabStock Ledger Entry` + set qty_after_transaction = qty_after_transaction + {qty} + where + item_code = %(item_code)s + and warehouse = %(warehouse)s + and voucher_no != %(voucher_no)s + and is_cancelled = 0 + and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) + or ( + timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) + and creation > %(creation)s + ) + ) + """.format(qty=args.actual_qty), args) + + validate_negative_qty_in_future_sle(args, allow_negative_stock) + def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): allow_negative_stock = allow_negative_stock \ or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) @@ -793,7 +852,7 @@ def get_future_sle_with_negative_qty(args): and voucher_no != %(voucher_no)s and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) and is_cancelled = 0 - and qty_after_transaction + {0} < 0 + and qty_after_transaction < 0 order by timestamp(posting_date, posting_time) asc limit 1 - """.format(args.actual_qty), args, as_dict=1) + """, args, as_dict=1) \ No newline at end of file From 3508ed176beb59085b31faae6fd7c23e5e6b1f3e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 16 Feb 2021 18:45:39 +0530 Subject: [PATCH 0277/1264] test(selling): add test for cancelling sales order Add failing test to reproduce bug in cancelling sales order with advance payments Related issue: ISS-20-21-09586 --- .../doctype/sales_order/test_sales_order.py | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index cbfab8204f..52a0174798 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -17,6 +17,18 @@ from erpnext.selling.doctype.product_bundle.test_product_bundle import make_prod from erpnext.stock.doctype.item.test_item import make_item class TestSalesOrder(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.unlink_setting = int(frappe.db.get_value("Accounts Settings", "Accounts Settings", + "unlink_advance_payment_on_cancelation_of_order")) + + @classmethod + def tearDownClass(cls) -> None: + # reset config to previous state + frappe.db.set_value("Accounts Settings", "Accounts Settings", + "unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting) + def tearDown(self): frappe.set_user("Administrator") @@ -1049,6 +1061,38 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.LinkExistsError, so_doc.cancel) + def test_cancel_sales_order_after_cancel_payment_entry(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + # make a sales order + so = make_sales_order() + + # disable unlinking of payment entry + frappe.db.set_value("Accounts Settings", "Accounts Settings", + "unlink_advance_payment_on_cancelation_of_order", 0) + + # create a payment entry against sales order + pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = so.currency + pe.paid_to_account_currency = so.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = so.grand_total + pe.save(ignore_permissions=True) + pe.submit() + + # Cancel payment entry + po_doc = frappe.get_doc("Payment Entry", pe.name) + po_doc.cancel() + + # Cancel sales order + try: + so_doc = frappe.get_doc('Sales Order', so.name) + so_doc.cancel() + except Exception: + self.fail("Can not cancel sales order with linked cancelled payment entry") + def test_request_for_raw_materials(self): item = make_item("_Test Finished Item", {"is_stock_item": 1, "maintain_stock": 1, @@ -1207,4 +1251,4 @@ def make_sales_order_workflow(): )) workflow.insert(ignore_permissions=True) - return workflow \ No newline at end of file + return workflow From d9c84dff0f8eff0c719a63e1a0cc01d3ea09fca2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 16 Feb 2021 18:46:32 +0530 Subject: [PATCH 0278/1264] fix(selling): cancel sales order with cancelled PE Allow cancelling sales order with cancelled payment entry. Ignoring GL entries while cancelling the document is required to cancel it, reverse entries are created by accounts controller. Related issue: ISS-20-21-09586 --- erpnext/selling/doctype/sales_order/sales_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 1516dd6d95..e56129170c 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -180,6 +180,7 @@ class SalesOrder(SellingController): update_coupon_code_count(self.coupon_code,'used') def on_cancel(self): + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') super(SalesOrder, self).on_cancel() # Cannot cancel closed SO From 4cef0f59837d2161ac1c8309540e501b4b57a672 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Thu, 18 Feb 2021 15:52:29 +0530 Subject: [PATCH 0279/1264] fix: salary slip attribute error (#24455) Co-authored-by: Rucha Mahabal --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 2d3bc57900..60aff02b38 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1103,10 +1103,10 @@ class SalarySlip(TransactionBase): self.calculate_total_for_salary_slip_based_on_timesheet() else: self.total_deduction = 0.0 - if self.earnings: + if hasattr(self, "earnings"): for earning in self.earnings: self.gross_pay += flt(earning.amount, earning.precision("amount")) - if self.deductions: + if hasattr(self, "deductions"): for deduction in self.deductions: self.total_deduction += flt(deduction.amount, deduction.precision("amount")) self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment) From b1997da4883dafdc54cfef6d6bfdabf98b894247 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 11 Feb 2021 20:19:30 +0530 Subject: [PATCH 0280/1264] feat: capture Rate of stock UOM in purchase (#24315) * feat: capture Rate of stock UOM in purchase * fix: review changes * added server side code * added stock uom rate in sales transactions * fix: review changes * fix: resolving conflicts * adding patch * fix: removing patch --- .../purchase_invoice_item/purchase_invoice_item.json | 11 ++++++++++- .../sales_invoice_item/sales_invoice_item.json | 11 ++++++++++- .../purchase_order_item/purchase_order_item.json | 11 ++++++++++- erpnext/controllers/stock_controller.py | 6 ++++++ erpnext/public/js/controllers/buying.js | 1 - erpnext/public/js/controllers/transaction.js | 12 ++++++++++-- .../doctype/quotation_item/quotation_item.json | 11 ++++++++++- .../doctype/sales_order_item/sales_order_item.json | 12 ++++++++++-- .../delivery_note_item/delivery_note_item.json | 11 ++++++++++- .../purchase_receipt_item/purchase_receipt_item.json | 11 ++++++++++- 10 files changed, 86 insertions(+), 11 deletions(-) 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 1f7853dbf7..07e75acb41 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -40,6 +40,7 @@ "base_rate", "base_amount", "pricing_rules", + "stock_uom_rate", "is_free_item", "section_break_22", "net_rate", @@ -783,6 +784,14 @@ "print_hide": 1, "read_only": 1 }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "options": "currency", + "read_only": 1 + }, { "fieldname": "sales_invoice_item", "fieldtype": "Data", @@ -795,7 +804,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-26 17:20:36.415791", + "modified": "2021-01-30 21:43:21.488258", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", 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 7a98afff36..b403c7b237 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -45,6 +45,7 @@ "base_rate", "base_amount", "pricing_rules", + "stock_uom_rate", "is_free_item", "section_break_21", "net_rate", @@ -811,12 +812,20 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "options": "currency", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-26 17:25:04.090630", + "modified": "2021-01-30 21:42:37.796771", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index c691e9f9f8..75b2954ddd 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -40,6 +40,7 @@ "base_rate", "base_amount", "pricing_rules", + "stock_uom_rate", "is_free_item", "section_break_29", "net_rate", @@ -726,13 +727,21 @@ "fieldname": "more_info_section_break", "fieldtype": "Section Break", "label": "More Information" + }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "options": "currency", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-07 11:59:47.670951", + "modified": "2021-01-30 21:44:41.816974", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 94735733b2..8c0a62c026 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -24,6 +24,7 @@ class StockController(AccountsController): self.validate_inspection() self.validate_serialized_batch() self.validate_customer_provided_item() + self.set_rate_of_stock_uom() self.validate_internal_transfer() self.validate_putaway_capacity() @@ -395,6 +396,11 @@ class StockController(AccountsController): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): d.allow_zero_valuation_rate = 1 + def set_rate_of_stock_uom(self): + if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]: + for d in self.get("items"): + d.stock_uom_rate = d.rate / d.conversion_factor + def validate_internal_transfer(self): if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \ and self.is_internal_transfer(): diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index a2a723dd77..c96386611b 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -191,7 +191,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty); } - this._super(doc, cdt, cdn); }, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 9627600a17..123d998838 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -40,7 +40,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ cur_frm.cscript.set_gross_profit(item); cur_frm.cscript.calculate_taxes_and_totals(); - + cur_frm.cscript.calculate_stock_uom_rate(frm, cdt, cdn); }); @@ -1121,6 +1121,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }); } + me.calculate_stock_uom_rate(doc, cdt, cdn); }, conversion_factor: function(doc, cdt, cdn, dont_fetch_price_list_rate) { @@ -1141,6 +1142,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.meta.has_field(doc.doctype, "price_list_currency")) { this.apply_price_list(item, true); } + this.calculate_stock_uom_rate(doc, cdt, cdn); } }, @@ -1161,9 +1163,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ qty: function(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); this.conversion_factor(doc, cdt, cdn, true); + this.calculate_stock_uom_rate(doc, cdt, cdn); this.apply_pricing_rule(item, true); }, + calculate_stock_uom_rate: function(doc, cdt, cdn) { + let item = frappe.get_doc(cdt, cdn); + item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor); + refresh_field("stock_uom_rate", item.name, item.parentfield); + }, service_stop_date: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; @@ -1274,7 +1282,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount"], company_currency, "items"); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount"], + this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate"], this.frm.doc.currency, "items"); if(this.frm.fields_dict["operations"]) { diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 59ae7b2323..a6785f709a 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -47,6 +47,7 @@ "base_amount", "base_net_amount", "pricing_rules", + "stock_uom_rate", "is_free_item", "section_break_43", "valuation_rate", @@ -634,12 +635,20 @@ "print_hide": 1, "read_only": 1, "report_hide": 1 + }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "options": "currency", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-05-19 20:48:43.222229", + "modified": "2021-01-30 21:39:40.174551", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 159655b74b..37e47a9d41 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -46,6 +46,7 @@ "base_rate", "base_amount", "pricing_rules", + "stock_uom_rate", "is_free_item", "section_break_24", "net_rate", @@ -214,7 +215,6 @@ "fieldtype": "Link", "label": "UOM", "options": "UOM", - "print_hide": 0, "reqd": 1 }, { @@ -780,12 +780,20 @@ "fieldname": "manufacturing_section_section", "fieldtype": "Section Break", "label": "Manufacturing Section" + }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "options": "currency", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-012-07 20:54:32.309460", + "modified": "2021-01-30 21:35:07.617320", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", 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 9de088df0e..17996247c5 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -47,6 +47,7 @@ "base_rate", "base_amount", "pricing_rules", + "stock_uom_rate", "is_free_item", "section_break_25", "net_rate", @@ -743,13 +744,21 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "options": "currency", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-26 17:31:27.029803", + "modified": "2021-01-30 21:42:03.767968", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index e99119202e..8974ad9318 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -48,6 +48,7 @@ "base_rate", "base_amount", "pricing_rules", + "stock_uom_rate", "is_free_item", "section_break_29", "net_rate", @@ -874,6 +875,14 @@ "label": "Received Qty in Stock UOM", "print_hide": 1 }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "options": "currency", + "read_only": 1 + }, { "fieldname": "delivery_note_item", "fieldtype": "Data", @@ -886,7 +895,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-26 16:50:56.479347", + "modified": "2021-01-30 21:44:06.918515", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 3248da9089448063e45e2d17c7e332192803b16d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 15 Feb 2021 14:16:00 +0530 Subject: [PATCH 0281/1264] fix(India): Add GST state code for Ladakh --- erpnext/patches.txt | 1 + .../patches/v12_0/add_state_code_for_ladakh.py | 16 ++++++++++++++++ erpnext/regional/india/__init__.py | 4 +++- erpnext/regional/india/gst_state_code_data.json | 5 +++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/add_state_code_for_ladakh.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 25ebf67dc1..0003bb3b1a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -751,3 +751,4 @@ erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl +erpnext.patches.v12_0.add_state_code_for_ladakh diff --git a/erpnext/patches/v12_0/add_state_code_for_ladakh.py b/erpnext/patches/v12_0/add_state_code_for_ladakh.py new file mode 100644 index 0000000000..d41101cc46 --- /dev/null +++ b/erpnext/patches/v12_0/add_state_code_for_ladakh.py @@ -0,0 +1,16 @@ +import frappe +from erpnext.regional.india import states + +def execute(): + + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = ['Address-gst_state', 'Tax Category-gst_state'] + + # Update options in gst_state custom fields + for field in custom_fields: + gst_state_field = frappe.get_doc('Custom Field', field) + gst_state_field.options = '\n'.join(states) + gst_state_field.save() diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py index d6221a80aa..378b735e07 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -20,6 +20,7 @@ states = [ 'Jharkhand', 'Karnataka', 'Kerala', + 'Ladakh', 'Lakshadweep Islands', 'Madhya Pradesh', 'Maharashtra', @@ -59,6 +60,7 @@ state_numbers = { "Jharkhand": "20", "Karnataka": "29", "Kerala": "32", + "Ladakh": "38", "Lakshadweep Islands": "31", "Madhya Pradesh": "23", "Maharashtra": "27", @@ -80,4 +82,4 @@ state_numbers = { "West Bengal": "19", } -number_state_mapping = {v: k for k, v in iteritems(state_numbers)} \ No newline at end of file +number_state_mapping = {v: k for k, v in iteritems(state_numbers)} diff --git a/erpnext/regional/india/gst_state_code_data.json b/erpnext/regional/india/gst_state_code_data.json index ff88e0f9d6..8481c27972 100644 --- a/erpnext/regional/india/gst_state_code_data.json +++ b/erpnext/regional/india/gst_state_code_data.json @@ -168,5 +168,10 @@ "state_number": "37", "state_code": "AD", "state_name": "Andhra Pradesh (New)" + }, + { + "state_number": "38", + "state_code": "LA", + "state_name": "Ladakh" } ] From d46b23699c145de64c8998146d2152ad12314dfa Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 23 Feb 2021 16:38:52 +0530 Subject: [PATCH 0282/1264] fix: optimize reposting of gle and sle (#24702) * fix(india): escape for special characters in JSON (#24695) JSON does not accept special whitespace characters like tab, carriage return, line feed Ref: https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf Related issue: ISS-20-21-09811 * fix: Accounting Dimension creation background job timeout * fix(regional): vehicle no is mandatory for ewaybill generation (#24679) * fix: vehicle no required for e-invoice * fix: ewaybill generation dialog condition * fix: excluding unidentified accounts from gstr-1 * fix: optimize reposting of sle and gle (#24694) * fix: optimize update_gl_entries_after method * fix: Optimized reposting patch * fix: accounting dimensions * added reload_doc in patch * Update item_reposting_for_incorrect_sl_and_gl.py Co-authored-by: Rohit Waghchaure * fix: Replaced spaces with tabs * fix: merge conflict * fix: test cases Co-authored-by: Ankush Menat Co-authored-by: Deepesh Garg Co-authored-by: Saqib Co-authored-by: pateljannat Co-authored-by: Rohit Waghchaure --- .../accounting_dimension.py | 19 ++++--- erpnext/accounts/doctype/gl_entry/gl_entry.py | 50 ++++++++----------- .../doctype/pos_invoice/pos_invoice.js | 7 +++ .../doctype/pos_invoice/pos_invoice.py | 8 ++- .../pos_invoice_merge_log.py | 3 ++ .../doctype/pos_profile/pos_profile.json | 34 +++++++++++-- .../doctype/sales_invoice/sales_invoice.py | 4 +- erpnext/accounts/general_ledger.py | 14 +++--- erpnext/accounts/utils.py | 3 +- erpnext/controllers/accounts_controller.py | 1 + erpnext/controllers/selling_controller.py | 10 +++- erpnext/controllers/stock_controller.py | 14 ++++-- erpnext/controllers/taxes_and_totals.py | 2 +- .../mpesa_settings/test_mpesa_settings.py | 3 ++ erpnext/hr/doctype/employee/test_employee.py | 3 +- erpnext/patches.txt | 3 +- .../item_reposting_for_incorrect_sl_and_gl.py | 31 +++++++++--- .../v13_0/replace_pos_payment_mode_table.py | 4 +- .../v13_0/update_vehicle_no_reqd_condition.py | 9 ++++ .../payroll_entry/test_payroll_entry.py | 34 ------------- erpnext/regional/india/e_invoice/einvoice.js | 1 - erpnext/regional/india/e_invoice/utils.py | 2 +- erpnext/regional/report/gstr_1/gstr_1.py | 4 +- .../regional/united_arab_emirates/setup.py | 2 + .../page/point_of_sale/pos_controller.js | 38 ++++++++------ .../page/point_of_sale/pos_item_cart.js | 29 ++++++++--- .../page/point_of_sale/pos_item_details.js | 44 +++++++++++----- .../point_of_sale/pos_past_order_summary.js | 10 +++- erpnext/stock/doctype/item/item.py | 11 ++-- erpnext/stock/get_item_details.py | 2 +- erpnext/stock/stock_ledger.py | 13 ++--- 31 files changed, 257 insertions(+), 155 deletions(-) create mode 100644 erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 52e9ff8b76..239588f897 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -33,11 +33,11 @@ class AccountingDimension(Document): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) else: - frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self) + frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long') def on_trash(self): if frappe.flags.in_test: - delete_accounting_dimension(doc=self) + delete_accounting_dimension(doc=self, queue='long') else: frappe.enqueue(delete_accounting_dimension, doc=self) @@ -48,6 +48,9 @@ class AccountingDimension(Document): if not self.fieldname: self.fieldname = scrub(self.label) + def on_update(self): + frappe.flags.accounting_dimensions = None + def make_dimension_in_accounting_doctypes(doc): doclist = get_doctypes_with_dimensions() doc_count = len(get_accounting_dimensions()) @@ -165,9 +168,9 @@ def toggle_disabling(doc): frappe.clear_cache(doctype=doctype) def get_doctypes_with_dimensions(): - doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", + doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset", "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", - "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", + "Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", @@ -176,12 +179,14 @@ def get_doctypes_with_dimensions(): return doclist def get_accounting_dimensions(as_list=True): - accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"]) + if frappe.flags.accounting_dimensions is None: + frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension", + fields=["label", "fieldname", "disabled", "document_type"]) if as_list: - return [d.fieldname for d in accounting_dimensions] + return [d.fieldname for d in frappe.flags.accounting_dimensions] else: - return accounting_dimensions + return frappe.flags.accounting_dimensions def get_checks_for_pl_and_bs_accounts(): dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index b0a864f76c..ce76d0a39c 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -27,30 +27,30 @@ class GLEntry(Document): def validate(self): self.flags.ignore_submit_comment = True - self.check_mandatory() self.validate_and_set_fiscal_year() self.pl_must_have_cost_center() - self.validate_cost_center() if not self.flags.from_repost: + self.check_mandatory() + self.validate_cost_center() self.check_pl_account() self.validate_party() self.validate_currency() - def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): - if not from_repost: + def on_update(self): + adv_adj = self.flags.adv_adj + if not self.flags.from_repost: self.validate_account_details(adv_adj) self.validate_dimensions_for_pl_and_bs() self.validate_allowed_dimensions() + validate_balance_type(self.account, adv_adj) + validate_frozen_account(self.account, adv_adj) - validate_frozen_account(self.account, adv_adj) - validate_balance_type(self.account, adv_adj) - - # Update outstanding amt on against voucher - if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \ - and self.against_voucher and update_outstanding == 'Yes' and not from_repost: - update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, - self.against_voucher) + # Update outstanding amt on against voucher + if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] + and self.against_voucher and self.flags.update_outstanding == 'Yes'): + update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, + self.against_voucher) def check_mandatory(self): mandatory = ['account','voucher_type','voucher_no','company'] @@ -58,7 +58,7 @@ class GLEntry(Document): if not self.get(k): frappe.throw(_("{0} is required").format(_(self.meta.get_label(k)))) - account_type = frappe.db.get_value("Account", self.account, "account_type") + account_type = frappe.get_cached_value("Account", self.account, "account_type") if not (self.party_type and self.party): if account_type == "Receivable": frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}") @@ -73,7 +73,7 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.account)) def pl_must_have_cost_center(self): - if frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss": + if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": 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)) @@ -140,25 +140,16 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.account, self.company)) def validate_cost_center(self): - if not hasattr(self, "cost_center_company"): - self.cost_center_company = {} + if not self.cost_center: return - def _get_cost_center_company(): - if not self.cost_center_company.get(self.cost_center): - self.cost_center_company[self.cost_center] = frappe.db.get_value( - "Cost Center", self.cost_center, "company") + is_group, company = frappe.get_cached_value('Cost Center', + self.cost_center, ['is_group', 'company']) - return self.cost_center_company[self.cost_center] - - def _check_is_group(): - return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group')) - - if self.cost_center and _get_cost_center_company() != self.company: + if company != self.company: frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") .format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) - if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \ - and self.cost_center and _check_is_group(): + if (self.voucher_type != 'Period Closing Voucher' and is_group): frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format( self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) @@ -184,7 +175,6 @@ class GLEntry(Document): if not self.fiscal_year: self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0] - def validate_balance_type(account, adv_adj=False): if not adv_adj and account: balance_must_be = frappe.db.get_value("Account", account, "balance_must_be") @@ -250,7 +240,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga def validate_frozen_account(account, adv_adj=None): - frozen_account = frappe.db.get_value("Account", account, "freeze_account") + frozen_account = frappe.get_cached_value("Account", account, "freeze_account") if frozen_account == 'Yes' and not adv_adj: frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None, 'frozen_accounts_modifier') diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index 465f0d31c4..493bd44802 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -2,6 +2,7 @@ // For license information, please see license.txt {% include 'erpnext/selling/sales_common.js' %}; +frappe.provide("erpnext.accounts"); erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({ setup(doc) { @@ -9,6 +10,10 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend( this._super(doc); }, + company: function() { + erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype); + }, + onload(doc) { this._super(); this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log']; @@ -16,6 +21,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend( this.frm.script_manager.trigger("is_pos"); this.frm.refresh_fields(); } + + erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype); }, refresh(doc) { diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 9d8f848b02..0c1406c1ce 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -93,7 +93,7 @@ class POSInvoice(SalesInvoice): mode_of_payment=pay.mode_of_payment, status="Paid"), fieldname="grand_total") - if pay.amount != paid_amt: + if paid_amt and pay.amount != paid_amt: return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment)) def validate_stock_availablility(self): @@ -311,7 +311,9 @@ class POSInvoice(SalesInvoice): self.set(fieldname, profile.get(fieldname)) if self.customer: - customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group']) + customer_price_list, customer_group, customer_currency = frappe.db.get_value( + "Customer", self.customer, ['default_price_list', 'customer_group', 'default_currency'] + ) customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list') selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list') if customer_currency != profile.get('currency'): @@ -322,6 +324,8 @@ class POSInvoice(SalesInvoice): if selling_price_list: self.set('selling_price_list', selling_price_list) + if customer_currency != profile.get('currency'): + self.set('currency', customer_currency) # set pos values in items for item in self.get("items"): 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 index 5496804474..58409cd3c6 100644 --- 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 @@ -120,6 +120,7 @@ class POSInvoiceMergeLog(Document): i.qty = i.qty + item.qty if not found: item.rate = item.net_rate + item.price_list_rate = 0 si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"}) items.append(si_item) @@ -157,6 +158,8 @@ class POSInvoiceMergeLog(Document): invoice.set('taxes', taxes) invoice.additional_discount_percentage = 0 invoice.discount_amount = 0.0 + invoice.taxes_and_charges = None + invoice.ignore_pricing_rule = 1 return invoice diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 750ed82d15..4b69f6e2ef 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -12,8 +12,6 @@ "company", "country", "column_break_9", - "update_stock", - "ignore_pricing_rule", "warehouse", "campaign", "company_address", @@ -25,8 +23,14 @@ "hide_images", "hide_unavailable_items", "auto_add_item_to_cart", - "item_groups", "column_break_16", + "update_stock", + "ignore_pricing_rule", + "allow_rate_change", + "allow_discount_change", + "section_break_23", + "item_groups", + "column_break_25", "customer_groups", "section_break_16", "print_format", @@ -309,6 +313,7 @@ "default": "1", "fieldname": "update_stock", "fieldtype": "Check", + "hidden": 1, "label": "Update Stock", "read_only": 1 }, @@ -329,13 +334,34 @@ "fieldname": "auto_add_item_to_cart", "fieldtype": "Check", "label": "Automatically Add Filtered Item To Cart" + }, + { + "default": "0", + "fieldname": "allow_rate_change", + "fieldtype": "Check", + "label": "Allow User to Edit Rate" + }, + { + "default": "0", + "fieldname": "allow_discount_change", + "fieldtype": "Check", + "label": "Allow User to Edit Discount" + }, + { + "collapsible": 1, + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" } ], "icon": "icon-cog", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-20 13:59:28.877572", + "modified": "2021-01-06 14:42:41.713864", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index c39bd3954c..903a2ef5f7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -456,7 +456,9 @@ class SalesInvoice(SellingController): if not for_validate and not self.customer: self.customer = pos.customer - self.ignore_pricing_rule = pos.ignore_pricing_rule + if not for_validate: + self.ignore_pricing_rule = pos.ignore_pricing_rule + if pos.get('account_for_change_amount'): self.account_for_change_amount = pos.get('account_for_change_amount') diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 287c79f13f..b42c0c61d9 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -44,9 +44,9 @@ def validate_accounting_period(gl_map): frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}") .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod) -def process_gl_map(gl_map, merge_entries=True): +def process_gl_map(gl_map, merge_entries=True, precision=None): if merge_entries: - gl_map = merge_similar_entries(gl_map) + gl_map = merge_similar_entries(gl_map, precision) for entry in gl_map: # toggle debit, credit if negative entry if flt(entry.debit) < 0: @@ -69,7 +69,7 @@ def process_gl_map(gl_map, merge_entries=True): return gl_map -def merge_similar_entries(gl_map): +def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() for entry in gl_map: @@ -88,7 +88,9 @@ def merge_similar_entries(gl_map): company = gl_map[0].company if gl_map else erpnext.get_default_company() company_currency = erpnext.get_company_currency(company) - precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency) + + if not precision: + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency) # filter zero debit and credit entries merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map) @@ -132,8 +134,8 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.update(args) gle.flags.ignore_permissions = 1 gle.flags.from_repost = from_repost - gle.insert() - gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) + gle.flags.adv_adj = adv_adj + gle.flags.update_outstanding = update_outstanding or 'Yes' gle.submit() if not from_repost: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 60d1e20fea..5eb2aab393 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -902,10 +902,9 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa warehouse_account = get_warehouse_account_map(company) gle = get_voucherwise_gl_entries(stock_vouchers, posting_date) - for voucher_type, voucher_no in stock_vouchers: existing_gle = gle.get((voucher_type, voucher_no), []) - voucher_obj = frappe.get_doc(voucher_type, voucher_no) + voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no) expected_gle = voucher_obj.get_gl_entries(warehouse_account) if expected_gle: if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a838259862..e6b14c2e40 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -302,6 +302,7 @@ class AccountsController(TransactionBase): args["doctype"] = self.doctype args["name"] = self.name args["child_docname"] = item.name + args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0 if not args.get("transaction_date"): args["transaction_date"] = args.get("posting_date") diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index aa7b27adf4..3e82d46c2d 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -473,13 +473,19 @@ class SellingController(StockController): non_stock_items = [d.item_code, d.description] if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1: + duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code)) + duplicate_items_msg += "

" + duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format( + frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"), + get_link_to_form("Selling Settings", "Selling Settings") + ) if stock_items in check_list: - frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) + frappe.throw(duplicate_items_msg) else: check_list.append(stock_items) else: if non_stock_items in chk_dupl_itm: - frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) + frappe.throw(duplicate_items_msg) else: chk_dupl_itm.append(non_stock_items) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 8c0a62c026..cb44b73caf 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -74,7 +74,7 @@ class StockController(AccountsController): gl_list = [] warehouse_with_no_account = [] - precision = frappe.get_precision("GL Entry", "debit_in_account_currency") + precision = self.get_debit_field_precision() for item_row in voucher_details: sle_list = sle_map.get(item_row.name) @@ -131,7 +131,13 @@ class StockController(AccountsController): if frappe.db.get_value("Warehouse", wh, "company"): frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company)) - return process_gl_map(gl_list) + return process_gl_map(gl_list, precision=precision) + + def get_debit_field_precision(self): + if not frappe.flags.debit_field_precision: + frappe.flags.debit_field_precision = frappe.get_precision("GL Entry", "debit_in_account_currency") + + return frappe.flags.debit_field_precision def update_stock_ledger_entries(self, sle): sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, @@ -244,7 +250,7 @@ class StockController(AccountsController): .format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing")) else: - is_expense_account = frappe.db.get_value("Account", + is_expense_account = frappe.get_cached_value("Account", item.get("expense_account"), "report_type")=="Profit and Loss" if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account: frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account") @@ -492,7 +498,7 @@ class StockController(AccountsController): elif not is_reposting_pending(): check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) - + def is_reposting_pending(): return frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 1f50e9c14d..6c7eb92221 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -107,7 +107,7 @@ class calculate_taxes_and_totals(object): elif item.discount_amount and item.pricing_rules: item.rate = item.price_list_rate - item.discount_amount - if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']: + if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']: item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 18d2732313..08b2bc2fa8 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -50,6 +50,7 @@ class TestMpesaSettings(unittest.TestCase): create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") pos_invoice = create_pos_invoice(do_not_submit=1) pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 500}) @@ -195,6 +196,8 @@ class TestMpesaSettings(unittest.TestCase): pr.delete() pos_invoice.delete() + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") + def create_mpesa_settings(payment_gateway_name="Express"): if frappe.db.exists("Mpesa Settings", payment_gateway_name): return frappe.get_doc("Mpesa Settings", payment_gateway_name) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index c0e614ac08..b88daaa34f 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -21,8 +21,7 @@ class TestEmployee(unittest.TestCase): from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders - employees_born_today = get_employees_who_are_born_today() - self.assertTrue(employees_born_today.get("_Test Company")) + self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()]) frappe.db.sql("delete from `tabEmail Queue`") diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0003bb3b1a..80e2f1c01a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -677,7 +677,7 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.rename_pos_closing_doctype -erpnext.patches.v13_0.replace_pos_payment_mode_table +erpnext.patches.v13_0.replace_pos_payment_mode_table #2020-12-29 erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22 erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive execute:frappe.reload_doc("HR", "doctype", "Employee Advance") @@ -752,3 +752,4 @@ erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v12_0.add_state_code_for_ladakh +erpnext.patches.v13_0.update_vehicle_no_reqd_condition diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index f60e0d3036..06f7f989bb 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -4,11 +4,26 @@ from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after def execute(): - data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time - from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0 - order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1) + frappe.reload_doc('stock', 'doctype', 'repost_item_valuation') - for index, d in enumerate(data): + reposting_project_deployed_on = frappe.db.get_value("DocType", "Repost Item Valuation", "creation") + + data = frappe.db.sql(''' + SELECT + name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time + FROM + `tabStock Ledger Entry` + WHERE + creation > %s + and is_cancelled = 0 + ORDER BY timestamp(posting_date, posting_time) asc, creation asc + ''', reposting_project_deployed_on, as_dict=1) + + frappe.db.auto_commit_on_many_writes = 1 + print("Reposting Stock Ledger Entries...") + total_sle = len(data) + i = 0 + for d in data: update_entries_after({ "item_code": d.item_code, "warehouse": d.warehouse, @@ -19,9 +34,13 @@ def execute(): "sle_id": d.name }, allow_negative_stock=True) - frappe.db.auto_commit_on_many_writes = 1 + i += 1 + if i%100 == 0: + print(i, "/", total_sle) + + print("Reposting General Ledger Entries...") for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): update_gl_entries_after('2020-12-25', '01:58:55', company=row.name) - frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file + frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py index 1ca211bf1b..7cb264830a 100644 --- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py +++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py @@ -6,12 +6,10 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("accounts", "doctype", "POS Payment Method") + frappe.reload_doc("accounts", "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) diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py new file mode 100644 index 0000000000..c26cddbe4e --- /dev/null +++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + if frappe.db.exists('Custom Field', { 'fieldname': 'vehicle_no' }): + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', '') diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py index e098ec79b0..9e68df99eb 100644 --- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -41,40 +41,6 @@ class TestPayrollEntry(unittest.TestCase): make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account=company_doc.default_payroll_payable_account, currency=company_doc.default_currency) - def test_multi_currency_payroll_entry(self): # pylint: disable=no-self-use - company = erpnext.get_default_company() - employee = make_employee("test_muti_currency_employee@payroll.com", company=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'): - get_salary_component_account(data.name) - - company_doc = frappe.get_doc('Company', company) - salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD') - create_salary_structure_assignment(employee, salary_structure.name, company=company) - frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"}))) - salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure") - dates = get_start_end_dates('Monthly', nowdate()) - payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, - payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70) - payroll_entry.make_payment_entry() - - salary_slip.load_from_db() - - payroll_je = salary_slip.journal_entry - payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je) - - self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit) - self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit) - - payment_entry = frappe.db.sql(''' - Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea - Where je.name = jea.parent - And jea.reference_name = %s - ''', (payroll_entry.name), as_dict=1) - - self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit) - self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit) def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use for data in frappe.get_all('Salary Component', fields = ["name"]): diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index a5306839b9..e8a7c30e19 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -188,7 +188,6 @@ const get_ewaybill_fields = (frm) => { 'fieldname': 'vehicle_no', 'label': 'Vehicle No', 'fieldtype': 'Data', - 'depends_on': 'eval:(doc.mode_of_transport === "Road")', 'default': frm.doc.vehicle_no }, { diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 410c09396d..438ec79ed1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -160,7 +160,7 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.description = d.item_name.replace('"', '\\"') + item.description = json.dumps(d.item_name)[1:-1] item.qty = abs(item.qty) item.discount_amount = 0 diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 96dc3f728d..09b04ff367 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -236,6 +236,7 @@ class Gstr1Report(object): self.cgst_sgst_invoices = [] unidentified_gst_accounts = [] + unidentified_gst_accounts_invoice = [] for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: if account in self.gst_accounts.cess_account: self.invoice_cess.setdefault(parent, tax_amount) @@ -251,6 +252,7 @@ class Gstr1Report(object): if not (cgst_or_sgst or account in self.gst_accounts.igst_account): if "gst" in account.lower() and account not in unidentified_gst_accounts: unidentified_gst_accounts.append(account) + unidentified_gst_accounts_invoice.append(parent) continue for item_code, tax_amounts in item_wise_tax_detail.items(): @@ -273,7 +275,7 @@ class Gstr1Report(object): # Build itemised tax for export invoices where tax table is blank for invoice, items in iteritems(self.invoice_items): - if invoice not in self.items_based_on_tax_rate \ + if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \ and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 013ae5cf73..776a82c730 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -110,9 +110,11 @@ def make_custom_fields(): 'Purchase Order': purchase_invoice_fields + invoice_fields, 'Purchase Receipt': purchase_invoice_fields + invoice_fields, 'Sales Invoice': sales_invoice_fields + invoice_fields, + 'POS Invoice': sales_invoice_fields + invoice_fields, 'Sales Order': sales_invoice_fields + invoice_fields, 'Delivery Note': sales_invoice_fields + invoice_fields, 'Sales Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt], + 'POS Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt], 'Purchase Invoice Item': invoice_item_fields, 'Sales Order Item': invoice_item_fields, 'Delivery Note Item': invoice_item_fields, diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index d4cde43359..45b4e30bf0 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -69,6 +69,10 @@ erpnext.PointOfSale.Controller = class { dialog.fields_dict.balance_details.grid.refresh(); }); } + const pos_profile_query = { + query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', + filters: { company: frappe.defaults.get_default('company') } + } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), static: true, @@ -80,6 +84,7 @@ erpnext.PointOfSale.Controller = class { { fieldtype: 'Link', label: __('POS Profile'), options: 'POS Profile', fieldname: 'pos_profile', reqd: 1, + get_query: () => pos_profile_query, onchange: () => fetch_pos_payment_methods() }, { @@ -124,9 +129,8 @@ erpnext.PointOfSale.Controller = class { }); frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => { + Object.assign(this.settings, profile); this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group); - this.settings.hide_images = profile.hide_images; - this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart; this.make_app(); }); } @@ -255,11 +259,9 @@ erpnext.PointOfSale.Controller = class { 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)) - ); + const search_field = batch_no ? 'batch_no' : 'item_code'; + const search_value = batch_no || item_code; + const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom); this.item_details.toggle_item_details_section(item_row); }, @@ -281,6 +283,7 @@ erpnext.PointOfSale.Controller = class { init_item_details() { this.item_details = new erpnext.PointOfSale.ItemDetails({ wrapper: this.$components_wrapper, + settings: this.settings, events: { get_frm: () => this.frm, @@ -415,6 +418,11 @@ erpnext.PointOfSale.Controller = class { () => this.item_selector.toggle_component(true) ]); }, + delete_order: (name) => { + frappe.model.delete_doc(this.frm.doc.doctype, name, () => { + this.recent_order_list.refresh_list(); + }); + }, new_order: () => { frappe.run_serially([ () => frappe.dom.freeze(), @@ -696,14 +704,14 @@ erpnext.PointOfSale.Controller = class { 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(); - }) + frappe.model.set_value(doctype, name, 'qty', 0) + .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(); + }) + .catch(e => console.log(e)); } } diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 03d99c6bfa..de70f167a5 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -5,6 +5,8 @@ erpnext.PointOfSale.ItemCart = class { this.customer_info = undefined; this.hide_images = settings.hide_images; this.allowed_customer_groups = settings.customer_groups; + this.allow_rate_change = settings.allow_rate_change; + this.allow_discount_change = settings.allow_discount_change; this.init_component(); } @@ -201,7 +203,7 @@ erpnext.PointOfSale.ItemCart = class { me.events.checkout(); me.toggle_checkout_btn(false); - me.$add_discount_elem.removeClass("d-none"); + me.allow_discount_change && me.$add_discount_elem.removeClass("d-none"); }); this.$totals_section.on('click', '.edit-cart-btn', () => { @@ -479,8 +481,8 @@ erpnext.PointOfSale.ItemCart = class { 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); + this.render_net_total(frm.doc.net_total); + this.render_grand_total(frm.doc.grand_total); const taxes = frm.doc.taxes.map(t => { return { @@ -549,7 +551,7 @@ erpnext.PointOfSale.ItemCart = class { 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 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}`; @@ -671,7 +673,7 @@ erpnext.PointOfSale.ItemCart = class { 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); + $item_to_update.attr(`data-${selector}`, escape(value)); } toggle_checkout_btn(show_checkout) { @@ -706,14 +708,26 @@ erpnext.PointOfSale.ItemCart = class { 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_allowed = action_is_field_edit ? ( + (current_action == 'rate' && this.allow_rate_change) || + (current_action == 'discount_percentage' && this.allow_discount_change) || + (current_action == 'qty')) : true; 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 (!action_is_allowed) { + const label = current_action == 'rate' ? 'Rate'.bold() : 'Discount'.bold(); + const message = __('Editing {0} is not allowed as per POS Profile settings', [label]); + frappe.show_alert({ + indicator: 'red', + message: message + }); + frappe.utils.play_sound("error"); + return; + } if (first_click_event || field_to_edit_changed) { this.prev_action = current_action; @@ -757,6 +771,7 @@ erpnext.PointOfSale.ItemCart = class { this.numpad_value = current_action; } + this.highlight_numpad_btn($btn, current_action); this.events.numpad_event(this.numpad_value, this.prev_action); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index a4de9f165d..259631d14d 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -1,7 +1,9 @@ erpnext.PointOfSale.ItemDetails = class { - constructor({ wrapper, events }) { + constructor({ wrapper, events, settings }) { this.wrapper = wrapper; this.events = events; + this.allow_rate_change = settings.allow_rate_change; + this.allow_discount_change = settings.allow_discount_change; this.current_item = {}; this.init_component(); @@ -207,17 +209,27 @@ erpnext.PointOfSale.ItemDetails = class { bind_custom_control_change_event() { const me = this; if (this.rate_control) { - this.rate_control.df.onchange = function() { - if (this.value || flt(this.value) === 0) { - 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.allow_rate_change) { + this.rate_control.df.onchange = function() { + if (this.value || flt(this.value) === 0) { + 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); + }); + } + }; + } else { + this.rate_control.df.read_only = 1; } + this.rate_control.refresh(); + } + + if (this.discount_percentage_control && !this.allow_discount_change) { + this.discount_percentage_control.df.read_only = 1; + this.discount_percentage_control.refresh(); } if (this.warehouse_control) { @@ -294,8 +306,16 @@ erpnext.PointOfSale.ItemDetails = class { } frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { + const { item_code, batch_no, uom } = this.current_item; + const item_code_is_same = item_code === item_row.item_code; + const batch_is_same = batch_no == item_row.batch_no; + const uom_is_same = uom === item_row.uom; + // check if current_item is same as item_row + const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false; + const field_control = me[`${fieldname}_control`]; - if (field_control) { + + if (item_is_same && field_control && field_control.get_value() !== value) { field_control.set_value(value); cur_pos.update_cart_html(item_row); } 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 index 6fd4c26bea..598f50f192 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -265,6 +265,14 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_wrapper.addClass('d-none'); }); + this.$summary_container.on('click', '.delete-btn', () => { + this.events.delete_order(this.doc.name); + this.show_summary_placeholder(); + // 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); @@ -401,7 +409,7 @@ erpnext.PointOfSale.PastOrderSummary = class { return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }]; return [ - { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] }, + { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order', 'Delete 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']} ]; diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index be845d9d9d..cda1069891 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -672,13 +672,14 @@ class Item(WebsiteGenerator): 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 = _("The items {0} and {1} are present in the following {2} : ").format( + frappe.bold(old_name), frappe.bold(new_name), document) + msg += '
' 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))) + 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")) @@ -971,7 +972,7 @@ class Item(WebsiteGenerator): frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) def check_if_linked_document_exists(self, field): - linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "Purchase Receipt Item", + linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item", "Purchase Invoice Item", "Stock Entry Detail", "Stock Reconciliation Item"] # For "Is Stock Item", following doctypes is important diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index dfe8fea67b..873cfec85e 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -19,7 +19,7 @@ from erpnext.stock.doctype.item_manufacturer.item_manufacturer import get_item_m from six import string_types, iteritems -sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'] +sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'POS Invoice'] purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] @frappe.whitelist() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e4f5725c68..f54b3c1bb2 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -58,8 +58,9 @@ def validate_cancellation(args): if repost_entry.status == 'In Progress': frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet.")) if repost_entry.status == 'Queued': - frappe.delete_doc("Repost Item Valuation", repost_entry.name) - + doc = frappe.get_doc("Repost Item Valuation", repost_entry.name) + doc.cancel() + doc.delete() def set_as_cancel(voucher_type, voucher_no): frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1, @@ -133,7 +134,7 @@ class update_entries_after(object): self.item_code = args.get("item_code") if self.args.sle_id: self.args['name'] = self.args.sle_id - + self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") self.get_precision() self.valuation_method = get_valuation_method(self.item_code) @@ -201,7 +202,7 @@ class update_entries_after(object): and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) order by timestamp(posting_date, posting_time) desc, creation desc limit 1""", args, as_dict=1) - + return sle[0] if sle else frappe._dict() @@ -224,7 +225,7 @@ class update_entries_after(object): if sle.dependant_sle_voucher_detail_no: entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle) - + self.update_bin() if self.exceptions: @@ -439,7 +440,7 @@ class update_entries_after(object): # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): - doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no) + doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) doc.update_valuation_rate(reset_outgoing_rate=False) for d in (doc.items + doc.supplied_items): d.db_update() From 39b5ad8e6910806163c93e651cb48e6442559a37 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 23 Feb 2021 17:50:49 +0530 Subject: [PATCH 0283/1264] fix: patch failing because of incorrect gl entries --- erpnext/accounts/utils.py | 14 ++++++++------ .../item_reposting_for_incorrect_sl_and_gl.py | 6 +++++- .../doctype/purchase_receipt/purchase_receipt.py | 3 ++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5eb2aab393..89a05b187d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -897,17 +897,18 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) - if not warehouse_account: warehouse_account = get_warehouse_account_map(company) + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2 + gle = get_voucherwise_gl_entries(stock_vouchers, posting_date) for voucher_type, voucher_no in stock_vouchers: existing_gle = gle.get((voucher_type, voucher_no), []) voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no) expected_gle = voucher_obj.get_gl_entries(warehouse_account) if expected_gle: - if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): + if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle, precision): _delete_gl_entries(voucher_type, voucher_no) voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True) else: @@ -953,16 +954,17 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): return gl_entries -def compare_existing_and_expected_gle(existing_gle, expected_gle): +def compare_existing_and_expected_gle(existing_gle, expected_gle, precision): matched = True for entry in expected_gle: account_existed = False for e in existing_gle: if entry.account == e.account: account_existed = True - if entry.account == e.account and entry.against_account == e.against_account \ - and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \ - and (entry.debit != e.debit or entry.credit != e.credit): + if (entry.account == e.account and entry.against_account == e.against_account + and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) + and ( flt(entry.debit, precision) != flt(e.debit, precision) or + flt(entry.credit, precision) != flt(e.credit, precision))): matched = False break if not account_existed: diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index 06f7f989bb..ca04e8acc2 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -1,5 +1,6 @@ import frappe from frappe import _ +from frappe.utils import getdate, get_time from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after @@ -40,7 +41,10 @@ def execute(): print("Reposting General Ledger Entries...") + posting_date = getdate(reposting_project_deployed_on) + posting_time = get_time(reposting_project_deployed_on) + for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): - update_gl_entries_after('2020-12-25', '01:58:55', company=row.name) + update_gl_entries_after(posting_date, posting_time, company=row.name) frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 550c849c5d..d72101412e 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -295,7 +295,8 @@ class PurchaseReceipt(BuyingController): "against": warehouse_account[d.warehouse]["account"], "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(amount["base_amount"]), + "credit": (flt(amount["base_amount"]) if (amount["base_amount"] or + account_currency!=self.company_currency) else flt(amount["amount"])), "credit_in_account_currency": flt(amount["amount"]), "project": d.project }, item=d)) From f613d4140856d0ca47883a1359ba1b10c8f4ce06 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Feb 2021 11:19:55 +0530 Subject: [PATCH 0284/1264] chore: change log --- erpnext/change_log/v13/v13_0_0-beta_12.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0-beta_12.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_12.md b/erpnext/change_log/v13/v13_0_0-beta_12.md new file mode 100644 index 0000000000..cb981f1488 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0-beta_12.md @@ -0,0 +1,14 @@ +### Version 13.0.0 Beta 12 Release Notes +#### Features +- Department wise Appointment Type charges ([#24572](https://github.com/frappe/erpnext/pull/24572)) +- Capture Rate of stock UOM in purchase ([#24315](https://github.com/frappe/erpnext/pull/24315)) + +#### Fixes + +- Fixed stock and account balance syncing ([#24644](https://github.com/frappe/erpnext/pull/24644)) +- Fixed incorrect stock ledger qty in the stock ledger report and bin ([#24649](https://github.com/frappe/erpnext/pull/24649)) +- Added patch to fix incorrect stock ledger and stock account value ([#24702](https://github.com/frappe/erpnext/pull/24702)) +- Skip e-invoice generation for non-taxable invoices ([#24568](https://github.com/frappe/erpnext/pull/24568)) +- Cannot cancel old invoices if eligible for e-invoicing ([#24608](https://github.com/frappe/erpnext/pull/24608)) +- Mpesa fixes and enhancement ([#24306](https://github.com/frappe/erpnext/pull/24306)) +- Fixed Consolidated Financial Statement report ([#24580](https://github.com/frappe/erpnext/pull/24580)) \ No newline at end of file From fefacced13168b6e76e8171ad04dd2ca29e93d75 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 24 Feb 2021 15:21:19 +0550 Subject: [PATCH 0285/1264] bumped to version 13.0.0-beta.12 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a754e1323b..2a474bfe7d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.11' +__version__ = '13.0.0-beta.12' def get_default_company(user=None): '''Get default company for user''' From 9f13a060f0cd78277e304c1ba0f2b6780a24cadc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 26 Feb 2021 17:34:53 +0530 Subject: [PATCH 0286/1264] fix: reposting patch --- .../patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py | 6 +++++- erpnext/stock/__init__.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index ca04e8acc2..3200363e01 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -7,7 +7,7 @@ from erpnext.accounts.utils import update_gl_entries_after def execute(): frappe.reload_doc('stock', 'doctype', 'repost_item_valuation') - reposting_project_deployed_on = frappe.db.get_value("DocType", "Repost Item Valuation", "creation") + reposting_project_deployed_on = get_creation_time() data = frappe.db.sql(''' SELECT @@ -48,3 +48,7 @@ def execute(): update_gl_entries_after(posting_date, posting_time, company=row.name) frappe.db.auto_commit_on_many_writes = 0 + +def get_creation_time(): + return frappe.db.sql(''' SELECT create_time FROM + INFORMATION_SCHEMA.TABLES where TABLE_NAME = "tabRepost Item Valuation" ''', as_list=1)[0][0] \ No newline at end of file diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index 9e240cc2b3..283f7d5fda 100644 --- a/erpnext/stock/__init__.py +++ b/erpnext/stock/__init__.py @@ -38,7 +38,7 @@ def get_warehouse_account_map(company=None): frappe.flags.warehouse_account_map[company] = warehouse_account else: frappe.flags.warehouse_account_map = warehouse_account - + return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map def get_warehouse_account(warehouse, warehouse_account=None): @@ -64,6 +64,10 @@ def get_warehouse_account(warehouse, warehouse_account=None): if not account and warehouse.company: account = get_company_default_inventory_account(warehouse.company) + if not account and warehouse.company: + account = frappe.db.get_value('Account', + {'account_type': 'Stock', 'is_group': 0, 'company': warehouse.company}, 'name') + if not account and warehouse.company and not warehouse.is_group: frappe.throw(_("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}") .format(warehouse.name, warehouse.company)) From d96be3f9f14b74d649a7e8c65d7444aeedeee9fa Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Mar 2021 17:02:47 +0530 Subject: [PATCH 0287/1264] Reposting patch and earned leave rounding (#24783) * fix: rounding of earned leave is optional (#24782) * fix: reposting patch fixes (#24775) --- .../leave_allocation/leave_allocation.py | 6 --- .../leave_policy_assignment.json | 6 ++- .../leave_policy_assignment.py | 48 ++++++++++++++++--- erpnext/hr/doctype/leave_type/leave_type.json | 5 +- erpnext/hr/utils.py | 23 ++++++--- .../item_reposting_for_incorrect_sl_and_gl.py | 17 +++++-- .../purchase_receipt/purchase_receipt.py | 4 +- 7 files changed, 81 insertions(+), 28 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index a09cd2ea11..a4f4f1ace9 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -18,7 +18,6 @@ class ValueMultiplierError(frappe.ValidationError): pass class LeaveAllocation(Document): def validate(self): self.validate_period() - self.validate_new_leaves_allocated_value() self.validate_allocation_overlap() self.validate_back_dated_allocation() self.set_total_leaves_allocated() @@ -72,11 +71,6 @@ class LeaveAllocation(Document): if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"): frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)) - def validate_new_leaves_allocated_value(self): - """validate that leave allocation is in multiples of 0.5""" - if flt(self.new_leaves_allocated) % 0.5: - frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) - def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" SELECT diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json index a0327bdaa0..3373350e73 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json @@ -106,12 +106,14 @@ "fieldname": "leaves_allocated", "fieldtype": "Check", "hidden": 1, - "label": "Leaves Allocated" + "label": "Leaves Allocated", + "no_copy": 1, + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-12-31 16:43:30.695206", + "modified": "2021-03-01 17:54:01.014509", "modified_by": "Administrator", "module": "HR", "name": "Leave Policy Assignment", diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index a5068bc26d..4064c56e44 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _, bold -from frappe.utils import getdate, date_diff, comma_and, formatdate +from frappe.utils import getdate, date_diff, comma_and, formatdate, get_datetime, flt from math import ceil import json from six import string_types @@ -84,17 +84,52 @@ class LeavePolicyAssignment(Document): return allocation.name, new_leaves_allocated def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining): + from frappe.model.meta import get_field_precision + precision = get_field_precision(frappe.get_meta("Leave Allocation").get_field("new_leaves_allocated")) + + # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 + if leave_type_details.get(leave_type).is_compensatory == 1: + new_leaves_allocated = 0 + + elif leave_type_details.get(leave_type).is_earned_leave == 1: + if self.assignment_based_on == "Leave Period": + new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining) + else: + new_leaves_allocated = 0 # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period - if getdate(date_of_joining) > getdate(self.effective_from): + elif getdate(date_of_joining) > getdate(self.effective_from): remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1)) new_leaves_allocated = ceil(new_leaves_allocated * remaining_period) - # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 - if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1: - new_leaves_allocated = 0 + return flt(new_leaves_allocated, precision) + + def get_leaves_for_passed_months(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining): + from erpnext.hr.utils import get_monthly_earned_leave + + current_month = get_datetime().month + current_year = get_datetime().year + + from_date = frappe.db.get_value("Leave Period", self.leave_period, "from_date") + if getdate(date_of_joining) > getdate(from_date): + from_date = date_of_joining + + from_date_month = get_datetime(from_date).month + from_date_year = get_datetime(from_date).year + + months_passed = 0 + if current_year == from_date_year and current_month > from_date_month: + months_passed = current_month - from_date_month + elif current_year > from_date_year: + months_passed = (12 - from_date_month) + current_month + + if months_passed > 0: + monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated, + leave_type_details.get(leave_type).earned_leave_frequency, leave_type_details.get(leave_type).rounding) + new_leaves_allocated = monthly_earned_leave * months_passed return new_leaves_allocated + @frappe.whitelist() def grant_leave_for_multiple_employees(leave_policy_assignments): leave_policy_assignments = json.loads(leave_policy_assignments) @@ -156,7 +191,8 @@ def automatically_allocate_leaves_based_on_leave_policy(): def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all("Leave Type", - fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"]) + fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", + "is_carry_forward", "expire_carry_forwarded_leaves_after_days", "earned_leave_frequency", "rounding"]) for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index a2092919f8..fc577ef1d3 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -172,7 +172,7 @@ "fieldname": "rounding", "fieldtype": "Select", "label": "Rounding", - "options": "0.5\n1.0" + "options": "\n0.25\n0.5\n1.0" }, { "depends_on": "is_carry_forward", @@ -197,6 +197,7 @@ "label": "Based On Date Of Joining" }, { + "default": "0", "depends_on": "eval:doc.is_lwp == 0", "fieldname": "is_ppl", "fieldtype": "Check", @@ -213,7 +214,7 @@ "icon": "fa fa-flag", "idx": 1, "links": [], - "modified": "2020-10-15 15:49:47.555105", + "modified": "2021-03-02 11:22:33.776320", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index d700e7fccf..3f8d61d414 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -316,13 +316,7 @@ def allocate_earned_leaves(): update_previous_leave_allocation(allocation, annual_allocation, e_leave_type) def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type): - divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} - if annual_allocation: - earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency] - if e_leave_type.rounding == "0.5": - earned_leaves = round(earned_leaves * 2) / 2 - else: - earned_leaves = round(earned_leaves) + earned_leaves = get_monthly_earned_leave(annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding) allocation = frappe.get_doc('Leave Allocation', allocation.name) new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves) @@ -335,6 +329,21 @@ def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type today_date = today() create_additional_leave_ledger_entry(allocation, earned_leaves, today_date) +def get_monthly_earned_leave(annual_leaves, frequency, rounding): + earned_leaves = 0.0 + divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} + if annual_leaves: + earned_leaves = flt(annual_leaves) / divide_by_frequency[frequency] + if rounding: + if rounding == "0.25": + earned_leaves = round(earned_leaves * 4) / 4 + elif rounding == "0.5": + earned_leaves = round(earned_leaves * 2) / 2 + else: + earned_leaves = round(earned_leaves) + + return earned_leaves + def get_leave_allocations(date, leave_type): return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index 3200363e01..d968e1fb76 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -1,13 +1,24 @@ import frappe from frappe import _ -from frappe.utils import getdate, get_time +from frappe.utils import getdate, get_time, today from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after def execute(): - frappe.reload_doc('stock', 'doctype', 'repost_item_valuation') + for doctype in ('repost_item_valuation', 'stock_entry_detail', 'purchase_receipt_item', + 'purchase_invoice_item', 'delivery_note_item', 'sales_invoice_item', 'packed_item'): + frappe.reload_doc('stock', 'doctype', doctype) + frappe.reload_doc('buying', 'doctype', 'purchase_receipt_item_supplied') reposting_project_deployed_on = get_creation_time() + posting_date = getdate(reposting_project_deployed_on) + posting_time = get_time(reposting_project_deployed_on) + + if posting_date == today(): + return + + frappe.clear_cache() + frappe.flags.warehouse_account_map = {} data = frappe.db.sql(''' SELECT @@ -41,8 +52,6 @@ def execute(): print("Reposting General Ledger Entries...") - posting_date = getdate(reposting_project_deployed_on) - posting_time = get_time(reposting_project_deployed_on) for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): update_gl_entries_after(posting_date, posting_time, company=row.name) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d72101412e..70687bdac2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -324,10 +324,12 @@ class PurchaseReceipt(BuyingController): else: loss_account = self.get_company_default("default_expense_account") + cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") + gl_entries.append(self.get_gl_dict({ "account": loss_account, "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, + "cost_center": cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": divisional_loss, "project": d.project From 11092d08240ce53139aefe3768ba994ee6466d01 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:36:48 +0530 Subject: [PATCH 0288/1264] feat: Additon of leave details in Salary Slip (#24674) * feat: Additon of leave details in Salary Slip * fix: Change leaves to leave --- .../payroll_settings/payroll_settings.json | 50 +++++------- .../doctype/salary_slip/salary_slip.json | 16 +++- .../doctype/salary_slip/salary_slip.py | 18 +++++ .../doctype/salary_slip_leave/__init__.py | 0 .../salary_slip_leave/salary_slip_leave.json | 78 +++++++++++++++++++ .../salary_slip_leave/salary_slip_leave.py | 10 +++ 6 files changed, 140 insertions(+), 32 deletions(-) create mode 100644 erpnext/payroll/doctype/salary_slip_leave/__init__.py create mode 100644 erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json create mode 100644 erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json index c47caa1227..680e518ca0 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json @@ -15,6 +15,7 @@ "daily_wages_fraction_for_half_day", "email_salary_slip_to_employee", "encrypt_salary_slips_in_emails", + "show_leave_balances_in_salary_slip", "password_policy" ], "fields": [ @@ -23,58 +24,44 @@ "fieldname": "payroll_based_on", "fieldtype": "Select", "label": "Calculate Payroll Working Days Based On", - "options": "Leave\nAttendance", - "show_days": 1, - "show_seconds": 1 + "options": "Leave\nAttendance" }, { "fieldname": "max_working_hours_against_timesheet", "fieldtype": "Float", - "label": "Max working hours against Timesheet", - "show_days": 1, - "show_seconds": 1 + "label": "Max working hours against Timesheet" }, { "default": "0", "description": "If checked, Total no. of Working Days will include holidays, and this will reduce the value of Salary Per Day", "fieldname": "include_holidays_in_total_working_days", "fieldtype": "Check", - "label": "Include holidays in Total no. of Working Days", - "show_days": 1, - "show_seconds": 1 + "label": "Include holidays in Total no. of Working Days" }, { "default": "0", "description": "If checked, hides and disables Rounded Total field in Salary Slips", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total", - "show_days": 1, - "show_seconds": 1 + "label": "Disable Rounded Total" }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0.5", "description": "The fraction of daily wages to be paid for half-day attendance", "fieldname": "daily_wages_fraction_for_half_day", "fieldtype": "Float", - "label": "Fraction of Daily Salary for Half Day", - "show_days": 1, - "show_seconds": 1 + "label": "Fraction of Daily Salary for Half Day" }, { "default": "1", "description": "Emails salary slip to employee based on preferred email selected in Employee", "fieldname": "email_salary_slip_to_employee", "fieldtype": "Check", - "label": "Email Salary Slip to Employee", - "show_days": 1, - "show_seconds": 1 + "label": "Email Salary Slip to Employee" }, { "default": "0", @@ -82,9 +69,7 @@ "description": "The salary slip emailed to the employee will be password protected, the password will be generated based on the password policy.", "fieldname": "encrypt_salary_slips_in_emails", "fieldtype": "Check", - "label": "Encrypt Salary Slips in Emails", - "show_days": 1, - "show_seconds": 1 + "label": "Encrypt Salary Slips in Emails" }, { "depends_on": "eval: doc.encrypt_salary_slips_in_emails == 1", @@ -92,24 +77,27 @@ "fieldname": "password_policy", "fieldtype": "Data", "in_list_view": 1, - "label": "Password Policy", - "show_days": 1, - "show_seconds": 1 + "label": "Password Policy" }, { "depends_on": "eval:doc.payroll_based_on == 'Attendance'", "fieldname": "consider_unmarked_attendance_as", "fieldtype": "Select", "label": "Consider Unmarked Attendance As", - "options": "Present\nAbsent", - "show_days": 1, - "show_seconds": 1 + "options": "Present\nAbsent" + }, + { + "default": "0", + "fieldname": "show_leave_balances_in_salary_slip", + "fieldtype": "Check", + "label": "Show Leave Balances in Salary Slip" } ], "icon": "fa fa-cog", + "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-06-22 17:00:58.408030", + "modified": "2021-02-19 11:07:55.873991", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Settings", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 9f9691b59d..6688368262 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -80,6 +80,8 @@ "total_in_words", "column_break_69", "base_total_in_words", + "leave_details_section", + "leave_details", "section_break_75", "amended_from" ], @@ -612,13 +614,25 @@ "label": "Month To Date(Company Currency)", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "leave_details_section", + "fieldtype": "Section Break", + "label": "Leave Details" + }, + { + "fieldname": "leave_details", + "fieldtype": "Table", + "label": "Leave Details", + "options": "Salary Slip Leave", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-01-14 13:37:38.180920", + "modified": "2021-02-19 11:48:05.383945", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 60aff02b38..5c5eccd7e5 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -19,6 +19,7 @@ from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_appli from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry from erpnext.accounts.utils import get_fiscal_year +from six import iteritems class SalarySlip(TransactionBase): def __init__(self, *args, **kwargs): @@ -53,6 +54,7 @@ class SalarySlip(TransactionBase): self.compute_year_to_date() self.compute_month_to_date() self.compute_component_wise_year_to_date() + self.add_leave_balances() if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"): max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet") @@ -1212,6 +1214,22 @@ class SalarySlip(TransactionBase): return period_start_date, period_end_date + def add_leave_balances(self): + self.set('leave_details', []) + + if frappe.db.get_single_value('Payroll Settings', 'show_leave_balances_in_salary_slip'): + from erpnext.hr.doctype.leave_application.leave_application import get_leave_details + leave_details = get_leave_details(self.employee, self.end_date) + + for leave_type, leave_values in iteritems(leave_details['leave_allocation']): + self.append('leave_details', { + 'leave_type': leave_type, + 'total_allocated_leaves': flt(leave_values.get('total_leaves')), + 'expired_leaves': flt(leave_values.get('expired_leaves')), + 'used_leaves': flt(leave_values.get('leaves_taken')), + 'pending_leaves': flt(leave_values.get('pending_leaves')), + 'available_leaves': flt(leave_values.get('remaining_leaves')) + }) def unlink_ref_doc_from_salary_slip(ref_no): linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip` diff --git a/erpnext/payroll/doctype/salary_slip_leave/__init__.py b/erpnext/payroll/doctype/salary_slip_leave/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json new file mode 100644 index 0000000000..7ac453b3c3 --- /dev/null +++ b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json @@ -0,0 +1,78 @@ +{ + "actions": [], + "creation": "2021-02-19 11:45:18.173417", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "leave_type", + "total_allocated_leaves", + "expired_leaves", + "used_leaves", + "pending_leaves", + "available_leaves" + ], + "fields": [ + { + "fieldname": "leave_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Leave Type", + "no_copy": 1, + "options": "Leave Type", + "read_only": 1 + }, + { + "fieldname": "total_allocated_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Allocated Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "expired_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Expired Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "used_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Used Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "pending_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Pending Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "available_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Available Leave", + "no_copy": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-02-19 10:47:48.546724", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Salary Slip Leave", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py new file mode 100644 index 0000000000..7a92bf18f7 --- /dev/null +++ b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 SalarySlipLeave(Document): + pass From 49e4693abf6314de9973b735a07954395a0748b5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 4 Mar 2021 11:58:45 +0530 Subject: [PATCH 0289/1264] fix(patch): updating pos closing reference in merge log --- erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py index 42bca7c53a..262e38dd05 100644 --- a/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py +++ b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py @@ -15,7 +15,7 @@ def execute(): log.pos_closing_entry = ( SELECT clo_ref.parent FROM `tabPOS Invoice Reference` clo_ref WHERE clo_ref.pos_invoice = log_ref.pos_invoice - AND clo_ref.parenttype = 'POS Closing Entry' + AND clo_ref.parenttype = 'POS Closing Entry' LIMIT 1 ) WHERE log_ref.parent = log.name From dcfc3d7d1278d90e0a9154c9d7d7d2dcd2b57bc4 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:29:55 +0100 Subject: [PATCH 0290/1264] fix: remove redundant calls to create_sales_tax --- erpnext/regional/saudi_arabia/setup.py | 5 +---- erpnext/regional/united_arab_emirates/setup.py | 4 +--- erpnext/setup/setup_wizard/utils.py | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index d9ac6cb0f6..9b3677d2c6 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -4,11 +4,8 @@ from __future__ import unicode_literals from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats -from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax + def setup(company=None, patch=True): make_custom_fields() add_print_formats() - - if company: - create_sales_tax(company) \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 776a82c730..d5a29fc200 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -6,15 +6,13 @@ from __future__ import unicode_literals import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property -from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax + def setup(company=None, patch=True): make_custom_fields() add_print_formats() add_custom_roles_for_reports() add_permissions() - if company: - create_sales_tax(company) def make_custom_fields(): is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated', diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py index e82bc96d93..4223f000a6 100644 --- a/erpnext/setup/setup_wizard/utils.py +++ b/erpnext/setup/setup_wizard/utils.py @@ -9,5 +9,4 @@ def complete(): 'data', 'test_mfg.json'), 'r') as f: data = json.loads(f.read()) - #setup_wizard.create_sales_tax(data) setup_complete(data) From 25afad3dc1d75031ba09b405ff8b12de5f0d8007 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Mar 2021 21:11:31 +0100 Subject: [PATCH 0291/1264] refactor: extend taxes and charges setup Add option to specify taxes and charges template depending on the CoA used. Differentiate between purchase, sales and item taxes. Maintain flexibility by using wildcards. --- erpnext/setup/doctype/company/company.py | 7 +- .../setup_wizard/data/country_wise_tax.json | 307 ++++++++++++++-- .../setup_wizard/operations/taxes_setup.py | 330 ++++++++++++------ 3 files changed, 513 insertions(+), 131 deletions(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 819ba78e66..d3021bafea 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -17,6 +17,7 @@ from frappe.utils.nestedset import NestedSet from past.builtins import cmp import functools from erpnext.accounts.doctype.account.account import get_account_currency +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges class Company(NestedSet): nsm_parent_field = 'parent_company' @@ -67,11 +68,7 @@ class Company(NestedSet): frappe.throw(_("Abbreviation already used for another company")) def create_default_tax_template(self): - from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax - create_sales_tax({ - 'country': self.country, - 'company_name': self.name - }) + setup_taxes_and_charges(self.name, self.country) def validate_default_accounts(self): accounts = [ diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index beddaeed79..9ccbdb965b 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -481,14 +481,230 @@ }, "Germany": { - "Germany VAT 19%": { - "account_name": "VAT 19%", - "tax_rate": 19.00, - "default": 1 - }, - "Germany VAT 7%": { - "account_name": "VAT 7%", - "tax_rate": 7.00 + "chart_of_accounts": { + "SKR04 mit Kontonummern": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + }, + { + "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", + "account_number": "1407", + "root_type": "Asset", + "tax_rate": 19.00, + "add_deduct_tax": "Add" + }, + { + "account_name": "Umsatzsteuer nach § 13b UStG 19%", + "account_number": "3837", + "root_type": "Liability", + "tax_rate": 19.00, + "add_deduct_tax": "Deduct" + } + ] + } + ] + }, + "SKR03 mit Kontonummern": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + }, + "Standard with Numbers": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + }, + "*": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + } } }, @@ -580,26 +796,61 @@ }, "India": { - "In State GST": { - "account_name": ["SGST", "CGST"], - "tax_rate": [9.00, 9.00], - "default": 1 - }, - "Out of State GST": { - "account_name": "IGST", - "tax_rate": 18.00 - }, - "VAT 5%": { - "account_name": "VAT 5%", - "tax_rate": 5.00 - }, - "VAT 4%": { - "account_name": "VAT 4%", - "tax_rate": 4.00 - }, - "VAT 14%": { - "account_name": "VAT 14%", - "tax_rate": 14.00 + "chart_of_accounts": { + "*": { + "*": [ + { + "title": "In State GST", + "is_default": 1, + "accounts": [ + { + "account_name": "SGST", + "tax_rate": 9.00 + }, + { + "account_name": "CGST", + "tax_rate": 9.00 + } + ] + }, + { + "title": "Out of State GST", + "accounts": [ + { + "account_name": "IGST", + "tax_rate": 18.00 + } + ] + }, + { + "title": "VAT 5%", + "accounts": [ + { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } + ] + }, + { + "title": "VAT 4%", + "accounts": [ + { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } + ] + }, + { + "title": "VAT 14%", + "accounts": [ + { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } + ] + } + ] + } } }, diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index c3c1593c04..81506c4352 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -1,123 +1,257 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, copy, os, json -from frappe.utils import flt -from erpnext.accounts.doctype.account.account import RootNotEditable -def create_sales_tax(args): - country_wise_tax = get_country_wise_tax(args.get("country")) - if country_wise_tax and len(country_wise_tax) > 0: - for sales_tax, tax_data in country_wise_tax.items(): - make_tax_account_and_template( - args.get("company_name"), - tax_data.get('account_name'), - tax_data.get('tax_rate'), sales_tax) +import os +import json -def make_tax_account_and_template(company, account_name, tax_rate, template_name=None): - if not isinstance(account_name, (list, tuple)): - account_name = [account_name] - tax_rate = [tax_rate] +import frappe +from frappe import _ - accounts = [] - for i, name in enumerate(account_name): - tax_account = make_tax_account(company, account_name[i], tax_rate[i]) - if tax_account: - accounts.append(tax_account) - try: - if accounts: - make_sales_and_purchase_tax_templates(accounts, template_name) - make_item_tax_templates(accounts, template_name) - except frappe.NameError: - if frappe.message_log: frappe.message_log.pop() - except RootNotEditable: - pass +def setup_taxes_and_charges(company_name: str, country: str): + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json') + with open(file_path, 'r') as json_file: + tax_data = json.load(json_file) -def make_tax_account(company, account_name, tax_rate): - tax_group = get_tax_account_group(company) - if tax_group: - try: - return frappe.get_doc({ - "doctype":"Account", - "company": company, - "parent_account": tax_group, - "account_name": account_name, - "is_group": 0, - "report_type": "Balance Sheet", - "root_type": "Liability", - "account_type": "Tax", - "tax_rate": flt(tax_rate) if tax_rate else None - }).insert(ignore_permissions=True, ignore_mandatory=True) - except frappe.NameError: - if frappe.message_log: frappe.message_log.pop() - abbr = frappe.get_cached_value('Company', company, 'abbr') - account = '{0} - {1}'.format(account_name, abbr) - return frappe.get_doc('Account', account) + country_wise_tax = tax_data.get(country) -def make_sales_and_purchase_tax_templates(accounts, template_name=None): - if not template_name: - template_name = accounts[0].name + if country_wise_tax: + if 'chart_of_accounts' in country_wise_tax: + from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) + else: + from_simple_data(company_name, country_wise_tax) - sales_tax_template = { - "doctype": "Sales Taxes and Charges Template", - "title": template_name, - "company": accounts[0].company, - 'taxes': [] + +def from_detailed_data(company_name, data): + """ + Create Taxes and Charges Templates from detailed data like this: + + { + "chart_of_accounts": { + coa_name: { + "sales_tax_templates": [ + { + 'title': '', + 'is_default': 1, + 'accounts': [ + { + 'account_name': '', + 'account_number': '', + 'root_type': '', + } + ] + } + ], + "purchase_tax_templates": [ ... ], + "item_tax_templates": [ ... ], + "*": [ ... ] + } + } } + """ + coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') + tax_templates = data.get(coa_name) or data.get('*') + sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') + purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*') + item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') + + if sales_tax_templates: + for template in sales_tax_templates: + make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + + if purchase_tax_templates: + for template in purchase_tax_templates: + make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + + if item_tax_templates: + for template in item_tax_templates: + make_item_tax_template(company_name, template) + + +def from_simple_data(company_name, data): + """ + Create Taxes and Charges Templates from simple data like this: + + "Austria Tax": { + "account_name": "VAT", + "tax_rate": 20.00 + } + """ + for template_name, tax_data in data.items(): + template = { + 'title': template_name, + 'is_default': tax_data.get('default'), + 'accounts': [ + { + 'account_name': tax_data.get('account_name'), + 'tax_rate': tax_data.get('tax_rate') + } + ] + } + make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + make_item_tax_template(company_name, template) + + +def make_tax_template(company_name, doctype, template): + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): + return + + accounts = get_or_create_accounts(company_name, template.get('accounts')) + + # Get all fields of the Taxes and Charges Template + tax_template = {'doctype': doctype} + tax_template_fields = frappe.get_meta(doctype).fields + tax_template_fieldnames = [field.fieldname for field in tax_template_fields] + + # Get all fields of the taxes child table + table_doctype = [field.options for field in tax_template_fields if field.fieldname=='taxes'][0] + table_fields = frappe.get_meta(table_doctype).fields + table_field_names = [field.fieldname for field in table_fields] + + # Check if field exists as a key in the import data and, if yes, set the + # value accordingly + for field in tax_template_fieldnames: + if field in template: + tax_template[field] = template.get(field) + + # However, company always fixed and taxes table must be empty to start with + tax_template['company'] = company_name + tax_template['taxes'] = [] for account in accounts: - sales_tax_template['taxes'].append({ - "category": "Total", - "charge_type": "On Net Total", - "account_head": account.name, - "description": "{0} @ {1}".format(account.account_name, account.tax_rate), - "rate": account.tax_rate - }) - # Sales - frappe.get_doc(copy.deepcopy(sales_tax_template)).insert(ignore_permissions=True) + row = { + 'category': 'Total', + 'charge_type': 'On Net Total', + 'account_head': account.get('name'), + 'description': '{0} @ {1}'.format(account.get('account_name'), account.get('tax_rate')), + 'rate': account.get('tax_rate') + } + # Check if field exists as a key in the import data and, if yes, set the + # value accordingly + for field in table_field_names: + if field in account: + row[field] = account.get(field) - # Purchase - purchase_tax_template = copy.deepcopy(sales_tax_template) - purchase_tax_template["doctype"] = "Purchase Taxes and Charges Template" + tax_template['taxes'].append(row) - doc = frappe.get_doc(purchase_tax_template) - doc.insert(ignore_permissions=True) + return frappe.get_doc(tax_template).insert(ignore_permissions=True) -def make_item_tax_templates(accounts, template_name=None): - if not template_name: - template_name = accounts[0].name + +def make_item_tax_template(company_name, template): + """Create an Item Tax Template. + + This requires a separate method because Item Tax Template is structured + differently from Sales and Purchase Tax Templates. + """ + doctype = 'Item Tax Template' + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): + return + + accounts = get_or_create_accounts(company_name, template.get('accounts')) item_tax_template = { - "doctype": "Item Tax Template", - "title": template_name, - "company": accounts[0].company, - 'taxes': [] + 'doctype': doctype, + 'title': template.get('title'), + 'company': company_name, + 'taxes': [{ + 'tax_type': account.get('name'), + 'tax_rate': account.get('tax_rate') + } for account in accounts] } + return frappe.get_doc(item_tax_template).insert(ignore_permissions=True) - for account in accounts: - item_tax_template['taxes'].append({ - "tax_type": account.name, - "tax_rate": account.tax_rate - }) - # Items - frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True) +def get_or_create_accounts(company: str, account_data: list): + for account in account_data: + if 'creation' in account: + # Hack to check if account already contains a real Account doc + # or just the attibutes from country_wise_tax.json + continue -def get_tax_account_group(company): - tax_group = frappe.db.get_value("Account", - {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) - if not tax_group: - tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability", - "account_type": "Tax", "company": company}) + # tax_rate should survive the following lines because it might not be + # specified in an existing account or different rates might get booked + # onto the same account. + tax_rate = account.get('tax_rate') + doc = get_or_create_account(company, account) + account.update(doc.as_dict()) + account['tax_rate'] = tax_rate + + return account_data + + +def get_or_create_account(company, account_data): + """ + Check if account already exists. If not, create it. + Return a tax account or None. + """ + root_type = account_data.get('root_type', 'Liability') + account_name = account_data.get('account_name') + account_number = account_data.get('account_number') + + existing_accounts = frappe.get_list('Account', + filters={ + 'company': company, + 'root_type': root_type + }, + or_filters={ + 'account_name': account_name, + 'account_number': account_number + } + ) + + if existing_accounts: + return frappe.get_doc('Account', existing_accounts[0].name) + + tax_group = get_or_create_tax_account_group(company, root_type) + full_account_data = { + 'doctype': 'Account', + 'account_name': account_name, + 'account_number': account_number, + 'tax_rate': account_data.get('tax_rate'), + 'company': company, + 'parent_account': tax_group, + 'is_group': 0, + 'report_type': 'Balance Sheet', + 'root_type': root_type, + 'account_type': 'Tax' + } + return frappe.get_doc(full_account_data).insert(ignore_permissions=True, ignore_mandatory=True) + + +def get_or_create_tax_account_group(company, root_type): + tax_group = frappe.db.get_value('Account', { + 'is_group': 1, + 'root_type': root_type, + 'account_type': 'Tax', + 'company': company + }) + + if tax_group: + return tax_group + + root = frappe.get_list('Account', { + 'is_group': 1, + 'root_type': root_type, + 'company': company, + 'report_type': 'Balance Sheet', + 'parent_account': ('is', 'not set') + }, limit=1)[0].name + + doc = frappe.get_doc({ + 'doctype': 'Account', + 'company': company, + 'is_group': 1, + 'report_type': 'Balance Sheet', + 'root_type': root_type, + 'account_type': 'Tax', + 'account_name': _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets'), + 'parent_account': root + }).insert(ignore_permissions=True) + + tax_group = doc.name return tax_group - -def get_country_wise_tax(country): - data = {} - with open (os.path.join(os.path.dirname(__file__), "..", "data", "country_wise_tax.json")) as countrywise_tax: - data = json.load(countrywise_tax).get(country) - - return data From 4e1206bf21aadb105ac7165e8fe7889f42d163cd Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 5 Mar 2021 09:54:01 +0550 Subject: [PATCH 0292/1264] bumped to version 13.0.0-beta.13 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2a474bfe7d..b122e5fa11 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.12' +__version__ = '13.0.0-beta.13' def get_default_company(user=None): '''Get default company for user''' From ebd1d08e55b8146e652096d2c591754f8d676504 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:53:50 +0100 Subject: [PATCH 0293/1264] refactor: taxes setup Better structure of input data. --- .../setup_wizard/data/country_wise_tax.json | 310 ++++++++++++------ .../setup_wizard/operations/taxes_setup.py | 279 +++++++--------- 2 files changed, 334 insertions(+), 255 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 9ccbdb965b..6305442ef2 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -487,23 +487,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "3806", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "3801", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + } } ] } @@ -512,41 +514,49 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1406", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1401", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] }, { "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", - "account_number": "1407", - "root_type": "Asset", - "tax_rate": 19.00, + "account_head": { + "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", + "account_number": "1407", + "root_type": "Asset", + "tax_rate": 19.00 + }, "add_deduct_tax": "Add" }, { - "account_name": "Umsatzsteuer nach § 13b UStG 19%", - "account_number": "3837", - "root_type": "Liability", - "tax_rate": 19.00, + "account_head": { + "account_name": "Umsatzsteuer nach § 13b UStG 19%", + "account_number": "3837", + "root_type": "Liability", + "tax_rate": 19.00 + }, "add_deduct_tax": "Deduct" } ] @@ -558,23 +568,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "1776", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "1771", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + } } ] } @@ -583,23 +595,27 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1576", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1571", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -610,23 +626,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "2301", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "2302", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + } } ] } @@ -635,23 +653,27 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1501", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1502", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -662,21 +684,23 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + } } ] } @@ -685,21 +709,25 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "tax_rate": 19.00, + "root_type": "Asset" + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -798,54 +826,130 @@ "India": { "chart_of_accounts": { "*": { - "*": [ + "item_tax_templates": [ { "title": "In State GST", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "SGST", - "tax_rate": 9.00 + "tax_type": { + "account_name": "SGST", + "tax_rate": 9.00 + } }, { - "account_name": "CGST", - "tax_rate": 9.00 + "tax_type": { + "account_name": "CGST", + "tax_rate": 9.00 + } } ] }, { "title": "Out of State GST", - "accounts": [ + "taxes": [ { - "account_name": "IGST", - "tax_rate": 18.00 + "tax_type": { + "account_name": "IGST", + "tax_rate": 18.00 + } } ] }, { "title": "VAT 5%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 5%", - "tax_rate": 5.00 + "tax_type": { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } } ] }, { "title": "VAT 4%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 4%", - "tax_rate": 4.00 + "tax_type": { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } } ] }, { "title": "VAT 14%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 14%", - "tax_rate": 14.00 + "tax_type": { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } + } + ] + } + ], + "*": [ + { + "title": "In State GST", + "is_default": 1, + "taxes": [ + { + "account_head": { + "account_name": "SGST", + "tax_rate": 9.00 + } + }, + { + "account_head": { + "account_name": "CGST", + "tax_rate": 9.00 + } + } + ] + }, + { + "title": "Out of State GST", + "taxes": [ + { + "account_head": { + "account_name": "IGST", + "tax_rate": 18.00 + } + } + ] + }, + { + "title": "VAT 5%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } + } + ] + }, + { + "title": "VAT 4%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } + } + ] + }, + { + "title": "VAT 14%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } } ] } diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 81506c4352..429a558c58 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -17,40 +17,62 @@ def setup_taxes_and_charges(company_name: str, country: str): country_wise_tax = tax_data.get(country) - if country_wise_tax: - if 'chart_of_accounts' in country_wise_tax: - from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) - else: - from_simple_data(company_name, country_wise_tax) + if not country_wise_tax: + return + + if 'chart_of_accounts' not in country_wise_tax: + country_wise_tax = simple_to_detailed(country_wise_tax) + + from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) -def from_detailed_data(company_name, data): +def simple_to_detailed(templates): """ - Create Taxes and Charges Templates from detailed data like this: + Convert a simple taxes object into a more detailed data structure. + + Example input: { - "chart_of_accounts": { - coa_name: { - "sales_tax_templates": [ - { - 'title': '', - 'is_default': 1, - 'accounts': [ - { - 'account_name': '', - 'account_number': '', - 'root_type': '', - } - ] - } - ], - "purchase_tax_templates": [ ... ], - "item_tax_templates": [ ... ], - "*": [ ... ] - } + "France VAT 20%": { + "account_name": "VAT 20%", + "tax_rate": 20, + "default": 1 + }, + "France VAT 10%": { + "account_name": "VAT 10%", + "tax_rate": 10 } } """ + return { + 'chart_of_accounts': { + '*': { + 'item_tax_templates': [{ + 'title': title, + 'taxes': [{ + 'tax_type': { + 'account_name': data.get('account_name'), + 'tax_rate': data.get('tax_rate') + } + }] + } for title, data in templates.items()], + '*': [{ + 'title': title, + 'is_default': data.get('default', 0), + 'taxes': [{ + 'account_head': { + 'account_name': data.get('account_name'), + 'tax_rate': data.get('tax_rate') + } + }] + } for title, data in templates.items()] + } + } + } + + +def from_detailed_data(company_name, data): + """Create Taxes and Charges Templates from detailed data.""" coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') tax_templates = data.get(coa_name) or data.get('*') sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') @@ -59,85 +81,44 @@ def from_detailed_data(company_name, data): if sales_tax_templates: for template in sales_tax_templates: - make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template) if purchase_tax_templates: for template in purchase_tax_templates: - make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + make_taxes_and_charges_template(company_name, 'Purchase Taxes and Charges Template', template) if item_tax_templates: for template in item_tax_templates: make_item_tax_template(company_name, template) -def from_simple_data(company_name, data): - """ - Create Taxes and Charges Templates from simple data like this: +def make_taxes_and_charges_template(company_name, doctype, template): + template['company'] = company_name + template['doctype'] = doctype - "Austria Tax": { - "account_name": "VAT", - "tax_rate": 20.00 - } - """ - for template_name, tax_data in data.items(): - template = { - 'title': template_name, - 'is_default': tax_data.get('default'), - 'accounts': [ - { - 'account_name': tax_data.get('account_name'), - 'tax_rate': tax_data.get('tax_rate') - } - ] - } - make_tax_template(company_name, 'Sales Taxes and Charges Template', template) - make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) - make_item_tax_template(company_name, template) - - -def make_tax_template(company_name, doctype, template): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - accounts = get_or_create_accounts(company_name, template.get('accounts')) - - # Get all fields of the Taxes and Charges Template - tax_template = {'doctype': doctype} - tax_template_fields = frappe.get_meta(doctype).fields - tax_template_fieldnames = [field.fieldname for field in tax_template_fields] - - # Get all fields of the taxes child table - table_doctype = [field.options for field in tax_template_fields if field.fieldname=='taxes'][0] - table_fields = frappe.get_meta(table_doctype).fields - table_field_names = [field.fieldname for field in table_fields] - - # Check if field exists as a key in the import data and, if yes, set the - # value accordingly - for field in tax_template_fieldnames: - if field in template: - tax_template[field] = template.get(field) - - # However, company always fixed and taxes table must be empty to start with - tax_template['company'] = company_name - tax_template['taxes'] = [] - - for account in accounts: - row = { + for tax_row in template.get('taxes'): + account_data = tax_row.get('account_head') + tax_row_defaults = { 'category': 'Total', - 'charge_type': 'On Net Total', - 'account_head': account.get('name'), - 'description': '{0} @ {1}'.format(account.get('account_name'), account.get('tax_rate')), - 'rate': account.get('tax_rate') + 'charge_type': 'On Net Total' } - # Check if field exists as a key in the import data and, if yes, set the - # value accordingly - for field in table_field_names: - if field in account: - row[field] = account.get(field) - tax_template['taxes'].append(row) + # if account_head is a dict, search or create the account and get it's name + if isinstance(account_data, dict): + tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate')) + tax_row_defaults['rate'] = account_data.get('tax_rate') + account = get_or_create_account(company_name, account_data) + tax_row['account_head'] = account.name - return frappe.get_doc(tax_template).insert(ignore_permissions=True) + # use the default value if nothing other is specified + for fieldname, default_value in tax_row_defaults.items(): + if fieldname not in tax_row: + tax_row[fieldname] = default_value + + return frappe.get_doc(template).insert(ignore_permissions=True) def make_item_tax_template(company_name, template): @@ -147,111 +128,105 @@ def make_item_tax_template(company_name, template): differently from Sales and Purchase Tax Templates. """ doctype = 'Item Tax Template' + template['company'] = company_name + template['doctype'] = doctype + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - accounts = get_or_create_accounts(company_name, template.get('accounts')) + for tax_row in template.get('taxes'): + account_data = tax_row.get('tax_type') - item_tax_template = { - 'doctype': doctype, - 'title': template.get('title'), - 'company': company_name, - 'taxes': [{ - 'tax_type': account.get('name'), - 'tax_rate': account.get('tax_rate') - } for account in accounts] - } + # if tax_type is a dict, search or create the account and get it's name + if isinstance(account_data, dict): + account = get_or_create_account(company_name, account_data) + tax_row['tax_type'] = account.name + if 'tax_rate' not in tax_row: + tax_row['tax_rate'] = account_data.get('tax_rate') - return frappe.get_doc(item_tax_template).insert(ignore_permissions=True) + return frappe.get_doc(template).insert(ignore_permissions=True) -def get_or_create_accounts(company: str, account_data: list): - for account in account_data: - if 'creation' in account: - # Hack to check if account already contains a real Account doc - # or just the attibutes from country_wise_tax.json - continue - - # tax_rate should survive the following lines because it might not be - # specified in an existing account or different rates might get booked - # onto the same account. - tax_rate = account.get('tax_rate') - doc = get_or_create_account(company, account) - account.update(doc.as_dict()) - account['tax_rate'] = tax_rate - - return account_data - - -def get_or_create_account(company, account_data): +def get_or_create_account(company_name, account): """ Check if account already exists. If not, create it. Return a tax account or None. """ - root_type = account_data.get('root_type', 'Liability') - account_name = account_data.get('account_name') - account_number = account_data.get('account_number') + default_root_type = 'Liability' + root_type = account.get('root_type', default_root_type) existing_accounts = frappe.get_list('Account', filters={ - 'company': company, + 'company': company_name, 'root_type': root_type }, or_filters={ - 'account_name': account_name, - 'account_number': account_number + 'account_name': account.get('account_name'), + 'account_number': account.get('account_number') } ) if existing_accounts: return frappe.get_doc('Account', existing_accounts[0].name) - tax_group = get_or_create_tax_account_group(company, root_type) - full_account_data = { - 'doctype': 'Account', - 'account_name': account_name, - 'account_number': account_number, - 'tax_rate': account_data.get('tax_rate'), - 'company': company, - 'parent_account': tax_group, - 'is_group': 0, - 'report_type': 'Balance Sheet', - 'root_type': root_type, - 'account_type': 'Tax' - } - return frappe.get_doc(full_account_data).insert(ignore_permissions=True, ignore_mandatory=True) + tax_group = get_or_create_tax_group(company_name, root_type) + + account['doctype'] = 'Account' + account['company'] = company_name + account['parent_account'] = tax_group + account['report_type'] = 'Balance Sheet' + account['account_type'] = 'Tax' + account['root_type'] = root_type + account['is_group'] = 0 + + return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True) -def get_or_create_tax_account_group(company, root_type): - tax_group = frappe.db.get_value('Account', { +def get_or_create_tax_group(company_name, root_type): + # Look for a group account of type 'Tax' + tax_group_name = frappe.db.get_value('Account', { 'is_group': 1, 'root_type': root_type, 'account_type': 'Tax', - 'company': company + 'company': company_name }) - if tax_group: - return tax_group + if tax_group_name: + return tax_group_name - root = frappe.get_list('Account', { + # Look for a group account named 'Duties and Taxes' or 'Tax Assets' + account_name = _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets') + tax_group_name = frappe.db.get_value('Account', { 'is_group': 1, 'root_type': root_type, - 'company': company, + 'account_name': account_name, + 'company': company_name + }) + + if tax_group_name: + return tax_group_name + + # Create a new group account named 'Duties and Taxes' or 'Tax Assets' just + # below the root account + root_account = frappe.get_list('Account', { + 'is_group': 1, + 'root_type': root_type, + 'company': company_name, 'report_type': 'Balance Sheet', 'parent_account': ('is', 'not set') - }, limit=1)[0].name + }, limit=1)[0] - doc = frappe.get_doc({ + tax_group_account = frappe.get_doc({ 'doctype': 'Account', - 'company': company, + 'company': company_name, 'is_group': 1, 'report_type': 'Balance Sheet', 'root_type': root_type, 'account_type': 'Tax', - 'account_name': _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets'), - 'parent_account': root + 'account_name': account_name, + 'parent_account': root_account.name }).insert(ignore_permissions=True) - tax_group = doc.name + tax_group_name = tax_group_account.name - return tax_group + return tax_group_name From 535406cb0cdc83a5493bc0e62fecc3b8846be253 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 Mar 2021 11:32:39 +0530 Subject: [PATCH 0294/1264] fix: allow to select item code in batch naming --- erpnext/stock/doctype/batch/batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index c8424f13e1..8fdda565d2 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -93,7 +93,7 @@ class Batch(Document): if create_new_batch: if batch_number_series: - self.batch_id = make_autoname(batch_number_series) + self.batch_id = make_autoname(batch_number_series, doc=self) elif batch_uses_naming_series(): self.batch_id = self.get_name_from_naming_series() else: From 0623a3421009d35536308875e666a93d6b85264d Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 10 Mar 2021 09:46:38 +0530 Subject: [PATCH 0295/1264] fix: added supplier warehouse field back again (#24827) --- .../purchase_invoice/purchase_invoice.json | 16 ++++++++++++++-- erpnext/controllers/buying_controller.py | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 451c936881..3ff0efe29e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -58,6 +58,7 @@ "rejected_warehouse", "col_break_warehouse", "set_from_warehouse", + "supplier_warehouse", "is_subcontracted", "items_section", "update_stock", @@ -1350,7 +1351,7 @@ "options": "Company" }, { - "depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)", + "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", "description": "Sets 'From Warehouse' in each row of the items table.", "fieldname": "set_from_warehouse", "fieldtype": "Link", @@ -1360,13 +1361,24 @@ "print_hide": 1, "print_width": "50px", "width": "50px" + }, + { + "depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"", + "fieldname": "supplier_warehouse", + "fieldtype": "Link", + "label": "Supplier Warehouse", + "no_copy": 1, + "options": "Warehouse", + "print_hide": 1, + "print_width": "50px", + "width": "50px" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2020-12-26 20:49:03.305063", + "modified": "2021-03-09 21:56:28.748582", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index ab1f02779b..305a162d12 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -288,7 +288,7 @@ class BuyingController(StockController): if self.is_subcontracted == "Yes": if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse: - frappe.throw(_("Supplier Warehouse mandatory for sub-contracted Purchase Receipt")) + frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype)) for item in self.get("items"): if item in self.sub_contracted_items and not item.bom: From 0b29f87fa26ef68eaaba546a238129f32eae00a4 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 11 Mar 2021 16:02:23 +0530 Subject: [PATCH 0296/1264] feat(Non Profit): 80G Certificates and Donations (#24848) * feat(Non Profit): 80G Certificates and Donations * fix(Membership): Generate Invoice for membership webhook only if automation is enabled (#24849) --- .../doctype/payment_entry/payment_entry.js | 26 +- .../doctype/payment_entry/payment_entry.py | 23 +- .../__init__.py | 0 .../non_profit/doctype/donation/donation.js | 26 ++ .../non_profit/doctype/donation/donation.json | 156 +++++++++ .../non_profit/doctype/donation/donation.py | 215 +++++++++++++ .../doctype/donation/donation_dashboard.py | 16 + .../doctype/donation/test_donation.py | 76 +++++ erpnext/non_profit/doctype/donor/donor.json | 9 +- erpnext/non_profit/doctype/donor/donor.py | 5 + erpnext/non_profit/doctype/member/member.js | 2 +- erpnext/non_profit/doctype/member/member.py | 11 +- .../doctype/membership/membership.js | 4 +- .../doctype/membership/membership.json | 10 +- .../doctype/membership/membership.py | 96 ++++-- .../doctype/membership/test_membership.py | 63 ++-- .../membership_settings.json | 192 ----------- .../membership_type/membership_type.js | 4 +- .../doctype/non_profit_settings/__init__.py | 0 .../non_profit_settings.js} | 73 +++-- .../non_profit_settings.json | 273 ++++++++++++++++ .../non_profit_settings.py} | 21 +- .../test_non_profit_settings.py} | 2 +- .../workspace/non_profit/non_profit.json | 251 +++++++++++++++ erpnext/patches.txt | 2 + ...bership_settings_to_non_profit_settings.py | 22 ++ ...fields_for_80g_certificate_and_donation.py | 16 + .../tax_exemption_80g_certificate/__init__.py | 0 .../tax_exemption_80g_certificate.js | 67 ++++ .../tax_exemption_80g_certificate.json | 297 ++++++++++++++++++ .../tax_exemption_80g_certificate.py | 89 ++++++ .../test_tax_exemption_80g_certificate.py | 101 ++++++ .../__init__.py | 0 .../tax_exemption_80g_certificate_detail.json | 66 ++++ .../tax_exemption_80g_certificate_detail.py | 10 + erpnext/regional/india/setup.py | 26 +- .../80g_certificate_for_donation.json | 26 ++ .../80g_certificate_for_donation/__init__.py | 0 .../80g_certificate_for_membership.json | 26 ++ .../__init__.py | 0 .../operations/install_fixtures.py | 1 + 41 files changed, 1997 insertions(+), 306 deletions(-) rename erpnext/non_profit/doctype/{membership_settings => donation}/__init__.py (100%) create mode 100644 erpnext/non_profit/doctype/donation/donation.js create mode 100644 erpnext/non_profit/doctype/donation/donation.json create mode 100644 erpnext/non_profit/doctype/donation/donation.py create mode 100644 erpnext/non_profit/doctype/donation/donation_dashboard.py create mode 100644 erpnext/non_profit/doctype/donation/test_donation.py delete mode 100644 erpnext/non_profit/doctype/membership_settings/membership_settings.json create mode 100644 erpnext/non_profit/doctype/non_profit_settings/__init__.py rename erpnext/non_profit/doctype/{membership_settings/membership_settings.js => non_profit_settings/non_profit_settings.js} (50%) create mode 100644 erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.json rename erpnext/non_profit/doctype/{membership_settings/membership_settings.py => non_profit_settings/non_profit_settings.py} (51%) rename erpnext/non_profit/doctype/{membership_settings/test_membership_settings.py => non_profit_settings/test_non_profit_settings.py} (79%) create mode 100644 erpnext/non_profit/workspace/non_profit/non_profit.json create mode 100644 erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py create mode 100644 erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.js create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.json create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate_detail/__init__.py create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.json create mode 100644 erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py create mode 100644 erpnext/regional/print_format/80g_certificate_for_donation/80g_certificate_for_donation.json create mode 100644 erpnext/regional/print_format/80g_certificate_for_donation/__init__.py create mode 100644 erpnext/regional/print_format/80g_certificate_for_membership/80g_certificate_for_membership.json create mode 100644 erpnext/regional/print_format/80g_certificate_for_membership/__init__.py diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index f5c488d0f9..6412772073 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -92,14 +92,16 @@ frappe.ui.form.on('Payment Entry', { }); frm.set_query("reference_doctype", "references", function() { - if (frm.doc.party_type=="Customer") { + if (frm.doc.party_type == "Customer") { var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"]; - } else if (frm.doc.party_type=="Supplier") { + } else if (frm.doc.party_type == "Supplier") { var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"]; - } else if (frm.doc.party_type=="Employee") { + } else if (frm.doc.party_type == "Employee") { var doctypes = ["Expense Claim", "Journal Entry"]; - } else if (frm.doc.party_type=="Student") { + } else if (frm.doc.party_type == "Student") { var doctypes = ["Fees"]; + } else if (frm.doc.party_type == "Donor") { + var doctypes = ["Donation"]; } else { var doctypes = ["Journal Entry"]; } @@ -128,7 +130,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', 'Dunning']; + 'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation']; if (in_list(party_type_doctypes, child.reference_doctype)) { filters[doc.party_type.toLowerCase()] = doc.party; @@ -281,7 +283,7 @@ frappe.ui.form.on('Payment Entry', { let party_types = Object.keys(frappe.boot.party_account_types); if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){ frm.set_value("party_type", ""); - frappe.throw(__("Party can only be one of "+ party_types.join(", "))); + frappe.throw(__("Party can only be one of {0}", [party_types.join(", ")])); } frm.set_query("party", function() { @@ -705,7 +707,8 @@ frappe.ui.form.on('Payment Entry', { (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") || - (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") + (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") || + (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor") ) { if(total_positive_outstanding > total_negative_outstanding) if (!frm.doc.paid_amount) @@ -748,7 +751,8 @@ frappe.ui.form.on('Payment Entry', { (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") || - (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") + (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") || + (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor") ) { if(total_positive_outstanding_including_order > paid_amount) { var remaining_outstanding = total_positive_outstanding_including_order - paid_amount; @@ -905,6 +909,12 @@ frappe.ui.form.on('Payment Entry', { frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx])); return false; } + + if (frm.doc.party_type == "Donor" && row.reference_doctype != "Donation") { + frappe.model.set_value(row.doctype, row.name, "reference_doctype", null); + frappe.msgprint(__("Row #{0}: Reference Document Type must be Donation", [row.idx])); + return false; + } } if (row) { diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 31a4c8a387..203d06a41f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -72,6 +72,7 @@ class PaymentEntry(AccountsController): self.update_outstanding_amounts() self.update_advance_paid() self.update_expense_claim() + self.update_donation() self.update_payment_schedule() self.set_status() @@ -82,6 +83,7 @@ class PaymentEntry(AccountsController): self.update_outstanding_amounts() self.update_advance_paid() self.update_expense_claim() + self.update_donation(cancel=1) self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) self.set_payment_req_status() @@ -245,6 +247,8 @@ class PaymentEntry(AccountsController): valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance") elif self.party_type == "Shareholder": valid_reference_doctypes = ("Journal Entry") + elif self.party_type == "Donor": + valid_reference_doctypes = ("Donation") for d in self.get("references"): if not d.allocated_amount: @@ -614,6 +618,13 @@ class PaymentEntry(AccountsController): doc = frappe.get_doc("Expense Claim", d.reference_name) update_reimbursed_amount(doc, self.name) + def update_donation(self, cancel=0): + if self.payment_type == "Receive" and self.party_type == "Donor" and self.party: + for d in self.get("references"): + if d.reference_doctype=="Donation" and d.reference_name: + is_paid = 0 if cancel else 1 + frappe.db.set_value("Donation", d.reference_name, "paid", is_paid) + def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name self.reference_date = nowdate() @@ -913,6 +924,9 @@ 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") + elif reference_doctype == "Donation": + total_amount = ref_doc.get("amount") + exchange_rate = 1 elif reference_doctype == "Dunning": total_amount = ref_doc.get("dunning_amount") exchange_rate = 1 @@ -1162,8 +1176,10 @@ def set_party_type(dt): party_type = "Supplier" elif dt in ("Expense Claim", "Employee Advance"): party_type = "Employee" - elif dt in ("Fees"): + elif dt == "Fees": party_type = "Student" + elif dt == "Donation": + party_type = "Donor" return party_type def set_party_account(dt, dn, doc, party_type): @@ -1189,7 +1205,7 @@ def set_party_account_currency(dt, party_account, doc): return party_account_currency def set_payment_type(dt, doc): - if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \ + if (dt in ("Sales Order", "Donation") 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: @@ -1222,6 +1238,9 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre elif dt == "Dunning": grand_total = doc.grand_total outstanding_amount = doc.grand_total + elif dt == "Donation": + grand_total = doc.amount + outstanding_amount = doc.amount else: if party_account_currency == doc.company_currency: grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total) diff --git a/erpnext/non_profit/doctype/membership_settings/__init__.py b/erpnext/non_profit/doctype/donation/__init__.py similarity index 100% rename from erpnext/non_profit/doctype/membership_settings/__init__.py rename to erpnext/non_profit/doctype/donation/__init__.py diff --git a/erpnext/non_profit/doctype/donation/donation.js b/erpnext/non_profit/doctype/donation/donation.js new file mode 100644 index 0000000000..10e8220144 --- /dev/null +++ b/erpnext/non_profit/doctype/donation/donation.js @@ -0,0 +1,26 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Donation', { + refresh: function(frm) { + if (frm.doc.docstatus === 1 && !frm.doc.paid) { + frm.add_custom_button(__('Create Payment Entry'), function() { + frm.events.make_payment_entry(frm); + }); + } + }, + + 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/non_profit/doctype/donation/donation.json b/erpnext/non_profit/doctype/donation/donation.json new file mode 100644 index 0000000000..6759569d54 --- /dev/null +++ b/erpnext/non_profit/doctype/donation/donation.json @@ -0,0 +1,156 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2021-02-17 10:28:52.645731", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "donor", + "donor_name", + "email", + "column_break_4", + "company", + "date", + "payment_details_section", + "paid", + "amount", + "mode_of_payment", + "razorpay_payment_id", + "amended_from" + ], + "fields": [ + { + "fieldname": "donor", + "fieldtype": "Link", + "label": "Donor", + "options": "Donor", + "reqd": 1 + }, + { + "fetch_from": "donor.donor_name", + "fieldname": "donor_name", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Donor Name", + "read_only": 1 + }, + { + "fetch_from": "donor.email", + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Email", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "reqd": 1 + }, + { + "fieldname": "payment_details_section", + "fieldtype": "Section Break", + "label": "Payment Details" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "reqd": 1 + }, + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment" + }, + { + "fieldname": "razorpay_payment_id", + "fieldtype": "Data", + "label": "Razorpay Payment ID", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "NPO-DTN-.YYYY.-" + }, + { + "default": "0", + "fieldname": "paid", + "fieldtype": "Check", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Paid" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Donation", + "print_hide": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-03-11 10:53:11.269005", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Donation", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "select": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Manager", + "select": 1, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "search_fields": "donor_name, email", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "donor_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py new file mode 100644 index 0000000000..e947588482 --- /dev/null +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import six +import json +from frappe.model.document import Document +from frappe import _ +from frappe.utils import getdate, flt, get_link_to_form +from frappe.email import sendmail_to_system_managers +from erpnext.non_profit.doctype.membership.membership import verify_signature + +class Donation(Document): + def validate(self): + if not self.donor or not frappe.db.exists('Donor', self.donor): + # for web forms + user_type = frappe.db.get_value('User', frappe.session.user, 'user_type') + if user_type == 'Website User': + self.create_donor_for_website_user() + else: + frappe.throw(_('Please select a Member')) + + def create_donor_for_website_user(self): + donor_name = frappe.get_value('Donor', dict(email=frappe.session.user)) + + if not donor_name: + user = frappe.get_doc('User', frappe.session.user) + donor = frappe.get_doc(dict( + doctype='Donor', + donor_type=self.get('donor_type'), + email=frappe.session.user, + member_name=user.get_fullname() + )).insert(ignore_permissions=True) + donor_name = donor.name + + if self.get('__islocal'): + self.donor = donor_name + + def on_payment_authorized(self, *args, **kwargs): + self.load_from_db() + self.create_payment_entry() + + def create_payment_entry(self): + settings = frappe.get_doc('Non Profit Settings') + if not settings.automate_donation_payment_entries: + return + + if not settings.donation_payment_account: + frappe.throw(_('You need to set Payment Account for Donation in {0}').format( + get_link_to_form('Non Profit Settings', 'Non Profit Settings'))) + + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + + frappe.flags.ignore_account_permission = True + pe = get_payment_entry(dt=self.doctype, dn=self.name) + frappe.flags.ignore_account_permission = False + pe.paid_from = settings.donation_debit_account + pe.paid_to = settings.donation_payment_account + pe.reference_no = self.name + pe.reference_date = getdate() + pe.flags.ignore_mandatory = True + pe.insert() + pe.submit() + + +@frappe.whitelist(allow_guest=True) +def capture_razorpay_donations(*args, **kwargs): + """ + Creates Donation from Razorpay Webhook Request Data on payment.captured event + Creates Donor from email if not found + """ + data = frappe.request.get_data(as_text=True) + + try: + verify_signature(data, endpoint='Donation') + except Exception as e: + log = frappe.log_error(e, 'Donation Webhook Verification Error') + notify_failure(log) + return { 'status': 'Failed', 'reason': e } + + if isinstance(data, six.string_types): + data = json.loads(data) + data = frappe._dict(data) + + payment = data.payload.get('payment', {}).get('entity', {}) + payment = frappe._dict(payment) + + try: + if not data.event == 'payment.captured': + return + + donor = get_donor(payment.email) + if not donor: + donor = create_donor(payment) + + donation = create_donation(donor, payment) + donation.run_method('create_payment_entry') + + except Exception as e: + message = '{0}\n\n{1}\n\n{2}: {3}'.format(e, frappe.get_traceback(), _('Payment ID'), payment.id) + log = frappe.log_error(message, _('Error creating donation entry for {0}').format(donor.name)) + notify_failure(log) + return { 'status': 'Failed', 'reason': e } + + return { 'status': 'Success' } + + +def create_donation(donor, payment): + if not frappe.db.exists('Mode of Payment', payment.method): + create_mode_of_payment(payment.method) + + company = get_company_for_donations() + donation = frappe.get_doc({ + 'doctype': 'Donation', + 'company': company, + 'donor': donor.name, + 'donor_name': donor.donor_name, + 'email': donor.email, + 'date': getdate(), + 'amount': flt(payment.amount), + 'mode_of_payment': payment.method, + 'razorpay_payment_id': payment.id + }).insert(ignore_mandatory=True) + + donation.submit() + return donation + + +def get_donor(email): + donors = frappe.get_all('Donor', + filters={'email': email}, + order_by='creation desc') + + try: + return frappe.get_doc('Donor', donors[0]['name']) + except Exception: + return None + + +@frappe.whitelist() +def create_donor(payment): + donor_details = frappe._dict(payment) + donor_type = frappe.db.get_single_value('Non Profit Settings', 'default_donor_type') + + donor = frappe.new_doc('Donor') + donor.update({ + 'donor_name': donor_details.email, + 'donor_type': donor_type, + 'email': donor_details.email, + 'contact': donor_details.contact + }) + + if donor_details.get('notes'): + donor = get_additional_notes(donor, donor_details) + + donor.insert(ignore_mandatory=True) + return donor + + +def get_company_for_donations(): + company = frappe.db.get_single_value('Non Profit Settings', 'donation_company') + if not company: + from erpnext.healthcare.setup import get_company + company = get_company() + return company + + +def get_additional_notes(donor, donor_details): + if type(donor_details.notes) == dict: + for k, v in donor_details.notes.items(): + notes = '\n'.join('{}: {}'.format(k, v)) + + # extract donor name from notes + if 'name' in k.lower(): + donor.update({ + 'donor_name': donor_details.notes.get(k) + }) + + # extract pan from notes + if 'pan' in k.lower(): + donor.update({ + 'pan_number': donor_details.notes.get(k) + }) + + donor.add_comment('Comment', notes) + + elif type(donor_details.notes) == str: + donor.add_comment('Comment', donor_details.notes) + + return donor + + +def create_mode_of_payment(method): + frappe.get_doc({ + 'doctype': 'Mode of Payment', + 'mode_of_payment': method + }).insert(ignore_mandatory=True) + + +def notify_failure(log): + try: + content = ''' + Dear System Manager, + Razorpay webhook for creating donation failed due to some reason. + Please check the error log linked below + Error Log: {0} + Regards, Administrator + '''.format(get_link_to_form('Error Log', log.name)) + + sendmail_to_system_managers(_('[Important] [ERPNext] Razorpay donation webhook failed, please check.'), content) + except Exception: + pass + diff --git a/erpnext/non_profit/doctype/donation/donation_dashboard.py b/erpnext/non_profit/doctype/donation/donation_dashboard.py new file mode 100644 index 0000000000..7e25c8d217 --- /dev/null +++ b/erpnext/non_profit/doctype/donation/donation_dashboard.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'donation', + 'non_standard_fieldnames': { + 'Payment Entry': 'reference_name' + }, + 'transactions': [ + { + 'label': _('Payment'), + 'items': ['Payment Entry'] + } + ] + } \ No newline at end of file diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py new file mode 100644 index 0000000000..c6a534dac3 --- /dev/null +++ b/erpnext/non_profit/doctype/donation/test_donation.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from erpnext.non_profit.doctype.donation.donation import create_donation + +class TestDonation(unittest.TestCase): + def setUp(self): + create_donor_type() + settings = frappe.get_doc('Non Profit Settings') + settings.company = '_Test Company' + settings.donation_company = '_Test Company' + settings.default_donor_type = '_Test Donor' + settings.automate_donation_payment_entries = 1 + settings.donation_debit_account = 'Debtors - _TC' + settings.donation_payment_account = 'Cash - _TC' + settings.creation_user = 'Administrator' + settings.flags.ignore_permissions = True + settings.save() + + def test_payment_entry_for_donations(self): + donor = create_donor() + create_mode_of_payment() + payment = frappe._dict({ + 'amount': 100, + 'method': 'Debit Card', + 'id': 'pay_MeXAmsgeKOhq7O' + }) + donation = create_donation(donor, payment) + + self.assertTrue(donation.name) + + # Naive test to check if at all payment entry is generated + # This method is actually triggered from Payment Gateway + # In any case if details were missing, this would throw an error + donation.on_payment_authorized() + donation.reload() + + self.assertEquals(donation.paid, 1) + self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name})) + + +def create_donor_type(): + if not frappe.db.exists('Donor Type', '_Test Donor'): + frappe.get_doc({ + 'doctype': 'Donor Type', + 'donor_type': '_Test Donor' + }).insert() + + +def create_donor(): + donor = frappe.db.exists('Donor', 'donor@test.com') + if donor: + return frappe.get_doc('Donor', 'donor@test.com') + else: + return frappe.get_doc({ + 'doctype': 'Donor', + 'donor_name': '_Test Donor', + 'donor_type': '_Test Donor', + 'email': 'donor@test.com' + }).insert() + + +def create_mode_of_payment(): + if not frappe.db.exists('Mode of Payment', 'Debit Card'): + frappe.get_doc({ + 'doctype': 'Mode of Payment', + 'mode_of_payment': 'Debit Card', + 'accounts': [{ + 'company': '_Test Company', + 'default_account': 'Cash - _TC' + }] + }).insert() \ No newline at end of file diff --git a/erpnext/non_profit/doctype/donor/donor.json b/erpnext/non_profit/doctype/donor/donor.json index 96392658f1..72f24ef922 100644 --- a/erpnext/non_profit/doctype/donor/donor.json +++ b/erpnext/non_profit/doctype/donor/donor.json @@ -76,8 +76,13 @@ } ], "image_field": "image", - "links": [], - "modified": "2020-09-16 23:46:04.083274", + "links": [ + { + "link_doctype": "Donation", + "link_fieldname": "donor" + } + ], + "modified": "2021-02-17 16:36:33.470731", "modified_by": "Administrator", "module": "Non Profit", "name": "Donor", diff --git a/erpnext/non_profit/doctype/donor/donor.py b/erpnext/non_profit/doctype/donor/donor.py index 9121d0cdfc..fb70e59575 100644 --- a/erpnext/non_profit/doctype/donor/donor.py +++ b/erpnext/non_profit/doctype/donor/donor.py @@ -11,3 +11,8 @@ class Donor(Document): """Load address and contacts in `__onload`""" load_address_and_contact(self) + def validate(self): + from frappe.utils import validate_email_address + if self.email: + validate_email_address(self.email.strip(), True) + diff --git a/erpnext/non_profit/doctype/member/member.js b/erpnext/non_profit/doctype/member/member.js index 199dcfc04f..6b8f1b1deb 100644 --- a/erpnext/non_profit/doctype/member/member.js +++ b/erpnext/non_profit/doctype/member/member.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Member', { setup: function(frm) { - frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => { if (val && (frm.doc.subscription_id || frm.doc.customer_id)) { frm.set_df_property('razorpay_details_section', 'hidden', false); } diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 04b99f93f2..3ba2ee71c6 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.contacts.address_and_contact import load_address_and_contact -from frappe.utils import cint +from frappe.utils import cint, get_link_to_form from frappe.integrations.utils import get_payment_gateway_controller from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type @@ -26,9 +26,10 @@ class Member(Document): validate_email_address(email.strip(), True) def setup_subscription(self): - membership_settings = frappe.get_doc("Membership Settings") - if not membership_settings.enable_razorpay: - frappe.throw("Please enable Razorpay to setup subscription") + non_profit_settings = frappe.get_doc('Non Profit Settings') + if not non_profit_settings.enable_razorpay_for_memberships: + frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format( + get_link_to_form('Non Profit Settings', 'Non Profit Settings')) controller = get_payment_gateway_controller("Razorpay") settings = controller.get_settings({}) @@ -40,7 +41,7 @@ class Member(Document): subscription_details = { "plan_id": plan_id, - "billing_frequency": cint(membership_settings.billing_frequency), + "billing_frequency": cint(non_profit_settings.billing_frequency), "customer_notify": 1 } diff --git a/erpnext/non_profit/doctype/membership/membership.js b/erpnext/non_profit/doctype/membership/membership.js index 573ac3319a..31872048a0 100644 --- a/erpnext/non_profit/doctype/membership/membership.js +++ b/erpnext/non_profit/doctype/membership/membership.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Membership', { setup: function(frm) { - frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + frappe.db.get_single_value("Non Profit Settings", "enable_razorpay_for_memberships").then(val => { if (val) frm.set_df_property("razorpay_details_section", "hidden", false); }) }, @@ -26,7 +26,7 @@ frappe.ui.form.on('Membership', { }); }); - frappe.db.get_single_value("Membership Settings", "send_email").then(val => { + frappe.db.get_single_value("Non Profit Settings", "send_email").then(val => { if (val) frm.add_custom_button("Send Acknowledgement", () => { frm.call("send_acknowlement").then(() => { frm.reload_doc(); diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json index 6da053f9fc..11d32f9c2b 100644 --- a/erpnext/non_profit/doctype/membership/membership.json +++ b/erpnext/non_profit/doctype/membership/membership.json @@ -10,6 +10,7 @@ "member_name", "membership_type", "column_break_3", + "company", "membership_status", "membership_validity_section", "from_date", @@ -132,11 +133,18 @@ "fieldtype": "Data", "label": "Member Name", "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-01-21 16:31:20.032656", + "modified": "2021-02-19 14:33:44.925122", "modified_by": "Administrator", "module": "Non Profit", "name": "Membership", diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index c113b80d56..191281f4ce 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import json import frappe import six +import os from datetime import datetime from frappe.model.document import Document from frappe.email import sendmail_to_system_managers @@ -58,7 +59,7 @@ class Membership(Document): else: self.from_date = nowdate() - if frappe.db.get_single_value("Membership Settings", "billing_cycle") == "Yearly": + if frappe.db.get_single_value("Non Profit Settings", "billing_cycle") == "Yearly": self.to_date = add_years(self.from_date, 1) else: self.to_date = add_months(self.from_date, 1) @@ -68,9 +69,9 @@ class Membership(Document): return self.load_from_db() self.db_set("paid", 1) - settings = frappe.get_doc("Membership Settings") - if settings.enable_invoicing and settings.create_for_web_forms: - self.generate_invoice(with_payment_entry=settings.make_payment_entry, save=True) + settings = frappe.get_doc("Non Profit Settings") + if settings.allow_invoicing and settings.automate_membership_invoicing: + self.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) def generate_invoice(self, save=True, with_payment_entry=False): @@ -85,7 +86,7 @@ class Membership(Document): frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member))) plan = frappe.get_doc("Membership Type", self.membership_type) - settings = frappe.get_doc("Membership Settings") + settings = frappe.get_doc("Non Profit Settings") self.validate_membership_type_and_settings(plan, settings) invoice = make_invoice(self, member, plan, settings) @@ -102,7 +103,7 @@ class Membership(Document): def validate_membership_type_and_settings(self, plan, settings): settings_link = get_link_to_form("Membership Type", self.membership_type) - if not settings.debit_account: + if not settings.membership_debit_account: frappe.throw(_("You need to set Debit Account in {0}").format(settings_link)) if not settings.company: @@ -113,25 +114,26 @@ class Membership(Document): get_link_to_form("Membership Type", self.membership_type))) def make_payment_entry(self, settings, invoice): - if not settings.payment_account: - frappe.throw(_("You need to set Payment Account in {0}").format( - get_link_to_form("Membership Type", self.membership_type))) + if not settings.membership_payment_account: + frappe.throw(_("You need to set Payment Account for Membership in {0}").format( + get_link_to_form("Non Profit Settings", "Non Profit Settings"))) from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry frappe.flags.ignore_account_permission = True pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total) frappe.flags.ignore_account_permission=False - pe.paid_to = settings.payment_account + pe.paid_to = settings.membership_payment_account pe.reference_no = self.name pe.reference_date = getdate() - pe.save(ignore_permissions=True) + pe.flags.ignore_mandatory = True + pe.save() pe.submit() def send_acknowlement(self): - settings = frappe.get_doc("Membership Settings") + settings = frappe.get_doc("Non Profit Settings") if not settings.send_email: frappe.throw(_("You need to enable Send Acknowledge Email in {0}").format( - get_link_to_form("Membership Settings", "Membership Settings"))) + get_link_to_form("Non Profit Settings", "Non Profit Settings"))) member = frappe.get_doc("Member", self.member) if not member.email_id: @@ -170,7 +172,7 @@ def make_invoice(membership, member, plan, settings): invoice = frappe.get_doc({ "doctype": "Sales Invoice", "customer": member.customer, - "debit_to": settings.debit_account, + "debit_to": settings.membership_debit_account, "currency": membership.currency, "company": settings.company, "is_pos": 0, @@ -183,7 +185,7 @@ def make_invoice(membership, member, plan, settings): ] }) invoice.set_missing_values() - invoice.insert(ignore_permissions=True) + invoice.insert() invoice.submit() frappe.msgprint(_("Sales Invoice created successfully")) @@ -203,17 +205,18 @@ def get_member_based_on_subscription(subscription_id, email): return None -def verify_signature(data): - if frappe.flags.in_test: +def verify_signature(data, endpoint="Membership"): + if frappe.flags.in_test or os.environ.get("CI"): return True signature = frappe.request.headers.get("X-Razorpay-Signature") - settings = frappe.get_doc("Membership Settings") - key = settings.get_webhook_secret() + settings = frappe.get_doc("Non Profit Settings") + key = settings.get_webhook_secret(endpoint) controller = frappe.get_doc("Razorpay Settings") controller.verify_signature(data, signature, key) + frappe.set_user(settings.creation_user) @frappe.whitelist(allow_guest=True) @@ -222,7 +225,7 @@ def trigger_razorpay_subscription(*args, **kwargs): try: verify_signature(data) except Exception as e: - log = frappe.log_error(e, "Webhook Verification Error") + log = frappe.log_error(e, "Membership Webhook Verification Error") notify_failure(log) return { "status": "Failed", "reason": e} @@ -250,16 +253,15 @@ def trigger_razorpay_subscription(*args, **kwargs): member.subscription_id = subscription.id member.customer_id = payment.customer_id - if subscription.notes and type(subscription.notes) == dict: - notes = "\n".join("{}: {}".format(k, v) for k, v in subscription.notes.items()) - member.add_comment("Comment", notes) - elif subscription.notes and type(subscription.notes) == str: - member.add_comment("Comment", subscription.notes) + if subscription.get("notes"): + member = get_additional_notes(member, subscription) + company = get_company_for_memberships() # Update Membership membership = frappe.new_doc("Membership") membership.update({ + "company": company, "member": member.name, "membership_status": "Current", "membership_type": member.membership_type, @@ -270,13 +272,20 @@ def trigger_razorpay_subscription(*args, **kwargs): "to_date": datetime.fromtimestamp(subscription.current_end), "amount": payment.amount / 100 # Convert to rupees from paise }) - membership.insert(ignore_permissions=True) + membership.flags.ignore_mandatory = True + membership.insert() # Update membership values member.subscription_start = datetime.fromtimestamp(subscription.start_at) member.subscription_end = datetime.fromtimestamp(subscription.end_at) member.subscription_activated = 1 - member.save(ignore_permissions=True) + member.flags.ignore_mandatory = True + member.save() + + settings = frappe.get_doc("Non Profit Settings") + if settings.allow_invoicing and settings.automate_membership_invoicing: + membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) + except Exception as e: message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) @@ -286,6 +295,39 @@ def trigger_razorpay_subscription(*args, **kwargs): return { "status": "Success" } +def get_company_for_memberships(): + company = frappe.db.get_single_value("Non Profit Settings", "company") + if not company: + from erpnext.healthcare.setup import get_company + company = get_company() + return company + + +def get_additional_notes(member, subscription): + if type(subscription.notes) == dict: + for k, v in subscription.notes.items(): + notes = "\n".join("{}: {}".format(k, v)) + + # extract member name from notes + if "name" in k.lower(): + member.update({ + "member_name": subscription.notes.get(k) + }) + + # extract pan number from notes + if "pan" in k.lower(): + member.update({ + "pan_number": subscription.notes.get(k) + }) + + member.add_comment("Comment", notes) + + elif type(subscription.notes) == str: + member.add_comment("Comment", subscription.notes) + + return member + + def notify_failure(log): try: content = """ diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py index ff7e6c473c..31da792e53 100644 --- a/erpnext/non_profit/doctype/membership/test_membership.py +++ b/erpnext/non_profit/doctype/membership/test_membership.py @@ -10,33 +10,7 @@ from frappe.utils import nowdate, add_months class TestMembership(unittest.TestCase): def setUp(self): - # Get default company - company = frappe.get_doc("Company", erpnext.get_default_company()) - - # update membership settings - settings = frappe.get_doc("Membership Settings") - # Enable razorpay - settings.enable_razorpay = 1 - settings.billing_cycle = "Monthly" - settings.billing_frequency = 24 - # Enable invoicing - settings.enable_invoicing = 1 - settings.make_payment_entry = 1 - settings.company = company.name - settings.payment_account = company.default_cash_account - settings.debit_account = company.default_receivable_account - settings.save() - - # make test plan - if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"): - plan = frappe.new_doc("Membership Type") - plan.membership_type = "_rzpy_test_milythm" - plan.amount = 100 - plan.razorpay_plan_id = "_rzpy_test_milythm" - plan.linked_item = create_item("_Test Item for Non Profit Membership").name - plan.insert() - else: - plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm") + plan = setup_membership() # make test member self.member_doc = create_member(frappe._dict({ @@ -78,7 +52,7 @@ class TestMembership(unittest.TestCase): }) def set_config(key, value): - frappe.db.set_value("Membership Settings", None, key, value) + frappe.db.set_value("Non Profit Settings", None, key, value) def make_membership(member, payload={}): data = { @@ -109,3 +83,36 @@ def create_item(item_code): else: item = frappe.get_doc("Item", item_code) return item + +def setup_membership(): + # Get default company + company = frappe.get_doc("Company", erpnext.get_default_company()) + + # update non profit settings + settings = frappe.get_doc("Non Profit Settings") + # Enable razorpay + settings.enable_razorpay_for_memberships = 1 + settings.billing_cycle = "Monthly" + settings.billing_frequency = 24 + # Enable invoicing + settings.allow_invoicing = 1 + settings.automate_membership_payment_entries = 1 + settings.company = company.name + settings.donation_company = company.name + settings.membership_payment_account = company.default_cash_account + settings.membership_debit_account = company.default_receivable_account + settings.flags.ignore_mandatory = True + settings.save() + + # make test plan + if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"): + plan = frappe.new_doc("Membership Type") + plan.membership_type = "_rzpy_test_milythm" + plan.amount = 100 + plan.razorpay_plan_id = "_rzpy_test_milythm" + plan.linked_item = create_item("_Test Item for Non Profit Membership").name + plan.insert() + else: + plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm") + + return plan \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json deleted file mode 100644 index 3887b0a2be..0000000000 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-29 12:57:03.005120", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "enable_razorpay", - "razorpay_settings_section", - "billing_cycle", - "billing_frequency", - "webhook_secret", - "column_break_6", - "enable_invoicing", - "create_for_web_forms", - "make_payment_entry", - "company", - "debit_account", - "payment_account", - "column_break_9", - "send_email", - "send_invoice", - "membership_print_format", - "inv_print_format", - "email_template" - ], - "fields": [ - { - "fieldname": "billing_cycle", - "fieldtype": "Select", - "label": "Billing Cycle", - "options": "Monthly\nYearly" - }, - { - "default": "0", - "fieldname": "enable_razorpay", - "fieldtype": "Check", - "label": "Enable RazorPay For Memberships" - }, - { - "depends_on": "eval:doc.enable_razorpay", - "fieldname": "razorpay_settings_section", - "fieldtype": "Section Break", - "label": "RazorPay Settings" - }, - { - "description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", - "fieldname": "billing_frequency", - "fieldtype": "Int", - "label": "Billing Frequency" - }, - { - "fieldname": "webhook_secret", - "fieldtype": "Password", - "label": "Webhook Secret", - "read_only": 1 - }, - { - "fieldname": "column_break_6", - "fieldtype": "Section Break", - "label": "Invoicing" - }, - { - "depends_on": "eval:doc.enable_invoicing", - "fieldname": "debit_account", - "fieldtype": "Link", - "label": "Debit Account", - "mandatory_depends_on": "eval:doc.enable_auto_invoicing", - "options": "Account" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.enable_invoicing", - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "mandatory_depends_on": "eval:doc.enable_auto_invoicing", - "options": "Company" - }, - { - "default": "0", - "depends_on": "eval:doc.enable_invoicing && doc.send_email", - "fieldname": "send_invoice", - "fieldtype": "Check", - "label": "Send Invoice with Email" - }, - { - "default": "0", - "fieldname": "send_email", - "fieldtype": "Check", - "label": "Send Membership Acknowledgement" - }, - { - "depends_on": "eval: doc.send_invoice", - "fieldname": "inv_print_format", - "fieldtype": "Link", - "label": "Invoice Print Format", - "mandatory_depends_on": "eval: doc.send_invoice", - "options": "Print Format" - }, - { - "depends_on": "eval:doc.send_email", - "fieldname": "membership_print_format", - "fieldtype": "Link", - "label": "Membership Print Format", - "options": "Print Format" - }, - { - "depends_on": "eval:doc.send_email", - "fieldname": "email_template", - "fieldtype": "Link", - "label": "Email Template", - "mandatory_depends_on": "eval:doc.send_email", - "options": "Email Template" - }, - { - "default": "0", - "fieldname": "enable_invoicing", - "fieldtype": "Check", - "label": "Enable Invoicing", - "mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry" - }, - { - "default": "0", - "depends_on": "eval:doc.enable_invoicing", - "description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.", - "fieldname": "make_payment_entry", - "fieldtype": "Check", - "label": "Make Payment Entry" - }, - { - "depends_on": "eval:doc.make_payment_entry", - "fieldname": "payment_account", - "fieldtype": "Link", - "label": "Payment To", - "mandatory_depends_on": "eval:doc.make_payment_entry", - "options": "Account" - }, - { - "default": "0", - "depends_on": "eval:doc.enable_invoicing", - "description": "Automatically create an invoice when payment is authorized from a web form entry", - "fieldname": "create_for_web_forms", - "fieldtype": "Check", - "label": "Auto Create Invoice for Web Forms" - } - ], - "index_web_pages_for_search": 1, - "issingle": 1, - "links": [], - "modified": "2021-01-21 19:57:53.213286", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Membership Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Non Profit Manager", - "share": 1, - "write": 1 - }, - { - "email": 1, - "print": 1, - "read": 1, - "role": "Non Profit Member", - "share": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js index 91a5cb74ba..2f2427629c 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.js +++ b/erpnext/non_profit/doctype/membership_type/membership_type.js @@ -3,11 +3,11 @@ frappe.ui.form.on('Membership Type', { refresh: function (frm) { - frappe.db.get_single_value('Membership Settings', 'enable_razorpay').then(val => { + frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => { if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false); }); - frappe.db.get_single_value('Membership Settings', 'enable_invoicing').then(val => { + frappe.db.get_single_value('Non Profit Settings', 'allow_invoicing').then(val => { if (val) frm.set_df_property('linked_item', 'hidden', false); }); diff --git a/erpnext/non_profit/doctype/non_profit_settings/__init__.py b/erpnext/non_profit/doctype/non_profit_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js similarity index 50% rename from erpnext/non_profit/doctype/membership_settings/membership_settings.js rename to erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js index c95aab2a7a..cff92b42ab 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js @@ -1,16 +1,8 @@ // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on("Membership Settings", { +frappe.ui.form.on("Non Profit Settings", { refresh: function(frm) { - if (frm.doc.webhook_secret) { - frm.add_custom_button(__("Revoke "), () => { - frm.call("revoke_key").then(() => { - frm.refresh(); - }) - }); - } - frm.set_query("inv_print_format", function() { return { filters: { @@ -37,7 +29,7 @@ frappe.ui.form.on("Membership Settings", { }; }); - frm.set_query("payment_account", function () { + frm.set_query("membership_payment_account", function () { var account_types = ["Bank", "Cash"]; return { filters: { @@ -51,31 +43,70 @@ frappe.ui.form.on("Membership Settings", { let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership"; frm.set_intro(__("You can learn more about memberships in the manual. ") + `${__('ERPNext Docs')}`, true); - - frm.trigger("add_generate_button"); - frm.trigger("add_copy_buttonn"); + frm.trigger("setup_buttons_for_membership"); + frm.trigger("setup_buttons_for_donation"); }, - add_generate_button: function(frm) { + setup_buttons_for_membership: function(frm) { let label; - if (frm.doc.webhook_secret) { + if (frm.doc.membership_webhook_secret) { + + frm.add_custom_button(__("Copy Webhook URL"), () => { + frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`); + }, __("Memberships")); + + frm.add_custom_button(__("Revoke Key"), () => { + frm.call("revoke_key", { + key: "membership_webhook_secret" + }).then(() => { + frm.refresh(); + }); + }, __("Memberships")); + label = __("Regenerate Webhook Secret"); + } else { label = __("Generate Webhook Secret"); } + frm.add_custom_button(label, () => { - frm.call("generate_webhook_key").then(() => { + frm.call("generate_webhook_secret", { + field: "membership_webhook_secret" + }).then(() => { frm.refresh(); }); - }); + }, __("Memberships")); }, - add_copy_buttonn: function(frm) { - if (frm.doc.webhook_secret) { + setup_buttons_for_donation: function(frm) { + let label; + + if (frm.doc.donation_webhook_secret) { + label = __("Regenerate Webhook Secret"); + frm.add_custom_button(__("Copy Webhook URL"), () => { - frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`); - }); + frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.donation.donation.capture_razorpay_donations`); + }, __("Donations")); + + frm.add_custom_button(__("Revoke Key"), () => { + frm.call("revoke_key", { + key: "donation_webhook_secret" + }).then(() => { + frm.refresh(); + }); + }, __("Donations")); + + } else { + label = __("Generate Webhook Secret"); } + + frm.add_custom_button(label, () => { + frm.call("generate_webhook_secret", { + field: "donation_webhook_secret" + }).then(() => { + frm.refresh(); + }); + }, __("Donations")); } }); diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.json b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.json new file mode 100644 index 0000000000..25ff0c1bb0 --- /dev/null +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.json @@ -0,0 +1,273 @@ +{ + "actions": [], + "creation": "2020-03-29 12:57:03.005120", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable_razorpay_for_memberships", + "razorpay_settings_section", + "billing_cycle", + "billing_frequency", + "membership_webhook_secret", + "column_break_6", + "allow_invoicing", + "automate_membership_invoicing", + "automate_membership_payment_entries", + "company", + "membership_debit_account", + "membership_payment_account", + "column_break_9", + "send_email", + "send_invoice", + "membership_print_format", + "inv_print_format", + "email_template", + "donation_settings_section", + "donation_company", + "default_donor_type", + "donation_webhook_secret", + "column_break_22", + "automate_donation_payment_entries", + "donation_debit_account", + "donation_payment_account", + "section_break_27", + "creation_user" + ], + "fields": [ + { + "fieldname": "billing_cycle", + "fieldtype": "Select", + "label": "Billing Cycle", + "options": "Monthly\nYearly" + }, + { + "depends_on": "eval:doc.enable_razorpay_for_memberships", + "fieldname": "razorpay_settings_section", + "fieldtype": "Section Break", + "label": "RazorPay Settings for Memberships" + }, + { + "description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", + "fieldname": "billing_frequency", + "fieldtype": "Int", + "label": "Billing Frequency" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Section Break", + "label": "Membership Invoicing" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "description": "This company will be set for the Memberships created via webhook.", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.allow_invoicing && doc.send_email", + "fieldname": "send_invoice", + "fieldtype": "Check", + "label": "Send Invoice with Email" + }, + { + "default": "0", + "fieldname": "send_email", + "fieldtype": "Check", + "label": "Send Membership Acknowledgement" + }, + { + "depends_on": "eval: doc.send_invoice", + "fieldname": "inv_print_format", + "fieldtype": "Link", + "label": "Invoice Print Format", + "mandatory_depends_on": "eval: doc.send_invoice", + "options": "Print Format" + }, + { + "depends_on": "eval:doc.send_email", + "fieldname": "membership_print_format", + "fieldtype": "Link", + "label": "Membership Print Format", + "options": "Print Format" + }, + { + "depends_on": "eval:doc.send_email", + "fieldname": "email_template", + "fieldtype": "Link", + "label": "Email Template", + "mandatory_depends_on": "eval:doc.send_email", + "options": "Email Template" + }, + { + "default": "0", + "fieldname": "allow_invoicing", + "fieldtype": "Check", + "label": "Allow Invoicing for Memberships", + "mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry" + }, + { + "default": "0", + "depends_on": "eval:doc.allow_invoicing", + "description": "Automatically create an invoice when payment is authorized from a web form entry", + "fieldname": "automate_membership_invoicing", + "fieldtype": "Check", + "label": "Automate Invoicing for Web Forms" + }, + { + "default": "0", + "depends_on": "eval:doc.allow_invoicing", + "description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.", + "fieldname": "automate_membership_payment_entries", + "fieldtype": "Check", + "label": "Automate Payment Entry Creation" + }, + { + "default": "0", + "fieldname": "enable_razorpay_for_memberships", + "fieldtype": "Check", + "label": "Enable RazorPay For Memberships" + }, + { + "depends_on": "eval:doc.automate_membership_payment_entries", + "description": "Account for accepting membership payments", + "fieldname": "membership_payment_account", + "fieldtype": "Link", + "label": "Membership Payment To", + "mandatory_depends_on": "eval:doc.automate_membership_payment_entries", + "options": "Account" + }, + { + "fieldname": "membership_webhook_secret", + "fieldtype": "Password", + "label": "Membership Webhook Secret", + "read_only": 1 + }, + { + "fieldname": "donation_webhook_secret", + "fieldtype": "Password", + "label": "Donation Webhook Secret", + "read_only": 1 + }, + { + "depends_on": "automate_donation_payment_entries", + "description": "Account for accepting donation payments", + "fieldname": "donation_payment_account", + "fieldtype": "Link", + "label": "Donation Payment To", + "mandatory_depends_on": "automate_donation_payment_entries", + "options": "Account" + }, + { + "default": "0", + "description": "Auto creates Payment Entry for Donations created from web forms.", + "fieldname": "automate_donation_payment_entries", + "fieldtype": "Check", + "label": "Automate Donation Payment Entries" + }, + { + "depends_on": "eval:doc.allow_invoicing", + "fieldname": "membership_debit_account", + "fieldtype": "Link", + "label": "Debit Account", + "mandatory_depends_on": "eval:doc.allow_invoicing", + "options": "Account" + }, + { + "depends_on": "automate_donation_payment_entries", + "fieldname": "donation_debit_account", + "fieldtype": "Link", + "label": "Debit Account", + "mandatory_depends_on": "automate_donation_payment_entries", + "options": "Account" + }, + { + "description": "This company will be set for the Donations created via webhook.", + "fieldname": "donation_company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "donation_settings_section", + "fieldtype": "Section Break", + "label": "Donation Settings" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "description": "This Donor Type will be set for the Donor created via Donation web form entry.", + "fieldname": "default_donor_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Default Donor Type", + "options": "Donor Type", + "reqd": 1 + }, + { + "fieldname": "section_break_27", + "fieldtype": "Section Break" + }, + { + "description": "The user that will be used to create Donations, Memberships, Invoices, and Payment Entries. This user should have the relevant permissions.", + "fieldname": "creation_user", + "fieldtype": "Link", + "label": "Creation User", + "options": "User", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-03-11 10:43:38.124240", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Non Profit Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Non Profit Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Non Profit Member", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.py b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py similarity index 51% rename from erpnext/non_profit/doctype/membership_settings/membership_settings.py rename to erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py index f3b2eee6f9..108554c6a0 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.py +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py @@ -8,23 +8,26 @@ from frappe import _ from frappe.integrations.utils import get_payment_gateway_controller from frappe.model.document import Document -class MembershipSettings(Document): - def generate_webhook_key(self): +class NonProfitSettings(Document): + def generate_webhook_secret(self, field="membership_webhook_secret"): key = frappe.generate_hash(length=20) - self.webhook_secret = key + self.set(field, key) self.save() + secret_for = "Membership" if field == "membership_webhook_secret" else "Donation" + frappe.msgprint( - _("Here is your webhook secret, this will be shown to you only once.") + "

" + key, + _("Here is your webhook secret for {0} API, this will be shown to you only once.").format(secret_for) + "

" + key, _("Webhook Secret") - ); + ) - def revoke_key(self): - self.webhook_secret = None; + def revoke_key(self, key): + self.set(key, None) self.save() - def get_webhook_secret(self): - return self.get_password(fieldname="webhook_secret", raise_exception=False) + def get_webhook_secret(self, endpoint="Membership"): + fieldname = "membership_webhook_secret" if endpoint == "Membership" else "donation_webhook_secret" + return self.get_password(fieldname=fieldname, raise_exception=False) @frappe.whitelist() def get_plans_for_membership(*args, **kwargs): diff --git a/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py b/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py similarity index 79% rename from erpnext/non_profit/doctype/membership_settings/test_membership_settings.py rename to erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py index 2ad7984583..3f0ede32e5 100644 --- a/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py +++ b/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestMembershipSettings(unittest.TestCase): +class TestNonProfitSettings(unittest.TestCase): pass diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json new file mode 100644 index 0000000000..2557d77d88 --- /dev/null +++ b/erpnext/non_profit/workspace/non_profit/non_profit.json @@ -0,0 +1,251 @@ +{ + "category": "Domains", + "charts": [], + "creation": "2020-03-02 17:23:47.811421", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "non-profit", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "label": "Non Profit", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Loan Management", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Type", + "link_to": "Loan Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Application", + "link_to": "Loan Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan", + "link_to": "Loan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Grant Application", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Grant Application", + "link_to": "Grant Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Membership", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Member", + "link_to": "Member", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Membership", + "link_to": "Membership", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Membership Type", + "link_to": "Membership Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Membership Settings", + "link_to": "Non Profit Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Volunteer", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Volunteer", + "link_to": "Volunteer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Volunteer Type", + "link_to": "Volunteer Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Chapter", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chapter", + "link_to": "Chapter", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Donation", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Donor", + "link_to": "Donor", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Donor Type", + "link_to": "Donor Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Donation", + "link_to": "Donation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Tax Exemption Certification (India)", + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Tax Exemption 80G Certificate", + "link_to": "Tax Exemption 80G Certificate", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2021-03-11 11:38:09.140655", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Non Profit", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "restrict_to_domain": "Non Profit", + "shortcuts": [ + { + "label": "Member", + "link_to": "Member", + "type": "DocType" + }, + { + "label": "Non Profit Settings", + "link_to": "Non Profit Settings", + "type": "DocType" + }, + { + "label": "Membership", + "link_to": "Membership", + "type": "DocType" + }, + { + "label": "Chapter", + "link_to": "Chapter", + "type": "DocType" + }, + { + "label": "Chapter Member", + "link_to": "Chapter Member", + "type": "DocType" + } + ] +} \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 80e2f1c01a..20ea5097bf 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -753,3 +753,5 @@ erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.update_vehicle_no_reqd_condition +erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation +erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings \ No newline at end of file diff --git a/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py new file mode 100644 index 0000000000..3fa09a7baa --- /dev/null +++ b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py @@ -0,0 +1,22 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if frappe.db.table_exists("Membership Settings"): + frappe.rename_doc("DocType", "Membership Settings", "Non Profit Settings") + frappe.reload_doctype("Non Profit Settings", force=True) + + if frappe.db.table_exists("Non Profit Settings"): + rename_fields_map = { + "enable_invoicing": "allow_invoicing", + "create_for_web_forms": "automate_membership_invoicing", + "make_payment_entry": "automate_membership_payment_entries", + "enable_razorpay": "enable_razorpay_for_memberships", + "debit_account": "membership_debit_account", + "payment_account": "membership_payment_account", + "webhook_secret": "membership_webhook_secret" + } + + for old_name, new_name in rename_fields_map.items(): + rename_field("Non Profit Settings", old_name, new_name) \ No newline at end of file diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py new file mode 100644 index 0000000000..aea53f8add --- /dev/null +++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py @@ -0,0 +1,16 @@ +import frappe +from erpnext.regional.india.setup import make_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + make_custom_fields() + + if not frappe.db.exists('Party Type', 'Donor'): + frappe.get_doc({ + 'doctype': 'Party Type', + 'party_type': 'Donor', + 'account_type': 'Receivable' + }).insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.js b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.js new file mode 100644 index 0000000000..54cde9c0cf --- /dev/null +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.js @@ -0,0 +1,67 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Tax Exemption 80G Certificate', { + refresh: function(frm) { + if (frm.doc.donor) { + frm.set_query('donation', function() { + return { + filters: { + docstatus: 1, + donor: frm.doc.donor + } + }; + }); + } + }, + + recipient: function(frm) { + if (frm.doc.recipient === 'Donor') { + frm.set_value({ + 'member': '', + 'member_name': '', + 'member_email': '', + 'member_pan_number': '', + 'fiscal_year': '', + 'total': 0, + 'payments': [] + }); + } else { + frm.set_value({ + 'donor': '', + 'donor_name': '', + 'donor_email': '', + 'donor_pan_number': '', + 'donation': '', + 'date_of_donation': '', + 'amount': 0, + 'mode_of_payment': '', + 'razorpay_payment_id': '' + }); + } + }, + + get_payments: function(frm) { + frm.call({ + doc: frm.doc, + method: 'get_payments', + freeze: true + }); + }, + + company: function(frm) { + if ((frm.doc.member || frm.doc.donor) && frm.doc.company) { + frm.call({ + doc: frm.doc, + method: 'set_company_address', + freeze: true + }); + } + }, + + donation: function(frm) { + if (frm.doc.recipient === 'Donor' && !frm.doc.donor) { + frappe.msgprint(__('Please select donor first')); + } + } +}); diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.json b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.json new file mode 100644 index 0000000000..9eee722f42 --- /dev/null +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.json @@ -0,0 +1,297 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2021-02-15 12:37:21.577042", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "recipient", + "member", + "member_name", + "member_email", + "member_pan_number", + "donor", + "donor_name", + "donor_email", + "donor_pan_number", + "column_break_4", + "date", + "fiscal_year", + "section_break_11", + "company", + "company_address", + "company_address_display", + "column_break_14", + "company_pan_number", + "company_80g_number", + "company_80g_wef", + "title", + "section_break_6", + "get_payments", + "payments", + "total", + "donation_details_section", + "donation", + "date_of_donation", + "amount", + "column_break_27", + "mode_of_payment", + "razorpay_payment_id" + ], + "fields": [ + { + "fieldname": "recipient", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Certificate Recipient", + "options": "Member\nDonor", + "reqd": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Member\";", + "fieldname": "member", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Member", + "mandatory_depends_on": "eval:doc.recipient === \"Member\";", + "options": "Member" + }, + { + "depends_on": "eval:doc.recipient === \"Member\";", + "fetch_from": "member.member_name", + "fieldname": "member_name", + "fieldtype": "Data", + "label": "Member Name", + "read_only": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Donor\";", + "fieldname": "donor", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Donor", + "mandatory_depends_on": "eval:doc.recipient === \"Donor\";", + "options": "Donor" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "reqd": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Member\";", + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "payments", + "fieldtype": "Table", + "label": "Payments", + "options": "Tax Exemption 80G Certificate Detail" + }, + { + "fieldname": "total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total", + "read_only": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Member\";", + "fieldname": "fiscal_year", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Fiscal Year", + "options": "Fiscal Year" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "get_payments", + "fieldtype": "Button", + "label": "Get Memberships" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "NPO-80G-.YYYY.-" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "label": "Company Details" + }, + { + "fieldname": "company_address", + "fieldtype": "Link", + "label": "Company Address", + "options": "Address" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fetch_from": "company.pan_details", + "fieldname": "company_pan_number", + "fieldtype": "Data", + "label": "PAN Number", + "read_only": 1 + }, + { + "fieldname": "company_address_display", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Company Address Display", + "print_hide": 1, + "read_only": 1 + }, + { + "fetch_from": "company.company_80g_number", + "fieldname": "company_80g_number", + "fieldtype": "Data", + "label": "80G Number", + "read_only": 1 + }, + { + "fetch_from": "company.with_effect_from", + "fieldname": "company_80g_wef", + "fieldtype": "Date", + "label": "80G With Effect From", + "read_only": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Donor\";", + "fieldname": "donation_details_section", + "fieldtype": "Section Break", + "label": "Donation Details" + }, + { + "fieldname": "donation", + "fieldtype": "Link", + "label": "Donation", + "mandatory_depends_on": "eval:doc.recipient === \"Donor\";", + "options": "Donation" + }, + { + "fetch_from": "donation.amount", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "read_only": 1 + }, + { + "fetch_from": "donation.mode_of_payment", + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment", + "read_only": 1 + }, + { + "fetch_from": "donation.razorpay_payment_id", + "fieldname": "razorpay_payment_id", + "fieldtype": "Data", + "label": "RazorPay Payment ID", + "read_only": 1 + }, + { + "fetch_from": "donation.date", + "fieldname": "date_of_donation", + "fieldtype": "Date", + "label": "Date of Donation", + "read_only": 1 + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.recipient === \"Donor\";", + "fetch_from": "donor.donor_name", + "fieldname": "donor_name", + "fieldtype": "Data", + "label": "Donor Name", + "read_only": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Donor\";", + "fetch_from": "donor.email", + "fieldname": "donor_email", + "fieldtype": "Data", + "label": "Email", + "read_only": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Member\";", + "fetch_from": "member.email_id", + "fieldname": "member_email", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Email", + "read_only": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Member\";", + "fetch_from": "member.pan_number", + "fieldname": "member_pan_number", + "fieldtype": "Data", + "label": "PAN Details", + "read_only": 1 + }, + { + "depends_on": "eval:doc.recipient === \"Donor\";", + "fetch_from": "donor.pan_number", + "fieldname": "donor_pan_number", + "fieldtype": "Data", + "label": "PAN Details", + "read_only": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "print_hide": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-02-22 00:03:34.215633", + "modified_by": "Administrator", + "module": "Regional", + "name": "Tax Exemption 80G Certificate", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "search_fields": "member, member_name", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py new file mode 100644 index 0000000000..d734a18c3a --- /dev/null +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 frappe.utils import getdate, flt, get_link_to_form +from erpnext.accounts.utils import get_fiscal_year +from frappe.contacts.doctype.address.address import get_company_address + +class TaxExemption80GCertificate(Document): + def validate(self): + self.validate_date() + self.validate_duplicates() + self.validate_company_details() + self.set_company_address() + self.set_title() + + def validate_date(self): + if self.recipient == 'Member': + if getdate(self.date): + fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True) + + if not (fiscal_year.year_start_date <= getdate(self.date) \ + <= fiscal_year.year_end_date): + frappe.throw(_('The Certificate Date is not in the Fiscal Year {0}').format(frappe.bold(self.fiscal_year))) + + def validate_duplicates(self): + if self.recipient == 'Donor': + certificate = frappe.db.exists(self.doctype, {'donation': self.donation}) + if certificate: + frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format( + get_link_to_form(self.doctype, certificate), frappe.bold(self.donation) + ), title=_('Duplicate Certificate')) + + def validate_company_details(self): + fields = ['company_80g_number', 'with_effect_from', 'pan_details'] + company_details = frappe.db.get_value('Company', self.company, fields, as_dict=True) + if not company_details.company_80g_number: + frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('80G Number'), + get_link_to_form('Company', self.company))) + + if not company_details.pan_details: + frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('PAN Number'), + get_link_to_form('Company', self.company))) + + def set_company_address(self): + address = get_company_address(self.company) + self.company_address = address.company_address + self.company_address_display = address.company_address_display + + def set_title(self): + if self.recipient == "Member": + self.title = self.member_name + else: + self.title = self.donor_name + + def get_payments(self): + if not self.member: + frappe.throw(_('Please select a Member first.')) + + fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True) + + memberships = frappe.db.get_all('Membership', { + 'member': self.member, + 'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], + 'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], + 'membership_status': ('!=', 'Cancelled') + }, ['from_date', 'amount', 'name', 'invoice', 'payment_id']) + + if not memberships: + frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member)) + + total = 0 + self.payments = [] + + for doc in memberships: + self.append('payments', { + 'date': doc.from_date, + 'amount': doc.amount, + 'invoice_id': doc.invoice, + 'razorpay_payment_id': doc.payment_id, + 'membership': doc.name + }) + total += flt(doc.amount) + + self.total = total diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py new file mode 100644 index 0000000000..346ebbf679 --- /dev/null +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from frappe.utils import getdate +from erpnext.accounts.utils import get_fiscal_year +from erpnext.non_profit.doctype.donation.test_donation import create_donor, create_mode_of_payment, create_donor_type +from erpnext.non_profit.doctype.donation.donation import create_donation +from erpnext.non_profit.doctype.membership.test_membership import setup_membership, make_membership +from erpnext.non_profit.doctype.member.member import create_member + +class TestTaxExemption80GCertificate(unittest.TestCase): + def setUp(self): + frappe.db.sql('delete from `tabTax Exemption 80G Certificate`') + frappe.db.sql('delete from `tabMembership`') + create_donor_type() + settings = frappe.get_doc('Non Profit Settings') + settings.company = '_Test Company' + settings.donation_company = '_Test Company' + settings.default_donor_type = '_Test Donor' + settings.creation_user = 'Administrator' + settings.save() + + company = frappe.get_doc('Company', '_Test Company') + company.pan_details = 'BBBTI3374C' + company.company_80g_number = 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087' + company.with_effect_from = getdate() + company.save() + + def test_duplicate_donation_certificate(self): + donor = create_donor() + create_mode_of_payment() + payment = frappe._dict({ + 'amount': 100, + 'method': 'Debit Card', + 'id': 'pay_MeXAmsgeKOhq7O' + }) + donation = create_donation(donor, payment) + + args = frappe._dict({ + 'recipient': 'Donor', + 'donor': donor.name, + 'donation': donation.name + }) + certificate = create_80g_certificate(args) + certificate.insert() + + # check company details + self.assertEquals(certificate.company_pan_number, 'BBBTI3374C') + self.assertEquals(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087') + + # check donation details + self.assertEquals(certificate.amount, donation.amount) + + duplicate_certificate = create_80g_certificate(args) + # duplicate validation + self.assertRaises(frappe.ValidationError, duplicate_certificate.insert) + + def test_membership_80g_certificate(self): + plan = setup_membership() + + # make test member + member_doc = create_member(frappe._dict({ + 'fullname': "_Test_Member", + 'email': "_test_member_erpnext@example.com", + 'plan_id': plan.name + })) + member_doc.make_customer_and_link() + member = member_doc.name + + membership = make_membership(member, { "from_date": getdate() }) + invoice = membership.generate_invoice(save=True) + + args = frappe._dict({ + 'recipient': 'Member', + 'member': member, + 'fiscal_year': get_fiscal_year(getdate(), as_dict=True).get('name') + }) + certificate = create_80g_certificate(args) + certificate.get_payments() + certificate.insert() + + self.assertEquals(len(certificate.payments), 1) + self.assertEquals(certificate.payments[0].amount, membership.amount) + self.assertEquals(certificate.payments[0].invoice_id, invoice.name) + + +def create_80g_certificate(args): + certificate = frappe.get_doc({ + 'doctype': 'Tax Exemption 80G Certificate', + 'recipient': args.recipient, + 'date': getdate(), + 'company': '_Test Company' + }) + + certificate.update(args) + + return certificate \ No newline at end of file diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/__init__.py b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.json b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.json new file mode 100644 index 0000000000..dfa817dd27 --- /dev/null +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.json @@ -0,0 +1,66 @@ +{ + "actions": [], + "creation": "2021-02-15 12:43:52.754124", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "date", + "amount", + "invoice_id", + "column_break_4", + "razorpay_payment_id", + "membership" + ], + "fields": [ + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "reqd": 1 + }, + { + "fieldname": "invoice_id", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Invoice ID", + "options": "Sales Invoice", + "reqd": 1 + }, + { + "fieldname": "razorpay_payment_id", + "fieldtype": "Data", + "label": "Razorpay Payment ID" + }, + { + "fieldname": "membership", + "fieldtype": "Link", + "label": "Membership", + "options": "Membership" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-02-15 16:35:10.777587", + "modified_by": "Administrator", + "module": "Regional", + "name": "Tax Exemption 80G Certificate Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py new file mode 100644 index 0000000000..bdad798d98 --- /dev/null +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 TaxExemption80GCertificateDetail(Document): + pass diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 526198424f..40247f7e3d 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -398,9 +398,9 @@ def make_custom_fields(update=True): si_einvoice_fields = [ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), - + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), - + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, @@ -498,6 +498,14 @@ def make_custom_fields(update=True): fieldtype='Link', options='Salary Component', insert_after='basic_component'), dict(fieldname='arrear_component', label='Arrear Component', fieldtype='Link', options='Salary Component', insert_after='hra_component'), + dict(fieldname='non_profit_section', label='Non Profit Settings', + fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1), + dict(fieldname='company_80g_number', label='80G Number', + fieldtype='Data', insert_after='non_profit_section'), + dict(fieldname='with_effect_from', label='80G With Effect From', + fieldtype='Date', insert_after='company_80g_number'), + dict(fieldname='pan_details', label='PAN Number', + fieldtype='Data', insert_after='with_effect_from') ], 'Employee Tax Exemption Declaration':[ dict(fieldname='hra_section', label='HRA Exemption', @@ -580,7 +588,15 @@ def make_custom_fields(update=True): 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } ], - "Member": [ + 'Member': [ + { + 'fieldname': 'pan_number', + 'label': 'PAN Details', + 'fieldtype': 'Data', + 'insert_after': 'email_id' + } + ], + 'Donor': [ { 'fieldname': 'pan_number', 'label': 'PAN Details', @@ -642,7 +658,7 @@ def set_tax_withholding_category(company): pass docs = get_tds_details(accounts, fiscal_year) - + for d in docs: try: doc = frappe.get_doc(d) @@ -660,7 +676,7 @@ def set_tax_withholding_category(company): fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year] if not fy_exist: doc.append("rates", d.get('rates')[0]) - + doc.flags.ignore_permissions = True doc.flags.ignore_mandatory = True doc.save() diff --git a/erpnext/regional/print_format/80g_certificate_for_donation/80g_certificate_for_donation.json b/erpnext/regional/print_format/80g_certificate_for_donation/80g_certificate_for_donation.json new file mode 100644 index 0000000000..a8da0bd209 --- /dev/null +++ b/erpnext/regional/print_format/80g_certificate_for_donation/80g_certificate_for_donation.json @@ -0,0 +1,26 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2021-02-22 00:17:33.878581", + "css": ".details {\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n line-height: 150%;\n}\n\n.certificate-footer {\n font-size: 15px;\n font-family: Tahoma, sans-serif;\n line-height: 140%;\n margin-top: 120px;\n}\n\n.company-address {\n color: #666666;\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n}", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Tax Exemption 80G Certificate", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "{% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n{%- endif %}\n\n
\n

{{ doc.company }} 80G Donor Certificate

\n
\n

\n\n
\n

{{ _(\"Certificate No. : \") }} {{ doc.name }}

\n

\n \t{{ _(\"Date\") }} : {{ doc.get_formatted(\"date\") }}
\n

\n

\n \n
\n\n This is to confirm that the {{ doc.company }} received an amount of {{doc.get_formatted(\"amount\")}}\n from {{ doc.donor_name }}\n {% if doc.pan_number -%}\n bearing PAN Number {{ doc.member_pan_number }}\n {%- endif %}\n\n via the Mode of Payment {{doc.mode_of_payment}}\n\n {% if doc.razorpay_payment_id -%}\n bearing RazorPay Payment ID {{ doc.razorpay_payment_id }}\n {%- endif %}\n\n on {{ doc.get_formatted(\"date_of_donation\") }}\n

\n \n

\n We thank you for your contribution towards the corpus of the {{ doc.company }} and helping support our work.\n

\n\n
\n
\n\n

\n

{{doc.company_address_display }}

\n\n", + "idx": 0, + "line_breaks": 0, + "modified": "2021-02-22 00:20:08.516600", + "modified_by": "Administrator", + "module": "Regional", + "name": "80G Certificate for Donation", + "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/regional/print_format/80g_certificate_for_donation/__init__.py b/erpnext/regional/print_format/80g_certificate_for_donation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/print_format/80g_certificate_for_membership/80g_certificate_for_membership.json b/erpnext/regional/print_format/80g_certificate_for_membership/80g_certificate_for_membership.json new file mode 100644 index 0000000000..f1b15aab29 --- /dev/null +++ b/erpnext/regional/print_format/80g_certificate_for_membership/80g_certificate_for_membership.json @@ -0,0 +1,26 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2021-02-15 16:53:55.026611", + "css": ".details {\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n line-height: 150%;\n}\n\n.certificate-footer {\n font-size: 15px;\n font-family: Tahoma, sans-serif;\n line-height: 140%;\n margin-top: 120px;\n}\n\n.company-address {\n color: #666666;\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n}", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Tax Exemption 80G Certificate", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "{% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n{%- endif %}\n\n
\n

{{ doc.company }} Members 80G Donor Certificate

\n

Financial Cycle {{ doc.fiscal_year }}

\n
\n

\n\n
\n

{{ _(\"Certificate No. : \") }} {{ doc.name }}

\n

\n \t{{ _(\"Date\") }} : {{ doc.get_formatted(\"date\") }}
\n

\n

\n \n
\n This is to confirm that the {{ doc.company }} received a total amount of {{doc.get_formatted(\"total\")}}\n from {{ doc.member_name }}\n {% if doc.pan_number -%}\n bearing PAN Number {{ doc.member_pan_number }}\n {%- endif %}\n as per the payment details given below:\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 payment in doc.payments -%}\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
{{ _(\"Date\") }}{{ _(\"Amount\") }}{{ _(\"Invoice ID\") }}
{{ payment.date }} {{ payment.get_formatted(\"amount\") }}{{ payment.invoice_id }}
\n \n
\n \n

\n We thank you for your contribution towards the corpus of the {{ doc.company }} and helping support our work.\n

\n\n
\n
\n\n

\n

{{doc.company_address_display }}

\n\n", + "idx": 0, + "line_breaks": 0, + "modified": "2021-02-21 23:29:00.778973", + "modified_by": "Administrator", + "module": "Regional", + "name": "80G Certificate for Membership", + "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/regional/print_format/80g_certificate_for_membership/__init__.py b/erpnext/regional/print_format/80g_certificate_for_membership/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 72ed00293e..5053c6a512 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -195,6 +195,7 @@ def install(country=None): {'doctype': "Party Type", "party_type": "Member", "account_type": "Receivable"}, {'doctype': "Party Type", "party_type": "Shareholder", "account_type": "Payable"}, {'doctype': "Party Type", "party_type": "Student", "account_type": "Receivable"}, + {'doctype': "Party Type", "party_type": "Donor", "account_type": "Receivable"}, {'doctype': "Opportunity Type", "name": "Hub"}, {'doctype': "Opportunity Type", "name": _("Sales")}, From 2b61491adb3c4de3a3068d5425384409b9b89291 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 11 Mar 2021 16:05:58 +0530 Subject: [PATCH 0297/1264] fix: use account_name only in consolidated report (#24840) Don't use account_number and only rely on account_name for preparing consolidated financial statement. Related issue: ISS-20-21-10217 --- .../consolidated_financial_statement.py | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 76f3c50578..0c4a422440 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -240,8 +240,7 @@ def get_company_currency(filters=None): def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters): for entries in gl_entries_by_account.values(): for entry in entries: - key = entry.account_number or entry.account_name - d = accounts_by_name.get(key) + d = accounts_by_name.get(entry.account_name) if d: for company in companies: # check if posting date is within the period @@ -256,7 +255,8 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies): """accumulate children's values in parent accounts""" for d in reversed(accounts): if d.parent_account: - account = d.parent_account.split(' - ')[0].strip() + account = d.parent_account_name + if not accounts_by_name.get(account): continue @@ -267,16 +267,34 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies): accounts_by_name[account]["opening_balance"] = \ accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0) + def get_account_heads(root_type, companies, filters): accounts = get_accounts(root_type, filters) if not accounts: return None, None + accounts = update_parent_account_names(accounts) + accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) return accounts, accounts_by_name +def update_parent_account_names(accounts): + """Update parent_account_name in accounts list. + + parent_name is `name` of parent account which could have other prefix + of account_number and suffix of company abbr. This function adds key called + `parent_account_name` which does not have such prefix/suffix. + """ + name_to_account_map = { d.name : d.account_name for d in accounts } + + for account in accounts: + if account.parent_account: + account["parent_account_name"] = name_to_account_map[account.parent_account] + + return accounts + def get_companies(filters): companies = {} all_companies = get_subsidiary_companies(filters.get('company')) @@ -381,9 +399,9 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g convert_to_presentation_currency(gl_entries, currency_info, filters.get('company')) for entry in gl_entries: - key = entry.account_number or entry.account_name - validate_entries(key, entry, accounts_by_name, accounts) - gl_entries_by_account.setdefault(key, []).append(entry) + account_name = entry.account_name + validate_entries(account_name, entry, accounts_by_name, accounts) + gl_entries_by_account.setdefault(account_name, []).append(entry) return gl_entries_by_account @@ -452,8 +470,7 @@ def filter_accounts(accounts, depth=10): parent_children_map = {} accounts_by_name = {} for d in accounts: - key = d.account_number or d.account_name - accounts_by_name[key] = d + accounts_by_name[d.account_name] = d parent_children_map.setdefault(d.parent_account or None, []).append(d) filtered_accounts = [] From 9ab3bedd0ad63a49e8c19eab13a0caa7c4d6cfa5 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 12 Mar 2021 15:51:23 +0530 Subject: [PATCH 0298/1264] fix: added correct path in hooks (#24865) --- erpnext/hooks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1c20555b82..fe80c6585d 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -357,13 +357,13 @@ scheduler_events = { "erpnext.hr.utils.generate_leave_encashment", "erpnext.hr.utils.allocate_earned_leaves", "erpnext.hr.utils.grant_leaves_automatically", - "erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall.create_process_loan_security_shortfall", - "erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans", + "erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall", + "erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans", "erpnext.crm.doctype.lead.lead.daily_open_lead" ], "monthly_long": [ "erpnext.accounts.deferred_revenue.process_deferred_accounting", - "erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans" + "erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_demand_loans" ] } From 51c500d446ed1598e63c2702471a7ebd3960a825 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 12 Mar 2021 15:51:45 +0530 Subject: [PATCH 0299/1264] fix: Don't throw exception on invoice lines when there is no item_code (fixes #24640) (#24864) Co-authored-by: casesolved-co-uk --- erpnext/public/js/controllers/transaction.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 123d998838..dce8e5d68b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1884,7 +1884,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.throw(__("Please enter Item Code to get batch no")); } else if (doc.doctype == "Purchase Receipt" || (doc.doctype == "Purchase Invoice" && doc.update_stock)) { - return { filters: {'item': item.item_code} } @@ -1910,9 +1909,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ set_query_for_item_tax_template: function(doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); if(!item.item_code) { - frappe.throw(__("Please enter Item Code to get item taxes")); + return doc.company ? {filters: {company: doc.company}} : {}; } else { - let filters = { 'item_code': item.item_code, 'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date], @@ -2123,4 +2121,4 @@ erpnext.apply_putaway_rule = (frm, purpose=null) => { } } }); -}; \ No newline at end of file +}; From d8f7a75eae189f4f59124500d76eb6509bf66537 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 15 Mar 2021 10:31:53 +0530 Subject: [PATCH 0300/1264] feat: add total available stock field --- .../purchase_order_item/purchase_order_item.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 75b2954ddd..eba52f2cd2 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -50,6 +50,7 @@ "base_net_amount", "warehouse_and_reference", "warehouse", + "actual_qty", "material_request", "material_request_item", "sales_order", @@ -735,13 +736,21 @@ "label": "Rate of Stock UOM", "options": "currency", "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": "Available Qty at Warehouse", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:44:41.816974", + "modified": "2021-03-15 10:10:16.342693", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", From 00a0e8da10b0756854ee75a580fe429ca545f8ec Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 11 Feb 2021 18:30:10 +0530 Subject: [PATCH 0301/1264] fix: validation of job card in stock entry --- .../doctype/job_card/job_card.py | 18 +- .../doctype/job_card_item/job_card_item.json | 455 ++++-------------- .../stock/doctype/stock_entry/stock_entry.py | 3 +- .../stock_entry_detail.json | 14 +- 4 files changed, 136 insertions(+), 354 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ec28eb7795..662a06b1ee 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -267,6 +267,17 @@ class JobCard(Document): fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id}) + def set_transferred_qty_in_job_card(self, ste_doc): + for row in ste_doc.items: + if not row.job_card_item: continue + + qty = frappe.db.sql(""" SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se + WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and + se.purpose = 'Material Transfer for Manufacture' + """, (row.job_card_item))[0][0] + + frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty)) + def set_transferred_qty(self, update_status=False): if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 @@ -279,7 +290,8 @@ class JobCard(Document): self.transferred_qty = frappe.db.get_value('Stock Entry', { 'job_card': self.name, 'work_order': self.work_order, - 'docstatus': 1 + 'docstatus': 1, + 'purpose': 'Material Transfer for Manufacture' }, 'sum(fg_completed_qty)') or 0 self.db_set("transferred_qty", self.transferred_qty) @@ -420,6 +432,7 @@ def make_stock_entry(source_name, target_doc=None): target.purpose = "Material Transfer for Manufacture" target.from_bom = 1 target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + target.set_transfer_qty() target.calculate_rate_and_amount() target.set_missing_values() target.set_stock_entry_type() @@ -437,9 +450,10 @@ def make_stock_entry(source_name, target_doc=None): "field_map": { "source_warehouse": "s_warehouse", "required_qty": "qty", - "uom": "stock_uom" + "name": "job_card_item" }, "postprocess": update_item, + "condition": lambda doc: doc.required_qty > 0 } }, target_doc, set_missing_values) diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json index bc9fe108ca..100ef4ca3a 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json @@ -1,363 +1,120 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-07-09 17:20:44.737289", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-07-09 17:20:44.737289", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "source_warehouse", + "uom", + "item_group", + "column_break_3", + "stock_uom", + "item_name", + "description", + "qty_section", + "required_qty", + "column_break_9", + "transferred_qty", + "allow_alternative_item" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "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": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "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": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "source_warehouse", - "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": "Source Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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": "source_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Source Warehouse", + "options": "Warehouse" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "uom", - "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": "UOM", - "length": 0, - "no_copy": 0, - "options": "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": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM" + }, { - "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 - }, + "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, - "fieldname": "item_name", - "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": "Item 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 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text", - "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": "Description", - "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 - }, + "fieldname": "description", + "fieldtype": "Text", + "label": "Description", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty_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": "Qty", - "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": "qty_section", + "fieldtype": "Section Break", + "label": "Qty" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "required_qty", - "fieldtype": "Float", - "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": "Required Qty", - "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 - }, + "fieldname": "required_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty", + "read_only": 1 + }, { - "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 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allow_alternative_item", - "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 Alternative Item", - "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": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" + }, + { + "fetch_from": "item_code.item_group", + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "read_only": 1 + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM" + }, + { + "fieldname": "transferred_qty", + "fieldtype": "Float", + "label": "Transferred Qty", + "no_copy": 1, + "print_hide": 1, + "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-08-28 15:23:48.099459", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Job Card Item", - "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, - "track_views": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-02-11 13:50:13.804108", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Item", + "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/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d77b70ff14..9cdc3cfa55 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -163,7 +163,7 @@ class StockEntry(StockController): if self.purpose not in valid_purposes: frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes))) - if self.job_card and self.purpose != 'Material Transfer for Manufacture': + if self.job_card and self.purpose not in ['Material Transfer for Manufacture', 'Repack']: frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry") .format(self.job_card)) @@ -823,6 +823,7 @@ class StockEntry(StockController): if self.job_card: job_doc = frappe.get_doc('Job Card', self.job_card) job_doc.set_transferred_qty(update_status=True) + job_doc.set_transferred_qty_in_job_card(self) if self.work_order: pro_doc = frappe.get_doc("Work Order", self.work_order) diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 988ae92969..864ff488b2 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -69,7 +69,8 @@ "putaway_rule", "column_break_51", "reference_purchase_receipt", - "quality_inspection" + "quality_inspection", + "job_card_item" ], "fields": [ { @@ -532,13 +533,22 @@ "fieldname": "is_finished_item", "fieldtype": "Check", "label": "Is Finished Item" + }, + { + "fieldname": "job_card_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Job Card Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-30 15:00:44.489442", + "modified": "2021-02-11 13:47:50.158754", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", From 7f2e45e0f4acd9e1abbb1dae6622c85776329b62 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 15 Mar 2021 18:04:47 +0530 Subject: [PATCH 0302/1264] fix: Unequal debit and credit issue on RCM Invoice (#24838) * fix: Unequal debit and credit issue on RCM Invoice * fix: Travis --- erpnext/regional/india/utils.py | 41 +++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index d6200c9fd6..fd1d5e8bf9 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -693,25 +693,12 @@ def update_grand_total_for_rcm(doc, method): if country != 'India': return - if not doc.total_taxes_and_charges: + gst_tax, base_gst_tax = get_gst_tax_amount(doc) + + if not base_gst_tax: 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') \ - + gst_accounts.get('igst_account') - - base_gst_tax = 0 - 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: - base_gst_tax += tax.base_tax_amount_after_discount_amount - gst_tax += tax.tax_amount_after_discount_amount - doc.taxes_and_charges_added -= gst_tax doc.total_taxes_and_charges -= gst_tax doc.base_taxes_and_charges_added -= base_gst_tax @@ -745,7 +732,9 @@ def make_regional_gl_entries(gl_entries, doc): if country != 'India': return gl_entries - if not doc.total_taxes_and_charges: + gst_tax, base_gst_tax = get_gst_tax_amount(doc) + + if not base_gst_tax: return gl_entries if doc.reverse_charge == 'Y': @@ -775,3 +764,21 @@ def make_regional_gl_entries(gl_entries, doc): ) return gl_entries + +def get_gst_tax_amount(doc): + 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', []) + + base_gst_tax = 0 + 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: + base_gst_tax += tax.base_tax_amount_after_discount_amount + gst_tax += tax.tax_amount_after_discount_amount + + return gst_tax, base_gst_tax From dd7d71ca2ec07472f1175584d44a4d629c6f873f Mon Sep 17 00:00:00 2001 From: marination Date: Sun, 14 Mar 2021 18:20:23 +0530 Subject: [PATCH 0303/1264] fix: POS Opening Entry with empty balance detail rows --- .../doctype/pos_opening_entry/pos_opening_entry.py | 13 +++++++------ .../selling/page/point_of_sale/pos_controller.js | 4 ++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py index cb5b3a58fe..0023a84a46 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py @@ -20,15 +20,16 @@ class POSOpeningEntry(StatusUpdater): if not cint(frappe.db.get_value("User", self.user, "enabled")): frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user)) - + def validate_payment_method_account(self): invalid_modes = [] for d in self.balance_details: - account = frappe.db.get_value("Mode of Payment Account", - {"parent": d.mode_of_payment, "company": self.company}, "default_account") - if not account: - invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment)) - + if d.mode_of_payment: + account = frappe.db.get_value("Mode of Payment Account", + {"parent": d.mode_of_payment, "company": self.company}, "default_account") + if not account: + invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment)) + if invalid_modes: if invalid_modes == 1: msg = _("Please set default Cash or Bank account in Mode of Payment {}") diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 45b4e30bf0..89fd9c7d8c 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -106,6 +106,10 @@ erpnext.PointOfSale.Controller = class { }) return frappe.utils.play_sound("error"); } + + // filter balance details for empty rows + balance_details = balance_details.filter(d => d.mode_of_payment); + const method = "erpnext.selling.page.point_of_sale.point_of_sale.create_opening_voucher"; const res = await frappe.call({ method, args: { pos_profile, company, balance_details }, freeze:true }); !res.exc && this.prepare_app_defaults(res.message); From 635c480771b67c16381f583a01aca7a1551cd767 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 16 Mar 2021 13:09:59 +0530 Subject: [PATCH 0304/1264] fix: Add method for regional round off account back --- erpnext/regional/india/utils.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 95ff291516..7f00d1ea0e 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -772,3 +772,42 @@ def make_regional_gl_entries(gl_entries, doc): ) return gl_entries + +def get_gst_tax_amount(doc): + 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', []) + + base_gst_tax = 0 + 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: + base_gst_tax += tax.base_tax_amount_after_discount_amount + gst_tax += tax.tax_amount_after_discount_amount + + return gst_tax, base_gst_tax + +@frappe.whitelist() +def get_regional_round_off_accounts(company, account_list): + country = frappe.get_cached_value('Company', company, 'country') + + if country != 'India': + return + + if isinstance(account_list, string_types): + account_list = json.loads(account_list) + + if not frappe.db.get_single_value('GST Settings', 'round_off_gst_values'): + return + + gst_accounts = get_gst_accounts(company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + account_list.extend(gst_account_list) + + return account_list From 00cce433a5bfce95c19151fdcdf6a7f823706598 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 16 Mar 2021 20:52:53 +0530 Subject: [PATCH 0305/1264] fix(Non Profit): Membership and Donation API fixes (#24900) (#24905) * fix: Donation fixes - differentiate between subscription payment and payment - issue with donation amount * fix: existing membership validation * fix: ignore subscription payments while capturing donations --- erpnext/non_profit/doctype/donation/donation.py | 6 +++++- erpnext/non_profit/doctype/membership/membership.py | 4 ++-- .../tax_exemption_80g_certificate.py | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index e947588482..6a2a06dbc8 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -91,6 +91,10 @@ def capture_razorpay_donations(*args, **kwargs): if not data.event == 'payment.captured': return + # to avoid capturing subscription payments as donations + if payment.description and 'subscription' in str(payment.description).lower(): + return + donor = get_donor(payment.email) if not donor: donor = create_donor(payment) @@ -119,7 +123,7 @@ def create_donation(donor, payment): 'donor_name': donor.donor_name, 'email': donor.email, 'date': getdate(), - 'amount': flt(payment.amount), + 'amount': flt(payment.amount) / 100, # Convert to rupees from paise 'mode_of_payment': payment.method, 'razorpay_payment_id': payment.id }).insert(ignore_mandatory=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 191281f4ce..c41a2f5165 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and not frappe.session.user == "Administrator": + if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -287,7 +287,7 @@ def trigger_razorpay_subscription(*args, **kwargs): membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: - message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id) + message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) return { "status": "Failed", "reason": e} diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index d734a18c3a..ef384d4602 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -29,7 +29,10 @@ class TaxExemption80GCertificate(Document): def validate_duplicates(self): if self.recipient == 'Donor': - certificate = frappe.db.exists(self.doctype, {'donation': self.donation}) + certificate = frappe.db.exists(self.doctype, { + 'donation': self.donation, + 'name': ('!=', self.name) + }) if certificate: frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format( get_link_to_form(self.doctype, certificate), frappe.bold(self.donation) From 6117ac5affff70b9042f19ed04774ccd153a61f2 Mon Sep 17 00:00:00 2001 From: Andy Zhu Date: Wed, 17 Mar 2021 13:53:02 +1300 Subject: [PATCH 0306/1264] fix: copy_parent_value_in_all_row function error --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index e5b50d86ed..e7c7d11b0d 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -293,7 +293,7 @@ $.extend(erpnext.utils, { }, copy_parent_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) { var d = locals[dt][dn]; - if(d[fieldname]){ + if(d[parent_fieldname]){ var cl = doc[table_fieldname] || []; for(var i = 0; i < cl.length; i++) { cl[i][fieldname] = doc[parent_fieldname]; From a5987782bdd694132f8b1e6a6e014a8afabfa0dc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 15:47:03 +0530 Subject: [PATCH 0307/1264] fix(India): Incorrect Nil Exempt and Non GST amount in GSTR3B report --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 68c8a0d4d3..0d08a35787 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -353,7 +353,7 @@ class GSTR3BReport(Document): inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i where p.docstatus = 1 and p.name = i.parent - and i.is_nil_exempt = 1 or i.is_non_gst = 1 and + and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s group by p.place_of_supply """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) From f9519c7e13cc565358b558f0b0998396bf02aa8b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 18:00:08 +0530 Subject: [PATCH 0308/1264] fix: Group nil exempted and non gst items separately --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 0d08a35787..a8e843b61d 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -349,14 +349,15 @@ class GSTR3BReport(Document): return inter_state_supply_details def get_inward_nil_exempt(self, state): - + print("@@@@@@@@@@@@") inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i where p.docstatus = 1 and p.name = i.parent and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s - group by p.place_of_supply """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + print(inward_nil_exempt, "$#$#$#$") inward_nil_exempt_details = { "gst": { "intra": 0.0, From 438d9cad900f0fe26244bafaa9a5e736b8458f1b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 18:03:17 +0530 Subject: [PATCH 0309/1264] fix: Remove print statement --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 2 -- 1 file changed, 2 deletions(-) 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 a8e843b61d..a49996d107 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -349,7 +349,6 @@ class GSTR3BReport(Document): return inter_state_supply_details def get_inward_nil_exempt(self, state): - print("@@@@@@@@@@@@") inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i where p.docstatus = 1 and p.name = i.parent @@ -357,7 +356,6 @@ class GSTR3BReport(Document): month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - print(inward_nil_exempt, "$#$#$#$") inward_nil_exempt_details = { "gst": { "intra": 0.0, From bacfaa4396b16b0a32e640f9e34b52de1feaf91c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 17 Mar 2021 19:51:59 +0530 Subject: [PATCH 0310/1264] fix: calculate 80g certificate amount on validate for memberships (#24925) (#24926) --- .../tax_exemption_80g_certificate.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index ef384d4602..5bbd5750f9 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -16,6 +16,7 @@ class TaxExemption80GCertificate(Document): self.validate_duplicates() self.validate_company_details() self.set_company_address() + self.calculate_total() self.set_title() def validate_date(self): @@ -54,8 +55,17 @@ class TaxExemption80GCertificate(Document): self.company_address = address.company_address self.company_address_display = address.company_address_display + def calculate_total(self): + if self.recipient == 'Donor': + return + + total = 0 + for entry in self.payments: + total += flt(entry.amount) + self.total = total + def set_title(self): - if self.recipient == "Member": + if self.recipient == 'Member': self.title = self.member_name else: self.title = self.donor_name From 95e9b2fd6e4131b149ab6fb5132642fc7b7214c2 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 19:58:56 +0530 Subject: [PATCH 0311/1264] refactor(payroll): simplified logic for additional salary (#24907) --- .../additional_salary/additional_salary.py | 53 +++---- .../doctype/salary_slip/salary_slip.py | 132 ++++++++++-------- .../doctype/salary_slip/test_salary_slip.py | 2 +- 3 files changed, 92 insertions(+), 95 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index f5af677fce..029e11ff9b 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -89,10 +89,11 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days -@frappe.whitelist() -def get_additional_salary_component(employee, start_date, end_date, component_type): - additional_salaries = frappe.db.sql(""" - select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date +def get_additional_salaries(employee, start_date, end_date, component_type): + additional_salary_list = frappe.db.sql(""" + select name, salary_component as component, type, amount, + overwrite_salary_structure_amount as overwrite, + deduct_full_tax_on_selected_payroll_date from `tabAdditional Salary` where employee=%(employee)s and docstatus = 1 @@ -102,7 +103,7 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty from_date <= %(to_date)s and to_date >= %(to_date)s ) and type = %(component_type)s - order by salary_component, overwrite_salary_structure_amount DESC + order by salary_component, overwrite ASC """, { 'employee': employee, 'from_date': start_date, @@ -110,38 +111,18 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty 'component_type': "Earning" if component_type == "earnings" else "Deduction" }, as_dict=1) - existing_salary_components= [] - salary_components_details = {} - additional_salary_details = [] + additional_salaries = [] + components_to_overwrite = [] - overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1] + for d in additional_salary_list: + if d.overwrite: + if d.component in components_to_overwrite: + frappe.throw(_("Multiple Additional Salaries with overwrite " + "property exist for Salary Component {0} between {1} and {2}.").format( + frappe.bold(d.component), start_date, end_date), title=_("Error")) - component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type'] - for d in additional_salaries: + components_to_overwrite.append(d.component) - if d.salary_component not in existing_salary_components: - component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields) - struct_row = frappe._dict({'salary_component': d.salary_component}) - if component: - struct_row.update(component[0]) + additional_salaries.append(d) - struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date - struct_row['is_additional_component'] = 1 - - salary_components_details[d.salary_component] = struct_row - - - if overwrites_components.count(d.salary_component) > 1: - frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error")) - else: - additional_salary_details.append({ - 'name': d.name, - 'component': d.salary_component, - 'amount': d.amount, - 'type': d.type, - 'overwrite': d.overwrite_salary_structure_amount, - }) - - existing_salary_components.append(d.salary_component) - - return salary_components_details, additional_salary_details + return additional_salaries diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 5c5eccd7e5..55e1c63a3f 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -13,7 +13,7 @@ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_da from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase from frappe.utils.background_jobs import enqueue -from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salary_component +from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits @@ -540,15 +540,16 @@ class SalarySlip(TransactionBase): self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") def add_additional_salary_components(self, component_type): - salary_components_details, additional_salary_details = get_additional_salary_component(self.employee, + additional_salaries = get_additional_salaries(self.employee, self.start_date, self.end_date, component_type) - if salary_components_details and additional_salary_details: - for additional_salary in additional_salary_details: - additional_salary =frappe._dict(additional_salary) - amount = additional_salary.amount - overwrite = additional_salary.overwrite - self.update_component_row(frappe._dict(salary_components_details[additional_salary.component]), amount, - component_type, overwrite=overwrite, additional_salary=additional_salary.name) + + for additional_salary in additional_salaries: + self.update_component_row( + get_salary_component_data(additional_salary.component), + additional_salary.amount, + component_type, + additional_salary + ) def add_tax_components(self, payroll_period): # Calculate variable_based_on_taxable_salary after all components updated in salary slip @@ -565,46 +566,59 @@ class SalarySlip(TransactionBase): for d in tax_components: tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period) - tax_row = self.get_salary_slip_row(d) + tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, struct_row, amount, key, overwrite=1, additional_salary = ''): + def update_component_row(self, component_data, amount, component_type, additional_salary=None): component_row = None - for d in self.get(key): - if d.salary_component == struct_row.salary_component: + for d in self.get(component_type): + if d.salary_component != component_data.salary_component: + continue + + if ( + not d.additional_salary + and (not additional_salary or additional_salary.overwrite) + or additional_salary + and additional_salary.name == d.additional_salary + ): component_row = d - if not component_row or (struct_row.get("is_additional_component") and not overwrite): - if amount: - self.append(key, { - 'amount': amount, - 'default_amount': amount if not struct_row.get("is_additional_component") else 0, - 'depends_on_payment_days' : struct_row.depends_on_payment_days, - 'salary_component' : struct_row.salary_component, - 'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"), - 'additional_salary': additional_salary, - 'do_not_include_in_total' : struct_row.do_not_include_in_total, - 'is_tax_applicable': struct_row.is_tax_applicable, - 'is_flexible_benefit': struct_row.is_flexible_benefit, - 'variable_based_on_taxable_salary': struct_row.variable_based_on_taxable_salary, - 'deduct_full_tax_on_selected_payroll_date': struct_row.deduct_full_tax_on_selected_payroll_date, - 'additional_amount': amount if struct_row.get("is_additional_component") else 0, - 'exempted_from_income_tax': struct_row.exempted_from_income_tax - }) + break + + if additional_salary and additional_salary.overwrite: + # Additional Salary with overwrite checked, remove default rows of same component + self.set(component_type, [ + d for d in self.get(component_type) + if d.salary_component != component_data.salary_component + or d.additional_salary and additional_salary.name != d.additional_salary + or d == component_row + ]) + + if not component_row: + if not amount: + return + + component_row = self.append(component_type) + for attr in ( + 'depends_on_payment_days', 'salary_component', 'abbr' + 'do_not_include_in_total', 'is_tax_applicable', + 'is_flexible_benefit', 'variable_based_on_taxable_salary', + 'exempted_from_income_tax' + ): + component_row.set(attr, component_data.get(attr)) + + if additional_salary: + component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name + component_row.deduct_full_tax_on_selected_payroll_date = \ + additional_salary.deduct_full_tax_on_selected_payroll_date else: - if struct_row.get("is_additional_component"): - if overwrite: - component_row.additional_amount = amount - component_row.get("default_amount", 0) - component_row.additional_salary = additional_salary - else: - component_row.additional_amount = amount + component_row.default_amount = amount + component_row.additional_amount = 0 + component_row.deduct_full_tax_on_selected_payroll_date = \ + component_data.deduct_full_tax_on_selected_payroll_date - if not overwrite and component_row.default_amount: - amount += component_row.default_amount - else: - component_row.default_amount = amount - - component_row.amount = amount - component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date + component_row.amount = amount def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period): if not payroll_period: @@ -937,19 +951,6 @@ class SalarySlip(TransactionBase): frappe.throw(_("Error in formula or condition: {0}").format(e)) raise - def get_salary_slip_row(self, salary_component): - component = frappe.get_doc("Salary Component", salary_component) - # Data for update_component_row - struct_row = frappe._dict() - struct_row['depends_on_payment_days'] = component.depends_on_payment_days - struct_row['salary_component'] = component.name - struct_row['abbr'] = component.salary_component_abbr - struct_row['do_not_include_in_total'] = component.do_not_include_in_total - struct_row['is_tax_applicable'] = component.is_tax_applicable - struct_row['is_flexible_benefit'] = component.is_flexible_benefit - struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary - return struct_row - def get_component_totals(self, component_type, depends_on_payment_days=0): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -1012,7 +1013,6 @@ class SalarySlip(TransactionBase): self.total_loan_repayment += payment.total_payment def get_loan_details(self): - return frappe.get_all("Loan", fields=["name", "interest_income_account", "loan_account", "loan_type"], filters = { @@ -1241,4 +1241,20 @@ def unlink_ref_doc_from_salary_slip(ref_no): def generate_password_for_pdf(policy_template, employee): employee = frappe.get_doc("Employee", employee) - return policy_template.format(**employee.as_dict()) \ No newline at end of file + return policy_template.format(**employee.as_dict()) + +def get_salary_component_data(component): + return frappe.get_value( + "Salary Component", + component, + [ + "name as salary_component", + "depends_on_payment_days", + "salary_component_abbr as abbr", + "do_not_include_in_total", + "is_tax_applicable", + "is_flexible_benefit", + "variable_based_on_taxable_salary", + ], + as_dict=1, + ) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index f58a8e58c2..1402f3a839 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -245,7 +245,7 @@ class TestSalarySlip(unittest.TestCase): make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR', payroll_period=payroll_period) - frappe.db.sql("""delete from `tabLoan""") + frappe.db.sql("delete from tabLoan") loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1)) loan.repay_from_salary = 1 loan.submit() From a2068d9b980f25e5e08c7b6d405a6c94c4b32367 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 18 Mar 2021 15:25:22 +0530 Subject: [PATCH 0312/1264] feat: profitability report --- .../projects/report/profitability/__init__.py | 0 .../report/profitability/profitability.js | 32 +++++ .../report/profitability/profitability.json | 39 ++++++ .../report/profitability/profitability.py | 119 ++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 erpnext/projects/report/profitability/__init__.py create mode 100644 erpnext/projects/report/profitability/profitability.js create mode 100644 erpnext/projects/report/profitability/profitability.json create mode 100644 erpnext/projects/report/profitability/profitability.py diff --git a/erpnext/projects/report/profitability/__init__.py b/erpnext/projects/report/profitability/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/report/profitability/profitability.js b/erpnext/projects/report/profitability/profitability.js new file mode 100644 index 0000000000..dbf918760f --- /dev/null +++ b/erpnext/projects/report/profitability/profitability.js @@ -0,0 +1,32 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Profitability"] = { + "filters": [ + { + "fieldname": "start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "reqd": 1 + }, + { + "fieldname": "end_date", + "label": __("End Date"), + "fieldtype": "Date", + "reqd": 1 + }, + { + "fieldname": "customer_name", + "label": __("Customer"), + "fieldtype": "Link", + "options": "Customer" + }, + { + "fieldname": "employee", + "label": __("Employee"), + "fieldtype": "Link", + "options": "Employee" + } + ] +}; diff --git a/erpnext/projects/report/profitability/profitability.json b/erpnext/projects/report/profitability/profitability.json new file mode 100644 index 0000000000..4f91accf58 --- /dev/null +++ b/erpnext/projects/report/profitability/profitability.json @@ -0,0 +1,39 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-18 10:19:40.124932", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2021-03-18 10:20:15.559305", + "modified_by": "Administrator", + "module": "Projects", + "name": "Profitability", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Timesheet", + "report_name": "Profitability", + "report_type": "Script Report", + "roles": [ + { + "role": "Projects User" + }, + { + "role": "HR User" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Employee" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py new file mode 100644 index 0000000000..48adf97264 --- /dev/null +++ b/erpnext/projects/report/profitability/profitability.py @@ -0,0 +1,119 @@ +# 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): + columns, data = [], [] + data = get_data(filters) + columns = get_columns() + return columns, data + +def get_columns(): + return [ + { + "fieldname": "customer_name", + "label": _("Customer"), + "fieldtype": "Link", + "options": "Customer", + "width": 150 + }, + { + "fieldname": "title", + "label": _("Name"), + "fieldtype": "Data", + "width": 120 + }, + { + "fieldname": "employee", + "label": _("Employee"), + "fieldtype": "Link", + "options": "employee", + "width": 150 + }, + { + "fieldname": "grand_total", + "label": _("Bill Amount"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "gross_pay", + "label": _("Cost"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "profit", + "label": _("Profit"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "end_date", + "label": _("End Date"), + "fieldtype": "Date", + "width": 120 + }, + { + "fieldname": "total_billed_hours", + "label": _("Total Billed Hours"), + "fieldtype": "Int", + "width": 120 + }, + { + "fieldname": "utilization", + "label": _("Utilization"), + "fieldtype": "Percentage", + "width": 120 + }, + { + "fieldname": "fractional_cost", + "label": _("Fractional Cost"), + "fieldtype": "Int", + "width": 100 + } + ] + +def get_data(filters): + conditions = get_conditions(filters) + sql = """ + select + *, + t.gross_pay * t.utilization as fractional_cost, + t.grand_total - t.gross_pay * t.utilization as profit + from + (select + si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, + ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours, + tabTimesheet.total_billed_hours/(ss.total_working_days * 8) as utilization + from + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet. name + join `tabSales Invoice` as si on si. name = sit.parent and si.status != "Cancelled" + join `tabSalary Slip` as ss on ss.name = sst.parent """ + if conditions: + sql += """ + where + %s) as t"""%(conditions) + data = frappe.db.sql(sql,filters, as_dict=True) + + return data + +def get_conditions(filters): + conditions = [] + if filters.get("customer_name"): + conditions.append("si.customer_name='%s'"%filters.get("customer_name")) + if filters.get("start_date"): + conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) + if filters.get("end_date"): + conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) + if filters.get("employee"): + conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) + conditions = " and ".join(conditions) + return conditions \ No newline at end of file From b75cbeee4d1f6cc3fc739b146ec4820d6069f568 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 10:56:52 +0530 Subject: [PATCH 0313/1264] fix: Allow user to update exchange rate in Multi-currency LCV --- erpnext/controllers/taxes_and_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 6c7eb92221..23541c1ba0 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -779,7 +779,7 @@ class init_landed_taxes_and_totals(object): for d in self.doc.get(self.tax_field): if d.account_currency == company_currency: d.exchange_rate = 1 - elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date: + elif not d.exchange_rate: d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account, account_currency=d.account_currency, company=self.doc.company) From 67d94ac0cc070edac49d41357ff2c6d85ae61d15 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 13 Mar 2021 12:48:14 +0530 Subject: [PATCH 0314/1264] fix: revert stock balance value calculation --- erpnext/stock/report/stock_balance/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index e5d4d626c4..6dfede4590 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -198,7 +198,7 @@ def get_item_warehouse_map(filters, sle): else: qty_diff = flt(d.actual_qty) - value_diff = flt(d.stock_value) - flt(qty_dict.bal_val) + value_diff = flt(d.stock_value_difference) if d.posting_date < from_date: qty_dict.opening_qty += qty_diff From dffa647071564fb3815f34cb9aeec3fc3ed62713 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 20 Mar 2021 22:24:55 +0530 Subject: [PATCH 0315/1264] fix: membership renewal validation (#24963) (#24964) --- erpnext/non_profit/doctype/membership/membership.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index c41a2f5165..52447e4386 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": + if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -90,6 +90,7 @@ class Membership(Document): self.validate_membership_type_and_settings(plan, settings) invoice = make_invoice(self, member, plan, settings) + self.reload() self.invoice = invoice.name if with_payment_entry: @@ -284,6 +285,7 @@ def trigger_razorpay_subscription(*args, **kwargs): settings = frappe.get_doc("Non Profit Settings") if settings.allow_invoicing and settings.automate_membership_invoicing: + membership.reload() membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: From c7c921495bc17fe39a3a0ae5b49d29b03c374d25 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 22 Mar 2021 11:21:15 +0530 Subject: [PATCH 0316/1264] fix: payment reference on adding cost center in PE and Issue Summary Report error fixes (#24951) --- .../doctype/payment_entry/payment_entry.js | 15 ++++++++++----- .../support/report/issue_summary/issue_summary.py | 6 ++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 6412772073..b5f6a401df 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -605,12 +605,22 @@ frappe.ui.form.on('Payment Entry', { {fieldtype:"Column Break"}, {fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, {fieldtype:"Section Break"}, + {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center", + "get_query": function() { + return { + "filters": {"company": frm.doc.company} + } + } + }, + {fieldtype:"Column Break"}, + {fieldtype:"Section Break"}, {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, ]; frappe.prompt(fields, function(filters){ frappe.flags.allocate_payment_amount = true; frm.events.validate_filters_data(frm, filters); + frm.doc.cost_center = filters.cost_center; frm.events.get_outstanding_documents(frm, filters); }, __("Filters"), __("Get Outstanding Documents")); }, @@ -1066,11 +1076,6 @@ frappe.ui.form.on('Payment Entry', { frm.set_value("paid_from_account_balance", r.message.paid_from_account_balance); frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance); frm.set_value("party_balance", r.message.party_balance); - }, - () => { - if(frm.doc.payment_type != "Internal") { - frm.clear_table("references"); - } } ]); diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py index 3d735314f4..7861e30d25 100644 --- a/erpnext/support/report/issue_summary/issue_summary.py +++ b/erpnext/support/report/issue_summary/issue_summary.py @@ -260,8 +260,7 @@ class IssueSummary(object): self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0 def get_chart_data(self): - if not self.data: - return None + self.chart = [] labels = [] open_issues = [] @@ -310,8 +309,7 @@ class IssueSummary(object): } def get_report_summary(self): - if not self.data: - return None + self.report_summary = [] open_issues = 0 replied = 0 From d1f15b2a88ba312c7b985c13cd9491b32544acc8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 23 Mar 2021 10:45:06 +0530 Subject: [PATCH 0317/1264] fix: TDS check getting checked after reload (#24973) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 06aa20bfc5..66a8e206a8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -524,7 +524,7 @@ frappe.ui.form.on("Purchase Invoice", { }, onload: function(frm) { - if(frm.doc.__onload) { + if(frm.doc.__onload && frm.is_new()) { if(frm.doc.supplier) { frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; } From 4ccda9f799f2128d17b187996c019e2adc39921a Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 23 Mar 2021 10:45:57 +0530 Subject: [PATCH 0318/1264] chore: Allow changing Work Stations in WO. (#24898) --- erpnext/manufacturing/doctype/job_card/job_card.py | 3 +++ erpnext/manufacturing/doctype/work_order/work_order.json | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 662a06b1ee..7aaf2a08ec 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -255,6 +255,9 @@ class JobCard(Document): data.actual_operation_time = time_in_mins data.actual_start_time = time_data[0].start_time if time_data else None data.actual_end_time = time_data[0].end_time if time_data else None + if data.get("workstation") != self.workstation: + # workstations can change in a job card + data.workstation = self.workstation wo.flags.ignore_validate_update_after_submit = True wo.update_operation_status() diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 585a09db2b..cd9edeeea8 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -333,8 +333,7 @@ "fieldname": "operations", "fieldtype": "Table", "label": "Operations", - "options": "Work Order Operation", - "read_only": 1 + "options": "Work Order Operation" }, { "depends_on": "operations", @@ -496,7 +495,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2020-05-05 19:32:43.323054", + "modified": "2021-03-16 13:27:51.116484", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", From 453e07e32a856039d8306ad6a432757e814c8574 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 23 Mar 2021 11:18:57 +0530 Subject: [PATCH 0319/1264] fix: exclude cancelled salary slips --- erpnext/projects/report/profitability/profitability.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py index 48adf97264..0edecd8e1b 100644 --- a/erpnext/projects/report/profitability/profitability.py +++ b/erpnext/projects/report/profitability/profitability.py @@ -96,7 +96,7 @@ def get_data(filters): `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet. name join `tabSales Invoice` as si on si. name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent """ + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """ if conditions: sql += """ where From 6a534ea82b857adaac74a1f6038aac0a76f4e401 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 15 Mar 2021 19:10:54 +0530 Subject: [PATCH 0320/1264] fix: Allow zero valuation in stock reconciliation Stock reconciliation can not be done for customer provided item as they have zero valuation. This change adds a checkbox in item table to allow such items. Related issue: ISS-20-21-10248 --- .../stock_reconciliation/stock_reconciliation.py | 5 +++-- .../stock_reconciliation_item.json | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index f0a90f9754..4d9a01de4c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -31,6 +31,7 @@ class StockReconciliation(StockController): self.validate_expense_account() self.set_total_qty_and_amount() self.validate_putaway_capacity() + self.validate_customer_provided_item() if self._action=="submit": self.make_batches('warehouse') @@ -217,7 +218,7 @@ class StockReconciliation(StockController): if row.valuation_rate in ("", None): row.valuation_rate = previous_sle.get("valuation_rate", 0) - if row.qty and not row.valuation_rate: + if row.qty and not row.valuation_rate and not row.allow_zero_valuation_rate: frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx)) if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction") @@ -531,4 +532,4 @@ def get_difference_account(purpose, company): account = frappe.db.get_value('Account', {'is_group': 0, 'company': company, 'account_type': 'Temporary'}, 'name') - return account \ No newline at end of file + return account diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index e53db0772b..85c7ebe263 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -13,6 +13,7 @@ "qty", "valuation_rate", "amount", + "allow_zero_valuation_rate", "serial_no_and_batch_section", "serial_no", "column_break_11", @@ -166,10 +167,19 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" + }, + { + "default": "0", + "fieldname": "allow_zero_valuation_rate", + "fieldtype": "Check", + "label": "Allow Zero Valuation Rate", + "print_hide": 1, + "read_only": 1 } ], "istable": 1, - "modified": "2019-06-14 17:10:53.188305", + "links": [], + "modified": "2021-03-23 11:09:44.407157", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", @@ -179,4 +189,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 2a391298a7ac567544889ab962fde69db75d7096 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Mar 2021 12:41:19 +0530 Subject: [PATCH 0321/1264] test: customer item in stock reconciliation --- .../stock_reconciliation/test_stock_reconciliation.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 088456f865..6690c6a606 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -193,6 +193,16 @@ class TestStockReconciliation(unittest.TestCase): stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel() + def test_customer_provided_items(self): + item_code = 'Stock-Reco-customer-Item-100' + create_item(item_code, is_customer_provided_item = 1, + customer = '_Test Customer', is_purchase_item = 0) + + sr = create_stock_reconciliation(item_code = item_code, qty = 10, rate = 420) + + self.assertEqual(sr.get("items")[0].allow_zero_valuation_rate, 1) + self.assertEqual(sr.get("items")[0].valuation_rate, 0) + self.assertEqual(sr.get("items")[0].amount, 0) def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 2ba198576c68c503cd7fb3da26341df8744f5275 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Mar 2021 12:16:58 +0530 Subject: [PATCH 0322/1264] fix: set valuation rate for customer items to zero - In stock reconciliation always set valuation rate of customer provided items to zero during validation. - Let user know the valuation has been changed. --- .../stock_reconciliation.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 4d9a01de4c..b452e96c5e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -29,9 +29,10 @@ class StockReconciliation(StockController): self.remove_items_with_no_change() self.validate_data() self.validate_expense_account() + self.validate_customer_provided_item() + self.set_zero_value_for_customer_provided_items() self.set_total_qty_and_amount() self.validate_putaway_capacity() - self.validate_customer_provided_item() if self._action=="submit": self.make_batches('warehouse') @@ -437,6 +438,20 @@ class StockReconciliation(StockController): if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss": frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError) + def set_zero_value_for_customer_provided_items(self): + changed_any_values = False + + for d in self.get('items'): + is_customer_item = frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item') + if is_customer_item and d.valuation_rate: + d.valuation_rate = 0.0 + changed_any_values = True + + if changed_any_values: + msgprint(_("Valuation rate for customer provided items has been set to zero."), + title=_("Note"), indicator="blue") + + def set_total_qty_and_amount(self): for d in self.get("items"): d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate")) From 940c75f8ac6ea265e25f9a9a63baa22d7a07b7d3 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 23 Mar 2021 16:54:54 +0530 Subject: [PATCH 0323/1264] fix: validate_series --- erpnext/setup/doctype/naming_series/naming_series.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index abff97364c..2ea0bc08ca 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -10,6 +10,7 @@ from frappe import msgprint, throw, _ from frappe.model.document import Document from frappe.model.naming import parse_naming_series from frappe.permissions import get_doctypes_with_read +from frappe.core.doctype.doctype.doctype import validate_series class NamingSeriesNotSetError(frappe.ValidationError): pass @@ -126,7 +127,7 @@ class NamingSeries(Document): dt = frappe.get_doc("DocType", self.select_doc_for_series) options = self.scrub_options_list(self.set_options.split("\n")) for series in options: - dt.validate_series(series) + validate_series(dt, series) for i in sr: if i[0]: existing_series = [d.split('.')[0] for d in i[0].split("\n")] From d7b139182bb2c5ec998b89ee44b93a72d5103c51 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 23 Mar 2021 21:09:54 +0530 Subject: [PATCH 0324/1264] fix: serial no trim issue (#24981) * fix: serial no trim issue * fix: sider --- erpnext/public/js/controllers/transaction.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index dce8e5d68b..7d90d2662e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -703,21 +703,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } else { var valid_serial_nos = []; - + var serialnos = []; // Replacing all occurences of comma with carriage return - var serial_nos = item.serial_no.trim().replace(/,/g, '\n'); - - serial_nos = serial_nos.trim().split('\n'); - - // Trim each string and push unique string to new list - for (var x=0; x<=serial_nos.length - 1; x++) { - if (serial_nos[x].trim() != "" && valid_serial_nos.indexOf(serial_nos[x].trim()) == -1) { - valid_serial_nos.push(serial_nos[x].trim()); + item.serial_no = item.serial_no.replace(/,/g, '\n'); + serialnos = item.serial_no.split("\n"); + for (var i = 0; i < serialnos.length; i++) { + if (serialnos[i] != "") { + valid_serial_nos.push(serialnos[i]); } } - - // Add the new list to the serial no. field in grid with each in new line - item.serial_no = valid_serial_nos.join('\n'); item.conversion_factor = item.conversion_factor || 1; refresh_field("serial_no", item.name, item.parentfield); From d26ed25c3eb2471c2934f77487757f5030a80521 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 23 Mar 2021 21:11:06 +0530 Subject: [PATCH 0325/1264] fix: Period list for exponential smoothing forecasting report (#24983) --- erpnext/accounts/report/financial_statements.py | 6 +++++- .../exponential_smoothing_forecasting.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 7dfce85629..14efa1f8fc 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -51,7 +51,11 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_ "from_date": start_date }) - to_date = add_months(start_date, months_to_add) + if i==0 and filter_based_on == 'Date Range': + to_date = add_months(get_first_day(start_date), months_to_add) + else: + to_date = add_months(start_date, months_to_add) + start_date = to_date # Subtract one day from to_date, as it may be first day in next fiscal year or month diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 2ca9f1694b..fc27d35598 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -61,7 +61,7 @@ class ForecastingReport(ExponentialSmoothingForecast): from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1) self.period_list = get_period_list(from_date, self.filters.to_date, - from_date, self.filters.to_date, None, self.filters.periodicity, ignore_fiscal_year=True) + from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True) order_data = self.get_data_for_forecast() or [] From 5c172044d7495f4a6ae0a2700e51a4b665e8681b Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 23 Mar 2021 21:13:06 +0530 Subject: [PATCH 0326/1264] fix: validate_series (#24987) --- erpnext/setup/doctype/naming_series/naming_series.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index abff97364c..2ea0bc08ca 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -10,6 +10,7 @@ from frappe import msgprint, throw, _ from frappe.model.document import Document from frappe.model.naming import parse_naming_series from frappe.permissions import get_doctypes_with_read +from frappe.core.doctype.doctype.doctype import validate_series class NamingSeriesNotSetError(frappe.ValidationError): pass @@ -126,7 +127,7 @@ class NamingSeries(Document): dt = frappe.get_doc("DocType", self.select_doc_for_series) options = self.scrub_options_list(self.set_options.split("\n")) for series in options: - dt.validate_series(series) + validate_series(dt, series) for i in sr: if i[0]: existing_series = [d.split('.')[0] for d in i[0].split("\n")] From 9165327cf6ed5997240d39457c3b8a7e705bbb4f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 23 Mar 2021 11:37:27 +0530 Subject: [PATCH 0327/1264] fix: repost not completed backdated transactions --- erpnext/hooks.py | 1 + .../repost_item_valuation.py | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index fe80c6585d..0401be47b2 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -320,6 +320,7 @@ scheduler_events = { "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", + "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries" ], "daily": [ "erpnext.stock.reorder_item.reorder_item", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 8436acbed2..559f9a5ed9 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form +from frappe.utils import cint, get_link_to_form, add_to_date, today from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -29,7 +29,7 @@ class RepostItemValuation(Document): self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") elif self.warehouse: self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") - + def set_status(self, status=None): if not status: status = 'Queued' @@ -54,7 +54,6 @@ def repost(doc): repost_sl_entries(doc) repost_gl_entries(doc) - check_if_stock_and_account_balance_synced(doc.posting_date, doc.company) doc.set_status('Completed') except Exception: @@ -103,7 +102,7 @@ def notify_error_to_stock_managers(doc, traceback): recipients = get_users_with_role("Stock Manager") if not recipients: get_users_with_role("System Manager") - + subject = _("Error while reposting item valuation") message = (_("Hi,") + "
" + _("An error has been appeared while reposting item valuation via {0}") @@ -112,4 +111,24 @@ def notify_error_to_stock_managers(doc, traceback): ) frappe.sendmail(recipients=recipients, subject=subject, message=message) +def repost_entries(): + riv_entries = get_repost_item_valuation_entries() + for row in riv_entries: + doc = frappe.get_cached_doc('Repost Item Valuation', row.name) + repost(doc) + + riv_entries = get_repost_item_valuation_entries() + if riv_entries: + return + + for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): + check_if_stock_and_account_balance_synced(today(), d.company) + +def get_repost_item_valuation_entries(): + date = add_to_date(today(), hours=-12) + + return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` + WHERE status != 'Completed' and creation <= %s and docstatus = 1 + ORDER BY timestamp(posting_date, posting_time) asc, creation asc + """, date, as_dict=1) \ No newline at end of file From de774ae080d8ddbc0e39f2ac0390c66a899921d9 Mon Sep 17 00:00:00 2001 From: Ishan Loya Date: Wed, 24 Mar 2021 12:30:51 +0530 Subject: [PATCH 0328/1264] Correct state code for 'Other Territory' Corrects state code for 'Other Territory' to match e-invoicing state codes list. Attempts to fix #24992 --- erpnext/regional/india/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py index 378b735e07..faeb36fc69 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -69,7 +69,7 @@ state_numbers = { "Mizoram": "15", "Nagaland": "13", "Odisha": "21", - "Other Territory": "98", + "Other Territory": "97", "Pondicherry": "34", "Punjab": "03", "Rajasthan": "08", From 244f3eeedcbc5633d632883578875e59231f864c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Mar 2021 16:42:22 +0530 Subject: [PATCH 0329/1264] fix: incorrect fieldname --- .../doctype/repost_item_valuation/repost_item_valuation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 559f9a5ed9..a75db1ac86 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -123,7 +123,7 @@ def repost_entries(): return for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): - check_if_stock_and_account_balance_synced(today(), d.company) + check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): date = add_to_date(today(), hours=-12) From 91026e026fe3c549f383d9dca08d5e4b9724e159 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 25 Mar 2021 11:53:07 +0530 Subject: [PATCH 0330/1264] chore: Added change log --- erpnext/change_log/v13/v13_0_0-beta_14.md | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_0-beta_14.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_14.md b/erpnext/change_log/v13/v13_0_0-beta_14.md new file mode 100644 index 0000000000..8ef3c92e31 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0-beta_14.md @@ -0,0 +1,24 @@ +### Fixes and Enhancements + +- Repost incompleted backdated transactions ([#24991](https://github.com/frappe/erpnext/pull/24991)) +- Revert stock balance value calculation ([#24957](https://github.com/frappe/erpnext/pull/24957)) +- Allow user to update exchange rate in Multi-currency LCV ([#24947](https://github.com/frappe/erpnext/pull/24947)) +- Added correct path in hooks ([#24865](https://github.com/frappe/erpnext/pull/24865)) +- Unequal debit and credit issue on RCM Invoice ([#24838](https://github.com/frappe/erpnext/pull/24838)) +- Period list for exponential smoothing forecasting report ([#24983](https://github.com/frappe/erpnext/pull/24983)) +- POS Opening Entry with empty balance detail rows ([#24891](https://github.com/frappe/erpnext/pull/24891)) +- Use account_name only in consolidated report ([#24840](https://github.com/frappe/erpnext/pull/24840)) +- Validation of job card in stock entry ([#24882](https://github.com/frappe/erpnext/pull/24882)) +- Added supplier warehouse field back again ([#24827](https://github.com/frappe/erpnext/pull/24827)) +- Don't throw exception on invoice lines when there is no item_cod… ([#24864](https://github.com/frappe/erpnext/pull/24864)) +- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24918](https://github.com/frappe/erpnext/pull/24918)) +- Payment References on adding Cost Center in PE and Report Issue Summary fix for V13 beta pre-release ([#24951](https://github.com/frappe/erpnext/pull/24951)) +- TDS check getting checked after reload ([#24973](https://github.com/frappe/erpnext/pull/24973)) +- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900)) +- Serial no trim issue ([#24981](https://github.com/frappe/erpnext/pull/24981)) +- Add method for regional round off account back ([#24894](https://github.com/frappe/erpnext/pull/24894)) +- Allow zero valuation in stock reconciliation ([#24985](https://github.com/frappe/erpnext/pull/24985)) +- Simplified logic for additional salary ([#24907](https://github.com/frappe/erpnext/pull/24907)) +- Allow to select item code in batch naming ([#24825](https://github.com/frappe/erpnext/pull/24825)) +- 80G Certificates and Donations ([#24848](https://github.com/frappe/erpnext/pull/24848)) +- Membership renewal validation (#24963) ([#24964](https://github.com/frappe/erpnext/pull/24964)) \ No newline at end of file From d4499277b4da8bae40a65f18adc0243e47e91e88 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 25 Mar 2021 12:18:22 +0530 Subject: [PATCH 0331/1264] chore: Added change log --- erpnext/change_log/v13/v13_0_0-beta_14.md | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/change_log/v13/v13_0_0-beta_14.md b/erpnext/change_log/v13/v13_0_0-beta_14.md index 8ef3c92e31..1fa4376a72 100644 --- a/erpnext/change_log/v13/v13_0_0-beta_14.md +++ b/erpnext/change_log/v13/v13_0_0-beta_14.md @@ -1,3 +1,4 @@ +## Version 13.0.0 Beta 14 Release Notes ### Fixes and Enhancements - Repost incompleted backdated transactions ([#24991](https://github.com/frappe/erpnext/pull/24991)) From f29c075bc3e9c1b2081c4d6621b715a7a9b08332 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 25 Mar 2021 12:56:13 +0530 Subject: [PATCH 0332/1264] fix: patch regional fields for old companies (#24999) * fix: patch regional fields for old companies * chore: fix sider --- erpnext/patches.txt | 1 + erpnext/patches/v13_0/setup_uae_vat_fields.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 erpnext/patches/v13_0/setup_uae_vat_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 59b12f319e..c686635b6c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -759,3 +759,4 @@ erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae +erpnext.patches.v13_0.setup_uae_vat_fields diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py new file mode 100644 index 0000000000..ee55bb8996 --- /dev/null +++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py @@ -0,0 +1,12 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from erpnext.regional.united_arab_emirates.setup import setup + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'United Arab Emirates'}) + if not company: + return + + setup() From 6597bf7dd76c9e24ff77ec35ee7a0eca994d2349 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 25 Mar 2021 13:31:43 +0530 Subject: [PATCH 0333/1264] feat: profitability report default working hours and tests --- .../hr/doctype/hr_settings/hr_settings.json | 9 +- .../doctype/timesheet/test_timesheet.py | 4 +- .../report/profitability/profitability.js | 14 +- .../report/profitability/profitability.py | 128 ++++++++++++------ .../profitability/test_profitability.py | 52 +++++++ .../projects/workspace/projects/projects.json | 13 +- 6 files changed, 172 insertions(+), 48 deletions(-) create mode 100644 erpnext/projects/report/profitability/test_profitability.py diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 09666c5db5..4fa50c4852 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -10,6 +10,7 @@ "retirement_age", "emp_created_by", "column_break_4", + "default_working_hours", "stop_birthday_reminders", "expense_approver_mandatory_in_expense_claim", "leave_settings", @@ -143,13 +144,19 @@ "fieldname": "send_leave_notification", "fieldtype": "Check", "label": "Send Leave Notification" + }, + { + "default": "8", + "fieldname": "default_working_hours", + "fieldtype": "Int", + "label": "Default Working Hours" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-03-14 02:04:22.907159", + "modified": "2021-03-25 13:18:21.648077", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index f7c764e1bd..d21ac0f2f0 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -151,11 +151,11 @@ class TestTimesheet(unittest.TestCase): settings.save() -def make_salary_structure_for_timesheet(employee): +def make_salary_structure_for_timesheet(employee, company=None): salary_structure_name = "Timesheet Salary Structure Test" frequency = "Monthly" - salary_structure = make_salary_structure(salary_structure_name, frequency, dont_submit=True) + salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True) salary_structure.salary_component = "Timesheet Component" salary_structure.salary_slip_based_on_timesheet = 1 salary_structure.hour_rate = 50.0 diff --git a/erpnext/projects/report/profitability/profitability.js b/erpnext/projects/report/profitability/profitability.js index dbf918760f..6cb6e39d34 100644 --- a/erpnext/projects/report/profitability/profitability.js +++ b/erpnext/projects/report/profitability/profitability.js @@ -4,17 +4,27 @@ frappe.query_reports["Profitability"] = { "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, { "fieldname": "start_date", "label": __("Start Date"), "fieldtype": "Date", - "reqd": 1 + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1) }, { "fieldname": "end_date", "label": __("End Date"), "fieldtype": "Date", - "reqd": 1 + "reqd": 1, + "default": frappe.datetime.now_date() }, { "fieldname": "customer_name", diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py index 0edecd8e1b..8c052b5e17 100644 --- a/erpnext/projects/report/profitability/profitability.py +++ b/erpnext/projects/report/profitability/profitability.py @@ -4,12 +4,80 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import nowdate, time_diff_in_hours def execute(filters=None): columns, data = [], [] data = get_data(filters) columns = get_columns() - return columns, data + charts = get_chart_data(data) + return columns, data, None, charts + +def get_data(filters): + conditions = get_conditions(filters) + default_working_hours = frappe.db.get_single_value("HR Settings", "default_working_hours") + sql = """ + select + *, + t.gross_pay * t.utilization as fractional_cost, + t.grand_total - t.gross_pay * t.utilization as profit + from + (select + si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, + ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, + tabTimesheet.total_billed_hours/(ss.total_working_days * %s) as utilization + from + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name + join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """%(default_working_hours) + if conditions: + sql += """ + where + %s) as t"""%(conditions) + data = frappe.db.sql(sql,filters, as_dict=True) + return data + +def get_conditions(filters): + conditions = [] + if filters.get("company"): + conditions.append("tabTimesheet.company='%s'"%filters.get("company")) + if filters.get("customer_name"): + conditions.append("si.customer_name='%s'"%filters.get("customer_name")) + if filters.get("start_date"): + conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) + if filters.get("end_date"): + conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) + if filters.get("employee"): + conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) + + conditions = " and ".join(conditions) + return conditions + +def get_chart_data(data): + if not data: + return None + + labels = [] + utilization = [] + + for entry in data: + labels.append(entry.get("title") + " - " + str(entry.get("end_date"))) + utilization.append(entry.get("utilization")) + charts = { + 'data': { + 'labels': labels, + 'datasets': [ + { + 'name': 'Utilization', + 'values': utilization + } + ] + }, + 'type': 'bar', + 'colors': ['#84BDD5'] + } + return charts def get_columns(): return [ @@ -30,7 +98,21 @@ def get_columns(): "fieldname": "employee", "label": _("Employee"), "fieldtype": "Link", - "options": "employee", + "options": "Employee", + "width": 150 + }, + { + "fieldname": "voucher_no", + "label": _("Sales Invoice"), + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 200 + }, + { + "fieldname": "timesheet", + "label": _("Timesheet"), + "fieldtype": "Link", + "options": "Timesheet", "width": 150 }, { @@ -64,7 +146,7 @@ def get_columns(): "fieldname": "total_billed_hours", "label": _("Total Billed Hours"), "fieldtype": "Int", - "width": 120 + "width": 100 }, { "fieldname": "utilization", @@ -78,42 +160,4 @@ def get_columns(): "fieldtype": "Int", "width": 100 } - ] - -def get_data(filters): - conditions = get_conditions(filters) - sql = """ - select - *, - t.gross_pay * t.utilization as fractional_cost, - t.grand_total - t.gross_pay * t.utilization as profit - from - (select - si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, - ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours, - tabTimesheet.total_billed_hours/(ss.total_working_days * 8) as utilization - from - `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet - join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet. name - join `tabSales Invoice` as si on si. name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """ - if conditions: - sql += """ - where - %s) as t"""%(conditions) - data = frappe.db.sql(sql,filters, as_dict=True) - - return data - -def get_conditions(filters): - conditions = [] - if filters.get("customer_name"): - conditions.append("si.customer_name='%s'"%filters.get("customer_name")) - if filters.get("start_date"): - conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) - if filters.get("end_date"): - conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) - if filters.get("employee"): - conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) - conditions = " and ".join(conditions) - return conditions \ No newline at end of file + ] \ No newline at end of file diff --git a/erpnext/projects/report/profitability/test_profitability.py b/erpnext/projects/report/profitability/test_profitability.py new file mode 100644 index 0000000000..dfdef0dcec --- /dev/null +++ b/erpnext/projects/report/profitability/test_profitability.py @@ -0,0 +1,52 @@ +from __future__ import unicode_literals +import unittest +import frappe +import datetime +from frappe.utils import getdate, nowdate, add_days, add_months +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet +from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice +from erpnext.projects.report.profitability.profitability import execute + +class TestProfitability(unittest.TestCase): + @classmethod + def setUp(self): + emp = make_employee("test_employee_9@salary.com", company="_Test Company") + if not frappe.db.exists("Salary Component", "Timesheet Component"): + frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert() + make_salary_structure_for_timesheet(emp, company="_Test Company") + self.timesheet = make_timesheet(emp, simulate = True, billable=1) + self.salary_slip = make_salary_slip(self.timesheet.name) + self.salary_slip.submit() + self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') + self.sales_invoice.due_date = nowdate() + self.sales_invoice.submit() + + def test_profitability(self): + filters = { + 'company': '_Test Company', + 'start_date': getdate(), + 'end_date': getdate() + } + + report = execute(filters) + expected_data = [ + { + "customer_name": "_Test Customer", + "title": "test_employee_9@salary.com", + "grand_total": 100.0, + "gross_pay": 78100.0, + "profit": -19425.0, + "total_billed_hours": 2.0, + "utilization": 0.25, + "fractional_cost": 19525.0, + "total_working_days": 1.0 + } + ] + for key in ["customer_name","title","grand_total","gross_pay","profit","total_billed_hours","utilization","fractional_cost","total_working_days"]: + self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) + + def tearDown(self): + frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() + frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() + frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index dbbd7e1458..8703ffb756 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "project", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Projects", "links": [ @@ -129,6 +130,16 @@ "onboard": 1, "type": "Link" }, + { + "dependencies": "Timesheet, Sales Invoice, Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Profitability", + "link_to": "Profitability", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Project", "hidden": 0, @@ -150,7 +161,7 @@ "type": "Link" } ], - "modified": "2020-12-01 13:38:37.856224", + "modified": "2021-03-25 13:25:17.609608", "modified_by": "Administrator", "module": "Projects", "name": "Projects", From c0b4eea415ca3cb3a242f78f882e7ea0f811a836 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 25 Mar 2021 14:39:05 +0530 Subject: [PATCH 0334/1264] fix: sider --- .../report/profitability/profitability.py | 17 ++++++++--------- .../report/profitability/test_profitability.py | 3 +-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py index 8c052b5e17..8ce2eb09ee 100644 --- a/erpnext/projects/report/profitability/profitability.py +++ b/erpnext/projects/report/profitability/profitability.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import nowdate, time_diff_in_hours def execute(filters=None): columns, data = [], [] @@ -25,31 +24,31 @@ def get_data(filters): (select si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, - tabTimesheet.total_billed_hours/(ss.total_working_days * %s) as utilization + tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization from `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """%(default_working_hours) + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(default_working_hours) if conditions: sql += """ where - %s) as t"""%(conditions) + {0}) as t""".format(conditions) data = frappe.db.sql(sql,filters, as_dict=True) return data def get_conditions(filters): conditions = [] if filters.get("company"): - conditions.append("tabTimesheet.company='%s'"%filters.get("company")) + conditions.append('tabTimesheet.company="{0}"'.format(filters.get("company"))) if filters.get("customer_name"): - conditions.append("si.customer_name='%s'"%filters.get("customer_name")) + conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) if filters.get("start_date"): - conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) + conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) if filters.get("end_date"): - conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) + conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) if filters.get("employee"): - conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) + conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) conditions = " and ".join(conditions) return conditions diff --git a/erpnext/projects/report/profitability/test_profitability.py b/erpnext/projects/report/profitability/test_profitability.py index dfdef0dcec..64ab6787eb 100644 --- a/erpnext/projects/report/profitability/test_profitability.py +++ b/erpnext/projects/report/profitability/test_profitability.py @@ -1,8 +1,7 @@ from __future__ import unicode_literals import unittest import frappe -import datetime -from frappe.utils import getdate, nowdate, add_days, add_months +from frappe.utils import getdate, nowdate from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice From 2a39b74ad24facc417c10ff7cadb60ea86116345 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Mar 2021 10:49:39 +0530 Subject: [PATCH 0335/1264] fix: Fixes on job card and salary slip (#25011) * fix: map conversion factor while making stock entry from job card * fix: fetch additional salary in salary slip --- erpnext/manufacturing/doctype/job_card/job_card.py | 1 + erpnext/payroll/doctype/salary_slip/salary_slip.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 7aaf2a08ec..d2ac71223d 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -430,6 +430,7 @@ def make_material_request(source_name, target_doc=None): def make_stock_entry(source_name, target_doc=None): def update_item(obj, target, source_parent): target.t_warehouse = source_parent.wip_warehouse + target.conversion_factor = 1 def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 55e1c63a3f..0053c0cd93 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -576,10 +576,10 @@ class SalarySlip(TransactionBase): continue if ( - not d.additional_salary - and (not additional_salary or additional_salary.overwrite) - or additional_salary - and additional_salary.name == d.additional_salary + (not d.additional_salary + and (not additional_salary or additional_salary.overwrite)) + or (additional_salary + and additional_salary.name == d.additional_salary) ): component_row = d break @@ -589,7 +589,7 @@ class SalarySlip(TransactionBase): self.set(component_type, [ d for d in self.get(component_type) if d.salary_component != component_data.salary_component - or d.additional_salary and additional_salary.name != d.additional_salary + or (d.additional_salary and additional_salary.name != d.additional_salary) or d == component_row ]) From e9c801e4bd5c2692de28aab8dfc5168b560c5798 Mon Sep 17 00:00:00 2001 From: "hasnain2808@gmail.com" Date: Fri, 15 Jan 2021 18:56:06 +0530 Subject: [PATCH 0336/1264] fix: ams integration breaks when error raised --- .../doctype/amazon_mws_settings/amazon_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py index cc75a0afbe..148c1a6a16 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py @@ -117,7 +117,7 @@ def call_mws_method(mws_method, *args, **kwargs): return response except Exception as e: delay = math.pow(4, x) * 125 - frappe.log_error(message=e, title=str(mws_method)) + frappe.log_error(message=e, title=f'Method "{mws_method.__name__}" failed') time.sleep(delay) continue From eeb3121622211efe25326b95e0ec94d36b521251 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 24 Mar 2021 16:44:49 +0530 Subject: [PATCH 0337/1264] fix: missing help links in navbar help dropdown --- erpnext/public/js/help_links.js | 244 ++++++++++++++++++++------------ 1 file changed, 152 insertions(+), 92 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 472c5374f5..66ff46405d 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -2,13 +2,13 @@ frappe.provide('frappe.help.help_links'); const docsUrl = 'https://erpnext.com/docs/'; -frappe.help.help_links['rename tool'] = [ +frappe.help.help_links['Form/Rename Tool'] = [ { label: 'Bulk Rename', url: docsUrl + 'user/manual/en/setting-up/data/bulk-rename' }, ] //Setup -frappe.help.help_links['user'] = [ +frappe.help.help_links['List/User'] = [ { label: 'New User', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/adding-users' }, { label: 'Rename User', url: docsUrl + 'user/manual/en/setting-up/articles/rename-user' }, ] @@ -21,7 +21,7 @@ frappe.help.help_links['permission-manager'] = [ { label: 'Password', url: docsUrl + 'user/manual/en/setting-up/articles/change-password' }, ] -frappe.help.help_links['system-settings'] = [ +frappe.help.help_links['Form/System Settings'] = [ { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/system-settings' }, ] @@ -30,60 +30,64 @@ frappe.help.help_links['data-import-tool'] = [ { label: 'Overwriting Data from Data Import Tool', url: docsUrl + 'user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' }, ] -frappe.help.help_links['naming-series'] = [ +frappe.help.help_links['module_setup'] = [ + { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' }, +] + +frappe.help.help_links['Form/Naming Series'] = [ { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/naming-series' }, { label: 'Setting the Current Value for Naming Series', url: docsUrl + 'user/manual/en/setting-up/articles/naming-series-current-value' }, ] -frappe.help.help_links['global-defaults'] = [ +frappe.help.help_links['Form/Global Defaults'] = [ { label: 'Global Settings', url: docsUrl + 'user/manual/en/setting-up/settings/global-defaults' }, ] -frappe.help.help_links['email-digest'] = [ +frappe.help.help_links['Form/Email Digest'] = [ { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, ] -frappe.help.help_links['print-heading'] = [ +frappe.help.help_links['List/Print Heading'] = [ { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, ] -frappe.help.help_links['letter-head'] = [ +frappe.help.help_links['List/Letter Head'] = [ { label: 'Letter Head', url: docsUrl + 'user/manual/en/setting-up/print/letter-head' }, ] -frappe.help.help_links['address-template'] = [ +frappe.help.help_links['List/Address Template'] = [ { label: 'Address Template', url: docsUrl + 'user/manual/en/setting-up/print/address-template' }, ] -frappe.help.help_links['terms-and-conditions'] = [ +frappe.help.help_links['List/Terms and Conditions'] = [ { label: 'Terms and Conditions', url: docsUrl + 'user/manual/en/setting-up/print/terms-and-conditions' }, ] -frappe.help.help_links['cheque-print-template'] = [ +frappe.help.help_links['List/Cheque Print Template'] = [ { label: 'Cheque Print Template', url: docsUrl + 'user/manual/en/setting-up/print/cheque-print-template' }, ] -frappe.help.help_links['email-account'] = [ +frappe.help.help_links['List/Email Account'] = [ { label: 'Email Account', url: docsUrl + 'user/manual/en/setting-up/email/email-account' }, ] -frappe.help.help_links['notification'] = [ +frappe.help.help_links['List/Notification'] = [ { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, ] -frappe.help.help_links['notification'] = [ +frappe.help.help_links['Form/Notification'] = [ { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, ] -frappe.help.help_links['email-digest'] = [ +frappe.help.help_links['List/Email Digest'] = [ { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, ] -frappe.help.help_links['auto-email-report'] = [ +frappe.help.help_links['List/Auto Email Report'] = [ { label: 'Auto Email Reports', url: docsUrl + 'user/manual/en/setting-up/email/email-reports' }, ] -frappe.help.help_links['print-settings'] = [ +frappe.help.help_links['Form/Print Settings'] = [ { label: 'Print Settings', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, ] @@ -91,60 +95,66 @@ frappe.help.help_links['print-format-builder'] = [ { label: 'Print Format Builder', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, ] -frappe.help.help_links['print-heading'] = [ +frappe.help.help_links['List/Print Heading'] = [ { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, ] //setup-integrations -frappe.help.help_links['paypal-settings'] = [ +frappe.help.help_links['Form/PayPal Settings'] = [ { label: 'PayPal Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/paypal-integration' }, ] -frappe.help.help_links['razorpay-settings'] = [ +frappe.help.help_links['Form/Razorpay Settings'] = [ { label: 'Razorpay Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/razorpay-integration' }, ] -frappe.help.help_links['dropbox-settings'] = [ +frappe.help.help_links['Form/Dropbox Settings'] = [ { label: 'Dropbox Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/dropbox-backup' }, ] -frappe.help.help_links['ldap-settings'] = [ +frappe.help.help_links['Form/LDAP Settings'] = [ { label: 'LDAP Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/ldap-integration' }, ] -frappe.help.help_links['stripe-settings'] = [ +frappe.help.help_links['Form/Stripe Settings'] = [ { label: 'Stripe Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/stripe-integration' }, ] //Sales -frappe.help.help_links['quotation'] = [ +frappe.help.help_links['Form/Quotation'] = [ { label: 'Quotation', url: docsUrl + 'user/manual/en/selling/quotation' }, { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' }, { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, ] -frappe.help.help_links['customer'] = [ +frappe.help.help_links['List/Customer'] = [ { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, ] -frappe.help.help_links['customer'] = [ +frappe.help.help_links['Form/Customer'] = [ { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, ] -frappe.help.help_links['sales-taxes-and-charges-template'] = [ +frappe.help.help_links['List/Sales Taxes and Charges Template'] = [ { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, ] -frappe.help.help_links['sales-taxes-and-charges-template'] = [ +frappe.help.help_links['Form/Sales Taxes and Charges Template'] = [ { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, ] -frappe.help.help_links['sales-order'] = [ +frappe.help.help_links['List/Sales Order'] = [ + { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, + { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, + { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, +] + +frappe.help.help_links['Form/Sales Order'] = [ { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, @@ -154,34 +164,43 @@ frappe.help.help_links['sales-order'] = [ { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, ] -frappe.help.help_links['product-bundle'] = [ +frappe.help.help_links['Form/Product Bundle'] = [ { label: 'Product Bundle', url: docsUrl + 'user/manual/en/selling/setup/product-bundle' }, ] -frappe.help.help_links['selling-settings'] = [ +frappe.help.help_links['Form/Selling Settings'] = [ { label: 'Selling Settings', url: docsUrl + 'user/manual/en/selling/setup/selling-settings' }, ] //Buying -frappe.help.help_links['supplier'] = [ +frappe.help.help_links['List/Supplier'] = [ { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, ] -frappe.help.help_links['request-for-quotation'] = [ +frappe.help.help_links['Form/Supplier'] = [ + { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, +] + +frappe.help.help_links['Form/Request for Quotation'] = [ { label: 'Request for Quotation', url: docsUrl + 'user/manual/en/buying/request-for-quotation' }, { label: 'RFQ Video', url: docsUrl + 'user/videos/learn/request-for-quotation.html' }, ] -frappe.help.help_links['supplier-quotation'] = [ +frappe.help.help_links['Form/Supplier Quotation'] = [ { label: 'Supplier Quotation', url: docsUrl + 'user/manual/en/buying/supplier-quotation' }, ] -frappe.help.help_links['buying-settings'] = [ +frappe.help.help_links['Form/Buying Settings'] = [ { label: 'Buying Settings', url: docsUrl + 'user/manual/en/buying/setup/buying-settings' }, ] -frappe.help.help_links['purchase-order'] = [ +frappe.help.help_links['List/Purchase Order'] = [ + { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, + { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, +] + +frappe.help.help_links['Form/Purchase Order'] = [ { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, { label: 'Item UoM', url: docsUrl + 'user/manual/en/buying/articles/purchasing-in-different-unit' }, { label: 'Supplier Item Code', url: docsUrl + 'user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item' }, @@ -189,44 +208,44 @@ frappe.help.help_links['purchase-order'] = [ { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, ] -frappe.help.help_links['purchase-taxes-and-charges-template'] = [ +frappe.help.help_links['List/Purchase Taxes and Charges Template'] = [ { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, ] -frappe.help.help_links['pos-profile'] = [ +frappe.help.help_links['List/POS Profile'] = [ { label: 'POS Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, ] -frappe.help.help_links['price-list'] = [ +frappe.help.help_links['List/Price List'] = [ { label: 'Price List', url: docsUrl + 'user/manual/en/setting-up/price-lists' }, ] -frappe.help.help_links['authorization-rule'] = [ +frappe.help.help_links['List/Authorization Rule'] = [ { label: 'Authorization Rule', url: docsUrl + 'user/manual/en/setting-up/authorization-rule' }, ] -frappe.help.help_links['sms-settings'] = [ +frappe.help.help_links['Form/SMS Settings'] = [ { label: 'SMS Settings', url: docsUrl + 'user/manual/en/setting-up/sms-setting' }, ] -frappe.help.help_links['stock-reconciliation'] = [ +frappe.help.help_links['List/Stock Reconciliation'] = [ { label: 'Stock Reconciliation', url: docsUrl + 'user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' }, ] -frappe.help.help_links['territory/view/tree'] = [ +frappe.help.help_links['Tree/Territory'] = [ { label: 'Territory', url: docsUrl + 'user/manual/en/setting-up/territory' }, ] -frappe.help.help_links['dropbox-backup'] = [ +frappe.help.help_links['Form/Dropbox Backup'] = [ { label: 'Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/third-party-backups' }, { label: 'Setting Up Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/articles/setting-up-dropbox-backups' }, ] -frappe.help.help_links['workflow'] = [ +frappe.help.help_links['List/Workflow'] = [ { label: 'Workflow', url: docsUrl + 'user/manual/en/setting-up/workflows' }, ] -frappe.help.help_links['company'] = [ +frappe.help.help_links['List/Company'] = [ { label: 'Company', url: docsUrl + 'user/manual/en/setting-up/company-setup' }, { label: 'Managing Multiple Companies', url: docsUrl + 'user/manual/en/setting-up/articles/managing-multiple-companies' }, { label: 'Delete All Related Transactions for a Company', url: docsUrl + 'user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' }, @@ -234,25 +253,25 @@ frappe.help.help_links['company'] = [ //Accounts -frappe.help.help_links['accounts'] = [ +frappe.help.help_links['modules/Accounts'] = [ { label: 'Introduction to Accounts', url: docsUrl + 'user/manual/en/accounts/' }, { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts.html' }, { label: 'Multi Currency Accounting', url: docsUrl + 'user/manual/en/accounts/multi-currency-accounting' }, ] -frappe.help.help_links['account/view/tree'] = [ +frappe.help.help_links['Tree/Account'] = [ { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts' }, { label: 'Managing Tree Mastes', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, ] -frappe.help.help_links['sales-invoice'] = [ +frappe.help.help_links['Form/Sales Invoice'] = [ { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, ] -frappe.help.help_links['sales-invoice'] = [ +frappe.help.help_links['List/Sales Invoice'] = [ { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, @@ -263,43 +282,43 @@ frappe.help.help_links['pos'] = [ { label: 'Point of Sale Invoice', url: docsUrl + 'user/manual/en/accounts/point-of-sale-pos-invoice' }, ] -frappe.help.help_links['pos-profile'] = [ +frappe.help.help_links['List/POS Profile'] = [ { label: 'Point of Sale Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, ] -frappe.help.help_links['purchase-invoice'] = [ +frappe.help.help_links['List/Purchase Invoice'] = [ { label: 'Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/purchase-invoice' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, { label: 'Recurring Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, ] -frappe.help.help_links['journal-entry'] = [ +frappe.help.help_links['List/Journal Entry'] = [ { label: 'Journal Entry', url: docsUrl + 'user/manual/en/accounts/journal-entry' }, { label: 'Advance Payment Entry', url: docsUrl + 'user/manual/en/accounts/advance-payment-entry' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, ] -frappe.help.help_links['payment-entry'] = [ +frappe.help.help_links['List/Payment Entry'] = [ { label: 'Payment Entry', url: docsUrl + 'user/manual/en/accounts/payment-entry' }, ] -frappe.help.help_links['payment-request'] = [ +frappe.help.help_links['List/Payment Request'] = [ { label: 'Payment Request', url: docsUrl + 'user/manual/en/accounts/payment-request' }, ] -frappe.help.help_links['asset'] = [ +frappe.help.help_links['List/Asset'] = [ { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, ] -frappe.help.help_links['asset-category'] = [ +frappe.help.help_links['List/Asset Category'] = [ { label: 'Asset Category', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, ] -frappe.help.help_links['cost-center/view/tree'] = [ +frappe.help.help_links['Tree/Cost Center'] = [ { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, ] -frappe.help.help_links['item'] = [ +frappe.help.help_links['List/Item'] = [ { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, @@ -310,42 +329,61 @@ frappe.help.help_links['item'] = [ { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, ] -frappe.help.help_links['purchase-receipt'] = [ +frappe.help.help_links['Form/Item'] = [ + { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, + { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, + { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, + { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' }, + { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, + { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' }, + { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' }, + { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, +] + +frappe.help.help_links['List/Purchase Receipt'] = [ { label: 'Purchase Receipt', url: docsUrl + 'user/manual/en/stock/purchase-receipt' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, ] -frappe.help.help_links['delivery-note'] = [ +frappe.help.help_links['List/Delivery Note'] = [ { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, ] -frappe.help.help_links['delivery-note'] = [ +frappe.help.help_links['Form/Delivery Note'] = [ { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, ] -frappe.help.help_links['installation-note'] = [ +frappe.help.help_links['List/Installation Note'] = [ { label: 'Installation Note', url: docsUrl + 'user/manual/en/stock/installation-note' }, ] +frappe.help.help_links['Tree'] = [ + { label: 'Managing Tree Structure Masters', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, +] -frappe.help.help_links['budget'] = [ +frappe.help.help_links['List/Budget'] = [ { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, ] //Stock -frappe.help.help_links['material-request'] = [ +frappe.help.help_links['List/Material Request'] = [ { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, ] -frappe.help.help_links['stock-entry'] = [ +frappe.help.help_links['Form/Material Request'] = [ + { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, + { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, +] + +frappe.help.help_links['Form/Stock Entry'] = [ { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, { label: 'Stock Entry Types', url: docsUrl + 'user/manual/en/stock/articles/stock-entry-purpose' }, { label: 'Repack Entry', url: docsUrl + 'user/manual/en/stock/articles/repack-entry' }, @@ -353,114 +391,136 @@ frappe.help.help_links['stock-entry'] = [ { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, ] -frappe.help.help_links['warehouse/view/tree'] = [ +frappe.help.help_links['List/Stock Entry'] = [ + { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, +] + +frappe.help.help_links['Tree/Warehouse'] = [ { label: 'Warehouse', url: docsUrl + 'user/manual/en/stock/warehouse' }, ] -frappe.help.help_links['serial-no'] = [ +frappe.help.help_links['List/Serial No'] = [ { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, ] -frappe.help.help_links['batch'] = [ +frappe.help.help_links['Form/Serial No'] = [ + { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, +] + +frappe.help.help_links['Form/Batch'] = [ { label: 'Batch', url: docsUrl + 'user/manual/en/stock/batch' }, ] -frappe.help.help_links['packing-slip'] = [ +frappe.help.help_links['Form/Packing Slip'] = [ { label: 'Packing Slip', url: docsUrl + 'user/manual/en/stock/tools/packing-slip' }, ] -frappe.help.help_links['quality-inspection'] = [ +frappe.help.help_links['Form/Quality Inspection'] = [ { label: 'Quality Inspection', url: docsUrl + 'user/manual/en/stock/tools/quality-inspection' }, ] -frappe.help.help_links['landed-cost-voucher'] = [ +frappe.help.help_links['Form/Landed Cost Voucher'] = [ { label: 'Landed Cost Voucher', url: docsUrl + 'user/manual/en/stock/tools/landed-cost-voucher' }, ] -frappe.help.help_links['item-group/view/tree'] = [ +frappe.help.help_links['Tree/Item Group'] = [ { label: 'Item Group', url: docsUrl + 'user/manual/en/stock/setup/item-group' }, ] -frappe.help.help_links['item-attribute'] = [ +frappe.help.help_links['Form/Item Attribute'] = [ { label: 'Item Attribute', url: docsUrl + 'user/manual/en/stock/setup/item-attribute' }, ] -frappe.help.help_links['uom'] = [ +frappe.help.help_links['Form/UOM'] = [ { label: 'Fractions in UOM', url: docsUrl + 'user/manual/en/stock/articles/managing-fractions-in-uom' }, ] -frappe.help.help_links['stock-reconciliation'] = [ +frappe.help.help_links['Form/Stock Reconciliation'] = [ { label: 'Opening Stock Entry', url: docsUrl + 'user/manual/en/stock/opening-stock' }, ] //CRM -frappe.help.help_links['lead'] = [ +frappe.help.help_links['Form/Lead'] = [ { label: 'Lead', url: docsUrl + 'user/manual/en/CRM/lead' }, ] -frappe.help.help_links['opportunity'] = [ +frappe.help.help_links['Form/Opportunity'] = [ { label: 'Opportunity', url: docsUrl + 'user/manual/en/CRM/opportunity' }, ] -frappe.help.help_links['address'] = [ +frappe.help.help_links['Form/Address'] = [ { label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' }, ] -frappe.help.help_links['contact'] = [ +frappe.help.help_links['Form/Contact'] = [ { label: 'Contact', url: docsUrl + 'user/manual/en/CRM/contact' }, ] -frappe.help.help_links['newsletter'] = [ +frappe.help.help_links['Form/Newsletter'] = [ { label: 'Newsletter', url: docsUrl + 'user/manual/en/CRM/newsletter' }, ] -frappe.help.help_links['campaign'] = [ +frappe.help.help_links['Form/Campaign'] = [ { label: 'Campaign', url: docsUrl + 'user/manual/en/CRM/setup/campaign' }, ] -frappe.help.help_links['sales-person/view/tree'] = [ +frappe.help.help_links['Tree/Sales Person'] = [ { label: 'Sales Person', url: docsUrl + 'user/manual/en/CRM/setup/sales-person' }, ] -frappe.help.help_links['sales-person'] = [ +frappe.help.help_links['Form/Sales Person'] = [ { label: 'Sales Person Target', url: docsUrl + 'user/manual/en/selling/setup/sales-person-target-allocation' }, ] +//Support + +frappe.help.help_links['List/Feedback Trigger'] = [ + { label: 'Feedback Trigger', url: docsUrl + 'user/manual/en/setting-up/feedback/setting-up-feedback' }, +] + +frappe.help.help_links['List/Feedback Request'] = [ + { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, +] + +frappe.help.help_links['List/Feedback Request'] = [ + { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, +] + //Manufacturing -frappe.help.help_links['bom'] = [ +frappe.help.help_links['Form/BOM'] = [ { label: 'Bill of Material', url: docsUrl + 'user/manual/en/manufacturing/bill-of-materials' }, { label: 'Nested BOM Structure', url: docsUrl + 'user/manual/en/manufacturing/articles/nested-bom-structure' }, ] -frappe.help.help_links['work-order'] = [ +frappe.help.help_links['Form/Work Order'] = [ { label: 'Work Order', url: docsUrl + 'user/manual/en/manufacturing/work-order' }, ] -frappe.help.help_links['workstation'] = [ +frappe.help.help_links['Form/Workstation'] = [ { label: 'Workstation', url: docsUrl + 'user/manual/en/manufacturing/workstation' }, ] -frappe.help.help_links['operation'] = [ +frappe.help.help_links['Form/Operation'] = [ { label: 'Operation', url: docsUrl + 'user/manual/en/manufacturing/operation' }, ] -frappe.help.help_links['bom-update-tool'] = [ +frappe.help.help_links['Form/BOM Update Tool'] = [ { label: 'BOM Update Tool', url: docsUrl + 'user/manual/en/manufacturing/tools/bom-update-tool' }, ] //Customize -frappe.help.help_links['customize-form'] = [ +frappe.help.help_links['Form/Customize Form'] = [ { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, { label: 'Customize Field', url: docsUrl + 'user/manual/en/customize-erpnext/customize-form' }, ] -frappe.help.help_links['custom-field'] = [ +frappe.help.help_links['Form/Custom Field'] = [ { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, ] -frappe.help.help_links['custom-field'] = [ +frappe.help.help_links['Form/Custom Field'] = [ { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, ] From 0ae0368d34cd252a803cc1c7d616674bae9d8816 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 24 Mar 2021 16:45:32 +0530 Subject: [PATCH 0338/1264] fix: re-execute add_standard_navbar_items patch - check that same items aren't appended again --- erpnext/patches.txt | 2 +- erpnext/setup/install.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c686635b6c..ff9433a7ef 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -720,7 +720,7 @@ 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 -erpnext.patches.v13_0.add_standard_navbar_items #4 +erpnext.patches.v13_0.add_standard_navbar_items #2021-03-24 erpnext.patches.v13_0.stock_entry_enhancements erpnext.patches.v12_0.update_state_code_for_daman_and_diu erpnext.patches.v12_0.rename_lost_reason_detail diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 0bb480bd4b..29fd0e659b 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -142,13 +142,15 @@ def add_standard_navbar_items(): } ] - current_nabvar_items = navbar_settings.help_dropdown + current_navbar_items = navbar_settings.help_dropdown navbar_settings.set('help_dropdown', []) for item in erpnext_navbar_items: - navbar_settings.append('help_dropdown', item) + current_labels = [item.get('item_label') for item in current_navbar_items] + if not item.get('item_label') in current_labels: + navbar_settings.append('help_dropdown', item) - for item in current_nabvar_items: + for item in current_navbar_items: navbar_settings.append('help_dropdown', { 'item_label': item.item_label, 'item_type': item.item_type, From 5243eea5320bbdd02ec913667a1ee864cc1a6938 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 26 Mar 2021 14:35:47 +0530 Subject: [PATCH 0339/1264] style: fix formatting --- erpnext/public/js/help_links.js | 1319 +++++++++++++++++++++---------- 1 file changed, 922 insertions(+), 397 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 66ff46405d..e78992302f 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -1,526 +1,1051 @@ -frappe.provide('frappe.help.help_links'); +frappe.provide("frappe.help.help_links"); -const docsUrl = 'https://erpnext.com/docs/'; +const docsUrl = "https://erpnext.com/docs/"; -frappe.help.help_links['Form/Rename Tool'] = [ - { label: 'Bulk Rename', url: docsUrl + 'user/manual/en/setting-up/data/bulk-rename' }, -] +frappe.help.help_links["Form/Rename Tool"] = [ + { + label: "Bulk Rename", + url: docsUrl + "user/manual/en/setting-up/data/bulk-rename", + }, +]; //Setup -frappe.help.help_links['List/User'] = [ - { label: 'New User', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/adding-users' }, - { label: 'Rename User', url: docsUrl + 'user/manual/en/setting-up/articles/rename-user' }, -] +frappe.help.help_links["List/User"] = [ + { + label: "New User", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/adding-users", + }, + { + label: "Rename User", + url: docsUrl + "user/manual/en/setting-up/articles/rename-user", + }, +]; -frappe.help.help_links['permission-manager'] = [ - { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' }, - { label: 'Managing Perm Level in Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/articles/managing-perm-level' }, - { label: 'User Permissions', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/user-permissions' }, - { label: 'Sharing', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/sharing' }, - { label: 'Password', url: docsUrl + 'user/manual/en/setting-up/articles/change-password' }, -] +frappe.help.help_links["permission-manager"] = [ + { + label: "Role Permissions Manager", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/role-based-permissions", + }, + { + label: "Managing Perm Level in Permissions Manager", + url: docsUrl + "user/manual/en/setting-up/articles/managing-perm-level", + }, + { + label: "User Permissions", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/user-permissions", + }, + { + label: "Sharing", + url: + docsUrl + "user/manual/en/setting-up/users-and-permissions/sharing", + }, + { + label: "Password", + url: docsUrl + "user/manual/en/setting-up/articles/change-password", + }, +]; -frappe.help.help_links['Form/System Settings'] = [ - { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/system-settings' }, -] +frappe.help.help_links["Form/System Settings"] = [ + { + label: "Naming Series", + url: docsUrl + "user/manual/en/setting-up/settings/system-settings", + }, +]; -frappe.help.help_links['data-import-tool'] = [ - { label: 'Importing and Exporting Data', url: docsUrl + 'user/manual/en/setting-up/data/data-import-tool' }, - { label: 'Overwriting Data from Data Import Tool', url: docsUrl + 'user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' }, -] +frappe.help.help_links["data-import-tool"] = [ + { + label: "Importing and Exporting Data", + url: docsUrl + "user/manual/en/setting-up/data/data-import-tool", + }, + { + label: "Overwriting Data from Data Import Tool", + url: + docsUrl + + "user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool", + }, +]; -frappe.help.help_links['module_setup'] = [ - { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' }, -] +frappe.help.help_links["module_setup"] = [ + { + label: "Role Permissions Manager", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/role-based-permissions", + }, +]; -frappe.help.help_links['Form/Naming Series'] = [ - { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/naming-series' }, - { label: 'Setting the Current Value for Naming Series', url: docsUrl + 'user/manual/en/setting-up/articles/naming-series-current-value' }, -] +frappe.help.help_links["Form/Naming Series"] = [ + { + label: "Naming Series", + url: docsUrl + "user/manual/en/setting-up/settings/naming-series", + }, + { + label: "Setting the Current Value for Naming Series", + url: + docsUrl + + "user/manual/en/setting-up/articles/naming-series-current-value", + }, +]; -frappe.help.help_links['Form/Global Defaults'] = [ - { label: 'Global Settings', url: docsUrl + 'user/manual/en/setting-up/settings/global-defaults' }, -] +frappe.help.help_links["Form/Global Defaults"] = [ + { + label: "Global Settings", + url: docsUrl + "user/manual/en/setting-up/settings/global-defaults", + }, +]; -frappe.help.help_links['Form/Email Digest'] = [ - { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, -] +frappe.help.help_links["Form/Email Digest"] = [ + { + label: "Email Digest", + url: docsUrl + "user/manual/en/setting-up/email/email-digest", + }, +]; -frappe.help.help_links['List/Print Heading'] = [ - { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, -] +frappe.help.help_links["List/Print Heading"] = [ + { + label: "Print Heading", + url: docsUrl + "user/manual/en/setting-up/print/print-headings", + }, +]; -frappe.help.help_links['List/Letter Head'] = [ - { label: 'Letter Head', url: docsUrl + 'user/manual/en/setting-up/print/letter-head' }, -] +frappe.help.help_links["List/Letter Head"] = [ + { + label: "Letter Head", + url: docsUrl + "user/manual/en/setting-up/print/letter-head", + }, +]; -frappe.help.help_links['List/Address Template'] = [ - { label: 'Address Template', url: docsUrl + 'user/manual/en/setting-up/print/address-template' }, -] +frappe.help.help_links["List/Address Template"] = [ + { + label: "Address Template", + url: docsUrl + "user/manual/en/setting-up/print/address-template", + }, +]; -frappe.help.help_links['List/Terms and Conditions'] = [ - { label: 'Terms and Conditions', url: docsUrl + 'user/manual/en/setting-up/print/terms-and-conditions' }, -] +frappe.help.help_links["List/Terms and Conditions"] = [ + { + label: "Terms and Conditions", + url: docsUrl + "user/manual/en/setting-up/print/terms-and-conditions", + }, +]; -frappe.help.help_links['List/Cheque Print Template'] = [ - { label: 'Cheque Print Template', url: docsUrl + 'user/manual/en/setting-up/print/cheque-print-template' }, -] +frappe.help.help_links["List/Cheque Print Template"] = [ + { + label: "Cheque Print Template", + url: docsUrl + "user/manual/en/setting-up/print/cheque-print-template", + }, +]; -frappe.help.help_links['List/Email Account'] = [ - { label: 'Email Account', url: docsUrl + 'user/manual/en/setting-up/email/email-account' }, -] +frappe.help.help_links["List/Email Account"] = [ + { + label: "Email Account", + url: docsUrl + "user/manual/en/setting-up/email/email-account", + }, +]; -frappe.help.help_links['List/Notification'] = [ - { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, -] +frappe.help.help_links["List/Notification"] = [ + { + label: "Notification", + url: docsUrl + "user/manual/en/setting-up/email/notifications", + }, +]; -frappe.help.help_links['Form/Notification'] = [ - { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, -] +frappe.help.help_links["Form/Notification"] = [ + { + label: "Notification", + url: docsUrl + "user/manual/en/setting-up/email/notifications", + }, +]; -frappe.help.help_links['List/Email Digest'] = [ - { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, -] +frappe.help.help_links["List/Email Digest"] = [ + { + label: "Email Digest", + url: docsUrl + "user/manual/en/setting-up/email/email-digest", + }, +]; -frappe.help.help_links['List/Auto Email Report'] = [ - { label: 'Auto Email Reports', url: docsUrl + 'user/manual/en/setting-up/email/email-reports' }, -] +frappe.help.help_links["List/Auto Email Report"] = [ + { + label: "Auto Email Reports", + url: docsUrl + "user/manual/en/setting-up/email/email-reports", + }, +]; -frappe.help.help_links['Form/Print Settings'] = [ - { label: 'Print Settings', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, -] +frappe.help.help_links["Form/Print Settings"] = [ + { + label: "Print Settings", + url: docsUrl + "user/manual/en/setting-up/print/print-settings", + }, +]; -frappe.help.help_links['print-format-builder'] = [ - { label: 'Print Format Builder', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, -] +frappe.help.help_links["print-format-builder"] = [ + { + label: "Print Format Builder", + url: docsUrl + "user/manual/en/setting-up/print/print-settings", + }, +]; -frappe.help.help_links['List/Print Heading'] = [ - { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, -] +frappe.help.help_links["List/Print Heading"] = [ + { + label: "Print Heading", + url: docsUrl + "user/manual/en/setting-up/print/print-headings", + }, +]; //setup-integrations -frappe.help.help_links['Form/PayPal Settings'] = [ - { label: 'PayPal Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/paypal-integration' }, -] +frappe.help.help_links["Form/PayPal Settings"] = [ + { + label: "PayPal Settings", + url: + docsUrl + + "user/manual/en/setting-up/integrations/paypal-integration", + }, +]; -frappe.help.help_links['Form/Razorpay Settings'] = [ - { label: 'Razorpay Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/razorpay-integration' }, -] +frappe.help.help_links["Form/Razorpay Settings"] = [ + { + label: "Razorpay Settings", + url: + docsUrl + + "user/manual/en/setting-up/integrations/razorpay-integration", + }, +]; -frappe.help.help_links['Form/Dropbox Settings'] = [ - { label: 'Dropbox Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/dropbox-backup' }, -] +frappe.help.help_links["Form/Dropbox Settings"] = [ + { + label: "Dropbox Settings", + url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup", + }, +]; -frappe.help.help_links['Form/LDAP Settings'] = [ - { label: 'LDAP Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/ldap-integration' }, -] +frappe.help.help_links["Form/LDAP Settings"] = [ + { + label: "LDAP Settings", + url: + docsUrl + "user/manual/en/setting-up/integrations/ldap-integration", + }, +]; -frappe.help.help_links['Form/Stripe Settings'] = [ - { label: 'Stripe Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/stripe-integration' }, -] +frappe.help.help_links["Form/Stripe Settings"] = [ + { + label: "Stripe Settings", + url: + docsUrl + + "user/manual/en/setting-up/integrations/stripe-integration", + }, +]; //Sales -frappe.help.help_links['Form/Quotation'] = [ - { label: 'Quotation', url: docsUrl + 'user/manual/en/selling/quotation' }, - { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, - { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' }, - { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, -] +frappe.help.help_links["Form/Quotation"] = [ + { label: "Quotation", url: docsUrl + "user/manual/en/selling/quotation" }, + { + label: "Applying Discount", + url: docsUrl + "user/manual/en/selling/articles/applying-discount", + }, + { + label: "Sales Person", + url: + docsUrl + + "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions", + }, + { + label: "Applying Margin", + url: docsUrl + "user/manual/en/selling/articles/adding-margin", + }, +]; -frappe.help.help_links['List/Customer'] = [ - { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, - { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, -] +frappe.help.help_links["List/Customer"] = [ + { label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" }, + { + label: "Credit Limit", + url: docsUrl + "user/manual/en/accounts/credit-limit", + }, +]; -frappe.help.help_links['Form/Customer'] = [ - { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, - { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, -] +frappe.help.help_links["Form/Customer"] = [ + { label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" }, + { + label: "Credit Limit", + url: docsUrl + "user/manual/en/accounts/credit-limit", + }, +]; -frappe.help.help_links['List/Sales Taxes and Charges Template'] = [ - { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, -] +frappe.help.help_links["List/Sales Taxes and Charges Template"] = [ + { + label: "Setting Up Taxes", + url: docsUrl + "user/manual/en/setting-up/setting-up-taxes", + }, +]; -frappe.help.help_links['Form/Sales Taxes and Charges Template'] = [ - { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, -] +frappe.help.help_links["Form/Sales Taxes and Charges Template"] = [ + { + label: "Setting Up Taxes", + url: docsUrl + "user/manual/en/setting-up/setting-up-taxes", + }, +]; -frappe.help.help_links['List/Sales Order'] = [ - { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, - { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, - { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, -] +frappe.help.help_links["List/Sales Order"] = [ + { + label: "Sales Order", + url: docsUrl + "user/manual/en/selling/sales-order", + }, + { + label: "Recurring Sales Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, + { + label: "Applying Discount", + url: docsUrl + "user/manual/en/selling/articles/applying-discount", + }, +]; -frappe.help.help_links['Form/Sales Order'] = [ - { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, - { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, - { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, - { label: 'Drop Shipping', url: docsUrl + 'user/manual/en/selling/articles/drop-shipping' }, - { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' }, - { label: 'Close Sales Order', url: docsUrl + 'user/manual/en/selling/articles/close-sales-order' }, - { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, -] +frappe.help.help_links["Form/Sales Order"] = [ + { + label: "Sales Order", + url: docsUrl + "user/manual/en/selling/sales-order", + }, + { + label: "Recurring Sales Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, + { + label: "Applying Discount", + url: docsUrl + "user/manual/en/selling/articles/applying-discount", + }, + { + label: "Drop Shipping", + url: docsUrl + "user/manual/en/selling/articles/drop-shipping", + }, + { + label: "Sales Person", + url: + docsUrl + + "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions", + }, + { + label: "Close Sales Order", + url: docsUrl + "user/manual/en/selling/articles/close-sales-order", + }, + { + label: "Applying Margin", + url: docsUrl + "user/manual/en/selling/articles/adding-margin", + }, +]; -frappe.help.help_links['Form/Product Bundle'] = [ - { label: 'Product Bundle', url: docsUrl + 'user/manual/en/selling/setup/product-bundle' }, -] +frappe.help.help_links["Form/Product Bundle"] = [ + { + label: "Product Bundle", + url: docsUrl + "user/manual/en/selling/setup/product-bundle", + }, +]; -frappe.help.help_links['Form/Selling Settings'] = [ - { label: 'Selling Settings', url: docsUrl + 'user/manual/en/selling/setup/selling-settings' }, -] +frappe.help.help_links["Form/Selling Settings"] = [ + { + label: "Selling Settings", + url: docsUrl + "user/manual/en/selling/setup/selling-settings", + }, +]; //Buying -frappe.help.help_links['List/Supplier'] = [ - { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, -] +frappe.help.help_links["List/Supplier"] = [ + { label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" }, +]; -frappe.help.help_links['Form/Supplier'] = [ - { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, -] +frappe.help.help_links["Form/Supplier"] = [ + { label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" }, +]; -frappe.help.help_links['Form/Request for Quotation'] = [ - { label: 'Request for Quotation', url: docsUrl + 'user/manual/en/buying/request-for-quotation' }, - { label: 'RFQ Video', url: docsUrl + 'user/videos/learn/request-for-quotation.html' }, -] +frappe.help.help_links["Form/Request for Quotation"] = [ + { + label: "Request for Quotation", + url: docsUrl + "user/manual/en/buying/request-for-quotation", + }, + { + label: "RFQ Video", + url: docsUrl + "user/videos/learn/request-for-quotation.html", + }, +]; -frappe.help.help_links['Form/Supplier Quotation'] = [ - { label: 'Supplier Quotation', url: docsUrl + 'user/manual/en/buying/supplier-quotation' }, -] +frappe.help.help_links["Form/Supplier Quotation"] = [ + { + label: "Supplier Quotation", + url: docsUrl + "user/manual/en/buying/supplier-quotation", + }, +]; -frappe.help.help_links['Form/Buying Settings'] = [ - { label: 'Buying Settings', url: docsUrl + 'user/manual/en/buying/setup/buying-settings' }, -] +frappe.help.help_links["Form/Buying Settings"] = [ + { + label: "Buying Settings", + url: docsUrl + "user/manual/en/buying/setup/buying-settings", + }, +]; -frappe.help.help_links['List/Purchase Order'] = [ - { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, - { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["List/Purchase Order"] = [ + { + label: "Purchase Order", + url: docsUrl + "user/manual/en/buying/purchase-order", + }, + { + label: "Recurring Purchase Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['Form/Purchase Order'] = [ - { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, - { label: 'Item UoM', url: docsUrl + 'user/manual/en/buying/articles/purchasing-in-different-unit' }, - { label: 'Supplier Item Code', url: docsUrl + 'user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item' }, - { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, - { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, -] +frappe.help.help_links["Form/Purchase Order"] = [ + { + label: "Purchase Order", + url: docsUrl + "user/manual/en/buying/purchase-order", + }, + { + label: "Item UoM", + url: + docsUrl + + "user/manual/en/buying/articles/purchasing-in-different-unit", + }, + { + label: "Supplier Item Code", + url: + docsUrl + + "user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item", + }, + { + label: "Recurring Purchase Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, + { + label: "Subcontracting", + url: docsUrl + "user/manual/en/manufacturing/subcontracting", + }, +]; -frappe.help.help_links['List/Purchase Taxes and Charges Template'] = [ - { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, -] +frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [ + { + label: "Setting Up Taxes", + url: docsUrl + "user/manual/en/setting-up/setting-up-taxes", + }, +]; -frappe.help.help_links['List/POS Profile'] = [ - { label: 'POS Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, -] +frappe.help.help_links["List/POS Profile"] = [ + { + label: "POS Profile", + url: docsUrl + "user/manual/en/setting-up/pos-setting", + }, +]; -frappe.help.help_links['List/Price List'] = [ - { label: 'Price List', url: docsUrl + 'user/manual/en/setting-up/price-lists' }, -] +frappe.help.help_links["List/Price List"] = [ + { + label: "Price List", + url: docsUrl + "user/manual/en/setting-up/price-lists", + }, +]; -frappe.help.help_links['List/Authorization Rule'] = [ - { label: 'Authorization Rule', url: docsUrl + 'user/manual/en/setting-up/authorization-rule' }, -] +frappe.help.help_links["List/Authorization Rule"] = [ + { + label: "Authorization Rule", + url: docsUrl + "user/manual/en/setting-up/authorization-rule", + }, +]; -frappe.help.help_links['Form/SMS Settings'] = [ - { label: 'SMS Settings', url: docsUrl + 'user/manual/en/setting-up/sms-setting' }, -] +frappe.help.help_links["Form/SMS Settings"] = [ + { + label: "SMS Settings", + url: docsUrl + "user/manual/en/setting-up/sms-setting", + }, +]; -frappe.help.help_links['List/Stock Reconciliation'] = [ - { label: 'Stock Reconciliation', url: docsUrl + 'user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' }, -] +frappe.help.help_links["List/Stock Reconciliation"] = [ + { + label: "Stock Reconciliation", + url: + docsUrl + + "user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item", + }, +]; -frappe.help.help_links['Tree/Territory'] = [ - { label: 'Territory', url: docsUrl + 'user/manual/en/setting-up/territory' }, -] +frappe.help.help_links["Tree/Territory"] = [ + { + label: "Territory", + url: docsUrl + "user/manual/en/setting-up/territory", + }, +]; -frappe.help.help_links['Form/Dropbox Backup'] = [ - { label: 'Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/third-party-backups' }, - { label: 'Setting Up Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/articles/setting-up-dropbox-backups' }, -] +frappe.help.help_links["Form/Dropbox Backup"] = [ + { + label: "Dropbox Backup", + url: docsUrl + "user/manual/en/setting-up/third-party-backups", + }, + { + label: "Setting Up Dropbox Backup", + url: + docsUrl + + "user/manual/en/setting-up/articles/setting-up-dropbox-backups", + }, +]; -frappe.help.help_links['List/Workflow'] = [ - { label: 'Workflow', url: docsUrl + 'user/manual/en/setting-up/workflows' }, -] +frappe.help.help_links["List/Workflow"] = [ + { label: "Workflow", url: docsUrl + "user/manual/en/setting-up/workflows" }, +]; -frappe.help.help_links['List/Company'] = [ - { label: 'Company', url: docsUrl + 'user/manual/en/setting-up/company-setup' }, - { label: 'Managing Multiple Companies', url: docsUrl + 'user/manual/en/setting-up/articles/managing-multiple-companies' }, - { label: 'Delete All Related Transactions for a Company', url: docsUrl + 'user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' }, -] +frappe.help.help_links["List/Company"] = [ + { + label: "Company", + url: docsUrl + "user/manual/en/setting-up/company-setup", + }, + { + label: "Managing Multiple Companies", + url: + docsUrl + + "user/manual/en/setting-up/articles/managing-multiple-companies", + }, + { + label: "Delete All Related Transactions for a Company", + url: + docsUrl + + "user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions", + }, +]; //Accounts -frappe.help.help_links['modules/Accounts'] = [ - { label: 'Introduction to Accounts', url: docsUrl + 'user/manual/en/accounts/' }, - { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts.html' }, - { label: 'Multi Currency Accounting', url: docsUrl + 'user/manual/en/accounts/multi-currency-accounting' }, -] +frappe.help.help_links["modules/Accounts"] = [ + { + label: "Introduction to Accounts", + url: docsUrl + "user/manual/en/accounts/", + }, + { + label: "Chart of Accounts", + url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html", + }, + { + label: "Multi Currency Accounting", + url: docsUrl + "user/manual/en/accounts/multi-currency-accounting", + }, +]; -frappe.help.help_links['Tree/Account'] = [ - { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts' }, - { label: 'Managing Tree Mastes', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, -] +frappe.help.help_links["Tree/Account"] = [ + { + label: "Chart of Accounts", + url: docsUrl + "user/manual/en/accounts/chart-of-accounts", + }, + { + label: "Managing Tree Mastes", + url: + docsUrl + + "user/manual/en/setting-up/articles/managing-tree-structure-masters", + }, +]; -frappe.help.help_links['Form/Sales Invoice'] = [ - { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, - { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["Form/Sales Invoice"] = [ + { + label: "Sales Invoice", + url: docsUrl + "user/manual/en/accounts/sales-invoice", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, + { + label: "Recurring Sales Invoice", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['List/Sales Invoice'] = [ - { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, - { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["List/Sales Invoice"] = [ + { + label: "Sales Invoice", + url: docsUrl + "user/manual/en/accounts/sales-invoice", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, + { + label: "Recurring Sales Invoice", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['pos'] = [ - { label: 'Point of Sale Invoice', url: docsUrl + 'user/manual/en/accounts/point-of-sale-pos-invoice' }, -] +frappe.help.help_links["pos"] = [ + { + label: "Point of Sale Invoice", + url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice", + }, +]; -frappe.help.help_links['List/POS Profile'] = [ - { label: 'Point of Sale Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, -] +frappe.help.help_links["List/POS Profile"] = [ + { + label: "Point of Sale Profile", + url: docsUrl + "user/manual/en/setting-up/pos-setting", + }, +]; -frappe.help.help_links['List/Purchase Invoice'] = [ - { label: 'Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/purchase-invoice' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, - { label: 'Recurring Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["List/Purchase Invoice"] = [ + { + label: "Purchase Invoice", + url: docsUrl + "user/manual/en/accounts/purchase-invoice", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, + { + label: "Recurring Purchase Invoice", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['List/Journal Entry'] = [ - { label: 'Journal Entry', url: docsUrl + 'user/manual/en/accounts/journal-entry' }, - { label: 'Advance Payment Entry', url: docsUrl + 'user/manual/en/accounts/advance-payment-entry' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, -] +frappe.help.help_links["List/Journal Entry"] = [ + { + label: "Journal Entry", + url: docsUrl + "user/manual/en/accounts/journal-entry", + }, + { + label: "Advance Payment Entry", + url: docsUrl + "user/manual/en/accounts/advance-payment-entry", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, +]; -frappe.help.help_links['List/Payment Entry'] = [ - { label: 'Payment Entry', url: docsUrl + 'user/manual/en/accounts/payment-entry' }, -] +frappe.help.help_links["List/Payment Entry"] = [ + { + label: "Payment Entry", + url: docsUrl + "user/manual/en/accounts/payment-entry", + }, +]; -frappe.help.help_links['List/Payment Request'] = [ - { label: 'Payment Request', url: docsUrl + 'user/manual/en/accounts/payment-request' }, -] +frappe.help.help_links["List/Payment Request"] = [ + { + label: "Payment Request", + url: docsUrl + "user/manual/en/accounts/payment-request", + }, +]; -frappe.help.help_links['List/Asset'] = [ - { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, -] +frappe.help.help_links["List/Asset"] = [ + { + label: "Managing Fixed Assets", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, +]; -frappe.help.help_links['List/Asset Category'] = [ - { label: 'Asset Category', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, -] +frappe.help.help_links["List/Asset Category"] = [ + { + label: "Asset Category", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, +]; -frappe.help.help_links['Tree/Cost Center'] = [ - { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, -] +frappe.help.help_links["Tree/Cost Center"] = [ + { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, +]; -frappe.help.help_links['List/Item'] = [ - { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, - { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' }, - { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, - { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' }, - { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' }, - { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, -] +frappe.help.help_links["List/Item"] = [ + { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, + { + label: "Item Price", + url: docsUrl + "user/manual/en/stock/item/item-price", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Item Wise Taxation", + url: docsUrl + "user/manual/en/accounts/item-wise-taxation", + }, + { + label: "Managing Fixed Assets", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, + { + label: "Item Codification", + url: docsUrl + "user/manual/en/stock/item/item-codification", + }, + { + label: "Item Variants", + url: docsUrl + "user/manual/en/stock/item/item-variants", + }, + { + label: "Item Valuation", + url: + docsUrl + + "user/manual/en/stock/item/item-valuation-fifo-and-moving-average", + }, +]; -frappe.help.help_links['Form/Item'] = [ - { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, - { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' }, - { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, - { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' }, - { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' }, - { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, -] +frappe.help.help_links["Form/Item"] = [ + { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, + { + label: "Item Price", + url: docsUrl + "user/manual/en/stock/item/item-price", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Item Wise Taxation", + url: docsUrl + "user/manual/en/accounts/item-wise-taxation", + }, + { + label: "Managing Fixed Assets", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, + { + label: "Item Codification", + url: docsUrl + "user/manual/en/stock/item/item-codification", + }, + { + label: "Item Variants", + url: docsUrl + "user/manual/en/stock/item/item-variants", + }, + { + label: "Item Valuation", + url: + docsUrl + + "user/manual/en/stock/item/item-valuation-fifo-and-moving-average", + }, +]; -frappe.help.help_links['List/Purchase Receipt'] = [ - { label: 'Purchase Receipt', url: docsUrl + 'user/manual/en/stock/purchase-receipt' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, -] +frappe.help.help_links["List/Purchase Receipt"] = [ + { + label: "Purchase Receipt", + url: docsUrl + "user/manual/en/stock/purchase-receipt", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, +]; -frappe.help.help_links['List/Delivery Note'] = [ - { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, -] +frappe.help.help_links["List/Delivery Note"] = [ + { + label: "Delivery Note", + url: docsUrl + "user/manual/en/stock/delivery-note", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, +]; -frappe.help.help_links['Form/Delivery Note'] = [ - { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, -] +frappe.help.help_links["Form/Delivery Note"] = [ + { + label: "Delivery Note", + url: docsUrl + "user/manual/en/stock/delivery-note", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Subcontracting", + url: docsUrl + "user/manual/en/manufacturing/subcontracting", + }, +]; -frappe.help.help_links['List/Installation Note'] = [ - { label: 'Installation Note', url: docsUrl + 'user/manual/en/stock/installation-note' }, -] +frappe.help.help_links["List/Installation Note"] = [ + { + label: "Installation Note", + url: docsUrl + "user/manual/en/stock/installation-note", + }, +]; +frappe.help.help_links["Tree"] = [ + { + label: "Managing Tree Structure Masters", + url: + docsUrl + + "user/manual/en/setting-up/articles/managing-tree-structure-masters", + }, +]; -frappe.help.help_links['Tree'] = [ - { label: 'Managing Tree Structure Masters', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, -] - -frappe.help.help_links['List/Budget'] = [ - { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, -] +frappe.help.help_links["List/Budget"] = [ + { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, +]; //Stock -frappe.help.help_links['List/Material Request'] = [ - { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, - { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, -] +frappe.help.help_links["List/Material Request"] = [ + { + label: "Material Request", + url: docsUrl + "user/manual/en/stock/material-request", + }, + { + label: "Auto-creation of Material Request", + url: + docsUrl + + "user/manual/en/stock/articles/auto-creation-of-material-request", + }, +]; -frappe.help.help_links['Form/Material Request'] = [ - { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, - { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, -] +frappe.help.help_links["Form/Material Request"] = [ + { + label: "Material Request", + url: docsUrl + "user/manual/en/stock/material-request", + }, + { + label: "Auto-creation of Material Request", + url: + docsUrl + + "user/manual/en/stock/articles/auto-creation-of-material-request", + }, +]; -frappe.help.help_links['Form/Stock Entry'] = [ - { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, - { label: 'Stock Entry Types', url: docsUrl + 'user/manual/en/stock/articles/stock-entry-purpose' }, - { label: 'Repack Entry', url: docsUrl + 'user/manual/en/stock/articles/repack-entry' }, - { label: 'Opening Stock', url: docsUrl + 'user/manual/en/stock/opening-stock' }, - { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, -] +frappe.help.help_links["Form/Stock Entry"] = [ + { label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" }, + { + label: "Stock Entry Types", + url: docsUrl + "user/manual/en/stock/articles/stock-entry-purpose", + }, + { + label: "Repack Entry", + url: docsUrl + "user/manual/en/stock/articles/repack-entry", + }, + { + label: "Opening Stock", + url: docsUrl + "user/manual/en/stock/opening-stock", + }, + { + label: "Subcontracting", + url: docsUrl + "user/manual/en/manufacturing/subcontracting", + }, +]; -frappe.help.help_links['List/Stock Entry'] = [ - { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, -] +frappe.help.help_links["List/Stock Entry"] = [ + { label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" }, +]; -frappe.help.help_links['Tree/Warehouse'] = [ - { label: 'Warehouse', url: docsUrl + 'user/manual/en/stock/warehouse' }, -] +frappe.help.help_links["Tree/Warehouse"] = [ + { label: "Warehouse", url: docsUrl + "user/manual/en/stock/warehouse" }, +]; -frappe.help.help_links['List/Serial No'] = [ - { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, -] +frappe.help.help_links["List/Serial No"] = [ + { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, +]; -frappe.help.help_links['Form/Serial No'] = [ - { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, -] +frappe.help.help_links["Form/Serial No"] = [ + { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, +]; -frappe.help.help_links['Form/Batch'] = [ - { label: 'Batch', url: docsUrl + 'user/manual/en/stock/batch' }, -] +frappe.help.help_links["Form/Batch"] = [ + { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, +]; -frappe.help.help_links['Form/Packing Slip'] = [ - { label: 'Packing Slip', url: docsUrl + 'user/manual/en/stock/tools/packing-slip' }, -] +frappe.help.help_links["Form/Packing Slip"] = [ + { + label: "Packing Slip", + url: docsUrl + "user/manual/en/stock/tools/packing-slip", + }, +]; -frappe.help.help_links['Form/Quality Inspection'] = [ - { label: 'Quality Inspection', url: docsUrl + 'user/manual/en/stock/tools/quality-inspection' }, -] +frappe.help.help_links["Form/Quality Inspection"] = [ + { + label: "Quality Inspection", + url: docsUrl + "user/manual/en/stock/tools/quality-inspection", + }, +]; -frappe.help.help_links['Form/Landed Cost Voucher'] = [ - { label: 'Landed Cost Voucher', url: docsUrl + 'user/manual/en/stock/tools/landed-cost-voucher' }, -] +frappe.help.help_links["Form/Landed Cost Voucher"] = [ + { + label: "Landed Cost Voucher", + url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher", + }, +]; -frappe.help.help_links['Tree/Item Group'] = [ - { label: 'Item Group', url: docsUrl + 'user/manual/en/stock/setup/item-group' }, -] +frappe.help.help_links["Tree/Item Group"] = [ + { + label: "Item Group", + url: docsUrl + "user/manual/en/stock/setup/item-group", + }, +]; -frappe.help.help_links['Form/Item Attribute'] = [ - { label: 'Item Attribute', url: docsUrl + 'user/manual/en/stock/setup/item-attribute' }, -] +frappe.help.help_links["Form/Item Attribute"] = [ + { + label: "Item Attribute", + url: docsUrl + "user/manual/en/stock/setup/item-attribute", + }, +]; -frappe.help.help_links['Form/UOM'] = [ - { label: 'Fractions in UOM', url: docsUrl + 'user/manual/en/stock/articles/managing-fractions-in-uom' }, -] +frappe.help.help_links["Form/UOM"] = [ + { + label: "Fractions in UOM", + url: + docsUrl + "user/manual/en/stock/articles/managing-fractions-in-uom", + }, +]; -frappe.help.help_links['Form/Stock Reconciliation'] = [ - { label: 'Opening Stock Entry', url: docsUrl + 'user/manual/en/stock/opening-stock' }, -] +frappe.help.help_links["Form/Stock Reconciliation"] = [ + { + label: "Opening Stock Entry", + url: docsUrl + "user/manual/en/stock/opening-stock", + }, +]; //CRM -frappe.help.help_links['Form/Lead'] = [ - { label: 'Lead', url: docsUrl + 'user/manual/en/CRM/lead' }, -] +frappe.help.help_links["Form/Lead"] = [ + { label: "Lead", url: docsUrl + "user/manual/en/CRM/lead" }, +]; -frappe.help.help_links['Form/Opportunity'] = [ - { label: 'Opportunity', url: docsUrl + 'user/manual/en/CRM/opportunity' }, -] +frappe.help.help_links["Form/Opportunity"] = [ + { label: "Opportunity", url: docsUrl + "user/manual/en/CRM/opportunity" }, +]; -frappe.help.help_links['Form/Address'] = [ - { label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' }, -] +frappe.help.help_links["Form/Address"] = [ + { label: "Address", url: docsUrl + "user/manual/en/CRM/address" }, +]; -frappe.help.help_links['Form/Contact'] = [ - { label: 'Contact', url: docsUrl + 'user/manual/en/CRM/contact' }, -] +frappe.help.help_links["Form/Contact"] = [ + { label: "Contact", url: docsUrl + "user/manual/en/CRM/contact" }, +]; -frappe.help.help_links['Form/Newsletter'] = [ - { label: 'Newsletter', url: docsUrl + 'user/manual/en/CRM/newsletter' }, -] +frappe.help.help_links["Form/Newsletter"] = [ + { label: "Newsletter", url: docsUrl + "user/manual/en/CRM/newsletter" }, +]; -frappe.help.help_links['Form/Campaign'] = [ - { label: 'Campaign', url: docsUrl + 'user/manual/en/CRM/setup/campaign' }, -] +frappe.help.help_links["Form/Campaign"] = [ + { label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" }, +]; -frappe.help.help_links['Tree/Sales Person'] = [ - { label: 'Sales Person', url: docsUrl + 'user/manual/en/CRM/setup/sales-person' }, -] +frappe.help.help_links["Tree/Sales Person"] = [ + { + label: "Sales Person", + url: docsUrl + "user/manual/en/CRM/setup/sales-person", + }, +]; -frappe.help.help_links['Form/Sales Person'] = [ - { label: 'Sales Person Target', url: docsUrl + 'user/manual/en/selling/setup/sales-person-target-allocation' }, -] +frappe.help.help_links["Form/Sales Person"] = [ + { + label: "Sales Person Target", + url: + docsUrl + + "user/manual/en/selling/setup/sales-person-target-allocation", + }, +]; //Support -frappe.help.help_links['List/Feedback Trigger'] = [ - { label: 'Feedback Trigger', url: docsUrl + 'user/manual/en/setting-up/feedback/setting-up-feedback' }, -] +frappe.help.help_links["List/Feedback Trigger"] = [ + { + label: "Feedback Trigger", + url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback", + }, +]; -frappe.help.help_links['List/Feedback Request'] = [ - { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, -] +frappe.help.help_links["List/Feedback Request"] = [ + { + label: "Feedback Request", + url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", + }, +]; -frappe.help.help_links['List/Feedback Request'] = [ - { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, -] +frappe.help.help_links["List/Feedback Request"] = [ + { + label: "Feedback Request", + url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", + }, +]; //Manufacturing -frappe.help.help_links['Form/BOM'] = [ - { label: 'Bill of Material', url: docsUrl + 'user/manual/en/manufacturing/bill-of-materials' }, - { label: 'Nested BOM Structure', url: docsUrl + 'user/manual/en/manufacturing/articles/nested-bom-structure' }, -] +frappe.help.help_links["Form/BOM"] = [ + { + label: "Bill of Material", + url: docsUrl + "user/manual/en/manufacturing/bill-of-materials", + }, + { + label: "Nested BOM Structure", + url: + docsUrl + + "user/manual/en/manufacturing/articles/nested-bom-structure", + }, +]; -frappe.help.help_links['Form/Work Order'] = [ - { label: 'Work Order', url: docsUrl + 'user/manual/en/manufacturing/work-order' }, -] +frappe.help.help_links["Form/Work Order"] = [ + { + label: "Work Order", + url: docsUrl + "user/manual/en/manufacturing/work-order", + }, +]; -frappe.help.help_links['Form/Workstation'] = [ - { label: 'Workstation', url: docsUrl + 'user/manual/en/manufacturing/workstation' }, -] +frappe.help.help_links["Form/Workstation"] = [ + { + label: "Workstation", + url: docsUrl + "user/manual/en/manufacturing/workstation", + }, +]; -frappe.help.help_links['Form/Operation'] = [ - { label: 'Operation', url: docsUrl + 'user/manual/en/manufacturing/operation' }, -] +frappe.help.help_links["Form/Operation"] = [ + { + label: "Operation", + url: docsUrl + "user/manual/en/manufacturing/operation", + }, +]; -frappe.help.help_links['Form/BOM Update Tool'] = [ - { label: 'BOM Update Tool', url: docsUrl + 'user/manual/en/manufacturing/tools/bom-update-tool' }, -] +frappe.help.help_links["Form/BOM Update Tool"] = [ + { + label: "BOM Update Tool", + url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool", + }, +]; //Customize -frappe.help.help_links['Form/Customize Form'] = [ - { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, - { label: 'Customize Field', url: docsUrl + 'user/manual/en/customize-erpnext/customize-form' }, -] +frappe.help.help_links["Form/Customize Form"] = [ + { + label: "Custom Field", + url: docsUrl + "user/manual/en/customize-erpnext/custom-field", + }, + { + label: "Customize Field", + url: docsUrl + "user/manual/en/customize-erpnext/customize-form", + }, +]; -frappe.help.help_links['Form/Custom Field'] = [ - { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, -] +frappe.help.help_links["Form/Custom Field"] = [ + { + label: "Custom Field", + url: docsUrl + "user/manual/en/customize-erpnext/custom-field", + }, +]; -frappe.help.help_links['Form/Custom Field'] = [ - { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, -] +frappe.help.help_links["Form/Custom Field"] = [ + { + label: "Custom Field", + url: docsUrl + "user/manual/en/customize-erpnext/custom-field", + }, +]; From f66aab6d98b7922b2327eaad1150348baa06aca7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 26 Mar 2021 16:40:51 +0530 Subject: [PATCH 0340/1264] fix: e-invoicing option visible even if settings disabled (#25021) --- erpnext/regional/india/e_invoice/einvoice.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index e8a7c30e19..cad2acd80e 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -1,7 +1,8 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.ui.form.on(doctype, { - refresh(frm) { - const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); + async refresh(frm) { + const { message } = await frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); + const einvoicing_enabled = cint(message.enable); const supply_type = frm.doc.gst_category; const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin; From efe2b425b15a36ca33b966fdcf2a232f2841a470 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 8 Mar 2021 20:58:57 +0530 Subject: [PATCH 0341/1264] feat(Production Plan): Consider Safety Stock in Required Qty Calculation --- .../material_request_plan_item.json | 9 ++++++++- .../doctype/production_plan/production_plan.json | 9 ++++++++- .../doctype/production_plan/production_plan.py | 13 +++++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index f93b244a50..88f8d6075d 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -15,6 +15,7 @@ "uom", "projected_qty", "actual_qty", + "safety_stock", "item_details", "description", "min_order_qty", @@ -129,11 +130,17 @@ "fieldtype": "Link", "label": "From Warehouse", "options": "Warehouse" + }, + { + "fetch_from": "item_code.safety_stock", + "fieldname": "safety_stock", + "fieldtype": "Float", + "label": "Safety Stock" } ], "istable": 1, "links": [], - "modified": "2020-02-03 12:22:29.913302", + "modified": "2021-03-08 18:39:17.553611", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 7daf7069f3..f11470086a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -32,6 +32,7 @@ "material_request_planning", "include_non_stock_items", "include_subcontracted_items", + "include_safety_stock", "ignore_existing_ordered_qty", "column_break_25", "for_warehouse", @@ -309,13 +310,19 @@ "fieldtype": "Select", "label": "Sales Order Status", "options": "\nTo Deliver and Bill\nTo Bill\nTo Deliver" + }, + { + "default": "0", + "fieldname": "include_safety_stock", + "fieldtype": "Check", + "label": "Include Safety Stock in Required Qty Calculation" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-10 18:01:54.991970", + "modified": "2021-03-08 11:17:25.470147", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 3833e86d27..730288be59 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -482,7 +482,7 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty, item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse, item.default_bom as default_bom, bom_item.description as description, - bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, + bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, item.safety_stock as safety_stock, item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor FROM `tabBOM Item` bom_item @@ -518,8 +518,8 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite include_non_stock_items, include_subcontracted_items, d.qty) return item_details -def get_material_request_items(row, sales_order, - company, ignore_existing_ordered_qty, warehouse, bin_dict): +def get_material_request_items(row, sales_order, company, + ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict): total_qty = row['qty'] required_qty = 0 @@ -543,6 +543,9 @@ def get_material_request_items(row, sales_order, if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"): required_qty = ceil(required_qty) + if include_safety_stock: + required_qty += flt(row['safety_stock']) + if required_qty > 0: return { 'item_code': row.item_code, @@ -660,6 +663,7 @@ def get_items_for_material_requests(doc, warehouses=None): company = doc.get('company') ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') + include_safety_stock = doc.get('include_safety_stock') so_item_details = frappe._dict() for data in po_items: @@ -711,6 +715,7 @@ def get_items_for_material_requests(doc, warehouses=None): 'description' : item_master.description, 'stock_uom' : item_master.stock_uom, 'conversion_factor' : conversion_factor, + 'safety_stock': item_master.safety_stock } ) @@ -732,7 +737,7 @@ def get_items_for_material_requests(doc, warehouses=None): if details.qty > 0: items = get_material_request_items(details, sales_order, company, - ignore_existing_ordered_qty, warehouse, bin_dict) + ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict) if items: mr_items.append(items) From 28a885a3a9e63f36311fcac252a4e4cc1b4608e0 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 22 Mar 2021 12:21:23 +0530 Subject: [PATCH 0342/1264] feat: show ordered and reserved qty in Material Request Plan Item table --- .../material_request_plan_item.json | 18 +++++++++++++++++- .../doctype/production_plan/production_plan.py | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 88f8d6075d..8d67827c81 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -15,6 +15,8 @@ "uom", "projected_qty", "actual_qty", + "ordered_qty", + "reserved_qty_for_production", "safety_stock", "item_details", "description", @@ -136,11 +138,25 @@ "fieldname": "safety_stock", "fieldtype": "Float", "label": "Safety Stock" + }, + { + "fieldname": "ordered_qty", + "fieldtype": "Float", + "label": "Ordered Qty", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "reserved_qty_for_production", + "fieldtype": "Float", + "label": "Reserved Qty for Production", + "no_copy": 1, + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-03-08 18:39:17.553611", + "modified": "2021-03-22 12:11:10.993737", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 730288be59..a15a806187 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -557,6 +557,8 @@ def get_material_request_items(row, sales_order, company, or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), 'actual_qty': bin_dict.get("actual_qty", 0), 'projected_qty': bin_dict.get("projected_qty", 0), + 'ordered_qty': bin_dict.get("ordered_qty", 0), + 'reserved_qty_for_production': bin_dict.get("reserved_qty_for_production", 0), 'min_order_qty': row['min_order_qty'], 'material_request_type': row.get("default_material_request_type"), 'sales_order': sales_order, From d74ff72527c842de603c879039ee9f6db51ec12c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Mar 2021 11:50:54 +0530 Subject: [PATCH 0343/1264] fix: Ordered and Reserved Qty for Production not getting fetched in Items --- .../manufacturing/doctype/production_plan/production_plan.js | 3 ++- .../manufacturing/doctype/production_plan/production_plan.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index b723387a09..cf892650c6 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -251,7 +251,8 @@ frappe.ui.form.on('Production Plan', { get_items_for_material_requests: function(frm, warehouses) { const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse', - 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type']; + 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty', + 'reserved_qty_for_production', 'material_request_type']; frappe.call({ method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a15a806187..dde7325a06 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -625,7 +625,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): """.format(lft, rgt, company) return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, - ifnull(sum(actual_qty),0) as actual_qty, warehouse from `tabBin` + ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty, + ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse from `tabBin` where item_code = %(item_code)s {conditions} group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) From 4cd68cdecbf6c3e7d7f34ba570ef64f587606816 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Mar 2021 12:33:21 +0530 Subject: [PATCH 0344/1264] feat: Add more fields to raw material download --- .../doctype/production_plan/production_plan.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index dde7325a06..60e4d88949 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -435,11 +435,13 @@ def download_raw_materials(doc): doc = frappe._dict(json.loads(doc)) item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', - 'projected Qty', 'Actual Qty']] + 'Projected Qty', 'Actual Qty', 'Ordered Qty', 'Reserved Qty for Production', + 'Safety Stock']] for d in get_items_for_material_requests(doc): item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'), - d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty')]) + d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), + d.get('reserved_qty_for_production'), d.get('safety_stock')]) if not doc.get('for_warehouse'): row = {'item_code': d.get('item_code')} @@ -448,7 +450,8 @@ def download_raw_materials(doc): continue item_list.append(['', '', '', '', bin_dict.get('warehouse'), - bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)]) + bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)], + bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)) build_csv_response(item_list, doc.name) @@ -555,6 +558,7 @@ def get_material_request_items(row, sales_order, company, 'stock_uom': row.get("stock_uom"), 'warehouse': warehouse or row.get('source_warehouse') \ or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), + 'safety_stock': row.safety_stock, 'actual_qty': bin_dict.get("actual_qty", 0), 'projected_qty': bin_dict.get("projected_qty", 0), 'ordered_qty': bin_dict.get("ordered_qty", 0), From 44853da7c2b081e0700f958059869352929a7387 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Mar 2021 13:39:35 +0530 Subject: [PATCH 0345/1264] feat: Show Required Qty as per BOM in Material Request Items --- .../material_request_plan_item.json | 14 ++++++++++++-- .../doctype/production_plan/production_plan.js | 2 +- .../doctype/production_plan/production_plan.py | 17 +++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 8d67827c81..6c60bbde86 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -11,6 +11,7 @@ "from_warehouse", "warehouse", "column_break_4", + "required_bom_qty", "quantity", "uom", "projected_qty", @@ -137,7 +138,9 @@ "fetch_from": "item_code.safety_stock", "fieldname": "safety_stock", "fieldtype": "Float", - "label": "Safety Stock" + "label": "Safety Stock", + "no_copy": 1, + "read_only": 1 }, { "fieldname": "ordered_qty", @@ -152,11 +155,18 @@ "label": "Reserved Qty for Production", "no_copy": 1, "read_only": 1 + }, + { + "fieldname": "required_bom_qty", + "fieldtype": "Float", + "label": "Required Qty as per BOM", + "no_copy": 1, + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-03-22 12:11:10.993737", + "modified": "2021-03-26 12:41:13.013149", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index cf892650c6..15ec6209c1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -251,7 +251,7 @@ frappe.ui.form.on('Production Plan', { get_items_for_material_requests: function(frm, warehouses) { const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse', - 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty', + 'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty', 'reserved_qty_for_production', 'material_request_type']; frappe.call({ diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 60e4d88949..2e6569faa1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -434,14 +434,14 @@ def download_raw_materials(doc): if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) - item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', + item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM', 'Projected Qty', 'Actual Qty', 'Ordered Qty', 'Reserved Qty for Production', - 'Safety Stock']] + 'Safety Stock', 'Required Qty']] for d in get_items_for_material_requests(doc): - item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'), - d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), - d.get('reserved_qty_for_production'), d.get('safety_stock')]) + item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('warehouse'), + d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), + d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')]) if not doc.get('for_warehouse'): row = {'item_code': d.get('item_code')} @@ -449,9 +449,9 @@ def download_raw_materials(doc): if d.get("warehouse") == bin_dict.get('warehouse'): continue - item_list.append(['', '', '', '', bin_dict.get('warehouse'), - bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)], - bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)) + item_list.append(['', '', '', bin_dict.get('warehouse'), '', + bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0), + bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)]) build_csv_response(item_list, doc.name) @@ -554,6 +554,7 @@ def get_material_request_items(row, sales_order, company, 'item_code': row.item_code, 'item_name': row.item_name, 'quantity': required_qty, + 'required_bom_qty': total_qty, 'description': row.description, 'stock_uom': row.get("stock_uom"), 'warehouse': warehouse or row.get('source_warehouse') \ From cd8422a840d97d748b0e1656f0da49ba6ea5647c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 6 Mar 2021 22:08:08 +0530 Subject: [PATCH 0346/1264] feat: recursive product discount --- .../doctype/pricing_rule/pricing_rule.json | 37 +++++++++++------- .../doctype/pricing_rule/pricing_rule.py | 1 + .../accounts/doctype/pricing_rule/utils.py | 38 +++++++++++-------- .../promotional_scheme/promotional_scheme.py | 2 +- .../promotional_scheme_product_discount.json | 15 +++++++- erpnext/controllers/taxes_and_totals.py | 5 ++- erpnext/public/js/controllers/transaction.js | 38 ++++++++++++------- 7 files changed, 90 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index d08a854142..81890d50b9 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -44,6 +44,14 @@ "column_break_21", "min_amt", "max_amt", + "product_discount_scheme_section", + "same_item", + "free_item", + "free_qty", + "free_item_rate", + "column_break_42", + "free_item_uom", + "is_recursive", "section_break_23", "valid_from", "valid_upto", @@ -62,13 +70,6 @@ "discount_amount", "discount_percentage", "for_price_list", - "product_discount_scheme_section", - "same_item", - "free_item", - "free_qty", - "column_break_51", - "free_item_uom", - "free_item_rate", "section_break_13", "threshold_percentage", "priority", @@ -459,10 +460,6 @@ "fieldtype": "Float", "label": "Qty" }, - { - "fieldname": "column_break_51", - "fieldtype": "Column Break" - }, { "fieldname": "free_item_uom", "fieldtype": "Link", @@ -553,19 +550,33 @@ "fieldname": "promotional_scheme", "fieldtype": "Link", "label": "Promotional Scheme", - "options": "Promotional Scheme" + "no_copy": 1, + "options": "Promotional Scheme", + "print_hide": 1, + "read_only": 1 }, { "description": "Simple Python Expression, Example: territory != 'All Territories'", "fieldname": "condition", "fieldtype": "Code", "label": "Condition" + }, + { + "fieldname": "column_break_42", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on", + "fieldname": "is_recursive", + "fieldtype": "Check", + "label": "Is Recursive" } ], "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2020-12-04 00:36:24.698219", + "modified": "2021-03-06 22:01:24.840422", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 05652642eb..9a3ea27621 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa "doctype": args.doctype, "has_margin": False, "name": args.name, + "free_item_data": [], "parent": args.parent, "parenttype": args.parenttype, "child_docname": args.get('child_docname') diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index d163335996..210cd16009 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -367,7 +367,7 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): if items and doc.get("items"): for row in doc.get('items'): - if row.get(apply_on) not in items: continue + if (row.get(apply_on) or args.get(apply_on)) not in items: continue if pr_doc.mixed_conditions: amt = args.get('qty') * args.get("price_list_rate") @@ -479,7 +479,7 @@ def apply_pricing_rule_on_transaction(doc): doc.calculate_taxes_and_totals() elif d.price_or_product_discount == 'Product': - item_details = frappe._dict({'parenttype': doc.doctype}) + item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []}) get_product_discount_rule(d, item_details, doc=doc) apply_pricing_rule_for_free_items(doc, item_details.free_item_data) doc.set_missing_values() @@ -508,9 +508,15 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): frappe.throw(_("Free item not set in the pricing rule {0}") .format(get_link_to_form("Pricing Rule", pricing_rule.name))) - item_details.free_item_data = { + qty = pricing_rule.free_qty or 1 + if pricing_rule.is_recursive: + transaction_qty = args.get('qty') if args else doc.total_qty + if transaction_qty: + qty = flt(transaction_qty) * qty + + free_item_data_args = { 'item_code': free_item, - 'qty': pricing_rule.free_qty or 1, + 'qty': qty, 'rate': pricing_rule.free_item_rate or 0, 'price_list_rate': pricing_rule.free_item_rate or 0, 'is_free_item': 1 @@ -519,24 +525,26 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): item_data = frappe.get_cached_value('Item', free_item, ['item_name', 'description', 'stock_uom'], as_dict=1) - item_details.free_item_data.update(item_data) - item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom - item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, - item_details.free_item_data['uom']).get("conversion_factor", 1) + free_item_data_args.update(item_data) + free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom + free_item_data_args['conversion_factor'] = get_conversion_factor(free_item, + free_item_data_args['uom']).get("conversion_factor", 1) if item_details.get("parenttype") == 'Purchase Order': - item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today() + free_item_data_args['schedule_date'] = doc.schedule_date if doc else today() if item_details.get("parenttype") == 'Sales Order': - item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() + free_item_data_args['delivery_date'] = doc.delivery_date if doc else today() + + item_details.free_item_data.append(free_item_data_args) def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): - if pricing_rule_args.get('item_code'): - items = [d.item_code for d in doc.items - if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item] + if pricing_rule_args: + items = tuple([d.item_code for d in doc.items if d.is_free_item]) - if not items: - doc.append('items', pricing_rule_args) + for args in pricing_rule_args: + if not items or args.get('item_code') not in items: + doc.append('items', args) def get_pricing_rule_items(pr_doc): apply_on_data = [] diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 89f7238a06..6e13f0694c 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -21,7 +21,7 @@ price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discoun 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule'] product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', - 'free_item_rate', 'same_item'] + 'free_item_rate', 'same_item', 'is_recursive'] class PromotionalScheme(Document): def validate(self): diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json index 72d53bfa01..3eab51510d 100644 --- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json +++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json @@ -1,10 +1,12 @@ { + "actions": [], "creation": "2019-03-24 14:48:59.649168", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "disable", + "apply_multiple_pricing_rules", "column_break_2", "rule_description", "section_break_1", @@ -25,7 +27,7 @@ "threshold_percentage", "column_break_15", "priority", - "apply_multiple_pricing_rules" + "is_recursive" ], "fields": [ { @@ -152,10 +154,19 @@ "fieldname": "apply_multiple_pricing_rules", "fieldtype": "Check", "label": "Apply Multiple Pricing Rules" + }, + { + "default": "0", + "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on", + "fieldname": "is_recursive", + "fieldtype": "Check", + "label": "Is Recursive" } ], + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-07-21 00:00:56.674284", + "links": [], + "modified": "2021-03-06 21:58:18.162346", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme Product Discount", diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 23541c1ba0..220c876cc2 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -111,7 +111,10 @@ class calculate_taxes_and_totals(object): item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) - item.discount_amount = item.rate_with_margin - item.rate + if not item.discount_amount: + item.discount_amount = item.rate_with_margin - item.rate + elif not item.discount_percentage: + item.rate -= item.discount_amount elif flt(item.price_list_rate) > 0: item.discount_amount = item.price_list_rate - item.rate elif flt(item.price_list_rate) > 0 and not item.discount_amount: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 7d90d2662e..6f577936f2 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -576,7 +576,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); if (d.free_item_data) { - me.apply_product_discount(d.free_item_data); + me.apply_product_discount(d); } }, () => { @@ -1163,7 +1163,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ calculate_stock_uom_rate: function(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); - item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor); + item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor); refresh_field("stock_uom_rate", item.name, item.parentfield); }, service_stop_date: function(frm, cdt, cdn) { @@ -1504,7 +1504,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } if (d.free_item_data) { - me.apply_product_discount(d.free_item_data); + me.apply_product_discount(d); } if (d.apply_rule_on_other_items) { @@ -1538,20 +1538,30 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - apply_product_discount: function(free_item_data) { - const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code - && d.is_free_item)) || []; + apply_product_discount: function(args) { + const items = this.frm.doc.items.filter(d => (d.is_free_item)) || []; - if (!items.length) { - let row_to_modify = frappe.model.add_child(this.frm.doc, - this.frm.doc.doctype + ' Item', 'items'); + const exist_items = items.map(row => row.item_code); - for (let key in free_item_data) { - row_to_modify[key] = free_item_data[key]; + args.free_item_data.forEach(pr_row => { + let row_to_modify = {}; + if (!items || !in_list(exist_items, pr_row.item_code)) { + + row_to_modify = frappe.model.add_child(this.frm.doc, + this.frm.doc.doctype + ' Item', 'items'); + + } else if(items) { + row_to_modify = items.filter(d => d.item_code === pr_row.item_code)[0]; } - } if (items && items.length && free_item_data) { - items[0].qty = free_item_data.qty - } + + for (let key in pr_row) { + row_to_modify[key] = pr_row[key]; + } + }); + + // free_item_data is a temporary variable + args.free_item_data = ''; + refresh_field('items'); }, apply_price_list: function(item, reset_plc_conversion) { From fce552b811983869358d2a8d1658853ee9889199 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 7 Mar 2021 11:43:22 +0530 Subject: [PATCH 0347/1264] fix: nonetype object has no attribute options --- .../promotional_scheme/promotional_scheme.py | 4 +- .../promotional_scheme_price_discount.json | 721 ++---------------- erpnext/public/js/controllers/transaction.js | 5 +- 3 files changed, 61 insertions(+), 669 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 6e13f0694c..e1725bc89e 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -18,10 +18,10 @@ other_fields = ['min_qty', 'max_qty', 'min_amt', 'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description'] price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate', - 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule'] + 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules'] product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', - 'free_item_rate', 'same_item', 'is_recursive'] + 'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules'] class PromotionalScheme(Document): def validate(self): diff --git a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json index 224b8de779..795fb1c6f4 100644 --- a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json +++ b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json @@ -1,792 +1,181 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, + "actions": [], "creation": "2019-03-24 14:48:59.649168", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "disable", + "apply_multiple_pricing_rules", + "column_break_2", + "rule_description", + "section_break_2", + "min_qty", + "max_qty", + "column_break_3", + "min_amount", + "max_amount", + "section_break_6", + "rate_or_discount", + "column_break_10", + "rate", + "discount_amount", + "discount_percentage", + "section_break_11", + "warehouse", + "threshold_percentage", + "validate_applied_rule", + "column_break_14", + "priority", + "apply_discount_on_rate" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "disable", "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": "Disable", - "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 + "label": "Disable" }, { - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "rule_description", "fieldtype": "Small Text", - "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": "Rule Description", - "length": 0, "no_copy": 1, - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "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, - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 1, "default": "0", "fieldname": "min_qty", "fieldtype": "Float", - "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": "Min Qty", - "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 + "label": "Min Qty" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 1, "default": "0", "fieldname": "max_qty", "fieldtype": "Float", - "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": "Max Qty", - "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 + "label": "Max Qty" }, { - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", "fieldname": "min_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": "Min 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 + "label": "Min Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "depends_on": "", "fieldname": "max_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": "Max 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 + "label": "Max Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "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, - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Discount Percentage", - "depends_on": "", "fieldname": "rate_or_discount", "fieldtype": "Select", - "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": "Discount Type", - "length": 0, - "no_copy": 0, - "options": "\nRate\nDiscount Percentage\nDiscount Amount", - "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 + "options": "\nRate\nDiscount Percentage\nDiscount Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "fieldname": "column_break_10", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 2, "depends_on": "eval:doc.rate_or_discount==\"Rate\"", "fieldname": "rate", "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": "Rate", - "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 + "label": "Rate" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.rate_or_discount==\"Discount Amount\"", "fieldname": "discount_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": "Discount 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 + "label": "Discount Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.rate_or_discount==\"Discount Percentage\"", "fieldname": "discount_percentage", "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": "Discount Percentage", - "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 + "label": "Discount Percentage" }, { - "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, - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "warehouse", "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": "Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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 + "options": "Warehouse" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "threshold_percentage", "fieldtype": "Percent", - "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": "Threshold for Suggestion", - "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 + "label": "Threshold for Suggestion" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "fieldname": "validate_applied_rule", "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": "Validate Applied Rule", - "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 + "label": "Validate Applied Rule" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_14", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "priority", "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": "Priority", - "length": 0, - "no_copy": 0, - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20", - "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 + "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "depends_on": "priority", "fieldname": "apply_multiple_pricing_rules", "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": "Apply Multiple Pricing Rules", - "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 + "label": "Apply Multiple Pricing Rules" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", "depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules", "fieldname": "apply_discount_on_rate", "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": "Apply Discount on Rate", - "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 + "label": "Apply Discount on Rate" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, + "index_web_pages_for_search": 1, "istable": 1, - "max_attachments": 0, - "modified": "2019-03-24 14:48:59.649168", + "links": [], + "modified": "2021-03-07 11:56:23.424137", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme Price Discount", - "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 + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 6f577936f2..d6d1e6f025 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1492,7 +1492,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(k=="price_list_rate") { if(flt(v) != flt(d.price_list_rate)) price_list_rate_changed = true; } - frappe.model.set_value(d.doctype, d.name, k, v); + + if (k !== 'free_item_data') { + frappe.model.set_value(d.doctype, d.name, k, v); + } } } From 0486276789eb517fbd20d70aa482798ca27e4803 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Mar 2021 18:49:54 +0530 Subject: [PATCH 0348/1264] fix: don't club same free item --- erpnext/accounts/doctype/pricing_rule/utils.py | 5 +++-- .../doctype/promotional_scheme/promotional_scheme.py | 2 +- erpnext/public/js/controllers/transaction.js | 7 ++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 210cd16009..c676abd4c6 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -517,6 +517,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): free_item_data_args = { 'item_code': free_item, 'qty': qty, + 'pricing_rules': pricing_rule.name, 'rate': pricing_rule.free_item_rate or 0, 'price_list_rate': pricing_rule.free_item_rate or 0, 'is_free_item': 1 @@ -540,10 +541,10 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): if pricing_rule_args: - items = tuple([d.item_code for d in doc.items if d.is_free_item]) + items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item]) for args in pricing_rule_args: - if not items or args.get('item_code') not in items: + if not items or (args.get('item_code'), args.get('pricing_rules')) not in items: doc.append('items', args) def get_pricing_rule_items(pr_doc): diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index e1725bc89e..523e9ee08a 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -12,7 +12,7 @@ from frappe.model.document import Document pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group' 'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from', 'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier', - 'supplier_group', 'company', 'currency'] + 'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules'] other_fields = ['min_qty', 'max_qty', 'min_amt', 'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description'] diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d6d1e6f025..4173beb576 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1544,17 +1544,18 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ apply_product_discount: function(args) { const items = this.frm.doc.items.filter(d => (d.is_free_item)) || []; - const exist_items = items.map(row => row.item_code); + const exist_items = items.map(row => (row.item_code, row.pricing_rules)); args.free_item_data.forEach(pr_row => { let row_to_modify = {}; - if (!items || !in_list(exist_items, pr_row.item_code)) { + if (!items || !in_list(exist_items, (pr_row.item_code, pr_row.pricing_rules))) { row_to_modify = frappe.model.add_child(this.frm.doc, this.frm.doc.doctype + ' Item', 'items'); } else if(items) { - row_to_modify = items.filter(d => d.item_code === pr_row.item_code)[0]; + row_to_modify = items.filter(d => (d.item_code === pr_row.item_code + && d.pricing_rules === pr_row.pricing_rules))[0]; } for (let key in pr_row) { From 256b9c7bf9b92efc1426605d3e1f71a5f4340a17 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 22 Mar 2021 23:36:48 +0530 Subject: [PATCH 0349/1264] fix: total weight not set for free items --- erpnext/controllers/accounts_controller.py | 3 ++- erpnext/stock/get_item_details.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e6b14c2e40..256437966b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -25,7 +25,8 @@ from erpnext.stock.doctype.packed_item.packed_item import make_packing_list class AccountMissingError(frappe.ValidationError): pass -force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") +force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", + "pricing_rules", "weight_per_unit", "weight_uom", "total_weight") class AccountsController(TransactionBase): def __init__(self, *args, **kwargs): diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 873cfec85e..70e4c2c40e 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -314,7 +314,9 @@ def get_basic_details(args, item, overwrite_warehouse=True): "last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0, "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), - "bom_no": item.get("default_bom") + "bom_no": item.get("default_bom"), + "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), + "weight_uom": args.get("weight_uom") or item.get("weight_uom") }) if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): @@ -369,6 +371,9 @@ def get_basic_details(args, item, overwrite_warehouse=True): if meta.get_field("barcode"): update_barcode_value(out) + if out.get("weight_per_unit"): + out['total_weight'] = out.weight_per_unit * out.stock_qty + return out def get_item_warehouse(item, args, overwrite_warehouse, defaults={}): From 096da429ed5581b488fb06daa79b27aedc352b2f Mon Sep 17 00:00:00 2001 From: Anupam Date: Sat, 27 Mar 2021 00:45:31 +0530 Subject: [PATCH 0350/1264] fix: unable to submit stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 64dcbed1d8..a4d4b5a07a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -849,7 +849,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ } erpnext.hide_company(); erpnext.utils.add_item(this.frm); - this.frm.trigger('add_to_transit'); }, scan_barcode: function() { From 5d994d8aa1f6c281212f9d37ada256eb6555421f Mon Sep 17 00:00:00 2001 From: Anupam Date: Sat, 27 Mar 2021 00:54:19 +0530 Subject: [PATCH 0351/1264] fix: unable to submit stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 274bbc28b1..4d1a71be8f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -817,7 +817,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ } erpnext.hide_company(); erpnext.utils.add_item(this.frm); - this.frm.trigger('add_to_transit'); }, scan_barcode: function() { From d429138dcc0600d2a2b9b72c1922c00999463391 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sat, 27 Mar 2021 17:09:39 +0530 Subject: [PATCH 0352/1264] fix: added flag for dont_fetch_price_list_rate in transaction --- erpnext/public/js/controllers/transaction.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 4173beb576..e0b0b272f3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1132,6 +1132,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.calculate_net_weight(); } + // for handling customization not to fetch price list rate + if(frappe.flags.dont_fetch_price_list_rate) { + return + } + if (!dont_fetch_price_list_rate && frappe.meta.has_field(doc.doctype, "price_list_currency")) { this.apply_price_list(item, true); From b38ea0cc8be3ee2d4693e2a5bd10c5c863a5ca04 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 28 Mar 2021 15:51:32 +0530 Subject: [PATCH 0353/1264] fix: patch (#25014) * fix: patch * fix: pricing_rule test cases --- .../doctype/pricing_rule/test_pricing_rule.py | 16 ++++++++++++++++ .../doctype/sales_invoice/test_sales_invoice.py | 4 +++- erpnext/controllers/stock_controller.py | 2 +- erpnext/controllers/taxes_and_totals.py | 8 +++++--- .../item_reposting_for_incorrect_sl_and_gl.py | 13 ++++++++++--- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index f28cee7c5a..ef9aad562d 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -328,6 +328,21 @@ class TestPricingRule(unittest.TestCase): self.assertEquals(item.discount_amount, 110) self.assertEquals(item.rate, 990) + def test_pricing_rule_with_margin_and_discount_amount(self): + frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') + make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10, + rate_or_discount="Discount Amount", discount_amount=110) + si = create_sales_invoice(do_not_save=True) + si.items[0].price_list_rate = 1000 + si.payment_schedule = [] + si.insert(ignore_permissions=True) + + item = si.items[0] + self.assertEquals(item.margin_rate_or_amount, 10) + self.assertEquals(item.rate_with_margin, 1100) + self.assertEquals(item.discount_amount, 110) + self.assertEquals(item.rate, 990) + def test_pricing_rule_for_product_discount_on_same_item(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') test_record = { @@ -560,6 +575,7 @@ def make_pricing_rule(**args): "margin_rate_or_amount": args.margin_rate_or_amount or 0.0, "condition": args.condition or '', "priority": 1, + "discount_amount": args.discount_amount or 0.0, "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 }) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 7cd1828343..979231a101 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1166,10 +1166,12 @@ class TestSalesInvoice(unittest.TestCase): def test_create_so_with_margin(self): si = create_sales_invoice(item_code="_Test Item", qty=1, do_not_submit=True) - price_list_rate = 100 + price_list_rate = flt(100) * flt(si.plc_conversion_rate) si.items[0].price_list_rate = price_list_rate si.items[0].margin_type = 'Percentage' si.items[0].margin_rate_or_amount = 25 + si.items[0].discount_amount = 0.0 + si.items[0].discount_percentage = 0.0 si.save() self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate)) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index cb44b73caf..f92e8849f6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -405,7 +405,7 @@ class StockController(AccountsController): def set_rate_of_stock_uom(self): if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]: for d in self.get("items"): - d.stock_uom_rate = d.rate / d.conversion_factor + d.stock_uom_rate = d.rate / (d.conversion_factor or 1) def validate_internal_transfer(self): if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \ diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 220c876cc2..f976b17ae6 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -111,10 +111,12 @@ class calculate_taxes_and_totals(object): item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) - if not item.discount_amount: + + if item.discount_amount and not item.discount_percentage: + item.rate = item.rate_with_margin - item.discount_amount + else: item.discount_amount = item.rate_with_margin - item.rate - elif not item.discount_percentage: - item.rate -= item.discount_amount + elif flt(item.price_list_rate) > 0: item.discount_amount = item.price_list_rate - item.rate elif flt(item.price_list_rate) > 0 and not item.discount_amount: diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index d968e1fb76..021bb72cae 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -20,9 +20,11 @@ def execute(): frappe.clear_cache() frappe.flags.warehouse_account_map = {} + company_list = [] + data = frappe.db.sql(''' SELECT - name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time + name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time, company FROM `tabStock Ledger Entry` WHERE @@ -36,6 +38,9 @@ def execute(): total_sle = len(data) i = 0 for d in data: + if d.company not in company_list: + company_list.append(d.company) + update_entries_after({ "item_code": d.item_code, "warehouse": d.warehouse, @@ -53,8 +58,10 @@ def execute(): print("Reposting General Ledger Entries...") - for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): - update_gl_entries_after(posting_date, posting_time, company=row.name) + if data: + for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): + if row.name in company_list: + update_gl_entries_after(posting_date, posting_time, company=row.name) frappe.db.auto_commit_on_many_writes = 0 From ceb026c5abf9509aad211154eeb05dd8689bd6ec Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sun, 28 Mar 2021 16:14:56 +0550 Subject: [PATCH 0354/1264] bumped to version 13.0.0-beta.14 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index b122e5fa11..78e87c8869 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.13' +__version__ = '13.0.0-beta.14' def get_default_company(user=None): '''Get default company for user''' From c2548ddc75b067714e71cab30bc2639cfc8949bc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 28 Jan 2021 21:23:16 +0530 Subject: [PATCH 0355/1264] feat: Normal rounding for GST Taxes --- erpnext/controllers/taxes_and_totals.py | 19 ++ erpnext/hooks.py | 1 + .../public/js/controllers/taxes_and_totals.js | 30 +- .../doctype/gst_settings/gst_settings.json | 290 +++++------------- erpnext/regional/india/utils.py | 21 ++ 5 files changed, 147 insertions(+), 214 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 6c7eb92221..0ab4bf0e23 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -15,6 +15,8 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_ra class calculate_taxes_and_totals(object): def __init__(self, doc): self.doc = doc + frappe.flags.round_off_applicable_accounts = [] + get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts) self.calculate() def calculate(self): @@ -335,10 +337,18 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty + current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount) self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount + def get_final_current_tax_amount(self, tax, current_tax_amount): + # Some countries need individual tax components to be rounded + # Handeled via regional doctypess + if tax.account_head in frappe.flags.round_off_applicable_accounts: + current_tax_amount = round(current_tax_amount, 0) + return current_tax_amount + def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount): # store tax breakup for each item key = item.item_code or item.item_name @@ -696,6 +706,15 @@ def get_itemised_tax_breakup_html(doc): ) ) +@frappe.whitelist() +def get_round_off_applicable_accounts(company, account_list): + account_list = get_regional_round_off_accounts(company, account_list) + + return account_list + +@erpnext.allow_regional +def get_regional_round_off_accounts(company, account_list): + pass @erpnext.allow_regional def update_itemised_tax_data(doc): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index fe80c6585d..88734cf317 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -401,6 +401,7 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header', '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.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts', '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.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 22e75780b8..ad19e6f91a 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -2,7 +2,9 @@ // License: GNU General Public License v3. See license.txt erpnext.taxes_and_totals = erpnext.payments.extend({ - setup: function() {}, + setup: function() { + this.fetch_round_off_accounts(); + }, apply_pricing_rule_on_item: function(item){ let effective_item_rate = item.price_list_rate; @@ -151,6 +153,22 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }); }, + fetch_round_off_accounts: function() { + let me = this; + frappe.flags.round_off_applicable_accounts = []; + + return frappe.call({ + "method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts", + "args": { + "company": me.frm.doc.company, + "account_list": frappe.flags.round_off_applicable_accounts + }, + callback: function(r) { + frappe.flags.round_off_applicable_accounts.push(...r.message); + } + }); + }, + determine_exclusive_rate: function() { var me = this; @@ -371,11 +389,21 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ } else if (tax.charge_type == "On Item Quantity") { current_tax_amount = tax_rate * item.qty; } + + current_tax_amount = this.get_final_tax_amount(tax, current_tax_amount); this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); return current_tax_amount; }, + get_final_tax_amount: function(tax, current_tax_amount) { + if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) { + current_tax_amount = Math.round(current_tax_amount); + } + + return current_tax_amount + }, + set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) { // store tax breakup for each item let tax_detail = tax.item_wise_tax_detail; diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.json b/erpnext/regional/doctype/gst_settings/gst_settings.json index 98c33ad33b..95b930c4c8 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.json +++ b/erpnext/regional/doctype/gst_settings/gst_settings.json @@ -1,222 +1,86 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-06-27 15:09:01.318003", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2017-06-27 15:09:01.318003", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "gst_summary", + "column_break_2", + "round_off_gst_values", + "gstin_email_sent_on", + "section_break_4", + "gst_accounts", + "b2c_limit" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gst_summary", - "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, - "label": "GST Summary", - "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, - "unique": 0 - }, + "fieldname": "gst_summary", + "fieldtype": "HTML", + "label": "GST Summary", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 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, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gstin_email_sent_on", - "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": "GSTIN Email Sent On", - "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, - "unique": 0 - }, + "fieldname": "gstin_email_sent_on", + "fieldtype": "Date", + "label": "GSTIN Email Sent On", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "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, - "unique": 0 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gst_accounts", - "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": "GST Accounts", - "length": 0, - "no_copy": 0, - "options": "GST 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, - "unique": 0 - }, + "fieldname": "gst_accounts", + "fieldtype": "Table", + "label": "GST Accounts", + "options": "GST Account", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "250000", - "description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.", - "fieldname": "b2c_limit", - "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": "B2C Limit", - "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 + "default": "250000", + "description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.", + "fieldname": "b2c_limit", + "fieldtype": "Data", + "in_list_view": 1, + "label": "B2C Limit", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Enabling this option will round off individual GST components in all the Invoices", + "fieldname": "round_off_gst_values", + "fieldtype": "Check", + "label": "Round Off GST Values", + "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": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-02-14 08:14:15.375181", - "modified_by": "Administrator", - "module": "Regional", - "name": "GST Settings", - "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 + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-01-28 17:19:47.969260", + "modified_by": "Administrator", + "module": "Regional", + "name": "GST Settings", + "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/regional/india/utils.py b/erpnext/regional/india/utils.py index fd1d5e8bf9..3757f6384f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -782,3 +782,24 @@ def get_gst_tax_amount(doc): gst_tax += tax.tax_amount_after_discount_amount return gst_tax, base_gst_tax + +@frappe.whitelist() +def get_regional_round_off_accounts(company, account_list): + country = frappe.get_cached_value('Company', company, 'country') + + if country != 'India': + return + + if isinstance(account_list, string_types): + account_list = json.loads(account_list) + + if not frappe.db.get_single_value('GST Settings', 'round_off_gst_values'): + return + + gst_accounts = get_gst_accounts(company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + account_list.extend(gst_account_list) + + return account_list From 237033704f2c903908a9a6b633a4974ded8f35f3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 Jan 2021 17:08:52 +0530 Subject: [PATCH 0356/1264] fix: Add test case --- erpnext/__init__.py | 2 +- .../public/js/controllers/taxes_and_totals.js | 2 +- .../gstr_3b_report/test_gstr_3b_report.py | 67 ++++++++++++++----- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index b122e5fa11..5ad34ea72a 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -109,7 +109,7 @@ def get_region(company=None): ''' if company or frappe.flags.company: return frappe.get_cached_value('Company', - company or frappe.flags.company, 'country') + company or frappe.flags.company, 'country') elif frappe.flags.country: return frappe.flags.country else: diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index ad19e6f91a..9f9ec04342 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -401,7 +401,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ current_tax_amount = Math.round(current_tax_amount); } - return current_tax_amount + return current_tax_amount; }, set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) { diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 8174da20cb..023b4ed22b 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -14,8 +14,20 @@ import json test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"] class TestGSTR3BReport(unittest.TestCase): - def test_gstr_3b_report(self): + def setUp(self): + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'") + frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'") + frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'") + + make_company() + make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000}) + set_account_heads() + make_customers() + make_suppliers() + + def test_gstr_3b_report(self): month_number_mapping = { 1: "January", 2: "February", @@ -31,17 +43,6 @@ class TestGSTR3BReport(unittest.TestCase): 12: "December" } - frappe.set_user("Administrator") - - frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'") - frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'") - frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'") - - make_company() - make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000}) - set_account_heads() - make_customers() - make_suppliers() make_sales_invoice() create_purchase_invoices() @@ -67,6 +68,42 @@ class TestGSTR3BReport(unittest.TestCase): self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50) self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50) + def test_gst_rounding(self): + gst_settings = frappe.get_doc('GST Settings') + gst_settings.round_off_gst_values = 1 + gst_settings.save() + + current_country = frappe.flags.country + frappe.flags.country = 'India' + + si = create_sales_invoice(company="_Test Company GST", + customer = '_Test GST Customer', + currency = 'INR', + warehouse = 'Finished Goods - _GST', + debit_to = 'Debtors - _GST', + income_account = 'Sales - _GST', + expense_account = 'Cost of Goods Sold - _GST', + cost_center = 'Main - _GST', + rate=216, + do_not_save=1 + ) + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "IGST - _GST", + "cost_center": "Main - _GST", + "description": "IGST @ 18.0", + "rate": 18 + }) + + si.save() + # Check for 39 instead of 38.88 + self.assertEqual(si.taxes[0].base_tax_amount_after_discount_amount, 39) + + frappe.flags.country = current_country + gst_settings.round_off_gst_values = 1 + gst_settings.save() + def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", customer = '_Test GST Customer', @@ -145,7 +182,6 @@ def make_sales_invoice(): si3.submit() def create_purchase_invoices(): - pi = make_purchase_invoice( company="_Test Company GST", supplier = '_Test Registered Supplier', @@ -193,7 +229,6 @@ def create_purchase_invoices(): pi1.submit() def make_suppliers(): - if not frappe.db.exists("Supplier", "_Test Registered Supplier"): frappe.get_doc({ "supplier_group": "_Test Supplier Group", @@ -257,7 +292,6 @@ def make_suppliers(): address.save() def make_customers(): - if not frappe.db.exists("Customer", "_Test GST Customer"): frappe.get_doc({ "customer_group": "_Test Customer Group", @@ -354,9 +388,9 @@ def make_customers(): address.save() def make_company(): - if frappe.db.exists("Company", "_Test Company GST"): return + company = frappe.new_doc("Company") company.company_name = "_Test Company GST" company.abbr = "_GST" @@ -388,7 +422,6 @@ def make_company(): address.save() def set_account_heads(): - gst_settings = frappe.get_doc("GST Settings") gst_account = frappe.get_all( From 29997414add75b48e6b7bc1defbb6e22b51f4f0e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 29 Mar 2021 19:49:52 +0530 Subject: [PATCH 0357/1264] fix: Add GST category field in Dellivery Note --- erpnext/regional/india/setup.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index ee49aae050..cbf74e0865 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -149,6 +149,13 @@ def make_custom_fields(update=True): fetch_if_empty=1), ] + delivery_note_gst_category = [ + dict(fieldname='gst_category', label='GST Category', + fieldtype='Select', insert_after='gst_vehicle_type', print_hide=1, + options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', + fetch_from='customer.gst_category', fetch_if_empty=1), + ] + invoice_gst_fields = [ dict(fieldname='invoice_copy', label='Invoice Copy', fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1, @@ -273,7 +280,7 @@ def make_custom_fields(update=True): 'allow_on_submit': 1, 'insert_after': 'customer_name_in_arabic', 'translatable': 0, - } + } ] si_ewaybill_fields = [ @@ -431,7 +438,7 @@ def make_custom_fields(update=True): 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields, - 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields, + 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, 'Sales Order': sales_invoice_gst_fields, 'Tax Category': inter_state_gst_field, 'Item': [ From f954ec7c7849bcad7b211e830b27b67eed93ddbf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 29 Mar 2021 20:31:11 +0530 Subject: [PATCH 0358/1264] fix: Add patch for GST category field in Delivery Note --- erpnext/patches.txt | 1 + .../add_gst_category_in_delivery_note.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 erpnext/patches/v12_0/add_gst_category_in_delivery_note.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 46f0d4ae79..40276fcc74 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -762,3 +762,4 @@ erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record +erpnext.patches.v12_0.add_gst_category_in_delivery_note diff --git a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py new file mode 100644 index 0000000000..1208222504 --- /dev/null +++ b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Delivery Note': [ + dict(fieldname='gst_category', label='GST Category', + fieldtype='Select', insert_after='gst_vehicle_type', print_hide=1, + options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', + fetch_from='customer.gst_category', fetch_if_empty=1), + ] + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file From 7f43c051df43f29e537e2157081b53985846d107 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 29 Mar 2021 21:18:26 +0530 Subject: [PATCH 0359/1264] fix(Italy): setup, validations, optimisations --- erpnext/regional/italy/sales_invoice.js | 13 +--- erpnext/regional/italy/setup.py | 7 +- erpnext/regional/italy/utils.py | 98 ++++++++++++++----------- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js index 586a52937b..b54ac53812 100644 --- a/erpnext/regional/italy/sales_invoice.js +++ b/erpnext/regional/italy/sales_invoice.js @@ -11,15 +11,10 @@ erpnext.setup_e_invoice_button = (doctype) => { callback: function(r) { frm.reload_doc(); if(r.message) { - var w = window.open( - frappe.urllib.get_full_url( - "/api/method/erpnext.regional.italy.utils.download_e_invoice_file?" - + "file_name=" + r.message - ) - ) - if (!w) { - frappe.msgprint(__("Please enable pop-ups")); return; - } + open_url_post(frappe.request.url, { + cmd: 'frappe.core.doctype.file.file.download_file', + file_url: r.message + }); } } }); diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 6ab73413df..309aedfb0d 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -128,11 +128,8 @@ def make_custom_fields(update=True): fetch_from="company.vat_collectability"), dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing', fieldtype='Section Break', insert_after='pos_total_qty', print_hide=1), - dict(fieldname='company_tax_id', label='Company Tax ID', - fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1, - fetch_from="company.tax_id"), dict(fieldname='company_fiscal_code', label='Company Fiscal Code', - fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1, + fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1, fetch_from="company.fiscal_code"), dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime', fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1, @@ -219,4 +216,4 @@ def add_permissions(): update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1) add_permission(doctype, 'Accounts Manager', 1) update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1) - update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1) \ No newline at end of file + update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1) diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 6842fb2a61..08573cddcd 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals -import frappe, json, os +import io +import json +import frappe from frappe.utils import flt, cstr from erpnext.controllers.taxes_and_totals import get_itemised_tax from frappe import _ @@ -28,20 +30,22 @@ def update_itemised_tax_data(doc): @frappe.whitelist() def export_invoices(filters=None): - saved_xmls = [] + frappe.has_permission('Sales Invoice', throw=True) - invoices = frappe.get_all("Sales Invoice", filters=get_conditions(filters), fields=["*"]) + invoices = frappe.get_all( + "Sales Invoice", + filters=get_conditions(filters), + fields=["name", "company_tax_id"] + ) - for invoice in invoices: - attachments = get_e_invoice_attachments(invoice) - saved_xmls += [attachment.file_name for attachment in attachments] + attachments = get_e_invoice_attachments(invoices) - zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S")) + zip_filename = "{0}-einvoices.zip".format( + frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S")) - download_zip(saved_xmls, zip_filename) + download_zip(attachments, zip_filename) -@frappe.whitelist() def prepare_invoice(invoice, progressive_number): #set company information company = frappe.get_doc("Company", invoice.company) @@ -98,7 +102,7 @@ def prepare_invoice(invoice, progressive_number): def get_conditions(filters): filters = json.loads(filters) - conditions = {"docstatus": 1} + conditions = {"docstatus": 1, "company_tax_id": ("!=", "")} if filters.get("company"): conditions["company"] = filters["company"] if filters.get("customer"): conditions["customer"] = filters["customer"] @@ -111,23 +115,22 @@ def get_conditions(filters): return conditions -#TODO: Use function from frappe once PR #6853 is merged. + def download_zip(files, output_filename): - from zipfile import ZipFile + import zipfile - input_files = [frappe.get_site_path('private', 'files', filename) for filename in files] - output_path = frappe.get_site_path('private', 'files', output_filename) + zip_stream = io.BytesIO() + with zipfile.ZipFile(zip_stream, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for file in files: + file_path = frappe.utils.get_files_path( + file.file_name, is_private=file.is_private) - with ZipFile(output_path, 'w') as output_zip: - for input_file in input_files: - output_zip.write(input_file, arcname=os.path.basename(input_file)) - - with open(output_path, 'rb') as fileobj: - filedata = fileobj.read() + zip_file.write(file_path, arcname=file.file_name) frappe.local.response.filename = output_filename - frappe.local.response.filecontent = filedata + frappe.local.response.filecontent = zip_stream.getvalue() frappe.local.response.type = "download" + zip_stream.close() def get_invoice_summary(items, taxes): summary_data = frappe._dict() @@ -307,23 +310,12 @@ def prepare_and_attach_invoice(doc, replace=False): @frappe.whitelist() def generate_single_invoice(docname): doc = frappe.get_doc("Sales Invoice", docname) - + frappe.has_permission("Sales Invoice", doc=doc, throw=True) e_invoice = prepare_and_attach_invoice(doc, True) + return e_invoice.file_url - return e_invoice.file_name - -@frappe.whitelist() -def download_e_invoice_file(file_name): - content = None - with open(frappe.get_site_path('private', 'files', file_name), "r") as f: - content = f.read() - - frappe.local.response.filename = file_name - frappe.local.response.filecontent = content - frappe.local.response.type = "download" - -#Delete e-invoice attachment on cancel. +# Delete e-invoice attachment on cancel. def sales_invoice_on_cancel(doc, method): if get_company_country(doc.company) not in ['Italy', 'Italia', 'Italian Republic', 'Repubblica Italiana']: @@ -335,16 +327,38 @@ def sales_invoice_on_cancel(doc, method): def get_company_country(company): return frappe.get_cached_value('Company', company, 'country') -def get_e_invoice_attachments(invoice): - if not invoice.company_tax_id: - return [] +def get_e_invoice_attachments(invoices): + if not isinstance(invoices, list): + if not invoices.company_tax_id: + return + + invoices = [invoices] + + tax_id_map = { + invoice.name: ( + invoice.company_tax_id + if invoice.company_tax_id.startswith("IT") + else "IT" + invoice.company_tax_id + ) for invoice in invoices + } + + attachments = frappe.get_all( + "File", + fields=("name", "file_name", "attached_to_name", "is_private"), + filters= { + "attached_to_name": ('in', tax_id_map), + "attached_to_doctype": 'Sales Invoice' + } + ) out = [] - attachments = get_attachments(invoice.doctype, invoice.name) - company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id - for attachment in attachments: - if attachment.file_name and attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"): + if ( + attachment.file_name + and attachment.file_name.endswith(".xml") + and attachment.file_name.startswith( + tax_id_map.get(attachment.attached_to_name)) + ): out.append(attachment) return out From 170d2efd3575b273be9dba26639650bb58d598f2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 30 Mar 2021 11:02:46 +0530 Subject: [PATCH 0360/1264] fix(Payroll): Exchange Rate not getting set in Salary Slip (#25013) --- .../doctype/salary_slip/salary_slip.js | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index 7460c75227..d5278393a1 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -74,43 +74,46 @@ frappe.ui.form.on("Salary Slip", { if (!frm.doc.letter_head && company.default_letter_head) { frm.set_value('letter_head', company.default_letter_head); } + }, + + currency: function(frm) { frm.trigger("set_dynamic_labels"); }, set_dynamic_labels: function(frm) { var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency"); - frappe.run_serially([ - () => frm.events.set_exchange_rate(frm, company_currency), - () => frm.events.change_form_labels(frm, company_currency), - () => frm.events.change_grid_labels(frm), - () => frm.refresh_fields() - ]); + if (frm.doc.employee && frm.doc.currency) { + frappe.run_serially([ + () => frm.events.set_exchange_rate(frm, company_currency), + () => frm.events.change_form_labels(frm, company_currency), + () => frm.events.change_grid_labels(frm), + () => frm.refresh_fields() + ]); + } }, set_exchange_rate: function(frm, company_currency) { - if (frm.doc.docstatus === 0) { - if (frm.doc.currency) { - var from_currency = frm.doc.currency; - if (from_currency != company_currency) { - frm.events.hide_loan_section(frm); - frappe.call({ - method: "erpnext.setup.utils.get_exchange_rate", - args: { - from_currency: from_currency, - to_currency: company_currency, - }, - callback: function(r) { - frm.set_value("exchange_rate", flt(r.message)); - frm.set_df_property('exchange_rate', 'hidden', 0); - frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency - + " = [?] " + company_currency); - } - }); - } else { - frm.set_value("exchange_rate", 1.0); - frm.set_df_property('exchange_rate', 'hidden', 1); - frm.set_df_property("exchange_rate", "description", "" ); - } + if (frm.doc.currency) { + var from_currency = frm.doc.currency; + if (from_currency != company_currency) { + frm.events.hide_loan_section(frm); + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency: from_currency, + to_currency: company_currency, + }, + callback: function(r) { + frm.set_value("exchange_rate", flt(r.message)); + frm.set_df_property("exchange_rate", "hidden", 0); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + + " = [?] " + company_currency); + } + }); + } else { + frm.set_value("exchange_rate", 1.0); + frm.set_df_property("exchange_rate", "hidden", 1); + frm.set_df_property("exchange_rate", "description", ""); } } }, From 5f4a2bfc61b8f2151f7ecd7f0ddd1ca5d1495392 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 16 Mar 2021 20:08:51 +0530 Subject: [PATCH 0361/1264] fix(Non Profit): Membership and Donation API fixes (#24900) * fix: Donation fixes - differentiate between subscription payment and payment - issue with donation amount * fix: existing membership validation * fix: ignore subscription payments while capturing donations --- erpnext/non_profit/doctype/donation/donation.py | 6 +++++- erpnext/non_profit/doctype/membership/membership.py | 4 ++-- .../tax_exemption_80g_certificate.py | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index e947588482..6a2a06dbc8 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -91,6 +91,10 @@ def capture_razorpay_donations(*args, **kwargs): if not data.event == 'payment.captured': return + # to avoid capturing subscription payments as donations + if payment.description and 'subscription' in str(payment.description).lower(): + return + donor = get_donor(payment.email) if not donor: donor = create_donor(payment) @@ -119,7 +123,7 @@ def create_donation(donor, payment): 'donor_name': donor.donor_name, 'email': donor.email, 'date': getdate(), - 'amount': flt(payment.amount), + 'amount': flt(payment.amount) / 100, # Convert to rupees from paise 'mode_of_payment': payment.method, 'razorpay_payment_id': payment.id }).insert(ignore_mandatory=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 191281f4ce..c41a2f5165 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and not frappe.session.user == "Administrator": + if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -287,7 +287,7 @@ def trigger_razorpay_subscription(*args, **kwargs): membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: - message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id) + message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) return { "status": "Failed", "reason": e} diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index d734a18c3a..ef384d4602 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -29,7 +29,10 @@ class TaxExemption80GCertificate(Document): def validate_duplicates(self): if self.recipient == 'Donor': - certificate = frappe.db.exists(self.doctype, {'donation': self.donation}) + certificate = frappe.db.exists(self.doctype, { + 'donation': self.donation, + 'name': ('!=', self.name) + }) if certificate: frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format( get_link_to_form(self.doctype, certificate), frappe.bold(self.donation) From 146cbb10f8f1e033c78d91e71da5b1b803882e73 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 17 Mar 2021 19:49:27 +0530 Subject: [PATCH 0362/1264] fix: calculate 80g certificate amount on validate for memberships (#24925) --- .../tax_exemption_80g_certificate.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index ef384d4602..5bbd5750f9 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -16,6 +16,7 @@ class TaxExemption80GCertificate(Document): self.validate_duplicates() self.validate_company_details() self.set_company_address() + self.calculate_total() self.set_title() def validate_date(self): @@ -54,8 +55,17 @@ class TaxExemption80GCertificate(Document): self.company_address = address.company_address self.company_address_display = address.company_address_display + def calculate_total(self): + if self.recipient == 'Donor': + return + + total = 0 + for entry in self.payments: + total += flt(entry.amount) + self.total = total + def set_title(self): - if self.recipient == "Member": + if self.recipient == 'Member': self.title = self.member_name else: self.title = self.donor_name From a54eecb230748b4541f02b71b7a5f9a5d086d219 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 20 Mar 2021 22:22:01 +0530 Subject: [PATCH 0363/1264] fix: membership renewal validation (#24963) --- erpnext/non_profit/doctype/membership/membership.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index c41a2f5165..52447e4386 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": + if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -90,6 +90,7 @@ class Membership(Document): self.validate_membership_type_and_settings(plan, settings) invoice = make_invoice(self, member, plan, settings) + self.reload() self.invoice = invoice.name if with_payment_entry: @@ -284,6 +285,7 @@ def trigger_razorpay_subscription(*args, **kwargs): settings = frappe.get_doc("Non Profit Settings") if settings.allow_invoicing and settings.automate_membership_invoicing: + membership.reload() membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: From 9be11afbdee128e422efd20e55460ab4755ff631 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 30 Mar 2021 12:09:46 +0530 Subject: [PATCH 0364/1264] feat: price margin in buying (#25058) --- .../doctype/pricing_rule/pricing_rule.json | 1 - .../purchase_invoice_item.json | 55 +++++++++++++++++- .../sales_invoice_item.json | 3 +- .../purchase_order_item.json | 58 +++++++++++++++++-- erpnext/controllers/taxes_and_totals.py | 2 +- erpnext/public/js/controllers/buying.js | 23 -------- erpnext/public/js/controllers/transaction.js | 44 ++++++++++++-- .../quotation_item/quotation_item.json | 3 +- .../sales_order_item/sales_order_item.json | 3 +- erpnext/selling/sales_common.js | 34 ----------- .../delivery_note_item.json | 3 +- .../purchase_receipt_item.json | 56 +++++++++++++++++- 12 files changed, 207 insertions(+), 78 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 81890d50b9..428989aa96 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -358,7 +358,6 @@ "reqd": 1 }, { - "depends_on": "eval: doc.selling == 1", "fieldname": "margin", "fieldtype": "Section Break", "label": "Margin" 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 07e75acb41..96ad0fd785 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -28,10 +28,16 @@ "stock_qty", "sec_break1", "price_list_rate", - "discount_percentage", - "discount_amount", "col_break3", "base_price_list_rate", + "section_break_26", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_30", + "discount_percentage", + "discount_amount", + "base_rate_with_margin", "sec_break2", "rate", "amount", @@ -789,6 +795,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 }, @@ -799,12 +806,54 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_26", + "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", + "read_only": 1 + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "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 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:43:21.488258", + "modified": "2021-02-23 00:59:52.614805", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", 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 b403c7b237..8e6952a93c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -818,6 +818,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -825,7 +826,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:42:37.796771", + "modified": "2021-02-23 01:05:22.123527", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 75b2954ddd..5baf6939cd 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -27,11 +27,17 @@ "stock_qty", "sec_break1", "price_list_rate", + "last_purchase_rate", + "col_break3", + "base_price_list_rate", + "discount_and_margin_section", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_28", "discount_percentage", "discount_amount", - "col_break3", - "last_purchase_rate", - "base_price_list_rate", + "base_rate_with_margin", "sec_break2", "rate", "amount", @@ -733,15 +739,59 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "discount_and_margin_section", + "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_28", + "fieldtype": "Column Break" + }, + { + "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 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:44:41.816974", + "modified": "2021-02-23 01:00:27.132705", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index f976b17ae6..eb33a47873 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -107,7 +107,7 @@ class calculate_taxes_and_totals(object): elif item.discount_amount and item.pricing_rules: item.rate = item.price_list_rate - item.discount_amount - if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']: + if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']: item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index c96386611b..67b12fbca4 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -141,29 +141,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ this.apply_price_list(); }, - price_list_rate: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - - frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]); - - let item_rate = item.price_list_rate; - if (doc.doctype == "Purchase Order" && item.blanket_order_rate) { - item_rate = item.blanket_order_rate; - } - - if (item.discount_percentage) { - item.discount_amount = flt(item_rate) * flt(item.discount_percentage) / 100; - } - - if (item.discount_amount) { - item.rate = flt((item.price_list_rate) - (item.discount_amount), precision('rate', item)); - } else { - item.rate = item_rate; - } - - this.calculate_taxes_and_totals(); - }, - discount_percentage: function(doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); item.discount_amount = 0.0; diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index e0b0b272f3..51e4905a93 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -648,6 +648,40 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, + price_list_rate: function(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + 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", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Purchase Receipt Item"]), cdt) + this.apply_pricing_rule_on_item(item); + else + item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), + precision("rate", item)); + + this.calculate_taxes_and_totals(); + }, + + margin_rate_or_amount: function(doc, cdt, cdn) { + // calculated the revised total margin and rate on margin rate changes + let item = frappe.get_doc(cdt, cdn); + this.apply_pricing_rule_on_item(item); + this.calculate_taxes_and_totals(); + cur_frm.refresh_fields(); + }, + + margin_type: function(doc, cdt, cdn) { + // calculate the revised total margin and rate on margin type changes + let item = frappe.get_doc(cdt, cdn); + if (!item.margin_type) { + frappe.model.set_value(cdt, cdn, "margin_rate_or_amount", 0); + } else { + this.apply_pricing_rule_on_item(item, doc, cdt, cdn); + this.calculate_taxes_and_totals(); + cur_frm.refresh_fields(); + } + }, + get_incoming_rate: function(item, posting_date, posting_time, voucher_type, company) { let item_args = { @@ -1023,7 +1057,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, set_margin_amount_based_on_currency: function(exchange_rate) { - if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]), this.frm.doc.doctype) { + if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "Purchase Invoice", "Purchase Order", "Purchase Receipt"]), this.frm.doc.doctype) { var me = this; $.each(this.frm.doc.items || [], function(i, d) { if(d.margin_type == "Amount") { @@ -1278,10 +1312,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ change_grid_labels: function(company_currency) { var me = this; - this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount"], + this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"], company_currency, "items"); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate"], + this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"], this.frm.doc.currency, "items"); if(this.frm.fields_dict["operations"]) { @@ -1319,7 +1353,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ // toggle columns var item_grid = this.frm.fields_dict["items"].grid; - $.each(["base_rate", "base_price_list_rate", "base_amount"], function(i, fname) { + $.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) { if(frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); @@ -1466,7 +1500,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); // if doctype is Quotation Item / Sales Order Iten then add Margin Type and rate in item_list - if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), d.doctype){ + if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Purchase Receipt Item"]), d.doctype) { item_list[0]["margin_type"] = d.margin_type; item_list[0]["margin_rate_or_amount"] = d.margin_rate_or_amount; } diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index a6785f709a..8b53902d32 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -641,6 +641,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -648,7 +649,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:39:40.174551", + "modified": "2021-02-23 01:13:54.670763", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 37e47a9d41..1e5590e748 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -786,6 +786,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -793,7 +794,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:35:07.617320", + "modified": "2021-02-23 01:15:05.803091", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index ce084646e1..04285735ab 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -127,20 +127,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ this.set_dynamic_labels(); }, - price_list_rate: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - 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", "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), - precision("rate", item)); - - this.calculate_taxes_and_totals(); - }, - discount_percentage: function(doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); item.discount_amount = 0.0; @@ -353,26 +339,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ refresh_field('product_bundle_help'); }, - margin_rate_or_amount: function(doc, cdt, cdn) { - // calculated the revised total margin and rate on margin rate changes - var item = locals[cdt][cdn]; - this.apply_pricing_rule_on_item(item) - this.calculate_taxes_and_totals(); - cur_frm.refresh_fields(); - }, - - margin_type: function(doc, cdt, cdn){ - // calculate the revised total margin and rate on margin type changes - var item = locals[cdt][cdn]; - if(!item.margin_type) { - frappe.model.set_value(cdt, cdn, "margin_rate_or_amount", 0); - } else { - this.apply_pricing_rule_on_item(item, doc,cdt, cdn) - this.calculate_taxes_and_totals(); - cur_frm.refresh_fields(); - } - }, - company_address: function() { var me = this; if(this.frm.doc.company_address) { 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 17996247c5..b05090a237 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -750,6 +750,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -758,7 +759,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:42:03.767968", + "modified": "2021-02-23 01:04:08.588104", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 8974ad9318..efe3642d23 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -37,10 +37,16 @@ "returned_qty", "rate_and_amount", "price_list_rate", - "discount_percentage", - "discount_amount", "col_break3", "base_price_list_rate", + "discount_and_margin_section", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_37", + "discount_percentage", + "discount_amount", + "base_rate_with_margin", "sec_break1", "rate", "amount", @@ -880,6 +886,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 }, @@ -890,12 +897,55 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "discount_and_margin_section", + "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_37", + "fieldtype": "Column Break" + }, + { + "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 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:44:06.918515", + "modified": "2021-02-23 00:59:14.360847", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From b7f2ba1556fc800526cc90e9b5a0f277441d39d9 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 30 Mar 2021 12:19:16 +0530 Subject: [PATCH 0365/1264] feat: Add Report boilerplate --- .../__init__.py | 0 ...ee_hours_utilisation_based_on_timesheet.js | 9 ++++++++ ..._hours_utilisation_based_on_timesheet.json | 22 +++++++++++++++++++ ...ee_hours_utilisation_based_on_timesheet.py | 9 ++++++++ 4 files changed, 40 insertions(+) create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js new file mode 100644 index 0000000000..f800877e38 --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js @@ -0,0 +1,9 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Employee Hours Utilisation Based On Timesheet"] = { + "filters": [ + + ] +}; diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json new file mode 100644 index 0000000000..1b504e011d --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json @@ -0,0 +1,22 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-30 12:18:02.090630", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-03-30 12:18:02.090630", + "modified_by": "Administrator", + "module": "Projects", + "name": "Employee Hours Utilisation Based On Timesheet", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Timesheet", + "report_name": "Employee Hours Utilisation Based On Timesheet", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py new file mode 100644 index 0000000000..de2aa1ff41 --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -0,0 +1,9 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe + +def execute(filters=None): + columns, data = [], [] + return columns, data From 04c02965584f55d08756e3694c8bbbd7436f28c8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 30 Mar 2021 12:30:40 +0530 Subject: [PATCH 0366/1264] fix: add filters to account link fields in Non Profit Settings - fetch memberships ordered by date in certificate --- .../non_profit/doctype/donation/donation.py | 5 ++-- .../non_profit_settings.js | 23 ++++++++++++++++++- .../tax_exemption_80g_certificate.py | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 6a2a06dbc8..4fd1a30ab9 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -42,7 +42,7 @@ class Donation(Document): self.load_from_db() self.create_payment_entry() - def create_payment_entry(self): + def create_payment_entry(self, date=None): settings = frappe.get_doc('Non Profit Settings') if not settings.automate_donation_payment_entries: return @@ -58,8 +58,9 @@ class Donation(Document): frappe.flags.ignore_account_permission = False pe.paid_from = settings.donation_debit_account pe.paid_to = settings.donation_payment_account + pe.posting_date = date or getdate() pe.reference_no = self.name - pe.reference_date = getdate() + pe.reference_date = date or getdate() pe.flags.ignore_mandatory = True pe.insert() pe.submit() diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js index cff92b42ab..4c4ca9834b 100644 --- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js @@ -19,7 +19,7 @@ frappe.ui.form.on("Non Profit Settings", { }; }); - frm.set_query("debit_account", function() { + frm.set_query("membership_debit_account", function() { return { filters: { "account_type": "Receivable", @@ -29,6 +29,16 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + frm.set_query("donation_debit_account", function() { + return { + filters: { + "account_type": "Receivable", + "is_group": 0, + "company": frm.doc.donation_company + } + }; + }); + frm.set_query("membership_payment_account", function () { var account_types = ["Bank", "Cash"]; return { @@ -40,6 +50,17 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + frm.set_query("donation_payment_account", function () { + var account_types = ["Bank", "Cash"]; + return { + filters: { + "account_type": ["in", account_types], + "is_group": 0, + "company": frm.doc.donation_company + } + }; + }); + let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership"; frm.set_intro(__("You can learn more about memberships in the manual. ") + `${__('ERPNext Docs')}`, true); diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index 5bbd5750f9..41c7b23146 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -81,7 +81,7 @@ class TaxExemption80GCertificate(Document): 'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], 'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], 'membership_status': ('!=', 'Cancelled') - }, ['from_date', 'amount', 'name', 'invoice', 'payment_id']) + }, ['from_date', 'amount', 'name', 'invoice', 'payment_id'], order_by='from_date') if not memberships: frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member)) From 2b85a81a1ca8d42b3ef9fd5e85aeab4d7d566f50 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 30 Mar 2021 13:57:44 +0530 Subject: [PATCH 0367/1264] feat: Add filters to front end --- ...ee_hours_utilisation_based_on_timesheet.js | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js index f800877e38..e9b817ab67 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js @@ -4,6 +4,39 @@ frappe.query_reports["Employee Hours Utilisation Based On Timesheet"] = { "filters": [ - + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.defaults.get_global_default("year_start_date"), + reqd: 1 + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.defaults.get_global_default("year_end_date"), + reqd: 1 + }, + { + fieldname: "employee", + label: __("Employee"), + fieldtype: "Link", + options: "Employee" + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "Link", + options: "Project" + } ] }; From 1a0229eb36ad0af47a7471718aeaf1493c3af701 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 30 Mar 2021 18:20:22 +0530 Subject: [PATCH 0368/1264] feat: Add report table data generation --- ...ee_hours_utilisation_based_on_timesheet.py | 126 +++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index de2aa1ff41..71ed2e79eb 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -2,8 +2,128 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ +from frappe.utils import flt, getdate +from six import iteritems def execute(filters=None): - columns, data = [], [] - return columns, data + return EmployeeHoursReport(filters).run() + +class EmployeeHoursReport: + '''Employee Hours Utilisation Report Based On Timesheet''' + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + + self.from_date = getdate(self.filters.from_date) + self.to_date = getdate(self.filters.to_date) + + self.validate_dates() + + def validate_dates(self): + self.day_span = (self.to_date - self.from_date).days + + if self.day_span < 0: + frappe.throw(_('From Date must come before To Date')) + + def run(self): + self.generate_columns() + self.generate_data() + + return self.columns, self.data + + def generate_columns(self): + self.columns = [ + { + 'label': _('Employee'), + 'options': 'Employee', + 'fieldname': 'employee', + 'fieldtype': 'Link', + 'width': 200 + }, + { + 'label': _('Total Hours'), + 'fieldname': 'total_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('Billed Hours'), + 'fieldname': 'billed_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('Non-Billed Hours'), + 'fieldname': 'non_billed_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('Untracked Hours'), + 'fieldname': 'untracked_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('% Utilization'), + 'fieldname': 'per_util', + 'fieldtype': 'Percentage', + 'width': 200 + } + ] + + def generate_data(self): + self.generate_filtered_time_logs() + self.generate_stats_by_employee() + self.calculate_utilisations() + + self.data = [] + + for emp, data in iteritems(self.stats_by_employee): + row = frappe._dict() + row['employee'] = emp + row.update(data) + self.data.append(row) + + def generate_filtered_time_logs(self): + additional_filters = '' + + if self.filters.employee: + additional_filters += f'AND tt.employee = {self.filters.employee}' + + if self.filters.project: + additional_filters += f'AND ttd.project = {self.filters.project}' + + self.filtered_time_logs = frappe.db.sql(''' + SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project + FROM `tabTimesheet Detail` AS ttd + JOIN `tabTimesheet` AS tt + ON ttd.parent = tt.name + WHERE tt.start_date BETWEEN '{0}' AND '{1}' + AND tt.end_date BETWEEN '{0}' AND '{1}' + {2}; + '''.format(self.filters.from_date, self.filters.to_date, additional_filters)) + + def generate_stats_by_employee(self): + self.stats_by_employee = frappe._dict() + + for emp, hours, billable, project in self.filtered_time_logs: + self.stats_by_employee.setdefault( + emp, frappe._dict() + ).setdefault('billed_hours', 0.0) + + self.stats_by_employee[emp].setdefault('non_billed_hours', 0.0) + + if billable: + self.stats_by_employee[emp]['billed_hours'] += flt(hours, 2) + else: + self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2) + + def calculate_utilisations(self): + # (9.0) Will be fetched from HR settings + TOTAL_HOURS = flt(9.0 * self.day_span, 2) + for emp, data in iteritems(self.stats_by_employee): + data['total_hours'] = TOTAL_HOURS + data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) \ No newline at end of file From a21e347bf167bd01c6c3743fb9d5e0ac0a4a3b62 Mon Sep 17 00:00:00 2001 From: Andy Zhu Date: Mon, 5 Oct 2020 12:16:48 +1300 Subject: [PATCH 0369/1264] fix: Update Items on Purchase Order If user add rows or remove rows to update items on purchase order, the quantity in bin won't get updated. This fix is not mature yet but to give an tempopary solution for fixing this issue. --- erpnext/controllers/accounts_controller.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 12a81c7887..69e5e1ddec 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1321,6 +1321,7 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date" child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)}) child_item.uom = trans_item.get("uom") or item.stock_uom + child_item.warehouse = p_doc.set_warehouse conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor if child_doctype == "Purchase Order Item": @@ -1359,6 +1360,12 @@ def validate_and_delete_children(parent, data): d.cancel() d.delete() + + from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty + frappe.errprint(f"Item Code: {d.item_code}, Warehouse: {d.warehouse}") + update_bin_qty(d.item_code, d.warehouse, { + "ordered_qty": get_ordered_qty(d.item_code, d.warehouse) + }) @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): From c0ba6eafd543b52bd36d1747415ac5e34404de74 Mon Sep 17 00:00:00 2001 From: Andy Zhu Date: Tue, 6 Oct 2020 10:51:42 +1300 Subject: [PATCH 0370/1264] fix: Update Items on Purchase Order 1. set warehouse using `get_item_warehouse` 2. update "reserved_qty" for sales order --- erpnext/controllers/accounts_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 69e5e1ddec..ba9f87f3a3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1321,7 +1321,7 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date" child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)}) child_item.uom = trans_item.get("uom") or item.stock_uom - child_item.warehouse = p_doc.set_warehouse + child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor if child_doctype == "Purchase Order Item": @@ -1361,9 +1361,9 @@ def validate_and_delete_children(parent, data): d.cancel() d.delete() - from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty - frappe.errprint(f"Item Code: {d.item_code}, Warehouse: {d.warehouse}") + from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty update_bin_qty(d.item_code, d.warehouse, { + "reserved_qty": get_reserved_qty(d.item_code, d.warehouse), "ordered_qty": get_ordered_qty(d.item_code, d.warehouse) }) From ba3578929eea16c83ea879381964f7374cab117a Mon Sep 17 00:00:00 2001 From: Andy Zhu Date: Tue, 27 Oct 2020 14:57:59 +1300 Subject: [PATCH 0371/1264] Update accounts_controller.py Updating Bin quantity based on doctype to optimize running efficiency. --- erpnext/controllers/accounts_controller.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ba9f87f3a3..8d53914f6c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1362,10 +1362,15 @@ def validate_and_delete_children(parent, data): d.delete() from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty - update_bin_qty(d.item_code, d.warehouse, { - "reserved_qty": get_reserved_qty(d.item_code, d.warehouse), - "ordered_qty": get_ordered_qty(d.item_code, d.warehouse) - }) + # updating both will be time consuming, update it based on the doctype. reserved qty if sales order, otherwise ordered qty + if parent.doctype == "Sales Order": + update_bin_qty(d.item_code, d.warehouse, { + "reserved_qty": get_reserved_qty(d.item_code, d.warehouse) + }) + else: + update_bin_qty(d.item_code, d.warehouse, { + "ordered_qty": get_ordered_qty(d.item_code, d.warehouse) + }) @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): From a5de6c779bdbf9d87f32c5f58819883011f3afa1 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 31 Mar 2021 01:38:22 +0530 Subject: [PATCH 0372/1264] fix: Cleaned up and fixed validation and bin updation on deletion - Created separate smaller functions for validation and bin updation of deleted row - Made sure previous doc (linked doc) was also updated after deletion of row - Tests to check bin updation on add/update/delete - Made reserved qty for subcontrating read only in bin --- .../doctype/purchase_order/purchase_order.py | 1 + .../purchase_order/test_purchase_order.py | 69 +++++++++++++++-- erpnext/controllers/accounts_controller.py | 76 ++++++++++++------- .../doctype/sales_order/sales_order.py | 2 +- .../doctype/sales_order/test_sales_order.py | 26 +++++++ erpnext/stock/doctype/bin/bin.json | 8 +- 6 files changed, 144 insertions(+), 38 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index d32e98e8d9..29a8d59cb0 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -252,6 +252,7 @@ class PurchaseOrder(BuyingController): self.update_prevdoc_status() # Must be called after updating ordered qty in Material Request + # bin uses Material Request Items to recalculate & update self.update_requested_qty() self.update_ordered_qty() diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 02d4865320..1b231b3acb 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -90,6 +90,50 @@ class TestPurchaseOrder(unittest.TestCase): frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 0) frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0) + def test_update_remove_child_linked_to_mr(self): + """Test impact on linked PO and MR on deleting/updating row.""" + mr = make_material_request(qty=10) + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.save() + po.submit() + + first_item_of_po = po.get("items")[0] + existing_ordered_qty = get_ordered_qty() # 10 + existing_requested_qty = get_requested_qty() # 0 + + # decrease ordered qty by 3 (10 -> 7) and add item + trans_item = json.dumps([ + { + 'item_code': first_item_of_po.item_code, + 'rate': first_item_of_po.rate, + 'qty': 7, + 'docname': first_item_of_po.name + }, + {'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2} + ]) + update_child_qty_rate('Purchase Order', trans_item, po.name) + mr.reload() + + # requested qty increases as ordered qty decreases + self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3 + self.assertEqual(mr.items[0].ordered_qty, 7) + + self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7 + + # delete first item linked to Material Request + trans_item = json.dumps([ + {'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2} + ]) + update_child_qty_rate('Purchase Order', trans_item, po.name) + mr.reload() + + # requested qty increases as ordered qty is 0 (deleted row) + self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10 + self.assertEqual(mr.items[0].ordered_qty, 0) + + # ordered qty decreases as ordered qty is 0 (deleted row) + self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0 def test_update_child(self): mr = make_material_request(qty=10) @@ -120,7 +164,6 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(po.get("items")[0].amount, 1400) self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) - def test_update_child_adding_new_item(self): po = create_purchase_order(do_not_save=1) po.items[0].qty = 4 @@ -129,6 +172,7 @@ class TestPurchaseOrder(unittest.TestCase): pr = make_pr_against_po(po.name, 2) po.load_from_db() + existing_ordered_qty = get_ordered_qty() first_item_of_po = po.get("items")[0] trans_item = json.dumps([ @@ -145,7 +189,8 @@ class TestPurchaseOrder(unittest.TestCase): po.reload() self.assertEquals(len(po.get('items')), 2) self.assertEqual(po.status, 'To Receive and Bill') - + # ordered qty should increase on row addition + self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7) def test_update_child_removing_item(self): po = create_purchase_order(do_not_save=1) @@ -156,6 +201,7 @@ class TestPurchaseOrder(unittest.TestCase): po.reload() first_item_of_po = po.get("items")[0] + existing_ordered_qty = get_ordered_qty() # add an item trans_item = json.dumps([ { @@ -168,6 +214,10 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() + + # ordered qty should increase on row addition + self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7) + # check if can remove received item trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name) @@ -187,6 +237,9 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEquals(len(po.get('items')), 1) self.assertEqual(po.status, 'To Receive and Bill') + # ordered qty should decrease (back to initial) on row deletion + self.assertEqual(get_ordered_qty(), existing_ordered_qty) + def test_update_child_perm(self): po = create_purchase_order(item_code= "_Test Item", qty=4) @@ -230,11 +283,13 @@ class TestPurchaseOrder(unittest.TestCase): new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax") - new_item_with_tax.append("taxes", { - "item_tax_template": "Test Update Items Template - _TC", - "valid_from": nowdate() - }) - new_item_with_tax.save() + if not frappe.db.exists("Item Tax", + {"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"}): + new_item_with_tax.append("taxes", { + "item_tax_template": "Test Update Items Template - _TC", + "valid_from": nowdate() + }) + new_item_with_tax.save() tax_template = "_Test Account Excise Duty @ 10 - _TC" item = "_Test Item Home Desktop 100" diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8d53914f6c..a73de44606 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1316,26 +1316,63 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) child_item = frappe.new_doc(child_doctype, p_doc, child_docname) item = frappe.get_doc("Item", trans_item.get('item_code')) + for field in ("item_code", "item_name", "description", "item_group"): - child_item.update({field: item.get(field)}) + child_item.update({field: item.get(field)}) + date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date" child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)}) + child_item.stock_uom = item.stock_uom child_item.uom = trans_item.get("uom") or item.stock_uom child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor + if child_doctype == "Purchase Order Item": - child_item.base_rate = 1 # Initiallize value will update in parent validation - child_item.base_amount = 1 # Initiallize value will update in parent validation + # Initialized value will update in parent validation + child_item.base_rate = 1 + child_item.base_amount = 1 if child_doctype == "Sales Order Item": child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) if not child_item.warehouse: frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") .format(frappe.bold("default warehouse"), frappe.bold(item.item_code))) + set_child_tax_template_and_map(item, child_item, p_doc) add_taxes_from_tax_template(child_item, p_doc) return child_item +def validate_child_on_delete(row, parent): + """Check if partially transacted item (row) is being deleted.""" + if parent.doctype == "Sales Order": + if flt(row.delivered_qty): + frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code)) + if flt(row.work_order_qty): + frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code)) + if flt(row.ordered_qty): + frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code)) + + if parent.doctype == "Purchase Order" and flt(row.received_qty): + frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code)) + + if flt(row.billed_amt): + frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code)) + +def update_bin_on_delete(row, doctype): + """Update bin for deleted item (row).""" + from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty, get_indented_qty + qty_dict = {} + + if doctype == "Sales Order": + qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse) + else: + if row.material_request_item: + qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse) + + qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse) + + update_bin_qty(row.item_code, row.warehouse, qty_dict) + def validate_and_delete_children(parent, data): deleted_children = [] updated_item_names = [d.get("docname") for d in data] @@ -1344,33 +1381,16 @@ def validate_and_delete_children(parent, data): deleted_children.append(item) for d in deleted_children: - if parent.doctype == "Sales Order": - if flt(d.delivered_qty): - frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code)) - if flt(d.work_order_qty): - frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code)) - if flt(d.ordered_qty): - frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code)) - - if parent.doctype == "Purchase Order" and flt(d.received_qty): - frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code)) - - if flt(d.billed_amt): - frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code)) - + validate_child_on_delete(d, parent) d.cancel() d.delete() - - from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty - # updating both will be time consuming, update it based on the doctype. reserved qty if sales order, otherwise ordered qty - if parent.doctype == "Sales Order": - update_bin_qty(d.item_code, d.warehouse, { - "reserved_qty": get_reserved_qty(d.item_code, d.warehouse) - }) - else: - update_bin_qty(d.item_code, d.warehouse, { - "ordered_qty": get_ordered_qty(d.item_code, d.warehouse) - }) + + # need to update ordered qty in Material Request first + # bin uses Material Request Items to recalculate & update + parent.update_prevdoc_status() + + for d in deleted_children: + update_bin_on_delete(d, parent.doctype) @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e56129170c..fd9ddd8463 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -150,7 +150,7 @@ class SalesOrder(SellingController): if enq: frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0])) - def update_prevdoc_status(self, flag): + def update_prevdoc_status(self, flag=None): for quotation in list(set([d.prevdoc_docname for d in self.get("items")])): if quotation: doc = frappe.get_doc("Quotation", quotation) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index ee16f44171..7752b7bb08 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -340,6 +340,9 @@ class TestSalesOrder(unittest.TestCase): prev_total = so.get("base_total") prev_total_in_words = so.get("base_in_words") + # get reserved qty before update items + reserved_qty_for_second_item = get_reserved_qty("_Test Item 2") + first_item_of_so = so.get("items")[0] trans_item = json.dumps([ {'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \ @@ -353,6 +356,10 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.get("items")[-1].rate, 200) self.assertEqual(so.get("items")[-1].qty, 7) self.assertEqual(so.get("items")[-1].amount, 1400) + + # reserved qty should increase after adding row + self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 7) + self.assertEqual(so.status, 'To Deliver and Bill') updated_total = so.get("base_total") @@ -372,6 +379,9 @@ class TestSalesOrder(unittest.TestCase): create_dn_against_so(so.name, 2) make_sales_invoice(so.name) + # get reserved qty before update items + reserved_qty_for_second_item = get_reserved_qty("_Test Item 2") + # add an item so as to try removing items trans_item = json.dumps([ {"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name}, @@ -381,6 +391,9 @@ class TestSalesOrder(unittest.TestCase): so.reload() self.assertEqual(len(so.get("items")), 2) + # reserved qty should increase after adding row + self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 2) + # check if delivered items can be removed trans_item = json.dumps([{ "item_code": '_Test Item 2', @@ -401,6 +414,10 @@ class TestSalesOrder(unittest.TestCase): so.reload() self.assertEqual(len(so.get("items")), 1) + + # reserved qty should decrease (back to initial) after deleting row + self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item) + self.assertEqual(so.status, 'To Deliver and Bill') @@ -508,12 +525,18 @@ class TestSalesOrder(unittest.TestCase): so = make_sales_order(item_code = "_Test Item", warehouse=None) + # get reserved qty of packed item + existing_reserved_qty = get_reserved_qty("_Packed Item") + added_item = json.dumps([{"item_code" : "_Product Bundle Item", "rate" : 200, 'qty' : 2}]) update_child_qty_rate('Sales Order', added_item, so.name) so.reload() self.assertEqual(so.packed_items[0].qty, 4) + # reserved qty in packed item should increase after adding bundle item + self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 4) + # test uom and conversion factor change update_uom_conv_factor = json.dumps([{ 'item_code': so.get("items")[0].item_code, @@ -528,6 +551,9 @@ class TestSalesOrder(unittest.TestCase): so.reload() self.assertEqual(so.packed_items[0].qty, 8) + # reserved qty in packed item should increase after changing bundle item uom + self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 8) + def test_update_child_with_tax_template(self): """ Test Action: Create a SO with one item having its tax account head already in the SO. diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index 04d624ec0b..8e79f0e555 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "MAT-BIN-.YYYY.-.#####", "creation": "2013-01-10 16:34:25", "doctype": "DocType", @@ -112,7 +113,8 @@ { "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", - "label": "Reserved Qty for sub contract" + "label": "Reserved Qty for sub contract", + "read_only": 1 }, { "fieldname": "ma_rate", @@ -166,7 +168,8 @@ "hide_toolbar": 1, "idx": 1, "in_create": 1, - "modified": "2019-11-18 18:34:59.456882", + "links": [], + "modified": "2021-03-30 23:09:39.572776", "modified_by": "Administrator", "module": "Stock", "name": "Bin", @@ -196,5 +199,6 @@ ], "quick_entry": 1, "search_fields": "item_code,warehouse", + "sort_field": "modified", "sort_order": "ASC" } \ No newline at end of file From 41229ba705db86b43ce8d9a9bc146639b968655c Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 31 Mar 2021 02:32:25 +0530 Subject: [PATCH 0373/1264] fix: unable to submit stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 4979234caf..af3c4e5aa0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -848,7 +848,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ } erpnext.hide_company(); erpnext.utils.add_item(this.frm); - this.frm.trigger('add_to_transit'); }, scan_barcode: function() { From 91a8a74d54e5fe7a52ae2dd23b8b733332869cdb Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 31 Mar 2021 11:30:04 +0530 Subject: [PATCH 0374/1264] fix: column width in Recruitment Analytics report (#25074) --- .../report/recruitment_analytics/recruitment_analytics.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py index e961114ac2..303c829eb6 100644 --- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py @@ -31,7 +31,7 @@ def get_columns(): "fieldtype": "Link", "fieldname": "job_opening", "options": "Job Opening", - "width": 100 + "width": 105 }, { "label": _("Job Applicant"), @@ -44,13 +44,13 @@ def get_columns(): "label": _("Applicant name"), "fieldtype": "data", "fieldname": "applicant_name", - "width": 120 + "width": 130 }, { "label": _("Application Status"), "fieldtype": "Data", "fieldname": "application_status", - "width": 100 + "width": 150 }, { "label": _("Job Offer"), @@ -187,4 +187,4 @@ def get_job_offer(ja_list): else: ja_joff_map[offer.job_applicant].append(offer) - return ja_joff_map \ No newline at end of file + return ja_joff_map From 410d36beca66f4fba4769dd647c016ae988237c6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 31 Mar 2021 12:11:00 +0530 Subject: [PATCH 0375/1264] fix: bom stock calculated report --- .../report/bom_stock_calculated/bom_stock_calculated.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index f7b407b792..f7cae025d5 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -88,7 +88,7 @@ def get_bom_stock(filters): GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) def get_manufacturer_records(): - details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no, parent"]) + details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) manufacture_details = frappe._dict() for detail in details: dic = manufacture_details.setdefault(detail.get('parent'), {}) From f5576d67dd4723d5f8be4aa83aa6a70174ccd2f5 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 31 Mar 2021 12:25:36 +0530 Subject: [PATCH 0376/1264] feat: Add Report Summary generation --- ...ee_hours_utilisation_based_on_timesheet.py | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 71ed2e79eb..702c85a824 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -29,8 +29,9 @@ class EmployeeHoursReport: def run(self): self.generate_columns() self.generate_data() + self.generate_report_summary() - return self.columns, self.data + return self.columns, self.data, None, None, self.report_summary def generate_columns(self): self.columns = [ @@ -126,4 +127,25 @@ class EmployeeHoursReport: for emp, data in iteritems(self.stats_by_employee): data['total_hours'] = TOTAL_HOURS data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) - data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) \ No newline at end of file + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) + + def generate_report_summary(self): + if not self.data: + return + + avg_utilisation = 0.0 + for row in self.data: + avg_utilisation += row['per_util'] + + avg_utilisation /= len(self.data) + avg_utilisation = flt(avg_utilisation, 2) + + THRESHOLD_PERCENTAGE = 70.0 + self.report_summary = [ + { + 'value': f'{avg_utilisation}%', + 'indicator': 'Red' if avg_utilisation < THRESHOLD_PERCENTAGE else 'Green', + 'label': _('Average Utilisation'), + 'datatype': 'Percentage' + } + ] From 92c768972ff4cdce051328b1c66a8ff10bff5168 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 31 Mar 2021 12:26:37 +0530 Subject: [PATCH 0377/1264] fix: Missing quotation marks in SQL query --- .../employee_hours_utilisation_based_on_timesheet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 702c85a824..e3d6b9b7f2 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -91,10 +91,10 @@ class EmployeeHoursReport: additional_filters = '' if self.filters.employee: - additional_filters += f'AND tt.employee = {self.filters.employee}' + additional_filters += f"AND tt.employee = '{self.filters.employee}'" if self.filters.project: - additional_filters += f'AND ttd.project = {self.filters.project}' + additional_filters += f"AND ttd.project = '{self.filters.project}'" self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project From 368cb451475c1acb8d6818fad1548926fe84ae25 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 31 Mar 2021 12:43:33 +0530 Subject: [PATCH 0378/1264] fix(Italy): setup, validations, optimisations (#25065) --- erpnext/regional/italy/sales_invoice.js | 13 +--- erpnext/regional/italy/setup.py | 7 +- erpnext/regional/italy/utils.py | 98 ++++++++++++++----------- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js index 586a52937b..b54ac53812 100644 --- a/erpnext/regional/italy/sales_invoice.js +++ b/erpnext/regional/italy/sales_invoice.js @@ -11,15 +11,10 @@ erpnext.setup_e_invoice_button = (doctype) => { callback: function(r) { frm.reload_doc(); if(r.message) { - var w = window.open( - frappe.urllib.get_full_url( - "/api/method/erpnext.regional.italy.utils.download_e_invoice_file?" - + "file_name=" + r.message - ) - ) - if (!w) { - frappe.msgprint(__("Please enable pop-ups")); return; - } + open_url_post(frappe.request.url, { + cmd: 'frappe.core.doctype.file.file.download_file', + file_url: r.message + }); } } }); diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 95b92e76a6..a1f5bb9836 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -128,11 +128,8 @@ def make_custom_fields(update=True): fetch_from="company.vat_collectability"), dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing', fieldtype='Section Break', insert_after='against_income_account', print_hide=1), - dict(fieldname='company_tax_id', label='Company Tax ID', - fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1, - fetch_from="company.tax_id"), dict(fieldname='company_fiscal_code', label='Company Fiscal Code', - fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1, + fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1, fetch_from="company.fiscal_code"), dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime', fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1, @@ -217,4 +214,4 @@ def add_permissions(): update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1) add_permission(doctype, 'Accounts Manager', 1) update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1) - update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1) \ No newline at end of file + update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1) diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 6842fb2a61..08573cddcd 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals -import frappe, json, os +import io +import json +import frappe from frappe.utils import flt, cstr from erpnext.controllers.taxes_and_totals import get_itemised_tax from frappe import _ @@ -28,20 +30,22 @@ def update_itemised_tax_data(doc): @frappe.whitelist() def export_invoices(filters=None): - saved_xmls = [] + frappe.has_permission('Sales Invoice', throw=True) - invoices = frappe.get_all("Sales Invoice", filters=get_conditions(filters), fields=["*"]) + invoices = frappe.get_all( + "Sales Invoice", + filters=get_conditions(filters), + fields=["name", "company_tax_id"] + ) - for invoice in invoices: - attachments = get_e_invoice_attachments(invoice) - saved_xmls += [attachment.file_name for attachment in attachments] + attachments = get_e_invoice_attachments(invoices) - zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S")) + zip_filename = "{0}-einvoices.zip".format( + frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S")) - download_zip(saved_xmls, zip_filename) + download_zip(attachments, zip_filename) -@frappe.whitelist() def prepare_invoice(invoice, progressive_number): #set company information company = frappe.get_doc("Company", invoice.company) @@ -98,7 +102,7 @@ def prepare_invoice(invoice, progressive_number): def get_conditions(filters): filters = json.loads(filters) - conditions = {"docstatus": 1} + conditions = {"docstatus": 1, "company_tax_id": ("!=", "")} if filters.get("company"): conditions["company"] = filters["company"] if filters.get("customer"): conditions["customer"] = filters["customer"] @@ -111,23 +115,22 @@ def get_conditions(filters): return conditions -#TODO: Use function from frappe once PR #6853 is merged. + def download_zip(files, output_filename): - from zipfile import ZipFile + import zipfile - input_files = [frappe.get_site_path('private', 'files', filename) for filename in files] - output_path = frappe.get_site_path('private', 'files', output_filename) + zip_stream = io.BytesIO() + with zipfile.ZipFile(zip_stream, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for file in files: + file_path = frappe.utils.get_files_path( + file.file_name, is_private=file.is_private) - with ZipFile(output_path, 'w') as output_zip: - for input_file in input_files: - output_zip.write(input_file, arcname=os.path.basename(input_file)) - - with open(output_path, 'rb') as fileobj: - filedata = fileobj.read() + zip_file.write(file_path, arcname=file.file_name) frappe.local.response.filename = output_filename - frappe.local.response.filecontent = filedata + frappe.local.response.filecontent = zip_stream.getvalue() frappe.local.response.type = "download" + zip_stream.close() def get_invoice_summary(items, taxes): summary_data = frappe._dict() @@ -307,23 +310,12 @@ def prepare_and_attach_invoice(doc, replace=False): @frappe.whitelist() def generate_single_invoice(docname): doc = frappe.get_doc("Sales Invoice", docname) - + frappe.has_permission("Sales Invoice", doc=doc, throw=True) e_invoice = prepare_and_attach_invoice(doc, True) + return e_invoice.file_url - return e_invoice.file_name - -@frappe.whitelist() -def download_e_invoice_file(file_name): - content = None - with open(frappe.get_site_path('private', 'files', file_name), "r") as f: - content = f.read() - - frappe.local.response.filename = file_name - frappe.local.response.filecontent = content - frappe.local.response.type = "download" - -#Delete e-invoice attachment on cancel. +# Delete e-invoice attachment on cancel. def sales_invoice_on_cancel(doc, method): if get_company_country(doc.company) not in ['Italy', 'Italia', 'Italian Republic', 'Repubblica Italiana']: @@ -335,16 +327,38 @@ def sales_invoice_on_cancel(doc, method): def get_company_country(company): return frappe.get_cached_value('Company', company, 'country') -def get_e_invoice_attachments(invoice): - if not invoice.company_tax_id: - return [] +def get_e_invoice_attachments(invoices): + if not isinstance(invoices, list): + if not invoices.company_tax_id: + return + + invoices = [invoices] + + tax_id_map = { + invoice.name: ( + invoice.company_tax_id + if invoice.company_tax_id.startswith("IT") + else "IT" + invoice.company_tax_id + ) for invoice in invoices + } + + attachments = frappe.get_all( + "File", + fields=("name", "file_name", "attached_to_name", "is_private"), + filters= { + "attached_to_name": ('in', tax_id_map), + "attached_to_doctype": 'Sales Invoice' + } + ) out = [] - attachments = get_attachments(invoice.doctype, invoice.name) - company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id - for attachment in attachments: - if attachment.file_name and attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"): + if ( + attachment.file_name + and attachment.file_name.endswith(".xml") + and attachment.file_name.startswith( + tax_id_map.get(attachment.attached_to_name)) + ): out.append(attachment) return out From e50324aed7fe64d4a22043876ea933dcfb5119db Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 31 Mar 2021 12:44:03 +0530 Subject: [PATCH 0379/1264] perf: reduce number of queries for checking if future SL entry exists (#25064) --- erpnext/controllers/stock_controller.py | 45 ++++++++++++++----------- erpnext/stock/stock_ledger.py | 6 ++-- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 11ac703311..f352bae30e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -495,7 +495,7 @@ class StockController(AccountsController): "voucher_no": self.name, "company": self.company }) - if check_if_future_sle_exists(args): + if future_sle_exists(args): create_repost_item_valuation_entry(args) elif not is_reposting_pending(): check_if_stock_and_account_balance_synced(self.posting_date, @@ -506,37 +506,42 @@ def is_reposting_pending(): {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) -def check_if_future_sle_exists(args): - sl_entries = frappe.db.get_all("Stock Ledger Entry", +def future_sle_exists(args): + sl_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no}, fields=["item_code", "warehouse"], order_by="creation asc") - distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries])) + if not sl_entries: + return - sle_exists = False - for item_code, warehouse in distinct_item_warehouses: - args.update({ - "item_code": item_code, - "warehouse": warehouse - }) - if get_sle(args): - sle_exists = True - break - return sle_exists + warehouse_items_map = {} + for entry in sl_entries: + if entry.warehouse not in warehouse_items_map: + warehouse_items_map[entry.warehouse] = set() + + warehouse_items_map[entry.warehouse].add(entry.item_code) + + or_conditions = [] + for warehouse, items in warehouse_items_map.items(): + or_conditions.append( + "warehouse = '{}' and item_code in ({})".format( + warehouse, + ", ".join(frappe.db.escape(item) for item in items) + ) + ) -def get_sle(args): return frappe.db.sql(""" select name from `tabStock Ledger Entry` where - item_code=%(item_code)s - and warehouse=%(warehouse)s - and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) + ({}) + and timestamp(posting_date, posting_time) + >= timestamp(%(posting_date)s, %(posting_time)s) and voucher_no != %(voucher_no)s and is_cancelled = 0 limit 1 - """, args) + """.format(" or ".join(or_conditions)), args) def create_repost_item_valuation_entry(args): args = frappe._dict(args) @@ -554,4 +559,4 @@ def create_repost_item_valuation_entry(args): repost_entry.allow_zero_rate = args.allow_zero_rate repost_entry.flags.ignore_links = True repost_entry.save() - repost_entry.submit() \ No newline at end of file + repost_entry.submit() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f54b3c1bb2..121c51cf6a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -207,11 +207,11 @@ class update_entries_after(object): def build(self): - from erpnext.controllers.stock_controller import check_if_future_sle_exists + from erpnext.controllers.stock_controller import future_sle_exists if self.args.get("sle_id"): self.process_sle_against_current_timestamp() - if not check_if_future_sle_exists(self.args): + if not future_sle_exists(self.args): self.update_bin() else: entries_to_fix = self.get_future_entries_to_fix() @@ -856,4 +856,4 @@ def get_future_sle_with_negative_qty(args): and qty_after_transaction < 0 order by timestamp(posting_date, posting_time) asc limit 1 - """, args, as_dict=1) \ No newline at end of file + """, args, as_dict=1) From 39b75c63ad053f9fc6b7315f75e86cef9d39edc9 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 31 Mar 2021 13:13:25 +0530 Subject: [PATCH 0380/1264] feat: Add chart generation --- ...ee_hours_utilisation_based_on_timesheet.py | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index e3d6b9b7f2..8124c7a1be 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -30,8 +30,9 @@ class EmployeeHoursReport: self.generate_columns() self.generate_data() self.generate_report_summary() + self.generate_chart_data() - return self.columns, self.data, None, None, self.report_summary + return self.columns, self.data, None, self.chart, self.report_summary def generate_columns(self): self.columns = [ @@ -87,6 +88,9 @@ class EmployeeHoursReport: row.update(data) self.data.append(row) + # Sort by descending order of percentage utilisation + self.data.sort(key=lambda x: x['per_util'], reverse=True) + def generate_filtered_time_logs(self): additional_filters = '' @@ -149,3 +153,45 @@ class EmployeeHoursReport: 'datatype': 'Percentage' } ] + + def generate_chart_data(self): + self.chart = {} + + labels = [] + billed_hours = [] + non_billed_hours = [] + untracked_hours = [] + + + for row in self.data: + emp_name = frappe.db.get_value( + 'Employee', row['employee'], 'employee_name' + ) + labels.append(emp_name) + billed_hours.append(row.get('billed_hours')) + non_billed_hours.append(row.get('non_billed_hours')) + untracked_hours.append(row.get('untracked_hours')) + + self.chart = { + 'data': { + 'labels': labels[:30], + 'datasets': [ + { + 'name': _('Billed Hours'), + 'values': billed_hours[:30] + }, + { + 'name': _('Non-Billed Hours'), + 'values': non_billed_hours[:30] + }, + { + 'name': _('Untracked Hours'), + 'values': untracked_hours[:30] + } + ] + }, + 'type': 'bar', + 'barOptions': { + 'stacked': True + } + } From 1999ae0a91d7b9a44e1c8db448aec64706923c24 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 12 Mar 2021 14:12:46 +0530 Subject: [PATCH 0381/1264] fix: PO not created against all selected suppliers (drop shipping) - Return list of created POs instead of first doc - test case added --- .../doctype/sales_order/sales_order.py | 6 ++- .../doctype/sales_order/test_sales_order.py | 45 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e56129170c..b5c5e1bc2f 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -778,6 +778,7 @@ def get_events(start, end, filters=None): @frappe.whitelist() def make_purchase_order_for_default_supplier(source_name, selected_items=None, target_doc=None): + """Creates Purchase Order for each Supplier. Returns a list of doc objects.""" if not selected_items: return if isinstance(selected_items, string_types): @@ -829,6 +830,7 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t if not suppliers: frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order.")) + purchase_orders = [] for supplier in suppliers: doc = get_mapped_doc("Sales Order", source_name, { "Sales Order": { @@ -872,7 +874,9 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t doc.insert() frappe.db.commit() - return doc + purchase_orders.append(doc) + + return purchase_orders @frappe.whitelist() def make_purchase_order(source_name, selected_items=None, target_doc=None): diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index ee16f44171..e10e5d0706 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -743,7 +743,7 @@ class TestSalesOrder(unittest.TestCase): so = make_sales_order(item_list=so_items, do_not_submit=True) so.submit() - po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]]) + po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0] po.submit() dn = create_dn_against_so(so.name, delivered_qty=2) @@ -825,7 +825,7 @@ class TestSalesOrder(unittest.TestCase): so.submit() # create po for only one item - po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]]) + po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0] po1.submit() self.assertEqual(so.customer, po1.customer) @@ -835,7 +835,7 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(len(po1.items), 1) # create po for remaining item - po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]]) + po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])[0] po2.submit() # teardown @@ -846,6 +846,45 @@ class TestSalesOrder(unittest.TestCase): so.load_from_db() so.cancel() + def test_drop_shipping_full_for_default_suppliers(self): + """Test if multiple POs are generated in one go against different default suppliers.""" + from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier + + if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"): + po_item1 = make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1}) + + if not frappe.db.exists("Item", "_Test Item for Drop Shipping 2"): + po_item2 = make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1}) + + so_items = [ + { + "item_code": "_Test Item for Drop Shipping 1", + "warehouse": "", + "qty": 2, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier' + }, + { + "item_code": "_Test Item for Drop Shipping 2", + "warehouse": "", + "qty": 2, + "rate": 400, + "delivered_by_supplier": 1, + "supplier": '_Test Supplier 1' + } + ] + + # create so and po + so = make_sales_order(item_list=so_items, do_not_submit=True) + so.submit() + + purchase_orders = make_purchase_order_for_default_supplier(so.name, selected_items=so_items) + + self.assertEqual(len(purchase_orders), 2) + self.assertEqual(purchase_orders[0].supplier, '_Test Supplier') + self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1') + def test_reserved_qty_for_closing_so(self): bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, fields=["reserved_qty"]) From 1b903e39c8a9bf57ed07c55ab9f64df743a3495a Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 12 Mar 2021 14:24:09 +0530 Subject: [PATCH 0382/1264] fix: Sider (unused variables) --- erpnext/selling/doctype/sales_order/test_sales_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index e10e5d0706..b636a944d1 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -851,10 +851,10 @@ class TestSalesOrder(unittest.TestCase): from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"): - po_item1 = make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1}) + make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1}) if not frappe.db.exists("Item", "_Test Item for Drop Shipping 2"): - po_item2 = make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1}) + make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1}) so_items = [ { From 59bff8a2f1a8e85def579772e1575070c9d34db6 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 31 Mar 2021 12:27:57 +0530 Subject: [PATCH 0383/1264] fix: Test - Preserve order of supplier list while removing duplicates - Dont use list of set, but list of dict with unique keys --- erpnext/selling/doctype/sales_order/sales_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b5c5e1bc2f..af3d461960 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -821,10 +821,10 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty)) target.project = source_parent.project - suppliers = [item.get('supplier') for item in selected_items if item.get('supplier') and item.get('supplier')] - suppliers = list(set(suppliers)) + suppliers = [item.get('supplier') for item in selected_items if item.get('supplier')] + suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order - items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')] + items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code')] items_to_map = list(set(items_to_map)) if not suppliers: From b07de03ddbea0a2150a3a22ebef1e1043750f389 Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 31 Mar 2021 13:39:04 +0530 Subject: [PATCH 0384/1264] fix: added blank option for status in report related to issue --- erpnext/support/report/issue_analytics/issue_analytics.js | 3 ++- erpnext/support/report/issue_summary/issue_summary.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/support/report/issue_analytics/issue_analytics.js b/erpnext/support/report/issue_analytics/issue_analytics.js index f87b2c2ddd..746eee025a 100644 --- a/erpnext/support/report/issue_analytics/issue_analytics.js +++ b/erpnext/support/report/issue_analytics/issue_analytics.js @@ -52,6 +52,7 @@ frappe.query_reports["Issue Analytics"] = { label: __("Status"), fieldtype: "Select", options:[ + "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, {label: __('Resolved'), value: 'Resolved'}, @@ -138,4 +139,4 @@ frappe.query_reports["Issue Analytics"] = { } }); } -}; \ No newline at end of file +}; diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js index 684482ac8d..eb0e06cd08 100644 --- a/erpnext/support/report/issue_summary/issue_summary.js +++ b/erpnext/support/report/issue_summary/issue_summary.js @@ -39,6 +39,7 @@ frappe.query_reports["Issue Summary"] = { label: __("Status"), fieldtype: "Select", options:[ + "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, {label: __('Resolved'), value: 'Resolved'}, @@ -70,4 +71,4 @@ frappe.query_reports["Issue Summary"] = { options: "User" } ] -}; \ No newline at end of file +}; From f54d596ae12f451ceea3855c75f4ad5986be0cdb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 31 Mar 2021 14:06:02 +0530 Subject: [PATCH 0385/1264] fix: Precision issues while allocating advance amoount --- erpnext/accounts/utils.py | 3 ++- erpnext/controllers/accounts_controller.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 89a05b187d..5a64e27ccb 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -406,9 +406,10 @@ def check_if_advance_entry_modified(args): throw(_("""Payment Entry has been modified after you pulled it. Please pull it again.""")) def validate_allocated_amount(args): + precision = args.get('precision') or frappe.db.get_single_value("System Settings", "currency_precision") if args.get("allocated_amount") < 0: throw(_("Allocated amount cannot be negative")) - elif args.get("allocated_amount") > args.get("unadjusted_amount"): + elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision): throw(_("Allocated amount cannot be greater than unadjusted amount")) def update_reference_in_journal_entry(d, jv_obj): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 12a81c7887..5341fc0b0d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -656,6 +656,7 @@ class AccountsController(TransactionBase): 'dr_or_cr': dr_or_cr, 'unadjusted_amount': flt(d.advance_amount), 'allocated_amount': flt(d.allocated_amount), + 'precision': d.precision('advance_amount'), 'exchange_rate': (self.conversion_rate if self.party_account_currency != self.company_currency else 1), 'grand_total': (self.base_grand_total @@ -1394,7 +1395,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ) def get_new_child_item(item_row): - child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" + child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row) def validate_quantity(child_item, d): From 5d73d8281bbbb83b3d4bc0f3082144aab8b3d46a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 31 Mar 2021 14:10:56 +0530 Subject: [PATCH 0386/1264] fix: Remove unintentionally added field --- .../doctype/purchase_invoice/purchase_invoice.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 18b66375e9..739bd671f7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -127,7 +127,6 @@ "write_off_cost_center", "advances_section", "allocate_advances_automatically", - "adjust_advance_taxes", "get_advances", "advances", "payment_schedule_section", @@ -1326,13 +1325,6 @@ "label": "Project", "options": "Project" }, - { - "default": "0", - "description": "Taxes paid while advance payment will be adjusted against this invoice", - "fieldname": "adjust_advance_taxes", - "fieldtype": "Check", - "label": "Adjust Advance Taxes" - }, { "depends_on": "eval:doc.is_internal_supplier", "description": "Unrealized Profit / Loss account for intra-company transfers", @@ -1378,7 +1370,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-03-09 21:12:30.422084", + "modified": "2021-03-30 21:45:58.334107", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From df1ef6f8e5d8efd5da31bb2979877592f80f8d34 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 31 Mar 2021 14:23:09 +0530 Subject: [PATCH 0387/1264] fix: make round off GLE always non-opening (#25087) --- erpnext/accounts/general_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index b42c0c61d9..dac0c216c8 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -196,7 +196,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): if not round_off_gle: for k in ["voucher_type", "voucher_no", "company", - "posting_date", "remarks", "is_opening"]: + "posting_date", "remarks"]: round_off_gle[k] = gl_map[0][k] round_off_gle.update({ @@ -208,6 +208,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): "cost_center": round_off_cost_center, "party_type": None, "party": None, + "is_opening": "No", "against_voucher_type": None, "against_voucher": None }) From f239dbdd030f83f9b0781be138d1ca15c6db6c72 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 31 Mar 2021 15:15:29 +0530 Subject: [PATCH 0388/1264] feat: tax collected at source using tax withholding category (#25090) * refactor: tax withholding category against customer * feat: pan and tax withholding category fields for customer * fix: test * feat: charging tcs on sales invoice * fix: tcs chargable amount * fix: tcs amount calculation * fix: sider --- .../doctype/sales_invoice/sales_invoice.py | 29 ++ .../tax_withholding_category.py | 400 ++++++++++++------ .../test_tax_withholding_category.py | 137 +++++- .../selling/doctype/customer/customer.json | 19 +- 4 files changed, 444 insertions(+), 141 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 903a2ef5f7..1893c71dfc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -23,6 +23,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import \ from erpnext.accounts.deferred_revenue import validate_service_stop_date from frappe.model.utils import get_fetch_values from frappe.contacts.doctype.address.address import get_address_display +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.healthcare.utils import manage_invoice_submit_cancel @@ -75,6 +76,8 @@ class SalesInvoice(SellingController): if not self.is_pos: self.so_dn_required() + + self.set_tax_withholding() self.validate_proj_cust() self.validate_pos_return() @@ -153,6 +156,32 @@ class SalesInvoice(SellingController): if cost_center_company != self.company: frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company))) + def set_tax_withholding(self): + tax_withholding_details = get_party_tax_withholding_details(self) + + if not tax_withholding_details: + return + + accounts = [] + tax_withholding_account = tax_withholding_details.get("account_head") + + for d in self.taxes: + if d.account_head == tax_withholding_account: + d.update(tax_withholding_details) + accounts.append(d.account_head) + + if not accounts or tax_withholding_account not in accounts: + self.append("taxes", tax_withholding_details) + + to_remove = [d for d in self.taxes + if not d.tax_amount and d.charge_type == "Actual" and d.account_head == tax_withholding_account] + + for d in to_remove: + self.remove(d) + + # calculate totals again after applying TDS + self.calculate_taxes_and_totals() + def before_save(self): set_account_for_mode_of_payment(self) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 32ad4cb03a..961bdb147f 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -12,37 +12,62 @@ from erpnext.accounts.utils import get_fiscal_year class TaxWithholdingCategory(Document): pass -def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None): +def get_party_details(inv): + party_type, party = '', '' + if inv.doctype == 'Sales Invoice': + party_type = 'Customer' + party = inv.customer + else: + party_type = 'Supplier' + party = inv.supplier + + return party_type, party + +def get_party_tax_withholding_details(inv, tax_withholding_category=None): pan_no = '' - suppliers = [] + parties = [] + party_type, party = get_party_details(inv) if not tax_withholding_category: - tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan']) + tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan']) if not tax_withholding_category: return + # if tax_withholding_category passed as an argument but not pan_no if not pan_no: - pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan') + pan_no = frappe.db.get_value(party_type, party, 'pan') # Get others suppliers with the same PAN No if pan_no: - suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})] + parties = frappe.get_all(party_type, filters={ 'pan': pan_no }, pluck='name') - if not suppliers: - suppliers.append(ref_doc.supplier) + if not parties: + parties.append(party) + + fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company) + tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company) - fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company) - tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company) if not tax_details: frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}') - .format(tax_withholding_category, ref_doc.company)) + .format(tax_withholding_category, inv.company)) - tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company, - tax_details, fy, ref_doc.posting_date, pan_no) + if party_type == 'Customer' and not tax_details.cumulative_threshold: + # TCS is only chargeable on sum of invoiced value + frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.') + .format(tax_withholding_category, inv.company, party)) - tax_row = get_tax_row(tax_details, tds_amount) + tax_amount, tax_deducted = get_tax_amount( + party_type, parties, + inv, tax_details, + fiscal_year, pan_no + ) + + if party_type == 'Supplier': + tax_row = get_tax_row_for_tds(tax_details, tax_amount) + else: + tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) return tax_row @@ -69,147 +94,254 @@ def get_tax_withholding_rates(tax_withholding, fiscal_year): frappe.throw(_("No Tax Withholding data found for the current Fiscal Year.")) -def get_tax_row(tax_details, tds_amount): - - return { +def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted): + row = { "category": "Total", - "add_deduct_tax": "Deduct", "charge_type": "Actual", - "account_head": tax_details.account_head, + "tax_amount": tax_amount, "description": tax_details.description, - "tax_amount": tds_amount + "account_head": tax_details.account_head } -def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None): - fiscal_year, year_start_date, year_end_date = fiscal_year_details - tds_amount = 0 - tds_deducted = 0 + if tax_deducted: + # TCS already deducted on previous invoices + # So, TCS will be calculated by 'Previous Row Total' - def _get_tds(amount, rate): - if amount <= 0: - return 0 - - return amount * rate / 100 - - ldc_name = frappe.db.get_value('Lower Deduction Certificate', - { - 'pan_no': pan_no, - 'fiscal_year': fiscal_year - }, 'name') - ldc = '' - - if ldc_name: - ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name) - - entries = frappe.db.sql(""" - select voucher_no, credit - from `tabGL Entry` - where company = %s and - party in %s and fiscal_year=%s and credit > 0 - and is_opening = 'No' - """, (company, tuple(suppliers), fiscal_year), as_dict=1) - - vouchers = [d.voucher_no for d in entries] - advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company) - - tds_vouchers = vouchers + advance_vouchers - - if tds_vouchers: - tds_deducted = frappe.db.sql(""" - SELECT sum(credit) FROM `tabGL Entry` - WHERE - account=%s and fiscal_year=%s and credit > 0 - and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))), - ((tax_details.account_head, fiscal_year) + tuple(tds_vouchers))) - - tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0 - - if tds_deducted: - if ldc: - limit_consumed = frappe.db.get_value('Purchase Invoice', - { - 'supplier': ('in', suppliers), - 'apply_tds': 1, - 'docstatus': 1 - }, 'sum(net_total)') - - if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total, - ldc.certificate_limit): - - tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details) + taxes_excluding_tcs = [d for d in inv.taxes if d.account_head != tax_details.account_head] + if taxes_excluding_tcs: + # chargeable amount is the total amount after other charges are applied + row.update({ + "charge_type": "On Previous Row Total", + "row_id": len(taxes_excluding_tcs), + "rate": tax_details.rate + }) else: - tds_amount = _get_tds(net_total, tax_details.rate) - else: - supplier_credit_amount = frappe.get_all('Purchase Invoice', - fields = ['sum(net_total)'], - filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1) + # if only TCS is to be charged, then net total is chargeable amount + row.update({ + "charge_type": "On Net Total", + "rate": tax_details.rate + }) - supplier_credit_amount = (supplier_credit_amount[0][0] - if supplier_credit_amount and supplier_credit_amount[0][0] else 0) + return row - jv_supplier_credit_amt = frappe.get_all('Journal Entry Account', - fields = ['sum(credit_in_account_currency)'], - filters = { - 'parent': ('in', vouchers), 'docstatus': 1, - 'party': ('in', suppliers), - 'reference_type': ('not in', ['Purchase Invoice']) - }, as_list=1) +def get_tax_row_for_tds(tax_details, tax_amount): + return { + "category": "Total", + "charge_type": "Actual", + "tax_amount": tax_amount, + "add_deduct_tax": "Deduct", + "description": tax_details.description, + "account_head": tax_details.account_head + } - supplier_credit_amount += (jv_supplier_credit_amt[0][0] - if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0) +def get_lower_deduction_certificate(fiscal_year, pan_no): + ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name') + if ldc_name: + return frappe.get_doc('Lower Deduction Certificate', ldc_name) - supplier_credit_amount += net_total +def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None): + fiscal_year = fiscal_year_details[0] - debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date) - supplier_credit_amount -= debit_note_amount + vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type) + advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type) + taxable_vouchers = vouchers + advance_vouchers - if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold) - or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)): + tax_deducted = 0 + if taxable_vouchers: + tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details) - if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total, - ldc.certificate_limit): - tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate, - tax_details) + tax_amount = 0 + posting_date = inv.posting_date + if party_type == 'Supplier': + ldc = get_lower_deduction_certificate(fiscal_year, pan_no) + if tax_deducted: + net_total = inv.net_total + if ldc: + tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total) else: - tds_amount = _get_tds(supplier_credit_amount, tax_details.rate) + tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 + else: + tax_amount = get_tds_amount( + ldc, parties, inv, tax_details, + fiscal_year_details, tax_deducted, vouchers + ) + + elif party_type == 'Customer': + if tax_deducted: + # if already TCS is charged, then amount will be calculated based on 'Previous Row Total' + tax_amount = 0 + else: + # if no TCS has been charged in FY, + # then chargeable value is "prev invoices + advances" value which cross the threshold + tax_amount = get_tcs_amount( + parties, inv, tax_details, + fiscal_year_details, vouchers, advance_vouchers + ) + + return tax_amount, tax_deducted + +def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'): + dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' + + filters = { + dr_or_cr: ['>', 0], + 'company': company, + 'party_type': party_type, + 'party': ['in', parties], + 'fiscal_year': fiscal_year, + 'is_opening': 'No', + 'is_cancelled': 0 + } + + return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""] + +def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'): + # for advance vouchers, debit and credit is reversed + dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit' + + filters = { + dr_or_cr: ['>', 0], + 'is_opening': 'No', + 'is_cancelled': 0, + 'party_type': party_type, + 'party': ['in', parties], + 'against_voucher': ['is', 'not set'] + } + + if fiscal_year: + filters['fiscal_year'] = fiscal_year + if company: + filters['company'] = company + if from_date and to_date: + filters['posting_date'] = ['between', (from_date, to_date)] + + return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""] + +def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): + # check if TDS / TCS account is already charged on taxable vouchers + filters = { + 'is_cancelled': 0, + 'credit': ['>', 0], + 'fiscal_year': fiscal_year, + 'account': tax_details.account_head, + 'voucher_no': ['in', taxable_vouchers], + } + field = "sum(credit)" + + return frappe.db.get_value('GL Entry', filters, field) or 0.0 + +def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): + tds_amount = 0 + + supp_credit_amt = frappe.db.get_value('Purchase Invoice', { + 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1 + }, 'sum(net_total)') or 0.0 + + supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', { + 'parent': ('in', vouchers), 'docstatus': 1, + 'party': ('in', parties), 'reference_type': ('!=', 'Purchase Invoice') + }, 'sum(credit_in_account_currency)') or 0.0 + + supp_credit_amt += supp_jv_credit_amt + supp_credit_amt += inv.net_total + + debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company) + supp_credit_amt -= debit_note_amount + + threshold = tax_details.get('threshold', 0) + cumulative_threshold = tax_details.get('cumulative_threshold', 0) + + if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): + if ldc and is_valid_certificate( + ldc.valid_from, ldc.valid_upto, + inv.posting_date, tax_deducted, + inv.net_total, ldc.certificate_limit + ): + tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) + else: + tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 return tds_amount -def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None): - condition = "fiscal_year=%s" % fiscal_year +def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers): + tcs_amount = 0 + fiscal_year, _, _ = fiscal_year_details + + # sum of debit entries made from sales invoices + invoiced_amt = frappe.db.get_value('GL Entry', { + 'is_cancelled': 0, + 'party': ['in', parties], + 'company': inv.company, + 'voucher_no': ['in', vouchers], + }, 'sum(debit)') or 0.0 + + # sum of credit entries made from PE / JV with unset 'against voucher' + advance_amt = frappe.db.get_value('GL Entry', { + 'is_cancelled': 0, + 'party': ['in', parties], + 'company': inv.company, + 'voucher_no': ['in', adv_vouchers], + }, 'sum(credit)') or 0.0 + + # sum of credit entries made from sales invoice + credit_note_amt = frappe.db.get_value('GL Entry', { + 'is_cancelled': 0, + 'credit': ['>', 0], + 'party': ['in', parties], + 'fiscal_year': fiscal_year, + 'company': inv.company, + 'voucher_type': 'Sales Invoice', + }, 'sum(credit)') or 0.0 + + cumulative_threshold = tax_details.get('cumulative_threshold', 0) + + current_invoice_total = get_invoice_total_without_tcs(inv, tax_details) + total_invoiced_amt = current_invoice_total + invoiced_amt + advance_amt - credit_note_amt + + if ((cumulative_threshold and total_invoiced_amt >= cumulative_threshold)): + chargeable_amt = total_invoiced_amt - cumulative_threshold + tcs_amount = chargeable_amt * tax_details.rate / 100 if chargeable_amt > 0 else 0 + + return tcs_amount + +def get_invoice_total_without_tcs(inv, tax_details): + tcs_tax_row = [d for d in inv.taxes if d.account_head == tax_details.account_head] + tcs_tax_row_amount = tcs_tax_row[0].base_tax_amount if tcs_tax_row else 0 + + return inv.grand_total - tcs_tax_row_amount + +def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total): + tds_amount = 0 + limit_consumed = frappe.db.get_value('Purchase Invoice', { + 'supplier': ('in', parties), + 'apply_tds': 1, + 'docstatus': 1 + }, 'sum(net_total)') + + if is_valid_certificate( + ldc.valid_from, ldc.valid_upto, + posting_date, limit_consumed, + net_total, ldc.certificate_limit + ): + tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details) + + return tds_amount + +def get_debit_note_amount(suppliers, fiscal_year_details, company=None): + _, year_start_date, year_end_date = fiscal_year_details + + filters = { + 'supplier': ['in', suppliers], + 'is_return': 1, + 'docstatus': 1, + 'posting_date': ['between', (year_start_date, year_end_date)] + } + fields = ['abs(sum(net_total)) as net_total'] if company: - condition += "and company =%s" % (company) - if from_date and to_date: - condition += "and posting_date between %s and %s" % (from_date, to_date) + filters['company'] = company - ## Appending the same supplier again if length of suppliers list is 1 - ## since tuple of single element list contains None, For example ('Test Supplier 1', ) - ## and the below query fails - if len(suppliers) == 1: - suppliers.append(suppliers[0]) - - return frappe.db.sql_list(""" - select distinct voucher_no - from `tabGL Entry` - where party in %s and %s and debit > 0 - and is_opening = 'No' - """, (tuple(suppliers), condition)) or [] - -def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None): - condition = "and 1=1" - if company: - condition = " and company=%s " % company - - if len(suppliers) == 1: - suppliers.append(suppliers[0]) - - return flt(frappe.db.sql(""" - select abs(sum(net_total)) - from `tabPurchase Invoice` - where supplier in %s and is_return=1 and docstatus=1 - and posting_date between %s and %s %s - """, (tuple(suppliers), year_start_date, year_end_date, condition))) + return frappe.get_all('Purchase Invoice', filters, fields)[0].get('net_total') or 0.0 def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details): if current_amount < (certificate_limit - deducted_amount): @@ -227,4 +359,4 @@ def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, certificate_limit > deducted_amount): valid = True - return valid \ No newline at end of file + return valid diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index ef77674372..9ce8e3fe83 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -9,7 +9,7 @@ from frappe.utils import today from erpnext.accounts.utils import get_fiscal_year from erpnext.buying.doctype.supplier.test_supplier import create_supplier -test_dependencies = ["Supplier Group"] +test_dependencies = ["Supplier Group", "Customer Group"] class TestTaxWithholdingCategory(unittest.TestCase): @classmethod @@ -18,6 +18,9 @@ class TestTaxWithholdingCategory(unittest.TestCase): create_records() create_tax_with_holding_category() + def tearDown(self): + cancel_invoices() + def test_cumulative_threshold_tds(self): frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS") invoices = [] @@ -128,9 +131,59 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_cumulative_threshold_tcs(self): + frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS") + invoices = [] + + # create invoices for lower than single threshold tax rate + for _ in range(2): + si = create_sales_invoice(customer = "Test TCS Customer") + si.submit() + invoices.append(si) + + # create another invoice whose total when added to previously created invoice, + # surpasses cumulative threshhold + si = create_sales_invoice(customer = "Test TCS Customer", rate=12000) + si.submit() + + # assert tax collection on total invoice amount created until now + tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC']) + self.assertEqual(tcs_charged, 200) + self.assertEqual(si.grand_total, 12200) + invoices.append(si) + + # TCS is already collected once, so going forward system will collect TCS on every invoice + si = create_sales_invoice(customer = "Test TCS Customer", rate=5000) + si.submit() + + tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC']) + self.assertEqual(tcs_charged, 500) + invoices.append(si) + + #delete invoices to avoid clashing + for d in invoices: + d.cancel() + +def cancel_invoices(): + purchase_invoices = frappe.get_all("Purchase Invoice", { + 'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']], + 'docstatus': 1 + }, pluck="name") + + sales_invoices = frappe.get_all("Sales Invoice", { + 'customer': 'Test TCS Customer', + 'docstatus': 1 + }, pluck="name") + + for d in purchase_invoices: + frappe.get_doc('Purchase Invoice', d).cancel() + + for d in sales_invoices: + frappe.get_doc('Sales Invoice', d).cancel() + def create_purchase_invoice(**args): # return sales invoice doc object - item = frappe.get_doc('Item', {'item_name': 'TDS Item'}) + item = frappe.db.get_value('Item', {'item_name': 'TDS Item'}, "name") args = frappe._dict(args) pi = frappe.get_doc({ @@ -145,7 +198,7 @@ def create_purchase_invoice(**args): "taxes": [], "items": [{ 'doctype': 'Purchase Invoice Item', - 'item_code': item.name, + 'item_code': item, 'qty': args.qty or 1, 'rate': args.rate or 10000, 'cost_center': 'Main - _TC', @@ -156,6 +209,33 @@ def create_purchase_invoice(**args): pi.save() return pi +def create_sales_invoice(**args): + # return sales invoice doc object + item = frappe.db.get_value('Item', {'item_name': 'TCS Item'}, "name") + + args = frappe._dict(args) + si = frappe.get_doc({ + "doctype": "Sales Invoice", + "posting_date": today(), + "customer": args.customer, + "company": '_Test Company', + "taxes_and_charges": "", + "currency": "INR", + "debit_to": "Debtors - _TC", + "taxes": [], + "items": [{ + 'doctype': 'Sales Invoice Item', + 'item_code': item, + 'qty': args.qty or 1, + 'rate': args.rate or 10000, + 'cost_center': 'Main - _TC', + 'expense_account': 'Cost of Goods Sold - _TC' + }] + }) + + si.save() + return si + def create_records(): # create a new suppliers for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']: @@ -168,7 +248,17 @@ def create_records(): "doctype": "Supplier", }).insert() - # create an item + for name in ['Test TCS Customer']: + if frappe.db.exists('Customer', name): + continue + + frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": name, + "doctype": "Customer" + }).insert() + + # create item if not frappe.db.exists('Item', "TDS Item"): frappe.get_doc({ "doctype": "Item", @@ -178,7 +268,16 @@ def create_records(): "is_stock_item": 0, }).insert() - # create an account + if not frappe.db.exists('Item', "TCS Item"): + frappe.get_doc({ + "doctype": "Item", + "item_code": "TCS Item", + "item_name": "TCS Item", + "item_group": "All Item Groups", + "is_stock_item": 1 + }).insert() + + # create tds account if not frappe.db.exists("Account", "TDS - _TC"): frappe.get_doc({ 'doctype': 'Account', @@ -189,6 +288,17 @@ def create_records(): 'root_type': 'Asset' }).insert() + # create tcs account + if not frappe.db.exists("Account", "TCS - _TC"): + frappe.get_doc({ + 'doctype': 'Account', + 'company': '_Test Company', + 'account_name': 'TCS', + 'parent_account': 'Duties and Taxes - _TC', + 'report_type': 'Balance Sheet', + 'root_type': 'Liability' + }).insert() + def create_tax_with_holding_category(): fiscal_year = get_fiscal_year(today(), company="_Test Company")[0] @@ -210,6 +320,23 @@ def create_tax_with_holding_category(): }] }).insert() + if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Cumulative Threshold TCS", + "category_name": "10% TCS", + "rates": [{ + 'fiscal_year': fiscal_year, + 'tax_withholding_rate': 10, + 'single_threshold': 0, + 'cumulative_threshold': 30000.00 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TCS - _TC' + }] + }).insert() + # Single thresold if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"): frappe.get_doc({ diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 557c7151d9..8fb3580747 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -16,6 +16,8 @@ "customer_name", "gender", "customer_type", + "pan", + "tax_withholding_category", "default_bank_account", "lead_name", "image", @@ -210,7 +212,8 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Represents Company", - "options": "Company" + "options": "Company", + "unique": 1 }, { "depends_on": "represents_company", @@ -479,13 +482,25 @@ "fieldname": "dn_required", "fieldtype": "Check", "label": "Allow Sales Invoice Creation Without Delivery Note" + }, + { + "fieldname": "pan", + "fieldtype": "Data", + "label": "PAN" + }, + { + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category" } ], "icon": "fa fa-user", "idx": 363, "image_field": "image", + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-03-17 11:03:42.706907", + "modified": "2021-01-27 12:54:57.258959", "modified_by": "Administrator", "module": "Selling", "name": "Customer", From 94c145f3c3789a2648347930e5f885e13568ed17 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 31 Mar 2021 15:28:26 +0530 Subject: [PATCH 0389/1264] fix: can't multiply sequence by non-int of type 'float' --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 70e4c2c40e..e23f7d43d9 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -110,7 +110,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru get_gross_profit(out) if args.doctype == 'Material Request': out.rate = args.rate or out.price_list_rate - out.amount = flt(args.qty * out.rate) + out.amount = flt(args.qty) * flt(out.rate) return out From 4d0939de8d7a576e2188617c3362cf7b1d521c3b Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 31 Mar 2021 15:51:48 +0530 Subject: [PATCH 0390/1264] feat: introduce parameter group in quality inspection (#25094) * feat: introduce parameter group in quality inspection * chore: make parameter group optional --- .../item_quality_inspection_parameter.json | 11 ++- .../quality_inspection_parameter.json | 12 ++- .../__init__.py | 0 .../quality_inspection_parameter_group.js | 8 ++ .../quality_inspection_parameter_group.json | 82 +++++++++++++++++++ .../quality_inspection_parameter_group.py | 10 +++ ...test_quality_inspection_parameter_group.py | 10 +++ .../quality_inspection_reading.json | 11 ++- 8 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 erpnext/stock/doctype/quality_inspection_parameter_group/__init__.py create mode 100644 erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.js create mode 100644 erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.json create mode 100644 erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.py create mode 100644 erpnext/stock/doctype/quality_inspection_parameter_group/test_quality_inspection_parameter_group.py diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index 471e6853b5..9b1a47eed6 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -7,6 +7,7 @@ "engine": "InnoDB", "field_order": [ "specification", + "parameter_group", "value", "numeric", "column_break_3", @@ -75,12 +76,20 @@ "in_list_view": 1, "label": "Numeric", "width": "80px" + }, + { + "fetch_from": "specification.parameter_group", + "fieldname": "parameter_group", + "fieldtype": "Link", + "label": "Parameter Group", + "options": "Quality Inspection Parameter Group", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-01 19:18:46.924399", + "modified": "2021-02-04 18:50:02.056173", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json index 0b5a9b5b3c..418b4825f2 100644 --- a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json +++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json @@ -7,24 +7,34 @@ "engine": "InnoDB", "field_order": [ "parameter", + "parameter_group", "description" ], "fields": [ { "fieldname": "parameter", "fieldtype": "Data", + "in_list_view": 1, "label": "Parameter", + "reqd": 1, "unique": 1 }, { "fieldname": "description", "fieldtype": "Text Editor", "label": "Description" + }, + { + "fieldname": "parameter_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parameter Group", + "options": "Quality Inspection Parameter Group" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-28 18:06:54.897317", + "modified": "2021-02-19 20:33:30.657406", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/quality_inspection_parameter_group/__init__.py b/erpnext/stock/doctype/quality_inspection_parameter_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.js b/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.js new file mode 100644 index 0000000000..8716a29871 --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Quality Inspection Parameter Group', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.json b/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.json new file mode 100644 index 0000000000..57264741a6 --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.json @@ -0,0 +1,82 @@ +{ + "actions": [], + "autoname": "field:group_name", + "creation": "2021-02-04 18:44:12.223295", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "group_name" + ], + "fields": [ + { + "fieldname": "group_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Parameter Group Name", + "reqd": 1, + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-02-04 18:44:12.223295", + "modified_by": "Administrator", + "module": "Stock", + "name": "Quality Inspection Parameter Group", + "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": "Stock User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.py b/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.py new file mode 100644 index 0000000000..1a3b1a0463 --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 QualityInspectionParameterGroup(Document): + pass diff --git a/erpnext/stock/doctype/quality_inspection_parameter_group/test_quality_inspection_parameter_group.py b/erpnext/stock/doctype/quality_inspection_parameter_group/test_quality_inspection_parameter_group.py new file mode 100644 index 0000000000..212d4b8c21 --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter_group/test_quality_inspection_parameter_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestQualityInspectionParameterGroup(unittest.TestCase): + pass diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index 35d58eff58..0eff5a8f00 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -7,6 +7,7 @@ "engine": "InnoDB", "field_order": [ "specification", + "parameter_group", "status", "value", "numeric", @@ -210,12 +211,20 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Numeric" + }, + { + "fetch_from": "specification.parameter_group", + "fieldname": "parameter_group", + "fieldtype": "Link", + "label": "Parameter Group", + "options": "Quality Inspection Parameter Group", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-01 19:46:22.138018", + "modified": "2021-02-04 19:15:37.991221", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", From fb4051bdddac7415eb370ccd8a25418797c0e35c Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 31 Mar 2021 15:52:51 +0530 Subject: [PATCH 0391/1264] feat: discount configuration on early payments (#25089) * feat: discount configuration on early payments Co-authored-by: Nabin Hait * fix: remove duplicate patch call Co-authored-by: Nabin Hait --- .../doctype/payment_entry/payment_entry.js | 42 +- .../doctype/payment_entry/payment_entry.py | 131 ++++- .../payment_entry/test_payment_entry.py | 48 ++ .../payment_entry_reference.json | 5 +- .../payment_schedule/payment_schedule.json | 87 ++- .../doctype/payment_term/payment_term.js | 20 + .../doctype/payment_term/payment_term.json | 512 +++++------------- .../payment_terms_template.js | 7 +- .../payment_terms_template.py | 6 - .../payment_terms_template_detail.json | 420 ++++++-------- .../accounts_receivable.py | 8 +- erpnext/controllers/accounts_controller.py | 23 +- erpnext/patches.txt | 1 + .../v13_0/update_payment_terms_outstanding.py | 15 + erpnext/setup/doctype/company/company.js | 3 +- erpnext/setup/doctype/company/company.json | 7 + 16 files changed, 644 insertions(+), 691 deletions(-) create mode 100644 erpnext/patches/v13_0/update_payment_terms_outstanding.py diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index b5f6a401df..c2e804e441 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -637,13 +637,13 @@ frappe.ui.form.on('Payment Entry', { let to_field = fields[key][1]; if (filters[from_field] && !filters[to_field]) { - frappe.throw(__("Error: {0} is mandatory field", - [to_field.replace(/_/g, " ")] - )); + frappe.throw( + __("Error: {0} is mandatory field", [to_field.replace(/_/g, " ")]) + ); } else if (filters[from_field] && filters[from_field] > filters[to_field]) { - frappe.throw(__("{0}: {1} must be less than {2}", - [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")] - )); + frappe.throw( + __("{0}: {1} must be less than {2}", [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")]) + ); } } }, @@ -692,6 +692,8 @@ frappe.ui.form.on('Payment Entry', { c.total_amount = d.invoice_amount; c.outstanding_amount = d.outstanding_amount; c.bill_no = d.bill_no; + c.payment_term = d.payment_term; + c.allocated_amount = d.allocated_amount; if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) { if(flt(d.outstanding_amount) > 0) @@ -774,12 +776,15 @@ frappe.ui.form.on('Payment Entry', { } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { if(paid_amount > total_negative_outstanding) { if(total_negative_outstanding == 0) { - frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice", - [frm.doc.payment_type, - (frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])); + frappe.msgprint( + __("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type, + (frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type]) + ); return false } else { - frappe.msgprint(__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])); + frappe.msgprint( + __("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding]) + ); return false; } } else { @@ -791,10 +796,13 @@ frappe.ui.form.on('Payment Entry', { } $.each(frm.doc.references || [], function(i, row) { - row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount - if(frappe.flags.allocate_payment_amount != 0){ - if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { - if(row.outstanding_amount >= allocated_positive_outstanding) { + if (frappe.flags.allocate_payment_amount == 0) { + //If allocate payment amount checkbox is unchecked, set zero to allocate amount + row.allocated_amount = 0; + + } else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) { + if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { + if (row.outstanding_amount >= allocated_positive_outstanding) { row.allocated_amount = allocated_positive_outstanding; } else { row.allocated_amount = row.outstanding_amount; @@ -802,9 +810,11 @@ frappe.ui.form.on('Payment Entry', { allocated_positive_outstanding -= flt(row.allocated_amount); } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { - if(Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) + if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) { row.allocated_amount = -1*allocated_negative_outstanding; - else row.allocated_amount = row.outstanding_amount; + } else { + row.allocated_amount = row.outstanding_amount; + }; allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount)); } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 203d06a41f..c0d53b52dd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -333,33 +333,50 @@ class PaymentEntry(AccountsController): invoice_payment_amount_map = {} invoice_paid_amount_map = {} - for reference in self.get('references'): - if reference.payment_term and reference.reference_name: - key = (reference.payment_term, reference.reference_name) + for ref in self.get('references'): + if ref.payment_term and ref.reference_name: + key = (ref.payment_term, ref.reference_name) invoice_payment_amount_map.setdefault(key, 0.0) - invoice_payment_amount_map[key] += reference.allocated_amount + invoice_payment_amount_map[key] += ref.allocated_amount if not invoice_paid_amount_map.get(key): - payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name}, - fields=['paid_amount', 'payment_amount', 'payment_term']) + payment_schedule = frappe.get_all( + 'Payment Schedule', + filters={'parent': ref.reference_name}, + fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding'] + ) for term in payment_schedule: - invoice_key = (term.payment_term, reference.reference_name) + invoice_key = (term.payment_term, ref.reference_name) invoice_paid_amount_map.setdefault(invoice_key, {}) - invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount + invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding + invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100) + + for key, allocated_amount in iteritems(invoice_payment_amount_map): + outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding')) + discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt')) - for key, amount in iteritems(invoice_payment_amount_map): if cancel: - frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s - WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0])) + frappe.db.sql(""" + UPDATE `tabPayment Schedule` + SET + paid_amount = `paid_amount` - %s, + discounted_amount = `discounted_amount` - %s, + outstanding = `outstanding` + %s + WHERE parent = %s and payment_term = %s""", + (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) else: - outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding')) - - if amount > outstanding: + if allocated_amount > outstanding: frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0])) - if amount and outstanding: - frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s - WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0])) + if allocated_amount and outstanding: + frappe.db.sql(""" + UPDATE `tabPayment Schedule` + SET + paid_amount = `paid_amount` + %s, + discounted_amount = `discounted_amount` + %s, + outstanding = `outstanding` - %s + WHERE parent = %s and payment_term = %s""", + (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) def set_status(self): if self.docstatus == 2: @@ -704,6 +721,8 @@ def get_outstanding_reference_documents(args): outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"), args.get("party_account"), filters=args, condition=condition) + outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) + for d in outstanding_invoices: d["exchange_rate"] = 1 if party_account_currency != company_currency: @@ -731,6 +750,46 @@ def get_outstanding_reference_documents(args): return data +def split_invoices_based_on_payment_terms(outstanding_invoices): + invoice_ref_based_on_payment_terms = {} + for idx, d in enumerate(outstanding_invoices): + if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']: + payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template') + if payment_term_template: + allocate_payment_based_on_payment_terms = frappe.db.get_value( + 'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms') + if allocate_payment_based_on_payment_terms: + payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"]) + + for payment_term in payment_schedule: + if payment_term.outstanding > 0.1: + invoice_ref_based_on_payment_terms.setdefault(idx, []) + invoice_ref_based_on_payment_terms[idx].append(frappe._dict({ + 'due_date': d.due_date, + 'currency': d.currency, + 'voucher_no': d.voucher_no, + 'voucher_type': d.voucher_type, + 'posting_date': d.posting_date, + 'invoice_amount': flt(d.invoice_amount), + 'outstanding_amount': flt(d.outstanding_amount), + 'payment_amount': payment_term.payment_amount, + 'payment_term': payment_term.payment_term, + 'allocated_amount': payment_term.outstanding + })) + + if invoice_ref_based_on_payment_terms: + for idx, ref in invoice_ref_based_on_payment_terms.items(): + voucher_no = outstanding_invoices[idx]['voucher_no'] + voucher_type = outstanding_invoices[idx]['voucher_type'] + + frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format( + voucher_type, voucher_no, len(ref)), alert=True) + + outstanding_invoices.pop(idx - 1) + outstanding_invoices += invoice_ref_based_on_payment_terms[idx] + + return outstanding_invoices + def get_orders_to_be_billed(posting_date, party_type, party, company, party_account_currency, company_currency, cost_center=None, filters=None): if party_type == "Customer": @@ -1083,6 +1142,8 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= paid_amount, received_amount = set_paid_amount_and_received_amount( dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc) + paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc) + pe = frappe.new_doc("Payment Entry") pe.payment_type = payment_type pe.company = doc.company @@ -1152,11 +1213,20 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.setup_party_account_field() pe.set_missing_values() + if party_account and bank: if dt == "Employee Advance": reference_doc = doc pe.set_exchange_rate(ref_doc=reference_doc) pe.set_amounts() + if discount_amount: + pe.set_gain_or_loss(account_details={ + 'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"), + 'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"), + 'amount': discount_amount * (-1 if payment_type == "Pay" else 1) + }) + pe.set_difference_amount() + return pe def get_bank_cash_account(doc, bank_account): @@ -1272,6 +1342,33 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta paid_amount = received_amount * doc.get('exchange_rate', 1) return paid_amount, received_amount +def apply_early_payment_discount(paid_amount, received_amount, doc): + total_discount = 0 + if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule: + for term in doc.payment_schedule: + if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date: + if term.discount_type == 'Percentage': + discount_amount = flt(doc.get('grand_total')) * (term.discount / 100) + else: + discount_amount = term.discount + + discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1) + + if doc.doctype == 'Sales Invoice': + paid_amount -= discount_amount + received_amount -= discount_amount_in_foreign_currency + else: + received_amount -= discount_amount + paid_amount -= discount_amount_in_foreign_currency + + total_discount += discount_amount + + if total_discount: + money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency')) + frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1) + + return paid_amount, received_amount, total_discount + def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount): references = [] for payment_term in payment_schedule: diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 772fc1a252..4641d6b5ff 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -193,6 +193,34 @@ class TestPaymentEntry(unittest.TestCase): self.assertEqual(si.payment_schedule[0].paid_amount, 200.0) self.assertEqual(si.payment_schedule[1].paid_amount, 36.0) + def test_payment_entry_against_payment_terms_with_discount(self): + si = create_sales_invoice(do_not_save=1, qty=1, rate=200) + create_payment_terms_template_with_discount() + si.payment_terms_template = 'Test Discount Template' + + frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC') + + si.append('taxes', { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 18 + }) + si.save() + + si.submit() + + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") + pe.submit() + si.load_from_db() + + self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount') + self.assertEqual(si.payment_schedule[0].payment_amount, 236.0) + self.assertEqual(si.payment_schedule[0].paid_amount, 212.40) + self.assertEqual(si.payment_schedule[0].outstanding, 0) + self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6) + def test_payment_against_purchase_invoice_to_check_status(self): pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC", @@ -591,6 +619,26 @@ def create_payment_terms_template(): }] }).insert() +def create_payment_terms_template_with_discount(): + + create_payment_term('30 Credit Days with 10% Discount') + + if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'): + payment_term_template = frappe.get_doc({ + 'doctype': 'Payment Terms Template', + 'template_name': 'Test Discount Template', + 'allocate_payment_based_on_payment_terms': 1, + 'terms': [{ + 'doctype': 'Payment Terms Template Detail', + 'payment_term': '30 Credit Days with 10% Discount', + 'invoice_portion': 100, + 'credit_days_based_on': 'Day(s) after invoice date', + 'credit_days': 2, + 'discount': 10, + 'discount_validity_based_on': 'Day(s) after invoice date', + 'discount_validity': 1 + }] + }).insert() def create_payment_term(name): if not frappe.db.exists('Payment Term', name): diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 8f5e9fbc28..912ad0977a 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -58,7 +58,7 @@ "fieldname": "total_amount", "fieldtype": "Float", "in_list_view": 1, - "label": "Total Amount", + "label": "Grand Total", "print_hide": 1, "read_only": 1 }, @@ -92,9 +92,10 @@ "options": "Payment Term" } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-03-13 12:07:19.362539", + "modified": "2021-02-10 11:25:47.144392", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index d363cf161b..e362566af0 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -6,11 +6,23 @@ "engine": "InnoDB", "field_order": [ "payment_term", + "section_break_15", "description", + "section_break_4", "due_date", - "invoice_portion", - "payment_amount", "mode_of_payment", + "column_break_5", + "invoice_portion", + "section_break_6", + "discount_type", + "discount_date", + "column_break_9", + "discount", + "section_break_9", + "payment_amount", + "discounted_amount", + "column_break_3", + "outstanding", "paid_amount" ], "fields": [ @@ -25,6 +37,7 @@ }, { "columns": 2, + "fetch_from": "payment_term.description", "fieldname": "description", "fieldtype": "Small Text", "in_list_view": 1, @@ -62,14 +75,82 @@ "options": "Mode of Payment" }, { + "depends_on": "paid_amount", "fieldname": "paid_amount", "fieldtype": "Currency", "label": "Paid Amount" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "discounted_amount", + "fieldname": "discounted_amount", + "fieldtype": "Currency", + "label": "Discounted Amount", + "read_only": 1 + }, + { + "fetch_from": "payment_amount", + "fieldname": "outstanding", + "fieldtype": "Currency", + "label": "Outstanding", + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "depends_on": "discount", + "fieldname": "discount_date", + "fieldtype": "Date", + "label": "Discount Date", + "mandatory_depends_on": "discount" + }, + { + "default": "Percentage", + "fetch_from": "payment_term.discount_type", + "fieldname": "discount_type", + "fieldtype": "Select", + "label": "Discount Type", + "options": "Percentage\nAmount" + }, + { + "fetch_from": "payment_term.discount", + "fieldname": "discount", + "fieldtype": "Float", + "label": "Discount" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-03-13 17:58:24.729526", + "modified": "2021-02-15 21:03:12.540546", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js index 054c2d1191..acd0144c2e 100644 --- a/erpnext/accounts/doctype/payment_term/payment_term.js +++ b/erpnext/accounts/doctype/payment_term/payment_term.js @@ -1,2 +1,22 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt +frappe.ui.form.on('Payment Term', { + onload(frm) { + frm.trigger('set_dynamic_description'); + }, + discount(frm) { + frm.trigger('set_dynamic_description'); + }, + discount_type(frm) { + frm.trigger('set_dynamic_description'); + }, + set_dynamic_description(frm) { + if (frm.doc.discount) { + let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]); + if (frm.doc.discount_type == 'Amount') { + description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]); + } + frm.set_df_property("discount", "description", description); + } + } +}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_term/payment_term.json b/erpnext/accounts/doctype/payment_term/payment_term.json index e77c244d3d..aec4965d79 100644 --- a/erpnext/accounts/doctype/payment_term/payment_term.json +++ b/erpnext/accounts/doctype/payment_term/payment_term.json @@ -1,386 +1,166 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:payment_term_name", - "beta": 0, - "creation": "2017-08-10 15:24:54.876365", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:payment_term_name", + "creation": "2017-08-10 15:24:54.876365", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "payment_term_name", + "invoice_portion", + "mode_of_payment", + "column_break_3", + "due_date_based_on", + "credit_days", + "credit_months", + "section_break_8", + "discount_type", + "discount", + "column_break_11", + "discount_validity_based_on", + "discount_validity", + "section_break_6", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_term_name", - "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": "Payment Term 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "bold": 1, + "fieldname": "payment_term_name", + "fieldtype": "Data", + "label": "Payment Term Name", + "unique": 1 + }, { - "description": "Provide the invoice portion in percent", - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "invoice_portion", - "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": "Invoice Portion", - "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 - }, + "bold": 1, + "fieldname": "invoice_portion", + "fieldtype": "Float", + "label": "Invoice Portion (%)" + }, { - "allow_bulk_edit": 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": 0, - "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment" + }, { - "allow_bulk_edit": 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 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "due_date_based_on", - "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": "Due Date Based On", - "length": 0, - "no_copy": 0, - "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", - "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 - }, + "bold": 1, + "fieldname": "due_date_based_on", + "fieldtype": "Select", + "label": "Due Date Based On", + "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" + }, { - "description": "Give number of days according to prior selection", - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", - "fieldname": "credit_days", - "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": "Credit Days", - "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 - }, + "bold": 1, + "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", + "fieldname": "credit_days", + "fieldtype": "Int", + "label": "Credit Days" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", - "fieldname": "credit_months", - "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": "Credit Months", - "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 - }, + "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", + "fieldname": "credit_months", + "fieldtype": "Int", + "label": "Credit Months" + }, { - "allow_bulk_edit": 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 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "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": "Description", - "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 + "bold": 1, + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Discount Settings" + }, + { + "default": "Percentage", + "fieldname": "discount_type", + "fieldtype": "Select", + "label": "Discount Type", + "options": "Percentage\nAmount" + }, + { + "fieldname": "discount", + "fieldtype": "Float", + "label": "Discount" + }, + { + "default": "Day(s) after invoice date", + "depends_on": "discount", + "fieldname": "discount_validity_based_on", + "fieldtype": "Select", + "label": "Discount Validity Based On", + "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" + }, + { + "depends_on": "discount", + "fieldname": "discount_validity", + "fieldtype": "Int", + "label": "Discount Validity", + "mandatory_depends_on": "discount" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" } - ], - "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": "2020-10-14 10:47:32.830478", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Payment Term", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2021-02-15 20:30:56.256403", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Term", + "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": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "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": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "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": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 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/accounts/doctype/payment_terms_template/payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js index f5c5bca87a..84c8d09b16 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js @@ -3,11 +3,6 @@ frappe.ui.form.on('Payment Terms Template', { setup: function(frm) { - frm.add_fetch("payment_term", "description", "description"); - frm.add_fetch("payment_term", "invoice_portion", "invoice_portion"); - frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on"); - frm.add_fetch("payment_term", "credit_days", "credit_days"); - frm.add_fetch("payment_term", "credit_months", "credit_months"); - frm.add_fetch("payment_term", "mode_of_payment", "mode_of_payment"); + } }); diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py index 2b2b6afe79..80e3348d81 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py @@ -13,7 +13,6 @@ from frappe import _ class PaymentTermsTemplate(Document): def validate(self): self.validate_invoice_portion() - self.validate_credit_days() self.check_duplicate_terms() def validate_invoice_portion(self): @@ -24,11 +23,6 @@ class PaymentTermsTemplate(Document): if flt(total_portion, 2) != 100.00: frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red') - def validate_credit_days(self): - for term in self.terms: - if cint(term.credit_days) < 0: - frappe.msgprint(_('Credit Days cannot be a negative number'), raise_exception=1, indicator='red') - def check_duplicate_terms(self): terms = [] for term in self.terms: diff --git a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json index eee3223314..20b3dca6aa 100644 --- a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json +++ b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json @@ -1,278 +1,164 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2017-08-10 15:34:09.409562", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2017-08-10 15:34:09.409562", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "payment_term", + "section_break_13", + "description", + "section_break_4", + "invoice_portion", + "mode_of_payment", + "column_break_3", + "due_date_based_on", + "credit_days", + "credit_months", + "section_break_8", + "discount_type", + "discount", + "column_break_11", + "discount_validity_based_on", + "discount_validity" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "payment_term", - "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": "Payment Term", - "length": 0, - "no_copy": 0, - "options": "Payment Term", - "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 - }, + "columns": 2, + "fieldname": "payment_term", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Payment Term", + "options": "Payment Term" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "description", - "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": "Description", - "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 - }, + "columns": 2, + "fetch_from": "payment_term.description", + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "default": "0", - "fieldname": "invoice_portion", - "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": "Invoice Portion", - "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 - }, + "columns": 2, + "fetch_from": "payment_term.invoice_portion", + "fetch_if_empty": 1, + "fieldname": "invoice_portion", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Invoice Portion (%)", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "due_date_based_on", - "fieldtype": "Select", - "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": "Due Date Based On", - "length": 0, - "no_copy": 0, - "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", - "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 - }, + "columns": 2, + "fetch_from": "payment_term.due_date_based_on", + "fetch_if_empty": 1, + "fieldname": "due_date_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Due Date Based On", + "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "default": "0", - "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", - "fieldname": "credit_days", - "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": "Credit Days", - "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 - }, + "columns": 2, + "default": "0", + "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", + "fetch_from": "payment_term.credit_days", + "fetch_if_empty": 1, + "fieldname": "credit_days", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Credit Days", + "non_negative": 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:doc.due_date_based_on=='Month(s) after the end of the invoice month'", - "fieldname": "credit_months", - "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": "Credit Months", - "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", + "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", + "fetch_from": "payment_term.credit_months", + "fetch_if_empty": 1, + "fieldname": "credit_months", + "fieldtype": "Int", + "label": "Credit Months", + "non_negative": 1 + }, { - "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": 0, - "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fetch_from": "payment_term.mode_of_payment", + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Discount Settings" + }, + { + "default": "Percentage", + "fetch_from": "payment_term.discount_type", + "fetch_if_empty": 1, + "fieldname": "discount_type", + "fieldtype": "Select", + "label": "Discount Type", + "options": "Percentage\nAmount" + }, + { + "fetch_from": "payment_term.discount", + "fetch_if_empty": 1, + "fieldname": "discount", + "fieldtype": "Float", + "label": "Discount" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "default": "Day(s) after invoice date", + "depends_on": "discount", + "fetch_from": "payment_term.discount_validity_based_on", + "fetch_if_empty": 1, + "fieldname": "discount_validity_based_on", + "fieldtype": "Select", + "label": "Discount Validity Based On", + "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" + }, + { + "collapsible": 1, + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "depends_on": "discount", + "fetch_from": "payment_term.discount_validity", + "fetch_if_empty": 1, + "fieldname": "discount_validity", + "fieldtype": "Int", + "label": "Discount Validity", + "mandatory_depends_on": "discount" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" } - ], - "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-08-21 16:15:55.143025", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Payment Terms Template 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, - "track_views": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-02-24 11:56:12.410807", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Terms Template 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/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 51fc7ec49a..444b40ed79 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -364,7 +364,7 @@ class ReceivablePayableReport(object): payment_terms_details = frappe.db.sql(""" select si.name, si.party_account_currency, si.currency, si.conversion_rate, - ps.due_date, ps.payment_amount, ps.description, ps.paid_amount + ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount from `tab{0}` si, `tabPayment Schedule` ps where si.name = ps.parent and @@ -395,13 +395,13 @@ class ReceivablePayableReport(object): "invoiced": invoiced, "invoice_grand_total": row.invoiced, "payment_term": d.description, - "paid": d.paid_amount, + "paid": d.paid_amount + d.discounted_amount, "credit_note": 0.0, - "outstanding": invoiced - d.paid_amount + "outstanding": invoiced - d.paid_amount - d.discounted_amount })) if d.paid_amount: - row['paid'] -= d.paid_amount + row['paid'] -= d.paid_amount + d.discounted_amount def allocate_closing_to_term(self, row, term, key): if row[key]: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 256437966b..3a0a0f18b1 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -904,7 +904,8 @@ class AccountsController(TransactionBase): else: for d in self.get("payment_schedule"): if d.invoice_portion: - d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount')) + d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.outstanding = d.payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -1219,18 +1220,24 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat term_details.description = term.description term_details.invoice_portion = term.invoice_portion term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 + term_details.discount_type = term.discount_type + term_details.discount = term.discount + # term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount + term_details.outstanding = term_details.payment_amount + term_details.mode_of_payment = term.mode_of_payment + if bill_date: term_details.due_date = get_due_date(term, bill_date) + term_details.discount_date = get_discount_date(term, bill_date) elif posting_date: term_details.due_date = get_due_date(term, posting_date) + term_details.discount_date = get_discount_date(term, posting_date) if getdate(term_details.due_date) < getdate(posting_date): term_details.due_date = posting_date - term_details.mode_of_payment = term.mode_of_payment return term_details - def get_due_date(term, posting_date=None, bill_date=None): due_date = None date = bill_date or posting_date @@ -1242,6 +1249,16 @@ def get_due_date(term, posting_date=None, bill_date=None): due_date = add_months(get_last_day(date), term.credit_months) return due_date +def get_discount_date(term, posting_date=None, bill_date=None): + discount_validity = None + date = bill_date or posting_date + if term.discount_validity_based_on == "Day(s) after invoice date": + discount_validity = add_days(date, term.discount_validity) + elif term.discount_validity_based_on == "Day(s) after the end of the invoice month": + discount_validity = add_days(get_last_day(date), term.discount_validity) + elif term.discount_validity_based_on == "Month(s) after the end of the invoice month": + discount_validity = add_months(get_last_day(date), term.discount_validity) + return discount_validity def get_supplier_block_status(party_name): """ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 20ea5097bf..bdae215dc8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -750,6 +750,7 @@ erpnext.patches.v13_0.set_company_in_leave_ledger_entry erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 +erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.update_vehicle_no_reqd_condition diff --git a/erpnext/patches/v13_0/update_payment_terms_outstanding.py b/erpnext/patches/v13_0/update_payment_terms_outstanding.py new file mode 100644 index 0000000000..4816b40250 --- /dev/null +++ b/erpnext/patches/v13_0/update_payment_terms_outstanding.py @@ -0,0 +1,15 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("accounts", "doctype", "Payment Schedule") + if frappe.db.count('Payment Schedule'): + frappe.db.sql(''' + UPDATE + `tabPayment Schedule` ps + SET + ps.outstanding = (ps.payment_amount - ps.paid_amount) + ''') diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 36033d9dae..5dacb17b81 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -259,6 +259,7 @@ erpnext.company.setup_queries = function(frm) { ["default_payroll_payable_account", {"root_type": "Liability"}], ["round_off_account", {"root_type": "Expense"}], ["write_off_account", {"root_type": "Expense"}], + ["default_discount_account", {}], ["discount_allowed_account", {"root_type": "Expense"}], ["discount_received_account", {"root_type": "Income"}], ["exchange_gain_loss_account", {"root_type": "Expense"}], @@ -275,7 +276,7 @@ erpnext.company.setup_queries = function(frm) { ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], - ["unrealized_profit_loss_account", {"root_type": "Liability"}] + ["unrealized_profit_loss_account", {"root_type": "Liability"},] ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index d49ae7ce8a..b27e2448d4 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -59,6 +59,7 @@ "default_deferred_expense_account", "default_payroll_payable_account", "default_expense_claim_payable_account", + "default_discount_account", "section_break_22", "cost_center", "column_break_26", @@ -733,6 +734,12 @@ "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", "options": "Account" + }, + { + "fieldname": "default_discount_account", + "fieldtype": "Link", + "label": "Default Payment Discount Account", + "options": "Account" } ], "icon": "fa fa-building", From c482c9592f821039cda757a02ff47a8de65428a8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 31 Mar 2021 16:04:46 +0530 Subject: [PATCH 0392/1264] fix: Purchase from registered composition dealer (#25057) * fix: Purchase from registered composition dealer * fix: Test case for GSTR 3b report --- .../doctype/gstr_3b_report/gstr_3b_report.html | 2 +- .../doctype/gstr_3b_report/gstr_3b_report.py | 16 ++++++++++++---- .../gstr_3b_report/test_gstr_3b_report.py | 15 ++++++++++++++- erpnext/regional/report/gstr_2/gstr_2.py | 4 ++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html index 888b2da48e..369a4001ef 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html @@ -109,7 +109,7 @@ - {{__("Suppliies made to Composition Taxable Persons")}} + {{__("Supplies made to Composition Taxable Persons")}} {% for row in data.inter_sup.comp_details %} {% if row %} 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 a49996d107..a5dd5a2e09 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -172,7 +172,6 @@ class GSTR3BReport(Document): self.json_output = frappe.as_json(self.report_dict) def set_inward_nil_exempt(self, inward_nil_exempt): - self.report_dict["inward_sup"]["isup_details"][0]["inter"] = flt(inward_nil_exempt.get("gst").get("inter"), 2) self.report_dict["inward_sup"]["isup_details"][0]["intra"] = flt(inward_nil_exempt.get("gst").get("intra"), 2) self.report_dict["inward_sup"]["isup_details"][1]["inter"] = flt(inward_nil_exempt.get("non_gst").get("inter"), 2) @@ -238,7 +237,6 @@ class GSTR3BReport(Document): self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2) def set_inter_state_supply(self, inter_state_supply): - osup_det = self.report_dict["sup_details"]["osup_det"] for key, value in iteritems(inter_state_supply): @@ -352,10 +350,18 @@ class GSTR3BReport(Document): inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i where p.docstatus = 1 and p.name = i.parent + and p.gst_category != 'Registered Composition' and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply + FROM `tabPurchase Invoice` + WHERE docstatus = 1 and gst_category = 'Registered Composition' + and month(posting_date) = %s and year(posting_date) = %s + and company = %s and company_gstin = %s + group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + inward_nil_exempt_details = { "gst": { "intra": 0.0, @@ -369,9 +375,11 @@ class GSTR3BReport(Document): for d in inward_nil_exempt: if d.place_of_supply: - if d.is_nil_exempt == 1 and state == d.place_of_supply.split("-")[1]: + if (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \ + and state == d.place_of_supply.split("-")[1]: inward_nil_exempt_details["gst"]["intra"] += d.base_amount - elif d.is_nil_exempt == 1 and state != d.place_of_supply.split("-")[1]: + elif (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \ + and state != d.place_of_supply.split("-")[1]: inward_nil_exempt_details["gst"]["inter"] += d.base_amount elif d.is_non_gst == 1 and state == d.place_of_supply.split("-")[1]: inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 023b4ed22b..ef8af24c42 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -64,7 +64,7 @@ class TestGSTR3BReport(unittest.TestCase): self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18), self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18), self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100), - self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250) + self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250) self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50) self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50) @@ -228,6 +228,19 @@ def create_purchase_invoices(): pi1.submit() + pi2 = make_purchase_invoice(company="_Test Company GST", + customer = '_Test Registered Supplier', + currency = 'INR', + item = 'Milk', + warehouse = 'Finished Goods - _GST', + expense_account = 'Cost of Goods Sold - _GST', + cost_center = 'Main - _GST', + rate=250, + qty=1, + do_not_save=1 + ) + pi2.submit() + def make_suppliers(): if not frappe.db.exists("Supplier", "_Test Registered Supplier"): frappe.get_doc({ diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py index f899349ccc..616c2b853d 100644 --- a/erpnext/regional/report/gstr_2/gstr_2.py +++ b/erpnext/regional/report/gstr_2/gstr_2.py @@ -44,7 +44,7 @@ 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 rate: + if rate or invoice_details.get('gst_category') == 'Registered Composition': if inv not in self.igst_invoices: rate = rate / 2 row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) @@ -86,7 +86,7 @@ class Gstr2Report(Gstr1Report): conditions += opts[1] if self.filters.get("type_of_business") == "B2B": - conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1 " + conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ', 'Registered Composition') and is_return != 1 " elif self.filters.get("type_of_business") == "CDNR": conditions += """ and is_return = 1 """ From c5ae9ee0e1255d9b8e0656b181de9e58fb98c379 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Wed, 31 Mar 2021 16:16:12 +0530 Subject: [PATCH 0393/1264] fix: do not set standard link in Sales Invoice as custom (#25097) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 720a9175e6..d382386a32 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1952,13 +1952,12 @@ "is_submittable": 1, "links": [ { - "custom": 1, "group": "Reference", "link_doctype": "POS Invoice", "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-02-01 15:42:26.261540", + "modified": "2021-03-31 15:42:26.261540", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From c814aab0b6a75b5b0737b738772e6bed9faf918a Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 31 Mar 2021 16:24:31 +0530 Subject: [PATCH 0394/1264] fix: delivery note print error (#25081) --- erpnext/stock/doctype/delivery_note/delivery_note.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 35443906c8..d326a04173 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -101,7 +101,7 @@ class DeliveryNote(SellingController): for f in fieldname: toggle_print_hide(self.meta if key == "parent" else item_meta, f) - super(DeliveryNote, self).before_print() + super(DeliveryNote, self).before_print(settings) def set_actual_qty(self): for d in self.get('items'): From 75ce336c8428c47f74b61c56c57b595d34959834 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 31 Mar 2021 16:27:20 +0530 Subject: [PATCH 0395/1264] chore: add sider config (#24892) (#25098) Co-authored-by: Sagar Vora Co-authored-by: Mohammad Hasnain Mohsin Rajan Co-authored-by: Sagar Vora --- .flake8 | 32 ++++++++++++++++++++++++++++++++ sider.yml | 3 +++ 2 files changed, 35 insertions(+) create mode 100644 .flake8 create mode 100644 sider.yml diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..399b176e1d --- /dev/null +++ b/.flake8 @@ -0,0 +1,32 @@ +[flake8] +ignore = + E121, + E126, + E127, + E128, + E203, + E225, + E226, + E231, + E241, + E251, + E261, + E265, + E302, + E303, + E305, + E402, + E501, + E741, + W291, + W292, + W293, + W391, + W503, + W504, + F403, + B007, + B950, + W191, + +max-line-length = 200 \ No newline at end of file diff --git a/sider.yml b/sider.yml new file mode 100644 index 0000000000..2ca6e8deb1 --- /dev/null +++ b/sider.yml @@ -0,0 +1,3 @@ +linter: + flake8: + config: .flake8 \ No newline at end of file From b6ce868199da35b051e03815cb24b4690505dd9c Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 17 Mar 2021 19:23:50 +0530 Subject: [PATCH 0396/1264] fix: POS print receipt --- .../page/point_of_sale/pos_past_order_summary.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 index 39f54fa1f8..5ac32b6a28 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -201,9 +201,13 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_container.on('click', '.print-btn', () => { const frm = this.events.get_frm(); - frm.doc = this.doc; - frm.print_preview.lang_code = frm.doc.language; - frm.print_preview.printit(true); + frappe.utils.print( + frm.doctype, + frm.docname, + frm.pos_print_format, + frm.doc.letter_head, + frm.doc.language || frappe.boot.lang + ) }); } From b5843dbdcdeb36626be0fef67eef21623b71002b Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 18 Mar 2021 17:02:19 +0530 Subject: [PATCH 0397/1264] fix: print recepit dialog --- .../point_of_sale/pos_past_order_summary.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) 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 index 5ac32b6a28..a5a739cff9 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -64,10 +64,7 @@ erpnext.PointOfSale.PastOrderSummary = class { {fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'} ], primary_action: () => { - const frm = this.events.get_frm(); - frm.doc = this.doc; - frm.print_preview.lang_code = frm.doc.language; - frm.print_preview.printit(true); + this.print_receipt(); }, primary_action_label: __('Print'), }); @@ -200,17 +197,21 @@ erpnext.PointOfSale.PastOrderSummary = class { }); this.$summary_container.on('click', '.print-btn', () => { - const frm = this.events.get_frm(); - frappe.utils.print( - frm.doctype, - frm.docname, - frm.pos_print_format, - frm.doc.letter_head, - frm.doc.language || frappe.boot.lang - ) + this.print_receipt(); }); } + print_receipt() { + const frm = this.events.get_frm(); + frappe.utils.print( + frm.doctype, + frm.docname, + frm.pos_print_format, + frm.doc.letter_head, + frm.doc.language || frappe.boot.lang + ); + } + attach_shortcuts() { const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; this.$summary_container.find('.print-btn').attr("title", `${ctrl_label}+P`); From 56ab3fb1327bf22cbdc167b4f58deb8290d7e54d Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 16 Mar 2021 14:15:59 +0530 Subject: [PATCH 0398/1264] fix: issue web list style --- erpnext/templates/includes/issue_row.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/issue_row.html b/erpnext/templates/includes/issue_row.html index d909c5feea..a04f558509 100644 --- a/erpnext/templates/includes/issue_row.html +++ b/erpnext/templates/includes/issue_row.html @@ -1,6 +1,6 @@
-
+
{% set indicator = 'red' if doc.status == 'Open' else 'gray' %} {% set indicator = 'green' if doc.status == 'Closed' else indicator %} From 5cdb1cb7a94ba0da2b9dd5072e3cba41094c4083 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 16 Mar 2021 14:16:22 +0530 Subject: [PATCH 0399/1264] fix: item variant dialog dropdown issue --- erpnext/stock/doctype/item/item.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 55391235cb..7489d1fefb 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -717,6 +717,18 @@ $.extend(erpnext.item, { .on('focus', function(e) { $(e.target).val('').trigger('input'); }) + .on("awesomplete-open", () => { + let modal = field.$input.parents('.modal-dialog')[0]; + if (modal) { + $(modal).removeClass("modal-dialog-scrollable"); + } + }) + .on("awesomplete-close", () => { + let modal = field.$input.parents('.modal-dialog')[0]; + if (modal) { + $(modal).addClass("modal-dialog-scrollable"); + } + }) }); }, From bcd63f04da541514b5e0517d2feedec51bc4223a Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 16 Mar 2021 15:39:16 +0530 Subject: [PATCH 0400/1264] style: missing semicolon --- erpnext/stock/doctype/item/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 7489d1fefb..2079cf88dd 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -728,7 +728,7 @@ $.extend(erpnext.item, { if (modal) { $(modal).addClass("modal-dialog-scrollable"); } - }) + }); }); }, From ad520e08bab34b58666ba140ce513e062c07e39d Mon Sep 17 00:00:00 2001 From: Anupam Date: Sat, 20 Feb 2021 22:32:00 +0530 Subject: [PATCH 0401/1264] feat: price margin in buying --- .../doctype/pricing_rule/pricing_rule.json | 3 +- .../purchase_invoice_item.json | 55 +++++++++++++++++- .../sales_invoice_item.json | 3 +- .../purchase_order_item.json | 58 +++++++++++++++++-- erpnext/controllers/taxes_and_totals.py | 2 +- erpnext/public/js/controllers/buying.js | 23 -------- erpnext/public/js/controllers/transaction.js | 44 ++++++++++++-- .../quotation_item/quotation_item.json | 3 +- .../sales_order_item/sales_order_item.json | 3 +- erpnext/selling/sales_common.js | 34 ----------- .../delivery_note_item.json | 3 +- .../purchase_receipt_item.json | 56 +++++++++++++++++- 12 files changed, 208 insertions(+), 79 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index d08a854142..33771645fe 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -357,7 +357,6 @@ "reqd": 1 }, { - "depends_on": "eval: doc.selling == 1", "fieldname": "margin", "fieldtype": "Section Break", "label": "Margin" @@ -565,7 +564,7 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2020-12-04 00:36:24.698219", + "modified": "2021-03-01 23:18:38.717613", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", 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 07e75acb41..96ad0fd785 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -28,10 +28,16 @@ "stock_qty", "sec_break1", "price_list_rate", - "discount_percentage", - "discount_amount", "col_break3", "base_price_list_rate", + "section_break_26", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_30", + "discount_percentage", + "discount_amount", + "base_rate_with_margin", "sec_break2", "rate", "amount", @@ -789,6 +795,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 }, @@ -799,12 +806,54 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_26", + "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", + "read_only": 1 + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "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 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:43:21.488258", + "modified": "2021-02-23 00:59:52.614805", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", 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 b403c7b237..8e6952a93c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -818,6 +818,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -825,7 +826,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:42:37.796771", + "modified": "2021-02-23 01:05:22.123527", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 75b2954ddd..5baf6939cd 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -27,11 +27,17 @@ "stock_qty", "sec_break1", "price_list_rate", + "last_purchase_rate", + "col_break3", + "base_price_list_rate", + "discount_and_margin_section", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_28", "discount_percentage", "discount_amount", - "col_break3", - "last_purchase_rate", - "base_price_list_rate", + "base_rate_with_margin", "sec_break2", "rate", "amount", @@ -733,15 +739,59 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "discount_and_margin_section", + "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_28", + "fieldtype": "Column Break" + }, + { + "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 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:44:41.816974", + "modified": "2021-02-23 01:00:27.132705", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 10271cbcc9..aab5770a94 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -109,7 +109,7 @@ class calculate_taxes_and_totals(object): elif item.discount_amount and item.pricing_rules: item.rate = item.price_list_rate - item.discount_amount - if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']: + if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']: item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index c96386611b..67b12fbca4 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -141,29 +141,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ this.apply_price_list(); }, - price_list_rate: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - - frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]); - - let item_rate = item.price_list_rate; - if (doc.doctype == "Purchase Order" && item.blanket_order_rate) { - item_rate = item.blanket_order_rate; - } - - if (item.discount_percentage) { - item.discount_amount = flt(item_rate) * flt(item.discount_percentage) / 100; - } - - if (item.discount_amount) { - item.rate = flt((item.price_list_rate) - (item.discount_amount), precision('rate', item)); - } else { - item.rate = item_rate; - } - - this.calculate_taxes_and_totals(); - }, - discount_percentage: function(doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); item.discount_amount = 0.0; diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 9351f6d206..1c0abdffcf 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -649,6 +649,40 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, + price_list_rate: function(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + 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", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Purchase Receipt Item"]), cdt) + this.apply_pricing_rule_on_item(item); + else + item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), + precision("rate", item)); + + this.calculate_taxes_and_totals(); + }, + + margin_rate_or_amount: function(doc, cdt, cdn) { + // calculated the revised total margin and rate on margin rate changes + let item = frappe.get_doc(cdt, cdn); + this.apply_pricing_rule_on_item(item); + this.calculate_taxes_and_totals(); + cur_frm.refresh_fields(); + }, + + margin_type: function(doc, cdt, cdn) { + // calculate the revised total margin and rate on margin type changes + let item = frappe.get_doc(cdt, cdn); + if (!item.margin_type) { + frappe.model.set_value(cdt, cdn, "margin_rate_or_amount", 0); + } else { + this.apply_pricing_rule_on_item(item, doc, cdt, cdn); + this.calculate_taxes_and_totals(); + cur_frm.refresh_fields(); + } + }, + get_incoming_rate: function(item, posting_date, posting_time, voucher_type, company) { let item_args = { @@ -1030,7 +1064,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, set_margin_amount_based_on_currency: function(exchange_rate) { - if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]), this.frm.doc.doctype) { + if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "Purchase Invoice", "Purchase Order", "Purchase Receipt"]), this.frm.doc.doctype) { var me = this; $.each(this.frm.doc.items || [], function(i, d) { if(d.margin_type == "Amount") { @@ -1280,10 +1314,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ change_grid_labels: function(company_currency) { var me = this; - this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount"], + this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"], company_currency, "items"); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate"], + this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"], this.frm.doc.currency, "items"); if(this.frm.fields_dict["operations"]) { @@ -1321,7 +1355,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ // toggle columns var item_grid = this.frm.fields_dict["items"].grid; - $.each(["base_rate", "base_price_list_rate", "base_amount"], function(i, fname) { + $.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) { if(frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); @@ -1468,7 +1502,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); // if doctype is Quotation Item / Sales Order Iten then add Margin Type and rate in item_list - if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), d.doctype){ + if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Purchase Receipt Item"]), d.doctype) { item_list[0]["margin_type"] = d.margin_type; item_list[0]["margin_rate_or_amount"] = d.margin_rate_or_amount; } diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index a6785f709a..8b53902d32 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -641,6 +641,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -648,7 +649,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:39:40.174551", + "modified": "2021-02-23 01:13:54.670763", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 37e47a9d41..1e5590e748 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -786,6 +786,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -793,7 +794,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:35:07.617320", + "modified": "2021-02-23 01:15:05.803091", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index ce084646e1..04285735ab 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -127,20 +127,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ this.set_dynamic_labels(); }, - price_list_rate: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - 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", "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), - precision("rate", item)); - - this.calculate_taxes_and_totals(); - }, - discount_percentage: function(doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); item.discount_amount = 0.0; @@ -353,26 +339,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ refresh_field('product_bundle_help'); }, - margin_rate_or_amount: function(doc, cdt, cdn) { - // calculated the revised total margin and rate on margin rate changes - var item = locals[cdt][cdn]; - this.apply_pricing_rule_on_item(item) - this.calculate_taxes_and_totals(); - cur_frm.refresh_fields(); - }, - - margin_type: function(doc, cdt, cdn){ - // calculate the revised total margin and rate on margin type changes - var item = locals[cdt][cdn]; - if(!item.margin_type) { - frappe.model.set_value(cdt, cdn, "margin_rate_or_amount", 0); - } else { - this.apply_pricing_rule_on_item(item, doc,cdt, cdn) - this.calculate_taxes_and_totals(); - cur_frm.refresh_fields(); - } - }, - company_address: function() { var me = this; if(this.frm.doc.company_address) { 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 17996247c5..b05090a237 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -750,6 +750,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 } @@ -758,7 +759,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:42:03.767968", + "modified": "2021-02-23 01:04:08.588104", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 8974ad9318..efe3642d23 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -37,10 +37,16 @@ "returned_qty", "rate_and_amount", "price_list_rate", - "discount_percentage", - "discount_amount", "col_break3", "base_price_list_rate", + "discount_and_margin_section", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_37", + "discount_percentage", + "discount_amount", + "base_rate_with_margin", "sec_break1", "rate", "amount", @@ -880,6 +886,7 @@ "fieldname": "stock_uom_rate", "fieldtype": "Currency", "label": "Rate of Stock UOM", + "no_copy": 1, "options": "currency", "read_only": 1 }, @@ -890,12 +897,55 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "discount_and_margin_section", + "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_37", + "fieldtype": "Column Break" + }, + { + "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 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:44:06.918515", + "modified": "2021-02-23 00:59:14.360847", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 35415fa575c66282e87235a758e83b720e9ef410 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 31 Mar 2021 19:39:33 +0530 Subject: [PATCH 0402/1264] fix: added flag for dont_fetch_price_list_rate in transaction (#25083) --- erpnext/public/js/controllers/transaction.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 1c0abdffcf..310f3d378b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1173,6 +1173,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.calculate_net_weight(); } + // for handling customization not to fetch price list rate + if (frappe.flags.dont_fetch_price_list_rate) { + return; + } + if (!dont_fetch_price_list_rate && frappe.meta.has_field(doc.doctype, "price_list_currency")) { this.apply_price_list(item, true); From 85a7ec91a13589624eabcce06de96eaa7537407a Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 31 Mar 2021 21:03:55 +0530 Subject: [PATCH 0403/1264] fix: Run Patches to updated Reserved, Requested and Ordered Qty --- erpnext/patches.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ff9433a7ef..578bc0abcc 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -99,7 +99,7 @@ execute:frappe.delete_doc("DocType", "Purchase Request") execute:frappe.delete_doc("DocType", "Purchase Request Item") erpnext.patches.v4_2.recalculate_bom_cost erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions -erpnext.patches.v4_2.update_requested_and_ordered_qty +erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31 execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True) erpnext.patches.v4_4.make_email_accounts execute:frappe.delete_doc("DocType", "Contact Control") @@ -208,7 +208,7 @@ erpnext.patches.v5_7.update_item_description_based_on_item_master erpnext.patches.v5_7.item_template_attributes execute:frappe.delete_doc_if_exists("DocType", "Manage Variants") execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item") -erpnext.patches.v4_2.repost_reserved_qty #2016-04-15 +erpnext.patches.v4_2.repost_reserved_qty #2021-03-31 erpnext.patches.v5_4.update_purchase_cost_against_project erpnext.patches.v5_8.update_order_reference_in_return_entries erpnext.patches.v5_8.add_credit_note_print_heading From 2fef245607b801896ef4a8f75b26118571a98747 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 31 Mar 2021 21:57:04 +0530 Subject: [PATCH 0404/1264] fix: don't set Company:company:default_currency as default for currency link fields (#25111) --- .../employee_advance/employee_advance.json | 3 +-- .../leave_encashment/leave_encashment.json | 3 +-- .../additional_salary/additional_salary.json | 3 +-- .../employee_benefit_application.json | 3 +-- .../employee_benefit_claim.js | 1 - .../employee_benefit_claim.json | 5 ++-- .../employee_incentive.json | 3 +-- .../employee_tax_exemption_declaration.js | 21 +++++++++++++++++ .../employee_tax_exemption_declaration.json | 4 ++-- ...employee_tax_exemption_proof_submission.js | 23 ++++++++++++++++++- ...ployee_tax_exemption_proof_submission.json | 4 ++-- .../income_tax_slab/income_tax_slab.json | 4 ++-- .../retention_bonus/retention_bonus.json | 3 +-- .../doctype/salary_slip/salary_slip.json | 3 +-- .../salary_structure_assignment.json | 3 +-- 15 files changed, 59 insertions(+), 27 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index cf6b5404ec..a25a828344 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -181,7 +181,6 @@ "read_only": 1 }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.employee)", "fieldname": "currency", "fieldtype": "Link", @@ -201,7 +200,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-25 12:01:55.980721", + "modified": "2021-03-31 21:31:53.746659", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json index 83eeae3adb..ec419ec2c6 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json @@ -130,7 +130,6 @@ "read_only": 1 }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.employee)", "fieldname": "currency", "fieldtype": "Link", @@ -155,7 +154,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-25 11:56:06.777241", + "modified": "2021-03-31 21:32:55.492327", "modified_by": "Administrator", "module": "HR", "name": "Leave Encashment", diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json index 2b29f667fb..3544244d60 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.json +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json @@ -163,7 +163,6 @@ "read_only": 1 }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.employee)", "fieldname": "currency", "fieldtype": "Link", @@ -176,7 +175,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-10-20 17:51:13.419716", + "modified": "2021-03-31 21:33:59.098532", "modified_by": "Administrator", "module": "Payroll", "name": "Additional Salary", diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json index 4c45580bf0..dcd01b5445 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json @@ -124,7 +124,6 @@ "read_only": 1 }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.employee)", "fieldname": "currency", "fieldtype": "Link", @@ -148,7 +147,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-12-14 15:52:08.566418", + "modified": "2021-03-31 21:35:08.940087", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Benefit Application", diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js index ea9ccd5205..e1f8431ec5 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js @@ -21,7 +21,6 @@ frappe.ui.form.on('Employee Benefit Claim', { callback: function(r) { if (r.message) { frm.set_value('currency', r.message); - frm.set_df_property('currency', 'hidden', 0); } } }); diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json index da24aacda1..d731ff90c0 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json @@ -125,10 +125,9 @@ "label": "Attachments" }, { - "default": "Company:company:default_currency", + "depends_on": "eval: doc.employee", "fieldname": "currency", "fieldtype": "Link", - "hidden": 1, "label": "Currency", "options": "Currency", "read_only": 1, @@ -145,7 +144,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-25 11:49:56.097352", + "modified": "2021-03-31 21:37:21.024625", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Benefit Claim", diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json index e5b1052b3a..f11e3aa32d 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json @@ -75,7 +75,6 @@ "reqd": 1 }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.employee)", "fieldname": "currency", "fieldtype": "Link", @@ -95,7 +94,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-10-20 17:22:16.468042", + "modified": "2021-03-31 21:38:20.332316", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Incentive", diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js index 0e0c9b5a1a..fb11875e96 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js @@ -47,5 +47,26 @@ frappe.ui.form.on('Employee Tax Exemption Declaration', { }); }).addClass("btn-primary"); } + }, + + employee: function(frm) { + if (frm.doc.employee) { + frm.trigger('get_employee_currency'); + } + }, + + get_employee_currency: function(frm) { + frappe.call({ + method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency", + args: { + employee: frm.doc.employee, + }, + callback: function(r) { + if (r.message) { + frm.set_value('currency', r.message); + frm.refresh_fields(); + } + } + }); } }); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json index 83d4ae53df..bceada3870 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json @@ -108,7 +108,7 @@ "read_only": 1 }, { - "default": "Company:company:default_currency", + "depends_on": "eval: doc.employee", "fieldname": "currency", "fieldtype": "Link", "label": "Currency", @@ -119,7 +119,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-10-20 16:42:24.493761", + "modified": "2021-03-31 21:39:59.237361", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Tax Exemption Declaration", diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js index 497f35c41e..4fb0a3771e 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js @@ -58,5 +58,26 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', { currency: function(frm) { frm.refresh_fields(); - } + }, + + employee: function(frm) { + if (frm.doc.employee) { + frm.trigger('get_employee_currency'); + } + }, + + get_employee_currency: function(frm) { + frappe.call({ + method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency", + args: { + employee: frm.doc.employee, + }, + callback: function(r) { + if (r.message) { + frm.set_value('currency', r.message); + frm.refresh_fields(); + } + } + }); + }, }); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json index 53f18cb1fe..6770d3e1a8 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json @@ -131,7 +131,7 @@ "read_only": 1 }, { - "default": "Company:company:default_currency", + "depends_on": "eval: doc.employee", "fieldname": "currency", "fieldtype": "Link", "label": "Currency", @@ -142,7 +142,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-10-20 16:47:03.410020", + "modified": "2021-03-31 21:41:13.723339", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Tax Exemption Proof Submission", diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json index 9fa261dea2..935d89fbc9 100644 --- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json +++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json @@ -93,7 +93,7 @@ "options": "Income Tax Slab Other Charges" }, { - "default": "Company:company:default_currency", + "fetch_from": "company.default_currency", "fieldname": "currency", "fieldtype": "Link", "label": "Currency", @@ -104,7 +104,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-10-19 13:54:24.728075", + "modified": "2021-03-31 21:42:08.139520", "modified_by": "Administrator", "module": "Payroll", "name": "Income Tax Slab", diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json index 6647230078..65b566f83a 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json @@ -93,7 +93,6 @@ "reqd": 1 }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.employee)", "fieldname": "currency", "fieldtype": "Link", @@ -106,7 +105,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-10-20 17:27:47.003134", + "modified": "2021-03-31 21:43:28.363644", "modified_by": "Administrator", "module": "Payroll", "name": "Retention Bonus", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 6688368262..262b7164a1 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -500,7 +500,6 @@ "fieldtype": "Column Break" }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)", "fetch_from": "salary_structure.currency", "fieldname": "currency", @@ -632,7 +631,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-02-19 11:48:05.383945", + "modified": "2021-03-31 21:44:09.772331", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json index 92bb347661..a4e1a5ad1a 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json @@ -125,7 +125,6 @@ "options": "Income Tax Slab" }, { - "default": "Company:company:default_currency", "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)", "fetch_from": "salary_structure.currency", "fieldname": "currency", @@ -146,7 +145,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-30 18:07:48.251311", + "modified": "2021-03-31 21:44:46.267974", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Structure Assignment", From 1e772bb9a4718e8c5f707f378f77e12ed38626e8 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Thu, 1 Apr 2021 00:44:35 +0530 Subject: [PATCH 0405/1264] test: Basic report data testing --- ...ee_hours_utilisation_based_on_timesheet.py | 7 +- .../test_employee_util.py | 147 ++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 8124c7a1be..19793917ce 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -99,7 +99,10 @@ class EmployeeHoursReport: if self.filters.project: additional_filters += f"AND ttd.project = '{self.filters.project}'" - + + if self.filters.company: + additional_filters += f"AND tt.company = '{self.filters.company}'" + self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project FROM `tabTimesheet Detail` AS ttd @@ -134,6 +137,8 @@ class EmployeeHoursReport: data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) def generate_report_summary(self): + self.report_summary = [] + if not self.data: return diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py new file mode 100644 index 0000000000..7b310fa1fe --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py @@ -0,0 +1,147 @@ +from __future__ import unicode_literals +import unittest +import frappe + +from frappe.utils.make_random import get_random +from frappe.utils import get_timestamp +from erpnext.projects.report.employee_hours_utilisation_based_on_timesheet.employee_hours_utilisation_based_on_timesheet import execute +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.projects.doctype.project.test_project import make_project + +class TestEmployeeUtilisation(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Create test employee + cls.test_emp1 = make_employee("test@example.com", "_Test Company") + cls.test_emp2 = make_employee("test1@example.com", "_Test Company") + + # Create test project + cls.test_project = make_project({"project_name": "_Test Project"}) + + # Create test timesheets + cls.create_test_timesheets() + + @classmethod + def create_test_timesheets(cls): + timesheet1 = frappe.new_doc("Timesheet") + timesheet1.employee = cls.test_emp1 + timesheet1.company = '_Test Company' + + timesheet1.append("time_logs", { + "activity_type": get_random("Activity Type"), + "hours": 5, + "billable": 1, + "from_time": '2021-04-01 13:30:00.000000', + "to_time": '2021-04-01 18:30:00.000000' + }) + + timesheet1.save() + timesheet1.submit() + + timesheet2 = frappe.new_doc("Timesheet") + timesheet2.employee = cls.test_emp2 + timesheet2.company = '_Test Company' + + timesheet2.append("time_logs", { + "activity_type": get_random("Activity Type"), + "hours": 10, + "billable": 0, + "from_time": '2021-04-01 13:30:00.000000', + "to_time": '2021-04-01 23:30:00.000000', + "project": cls.test_project.name + }) + + timesheet2.save() + timesheet2.submit() + + @classmethod + def tearDownClass(cls): + # Delete time logs + frappe.db.sql(""" + DELETE FROM `tabTimesheet Detail` + WHERE parent IN ( + SELECT name + FROM `tabTimesheet` + WHERE company = '_Test Company' + ) + """) + + frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'") + frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") + print(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") + + def test_utilisation_report_with_required_filters_only(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03" + } + + report = execute(filters) + + expected_data = [ + { + 'employee': 'EMP-00002', + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56 + }, + { + 'employee': 'EMP-00001', + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78 + } + ] + + self.assertEqual(report[1], expected_data) + + def test_utilisation_report_for_single_employee(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03", + "employee": self.test_emp1 + } + + report = execute(filters) + + expected_data = [ + { + 'employee': 'EMP-00001', + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78 + } + ] + + self.assertEqual(report[1], expected_data) + + def test_utilisation_report_for_project(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03", + "project": self.test_project.name + } + + report = execute(filters) + + expected_data = [ + { + 'employee': 'EMP-00002', + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56 + } + ] + + self.assertEqual(report[1], expected_data) \ No newline at end of file From d765b21825da6d83ed76259e7a1eabd721e76112 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 1 Apr 2021 14:39:52 +0530 Subject: [PATCH 0406/1264] fix: Salary Structure object has no attribute set_totals (#25114) --- erpnext/payroll/doctype/salary_slip/salary_slip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index d5278393a1..3e8a213ca9 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -216,7 +216,7 @@ frappe.ui.form.on('Salary Slip Timesheet', { }); var set_totals = function(frm) { - if (frm.doc.docstatus === 0) { + if (frm.doc.docstatus === 0 && frm.doc.doctype === "Salary Slip") { if (frm.doc.earnings || frm.doc.deductions) { frappe.call({ method: "set_totals", From 58ca5a73298ef0fd95e61992e244c1410fb5afa3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 23 Mar 2021 11:37:27 +0530 Subject: [PATCH 0407/1264] fix: repost not completed backdated transactions --- erpnext/hooks.py | 1 + .../repost_item_valuation.py | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f87769c182..a76fb07b80 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -324,6 +324,7 @@ scheduler_events = { "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", + "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries" ], "daily": [ "erpnext.stock.reorder_item.reorder_item", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 8436acbed2..559f9a5ed9 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form +from frappe.utils import cint, get_link_to_form, add_to_date, today from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -29,7 +29,7 @@ class RepostItemValuation(Document): self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") elif self.warehouse: self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") - + def set_status(self, status=None): if not status: status = 'Queued' @@ -54,7 +54,6 @@ def repost(doc): repost_sl_entries(doc) repost_gl_entries(doc) - check_if_stock_and_account_balance_synced(doc.posting_date, doc.company) doc.set_status('Completed') except Exception: @@ -103,7 +102,7 @@ def notify_error_to_stock_managers(doc, traceback): recipients = get_users_with_role("Stock Manager") if not recipients: get_users_with_role("System Manager") - + subject = _("Error while reposting item valuation") message = (_("Hi,") + "
" + _("An error has been appeared while reposting item valuation via {0}") @@ -112,4 +111,24 @@ def notify_error_to_stock_managers(doc, traceback): ) frappe.sendmail(recipients=recipients, subject=subject, message=message) +def repost_entries(): + riv_entries = get_repost_item_valuation_entries() + for row in riv_entries: + doc = frappe.get_cached_doc('Repost Item Valuation', row.name) + repost(doc) + + riv_entries = get_repost_item_valuation_entries() + if riv_entries: + return + + for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): + check_if_stock_and_account_balance_synced(today(), d.company) + +def get_repost_item_valuation_entries(): + date = add_to_date(today(), hours=-12) + + return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` + WHERE status != 'Completed' and creation <= %s and docstatus = 1 + ORDER BY timestamp(posting_date, posting_time) asc, creation asc + """, date, as_dict=1) \ No newline at end of file From 6717773c28bf3d1fa305ee61663f96d72c6bf495 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 1 Apr 2021 15:30:34 +0530 Subject: [PATCH 0408/1264] fix: Travis (#25078) * fix(test): set default accounts in mode of payment * fix(test): import_doc params in shopify settings test * fix: syntax error in salary slip test * fix(test): use Temporary Opening account for Stock Reco opening entry * fix(test): skip GST doc naming validations for tests * fix(test): Salary Structure Assignment date edge case * fix(test): GST doc naming validations * fix: sider * revert: skip GST doc naming validations for tests --- .../accounts/doctype/pos_profile/pos_profile.py | 5 +++-- .../doctype/pos_profile/test_pos_profile.py | 16 +++++++++++++--- .../shopify_settings/test_shopify_settings.py | 3 +-- .../doctype/salary_slip/test_salary_slip.py | 2 +- .../salary_structure/test_salary_structure.py | 8 +++++++- erpnext/regional/india/test_utils.py | 8 ++++---- erpnext/regional/india/utils.py | 1 + .../test_stock_ledger_entry.py | 16 ++++++++-------- 8 files changed, 38 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index ee76bba750..cf7ed26d27 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -62,14 +62,15 @@ class POSProfile(Document): if len(default_mode) > 1: frappe.throw(_("You can only select one mode of payment as default")) - + invalid_modes = [] for d in self.payments: account = frappe.db.get_value( - "Mode of Payment Account", + "Mode of Payment Account", {"parent": d.mode_of_payment, "company": self.company}, "default_account" ) + if not account: invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment)) diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 62dc1fcb20..0033965700 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -92,11 +92,21 @@ def make_pos_profile(**args): "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC" }) - payments = [{ + mode_of_payment = frappe.get_doc("Mode of Payment", "Cash") + company = args.company or "_Test Company" + default_account = args.income_account or "Sales - _TC" + + if not frappe.db.get_value("Mode of Payment Account", {"company": company, "parent": "Cash"}): + mode_of_payment.append("accounts", { + "company": company, + "default_account": default_account + }) + mode_of_payment.save() + + pos_profile.append("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/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py index 30fa23cfb4..24cbf744ae 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py @@ -17,8 +17,7 @@ class ShopifySettings(unittest.TestCase): frappe.set_user("Administrator") # use the fixture data - import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"), - ignore_links=True, overwrite=True) + import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json")) frappe.reload_doctype("Customer") frappe.reload_doctype("Sales Order") diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 143a306eb3..a59a67c51e 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -312,7 +312,7 @@ class TestSalarySlip(unittest.TestCase): frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'") create_salary_slips_for_payroll_period(applicant, salary_structure.name, - payroll_period, deduct_random=False) + payroll_period, deduct_random=False, num=6) salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name': 'test_ytd@salary.com'}, order_by = 'posting_date') diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index f2fb558a14..36387f23df 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -164,7 +164,13 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non salary_structure_assignment.employee = employee salary_structure_assignment.base = 50000 salary_structure_assignment.variable = 5000 - salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1) + + if getdate(nowdate()).day == 1: + date = from_date or nowdate() + else: + date = from_date or add_days(nowdate(), -1) + + salary_structure_assignment.from_date = date salary_structure_assignment.salary_structure = salary_structure salary_structure_assignment.currency = currency salary_structure_assignment.payroll_payable_account = get_payable_account(company) diff --git a/erpnext/regional/india/test_utils.py b/erpnext/regional/india/test_utils.py index 7ce27f6cf5..a16f56c704 100644 --- a/erpnext/regional/india/test_utils.py +++ b/erpnext/regional/india/test_utils.py @@ -12,14 +12,14 @@ class TestIndiaUtils(unittest.TestCase): mock_get_cached.return_value = "India" # mock country posting_date = "2021-05-01" - invalid_names = [ "SI$1231", "012345678901234567", "SI 2020 05", - "SI.2020.0001", "PI2021 - 001" ] + invalid_names = ["SI$1231", "012345678901234567", "SI 2020 05", + "SI.2020.0001", "PI2021 - 001"] for name in invalid_names: doc = frappe._dict(name=name, posting_date=posting_date) self.assertRaises(frappe.ValidationError, validate_document_name, doc) - valid_names = [ "012345678901236", "SI/2020/0001", "SI/2020-0001", - "2020-PI-0001", "PI2020-0001" ] + valid_names = ["012345678901236", "SI/2020/0001", "SI/2020-0001", + "2020-PI-0001", "PI2020-0001"] for name in valid_names: doc = frappe._dict(name=name, posting_date=posting_date) try: diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index e24bd6c3d0..ddcedd5e4f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -154,6 +154,7 @@ def set_place_of_supply(doc, method=None): def validate_document_name(doc, method=None): """Validate GST invoice number requirements.""" + country = frappe.get_cached_value("Company", doc.company, "country") # Date was chosen as start of next FY to avoid irritating current users. diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 59f1f3961b..ba01f70d1c 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -34,7 +34,7 @@ class TestStockLedgerEntry(unittest.TestCase): qty=50, rate=100, company=company, - expense_account = "Stock Adjustment - _TC", + expense_account = "Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-10', posting_time='14:00' ) @@ -46,7 +46,7 @@ class TestStockLedgerEntry(unittest.TestCase): qty=10, rate=200, company=company, - expense_account = "Stock Adjustment - _TC", + expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-20', posting_time='14:00' ) @@ -58,7 +58,7 @@ class TestStockLedgerEntry(unittest.TestCase): target="Finished Goods - _TC", company=company, qty=10, - expense_account="Stock Adjustment - _TC", + expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-30', posting_time='14:00' ) @@ -90,7 +90,7 @@ class TestStockLedgerEntry(unittest.TestCase): qty=50, rate=150, company=company, - expense_account = "Stock Adjustment - _TC", + expense_account ="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-12', posting_time='14:00' ) @@ -125,7 +125,7 @@ class TestStockLedgerEntry(unittest.TestCase): pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-10', warehouse="Stores - _TC", item_code="_Test Item for Reposting", qty=5, rate=100) - return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15', + return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15', warehouse="Stores - _TC", item_code="_Test Item for Reposting", is_return=1, return_against=pr.name, qty=-2) # check sle @@ -278,7 +278,7 @@ class TestStockLedgerEntry(unittest.TestCase): frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM") make_bom(item = subcontracted_item, raw_materials =[rm_item_code], currency="INR") - + # Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100 pr = make_purchase_receipt(company=company, posting_date='2020-04-10', warehouse="Stores - _TC", item_code=rm_item_code, qty=10, rate=100) @@ -292,7 +292,7 @@ class TestStockLedgerEntry(unittest.TestCase): # Update raw material's valuation via LCV, Additional cost = 50 lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) - + pr1.reload() self.assertEqual(pr1.items[0].valuation_rate, 125) @@ -310,7 +310,7 @@ class TestStockLedgerEntry(unittest.TestCase): # Back dated stock transactions are only allowed to stock managers frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", "Stock Manager") - + # Set User with Stock User role but not Stock Manager frappe.set_user("test@example.com") user = frappe.get_doc("User", "test@example.com") From a3da206b64262f64782753a22309c6234c35b7ff Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Apr 2021 12:53:22 +0530 Subject: [PATCH 0409/1264] fix: Don't string format args as they may not be escaped properly - Append even conditional args to args list and send to query executer - It will escape all values that are sent to it - String formatting without escaping causes issues with % sign, etc. --- .../doctype/quality_inspection/quality_inspection.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 58b1eca2d3..264a673ba4 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -62,17 +62,21 @@ class QualityInspection(Document): (quality_inspection, self.modified, self.reference_name, self.item_code)) else: + args = [quality_inspection, self.modified, self.reference_name, self.item_code] doctype = self.reference_type + ' Item' + if self.reference_type == 'Stock Entry': doctype = 'Stock Entry Detail' if self.reference_type and self.reference_name: conditions = "" if self.batch_no and self.docstatus == 1: - conditions += " and t1.batch_no = '%s'"%(self.batch_no) + conditions += " and t1.batch_no = %s" + args.append(self.batch_no) if self.docstatus == 2: # if cancel, then remove qi link wherever same name - conditions += " and t1.quality_inspection = '%s'"%(self.name) + conditions += " and t1.quality_inspection = %s" + args.append(self.name) frappe.db.sql(""" UPDATE @@ -85,7 +89,7 @@ class QualityInspection(Document): and t1.parent = t2.name {conditions} """.format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions), - (quality_inspection, self.modified, self.reference_name, self.item_code)) + args) def inspect_and_set_status(self): for reading in self.readings: From 98d250995d86d8721dd534e8467e7d111492aa72 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 1 Apr 2021 16:52:28 +0530 Subject: [PATCH 0410/1264] fix(India): create property setters for shorter naming series (#25134) --- .../accounts/doctype/purchase_invoice/test_records.json | 4 ++-- erpnext/accounts/doctype/sales_invoice/test_records.json | 8 ++++---- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 1 + erpnext/regional/india/setup.py | 9 ++++++++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json index e7166c5a12..9f9e90d8a7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_records.json +++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json @@ -43,7 +43,7 @@ } ], "grand_total": 0, - "naming_series": "_T-BILL", + "naming_series": "T-PINV-", "taxes": [ { "account_head": "_Test Account Shipping Charges - _TC", @@ -167,7 +167,7 @@ } ], "grand_total": 0, - "naming_series": "_T-Purchase Invoice-", + "naming_series": "T-PINV-", "taxes": [ { "account_head": "_Test Account Shipping Charges - _TC", diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index e00a58f864..3781f8ccc9 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -31,7 +31,7 @@ "base_grand_total": 561.8, "grand_total": 561.8, "is_pos": 0, - "naming_series": "_T-Sales Invoice-", + "naming_series": "T-SINV-", "base_net_total": 500.0, "taxes": [ { @@ -104,7 +104,7 @@ "base_grand_total": 630.0, "grand_total": 630.0, "is_pos": 0, - "naming_series": "_T-Sales Invoice-", + "naming_series": "T-SINV-", "base_net_total": 500.0, "taxes": [ { @@ -175,7 +175,7 @@ ], "grand_total": 0, "is_pos": 0, - "naming_series": "_T-Sales Invoice-", + "naming_series": "T-SINV-", "taxes": [ { "account_head": "_Test Account Shipping Charges - _TC", @@ -301,7 +301,7 @@ ], "grand_total": 0, "is_pos": 0, - "naming_series": "_T-Sales Invoice-", + "naming_series": "T-SINV-", "taxes": [ { "account_head": "_Test Account Excise Duty - _TC", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 72e3125c84..dd08e844b7 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2108,6 +2108,7 @@ def create_sales_invoice(**args): si.return_against = args.return_against si.currency=args.currency or "INR" si.conversion_rate = args.conversion_rate or 1 + si.naming_series = args.naming_series or "T-SINV-" si.append("items", { "item_code": args.item or args.item_code or "_Test Item", diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index ee49aae050..f7689cfa19 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.permissions import add_permission, update_permission_property from erpnext.regional.india import states from erpnext.accounts.utils import get_fiscal_year, FiscalYearError @@ -18,6 +19,7 @@ def setup(company=None, patch=True): # TODO: for all countries def setup_company_independent_fixtures(): make_custom_fields() + make_property_setters() add_permissions() add_custom_roles_for_reports() frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) @@ -110,6 +112,11 @@ def add_print_formats(): frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0) frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0) +def make_property_setters(): + # GST rules do not allow for an invoice no. bigger than 16 characters + make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') + make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', @@ -860,4 +867,4 @@ def create_gratuity_rule(): }) rule.flags.ignore_mandatory = True - rule.save() \ No newline at end of file + rule.save() From 0773d6772dcebde9cf21eb74e10143e529eeb8b7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 1 Apr 2021 17:47:19 +0530 Subject: [PATCH 0411/1264] fix(POS): local variable 'customer_currency' referenced before assignment (#25137) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 3fa9846c72..304e1f27c8 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -57,7 +57,7 @@ class POSInvoice(SalesInvoice): self.apply_loyalty_points() self.check_phone_payments() self.set_status(update=True) - + def before_cancel(self): if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1: pos_closing_entry = frappe.get_all( @@ -332,8 +332,6 @@ class POSInvoice(SalesInvoice): if selling_price_list: self.set('selling_price_list', selling_price_list) - if customer_currency != profile.get('currency'): - self.set('currency', customer_currency) # set pos values in items for item in self.get("items"): @@ -401,7 +399,7 @@ class POSInvoice(SalesInvoice): pay_req.request_phone_payment() return pay_req - + def get_new_payment_request(self, mop): payment_gateway_account = frappe.db.get_value("Payment Gateway Account", { "payment_account": mop.account, From db5f0f79b16775b92392c633573ca509dfa98dd5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 1 Apr 2021 19:59:02 +0530 Subject: [PATCH 0412/1264] chore: added v13 change log (#25140) --- erpnext/change_log/v13/v13_0_0-beta_1.md | 52 --- erpnext/change_log/v13/v13_0_0-beta_11.md | 77 ---- erpnext/change_log/v13/v13_0_0-beta_12.md | 14 - erpnext/change_log/v13/v13_0_0-beta_14.md | 25 -- erpnext/change_log/v13/v13_0_0-beta_2.md | 68 ---- erpnext/change_log/v13/v13_0_0-beta_3.md | 66 --- erpnext/change_log/v13/v13_0_0-beta_4.md | 72 ---- erpnext/change_log/v13/v13_0_0-beta_5.md | 89 ---- erpnext/change_log/v13/v13_0_0-beta_6.md | 151 ------- erpnext/change_log/v13/v13_0_0.md | 471 ++++++++++++++++++++++ 10 files changed, 471 insertions(+), 614 deletions(-) delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_1.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_11.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_12.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_14.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_2.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_3.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_4.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_5.md delete mode 100644 erpnext/change_log/v13/v13_0_0-beta_6.md create mode 100644 erpnext/change_log/v13/v13_0_0.md diff --git a/erpnext/change_log/v13/v13_0_0-beta_1.md b/erpnext/change_log/v13/v13_0_0-beta_1.md deleted file mode 100644 index 5bd13dd823..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_1.md +++ /dev/null @@ -1,52 +0,0 @@ -# Version 13.0.0 Beta 1 Release Notes - -## Accounting -- [Loan Management and Accounting](https://docs.erpnext.com/docs/user/manual/en/loan-management) -- [Accounting Dimensions in Budget Variance Report](https://github.com/frappe/erpnext/pull/19973) -- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile) -- [Custom Fields in POS](https://github.com/frappe/erpnext/pull/19876) -- [HSN Code Wise Item Tax](https://github.com/frappe/erpnext/pull/19478) -- Auto State-wise Taxation for GST India - - The Accounts entered in CGST and SGST accounts in GST Settings will be automatically skipped for Interstate Transaction and the Accounts in IGST Account will be skipped in Intrastate transaction. - -## Stock -- [Fetch Items from BOM in Stock Entry](https://github.com/frappe/erpnext/pull/19498) -- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note) - -## HR -- [Work From Home in Attendance](https://github.com/frappe/erpnext/pull/20464) -- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062) - -## Healthcare -- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare) -- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type) - -## CRM -- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post) -- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order) -- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280) - -## New Reports -- [Item-wise Sales Register](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) -- [Territory-wise Sales](https://github.com/frappe/erpnext/pull/20428) - -## Regional - -- Germany - - - [Update report DATEV Export to version 7.0](https://github.com/frappe/erpnext/pull/20582) and [allow to filter by voucher type](https://github.com/frappe/erpnext/pull/21060). - -- [Use any available Address Template](https://github.com/frappe/erpnext/pull/19862), not just your country's. - -## Other Changes -- [Report Summary in Financial Statement](https://github.com/frappe/erpnext/pull/20876) -- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) -- [Allow Purchase Invoice Creation Without Purchase Receipt Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) -- [Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier](https://github.com/frappe/erpnext/pull/20864) -- Add / Delete Items in submitted Sales / Purchase Order -- Provision to edit Item Details from Marketplace -- Nested Set filtering for Accounting Dimension -- UX changes and better validation message in all Modules -- Scan Barcode in Purchase Receipt -- Disable Rounded Totals Checkbox for Salary Slips in HR Settings - diff --git a/erpnext/change_log/v13/v13_0_0-beta_11.md b/erpnext/change_log/v13/v13_0_0-beta_11.md deleted file mode 100644 index 5c40ffbf73..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_11.md +++ /dev/null @@ -1,77 +0,0 @@ -### Version 13.0.0 Beta 11 Release Notes - -#### Features and Enhancements - -- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398)) -- Multi currency in landed cost voucher ([#24127](https://github.com/frappe/erpnext/pull/24127)) -- Putaway ([#23969](https://github.com/frappe/erpnext/pull/23969)) -- Item valuation for internal stock transfers ([#24200](https://github.com/frappe/erpnext/pull/24200)) -- Batch wise item pricing ([#24470](https://github.com/frappe/erpnext/pull/24470)) -- Project template with dependent tasks ([#24092](https://github.com/frappe/erpnext/pull/24092)) -- Patient History Enhancements ([#24033](https://github.com/frappe/erpnext/pull/24033)) -- Compute Year to Date for Salary Slip components ([#24362](https://github.com/frappe/erpnext/pull/24362)) -- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912)) -- Issue Summary Script Report ([#23603](https://github.com/frappe/erpnext/pull/23603)) -- Issue Analytics Script Report ([#23604](https://github.com/frappe/erpnext/pull/23604)) -- Loan report and enhancements ([#24370](https://github.com/frappe/erpnext/pull/24370)) -- Enhancements to erpnext membership ([#23865](https://github.com/frappe/erpnext/pull/23865)) -- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281)) -- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201)) -- Allow selecting admission service unit in Patient Appointment for inpatients ([#24410](https://github.com/frappe/erpnext/pull/24410)) -- Separate equity tree in CoA SKR04 ([#24095](https://github.com/frappe/erpnext/pull/24095)) -- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355)) -- Value Based and Numeric Quality Inspection ([#24181](https://github.com/frappe/erpnext/pull/24181)) -- Deleting account & stock entries on deletion of transaction ([#24298](https://github.com/frappe/erpnext/pull/24298)) -- Remove german sales invoice validation ([#24441](https://github.com/frappe/erpnext/pull/24441)) -- Voice Call Settings doctype added ([#24126](https://github.com/frappe/erpnext/pull/24126)) -- Shopping portal changes ([#24445](https://github.com/frappe/erpnext/pull/24445)) -- Add "Sync Now" to Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602)) - -#### Fixes - -- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204)) -- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424)) -- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119)) -- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339)) -- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191)) -- Cancelling of asset value adjustement ([#24193](https://github.com/frappe/erpnext/pull/24193)) -- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338)) -- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346)) -- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019)) -- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358)) -- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324)) -- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437)) -- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252)) -- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226)) -- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329)) -- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130)) -- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474)) -- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146)) -- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164)) -- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375)) -- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207)) -- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376)) -- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349)) -- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296)) -- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265)) -- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397)) -- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272)) -- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402)) -- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517)) -- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220)) -- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354)) -- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294)) -- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063)) -- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356)) -- Allow addition and removal of employee in payroll Entry ([#24169](https://github.com/frappe/erpnext/pull/24169)) -- Filter Therapy Types and Therapy Plan in Patient Appointment ([#24152](https://github.com/frappe/erpnext/pull/24152)) -- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378)) -- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072)) -- E-invoicing qrcode image generation ([#24395](https://github.com/frappe/erpnext/pull/24395)) -- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332)) -- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515)) -- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322)) -- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197)) -- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297)) -- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944)) -- Extra transferred qty has not consumed against work order ([#24495](https://github.com/frappe/erpnext/pull/24495)) \ No newline at end of file diff --git a/erpnext/change_log/v13/v13_0_0-beta_12.md b/erpnext/change_log/v13/v13_0_0-beta_12.md deleted file mode 100644 index cb981f1488..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_12.md +++ /dev/null @@ -1,14 +0,0 @@ -### Version 13.0.0 Beta 12 Release Notes -#### Features -- Department wise Appointment Type charges ([#24572](https://github.com/frappe/erpnext/pull/24572)) -- Capture Rate of stock UOM in purchase ([#24315](https://github.com/frappe/erpnext/pull/24315)) - -#### Fixes - -- Fixed stock and account balance syncing ([#24644](https://github.com/frappe/erpnext/pull/24644)) -- Fixed incorrect stock ledger qty in the stock ledger report and bin ([#24649](https://github.com/frappe/erpnext/pull/24649)) -- Added patch to fix incorrect stock ledger and stock account value ([#24702](https://github.com/frappe/erpnext/pull/24702)) -- Skip e-invoice generation for non-taxable invoices ([#24568](https://github.com/frappe/erpnext/pull/24568)) -- Cannot cancel old invoices if eligible for e-invoicing ([#24608](https://github.com/frappe/erpnext/pull/24608)) -- Mpesa fixes and enhancement ([#24306](https://github.com/frappe/erpnext/pull/24306)) -- Fixed Consolidated Financial Statement report ([#24580](https://github.com/frappe/erpnext/pull/24580)) \ No newline at end of file diff --git a/erpnext/change_log/v13/v13_0_0-beta_14.md b/erpnext/change_log/v13/v13_0_0-beta_14.md deleted file mode 100644 index 1fa4376a72..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_14.md +++ /dev/null @@ -1,25 +0,0 @@ -## Version 13.0.0 Beta 14 Release Notes -### Fixes and Enhancements - -- Repost incompleted backdated transactions ([#24991](https://github.com/frappe/erpnext/pull/24991)) -- Revert stock balance value calculation ([#24957](https://github.com/frappe/erpnext/pull/24957)) -- Allow user to update exchange rate in Multi-currency LCV ([#24947](https://github.com/frappe/erpnext/pull/24947)) -- Added correct path in hooks ([#24865](https://github.com/frappe/erpnext/pull/24865)) -- Unequal debit and credit issue on RCM Invoice ([#24838](https://github.com/frappe/erpnext/pull/24838)) -- Period list for exponential smoothing forecasting report ([#24983](https://github.com/frappe/erpnext/pull/24983)) -- POS Opening Entry with empty balance detail rows ([#24891](https://github.com/frappe/erpnext/pull/24891)) -- Use account_name only in consolidated report ([#24840](https://github.com/frappe/erpnext/pull/24840)) -- Validation of job card in stock entry ([#24882](https://github.com/frappe/erpnext/pull/24882)) -- Added supplier warehouse field back again ([#24827](https://github.com/frappe/erpnext/pull/24827)) -- Don't throw exception on invoice lines when there is no item_cod… ([#24864](https://github.com/frappe/erpnext/pull/24864)) -- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24918](https://github.com/frappe/erpnext/pull/24918)) -- Payment References on adding Cost Center in PE and Report Issue Summary fix for V13 beta pre-release ([#24951](https://github.com/frappe/erpnext/pull/24951)) -- TDS check getting checked after reload ([#24973](https://github.com/frappe/erpnext/pull/24973)) -- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900)) -- Serial no trim issue ([#24981](https://github.com/frappe/erpnext/pull/24981)) -- Add method for regional round off account back ([#24894](https://github.com/frappe/erpnext/pull/24894)) -- Allow zero valuation in stock reconciliation ([#24985](https://github.com/frappe/erpnext/pull/24985)) -- Simplified logic for additional salary ([#24907](https://github.com/frappe/erpnext/pull/24907)) -- Allow to select item code in batch naming ([#24825](https://github.com/frappe/erpnext/pull/24825)) -- 80G Certificates and Donations ([#24848](https://github.com/frappe/erpnext/pull/24848)) -- Membership renewal validation (#24963) ([#24964](https://github.com/frappe/erpnext/pull/24964)) \ No newline at end of file diff --git a/erpnext/change_log/v13/v13_0_0-beta_2.md b/erpnext/change_log/v13/v13_0_0-beta_2.md deleted file mode 100644 index 05c52c9c28..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_2.md +++ /dev/null @@ -1,68 +0,0 @@ -### Version 13.0.0 Beta 2 Release Notes - -#### Accounting -- Onboarding and Dashboard ([#21677](https://github.com/frappe/erpnext/pull/21677)) -- Immutable Ledger ([#18740](https://github.com/frappe/erpnext/pull/18740)) -- Process Deferred Accounting document ([#19658](https://github.com/frappe/erpnext/pull/19658)) -- Journal Entry Template ([#21404](https://github.com/frappe/erpnext/pull/21404)) - - -#### Buying -- Onboarding and Dashboard ([#21611](https://github.com/frappe/erpnext/pull/21611)) -- New Reports - - Requested Items To Order ([#21611](https://github.com/frappe/erpnext/pull/21611)) - - Purchase Order Analysis ([#21611](https://github.com/frappe/erpnext/pull/21611)) - - Refactored Quoted Item Comparison report ([#21273](https://github.com/frappe/erpnext/pull/21273)) - -#### Stock -- Onboarding and Dashboard ([#21727](https://github.com/frappe/erpnext/pull/21727)) -- Invoice from Purchase Receipt with duplicate items which has been partially returned ([#20724](https://github.com/frappe/erpnext/pull/20724)) -- Report Enhancements ([#21727](https://github.com/frappe/erpnext/pull/21727)) - - Item Shortage Report - - Stock Ageing - - Purchase Receipt Trends - - Delivery Note Trends - -#### Manufacturing -- Onboarding and Dashboard ([#21430](https://github.com/frappe/erpnext/pull/21430)) -- Production forecasting using exponential smoothing method ([#21724](https://github.com/frappe/erpnext/pull/21724)) -- BOM Template ([#21262](https://github.com/frappe/erpnext/pull/21262)) -- Run MRP at parent level in the production plan and make material transfer based upon materials availability ([#21545](https://github.com/frappe/erpnext/pull/21545)) -- Downtime Entry ([#21430](https://github.com/frappe/erpnext/pull/21430)) -- New Reports - - Production Planning Report ([#21763](https://github.com/frappe/erpnext/pull/21763)) - - BOM Operations Time ([#21763](https://github.com/frappe/erpnext/pull/21763)) - - Work Order Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) - - Job card Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) - - Downtime Analysis ([#21430](https://github.com/frappe/erpnext/pull/21430)) - - Quality Inspection ([#21430](https://github.com/frappe/erpnext/pull/21430)) - - -#### HR -- Onboarding and Dashboard ([#21705](https://github.com/frappe/erpnext/pull/21705)) -- Recurring Additional Salary and reference fields ([#20936](https://github.com/frappe/erpnext/pull/20936)) -- Payroll based on employee cost center ([#21609](https://github.com/frappe/erpnext/pull/21609)) -- Payroll based on attendance ([#21258](https://github.com/frappe/erpnext/pull/21258)) -- Monthly attendance sheet report enhancements, group by Department, Designation, Employee Grade and Branch ([#21331](https://github.com/frappe/erpnext/pull/21331)) -- Upload Attendance template now have pre-filled holiday status ([#20947](https://github.com/frappe/erpnext/pull/20947)) -- New and enhanced reports - - Employee Analytics report ([#21705](https://github.com/frappe/erpnext/pull/21705)) - - Employee Leave Balance ([#20754](https://github.com/frappe/erpnext/pull/20754)) - - Employee Leave Balance Summary ([#20754](https://github.com/frappe/erpnext/pull/20754)) - -#### Healthcare -- Onboarding and Dashboard ([#21774](https://github.com/frappe/erpnext/pull/21774)) -- Multi company support in Healthcare ([#21290](https://github.com/frappe/erpnext/pull/21290)) - -#### CRM -- Onboarding and Dashboard ([#21733](https://github.com/frappe/erpnext/pull/21733)) - -#### Selling -- Territory tree in Customer Acquisition and Loyalty report ([#21668](https://github.com/frappe/erpnext/pull/21668)) - -#### Project -- Onboarding and Dashboard ([#21587](https://github.com/frappe/erpnext/pull/21587)) -- Project Summary Report ([#21587](https://github.com/frappe/erpnext/pull/21587)) - -#### Integrations -- Woocommerce Integration ([#13217](https://github.com/frappe/erpnext/pull/13217)) \ No newline at end of file diff --git a/erpnext/change_log/v13/v13_0_0-beta_3.md b/erpnext/change_log/v13/v13_0_0-beta_3.md deleted file mode 100644 index 09ea90087d..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_3.md +++ /dev/null @@ -1,66 +0,0 @@ -### Version 13.0.0 Beta 3 Release Notes - -#### Features and Enhancements -- Dedicated Payroll module with Onboarding and Dashboard ([#21990](https://github.com/frappe/erpnext/pull/21990)) -- New Payroll Reports - - Income Tax Deductions - - Professional Tax Deductions - - Provident Fund Deductions - - Total Salary Payments Based on Payment Mode - - Salary Payments via ECS -- Distributed Cost Center ([#21531](https://github.com/frappe/erpnext/pull/21531)) -- More controlled deferred revenue booking ([#21671](https://github.com/frappe/erpnext/pull/21671)) - - Book deferred accounting via Journal Entry, provision to keep in draft - - Provision to book deferred revenue/expense on a monthly basis rather than by days -- Selling Desk, Dashboard and Onboarding ([#22055](https://github.com/frappe/erpnext/pull/22055)) -- Help Articles on support portal ([#22194](https://github.com/frappe/erpnext/pull/22194)) -- Issue Metrics and SLA Enhancements ([#21617](https://github.com/frappe/erpnext/pull/21617)) -- Multi UOM support in Request for Quotation ([#22249](https://github.com/frappe/erpnext/pull/22249)) -- The ability for a contract to be authorized internally using a signature field ([#22095](https://github.com/frappe/erpnext/pull/22095)) -- Gross Profit In Quotation ([#21795](https://github.com/frappe/erpnext/pull/21795)) -- Notify credit controller users for credit limit extension via Email ([#22213](https://github.com/frappe/erpnext/pull/22213)) -- Added In and Out time in attendance ([#21547](https://github.com/frappe/erpnext/pull/21547)) -- Renamed Loan Management to Loan on Desk Page ([#21877](https://github.com/frappe/erpnext/pull/21877)) -- Document tour to manufacturing settings ([#21962](https://github.com/frappe/erpnext/pull/21962)) -- Added Expense Approver field in Employee master ([#22244](https://github.com/frappe/erpnext/pull/22244)) - - -#### Fixes -- Bill all hours by default on Timesheet ([#22155](https://github.com/frappe/erpnext/pull/22155)) -- Unable to cancel employee advance ([#22374](https://github.com/frappe/erpnext/pull/22374)) -- Status error in purchase invoice ([#22351](https://github.com/frappe/erpnext/pull/22351)) -- Item-wise sales and purchase register export ([#22184](https://github.com/frappe/erpnext/pull/22184)) -- Billing address in for Purchase documents ([#22233](https://github.com/frappe/erpnext/pull/22233)) -- Handle canceled entries in financial statements ([#22231](https://github.com/frappe/erpnext/pull/22231)) -- Default period start date and period end date for financial statements ([#22011](https://github.com/frappe/erpnext/pull/22011)) -- Update Packed Items via Update Items in Sales Order ([#22392](https://github.com/frappe/erpnext/pull/22392)) -- Hide delete company transactions button if not system manager ([#21839](https://github.com/frappe/erpnext/pull/21839)) -- Skipping total row for tree-view reports ([#22350](https://github.com/frappe/erpnext/pull/22350)) -- Cancelled entries in tds payable monthly report ([#22131](https://github.com/frappe/erpnext/pull/22131)) -- Inter-company Invoice currency for multicurrency transactions ([#21984](https://github.com/frappe/erpnext/pull/21984)) -- Filter batches based on item and warehouse in Pick List (develop) ([#21780](https://github.com/frappe/erpnext/pull/21780)) -- Set cost center in Expense Claim child based on parent (if missing) ([#22175](https://github.com/frappe/erpnext/pull/22175)) -- Item wise backdated stock entry posting for immutable ledger ([#22366](https://github.com/frappe/erpnext/pull/22366)) -- Shopping cart UI fixes ([#22137](https://github.com/frappe/erpnext/pull/22137)) -- Filter Leave Type based on allocation for a particular employee ([#22050](https://github.com/frappe/erpnext/pull/22050)) -- Party validation for inter-warehouse transaction ([#22186](https://github.com/frappe/erpnext/pull/22186)) -- Manufacturing dashboard and work order summary chart ([#21946](https://github.com/frappe/erpnext/pull/21946)) -- IP Admission and Discharge, Minor fixes ([#21817](https://github.com/frappe/erpnext/pull/21817)) -- Validation of Purchase Order against Material Request missing ([#22192](https://github.com/frappe/erpnext/pull/22192)) -- Staffing Plan validation ([#22379](https://github.com/frappe/erpnext/pull/22379)) -- Do not allow backdated stock transactions in previous fiscal year ([#21967](https://github.com/frappe/erpnext/pull/21967)) -- Employee Advance Return not working ([#21812](https://github.com/frappe/erpnext/pull/21812)) -- Added card for reports on education desk ([#21853](https://github.com/frappe/erpnext/pull/21853)) -- Refactored project summary report ([#21943](https://github.com/frappe/erpnext/pull/21943)) -- Revenue and Customer Count only in date range in Customer Acquitition Report ([#22210](https://github.com/frappe/erpnext/pull/22210)) -- Alternative item not working for subcontract ([#22386](https://github.com/frappe/erpnext/pull/22386)) -- Unable to create batched Item ([#22393](https://github.com/frappe/erpnext/pull/22393)) -- Filters for the manufacturing reports ([#21960](https://github.com/frappe/erpnext/pull/21960)) -- Raw material warehouse in Production Planning Report ([#21982](https://github.com/frappe/erpnext/pull/21982)) -- Allowed LWP leave types to select in Leave Application even if there is no allocation against them ([#22197](https://github.com/frappe/erpnext/pull/22197)) -- Report not working on parameter Grade ([#21951](https://github.com/frappe/erpnext/pull/21951)) -- Allow to enter Relieving date if employee status is Left ([#22242](https://github.com/frappe/erpnext/pull/22242)) -- Resetting lost reason in opportunity and quotation ([#22378](https://github.com/frappe/erpnext/pull/22378)) -- Filtering issues in opening invoice creation tool ([#21969](https://github.com/frappe/erpnext/pull/21969)) -- Set default reference Id for "On Previous Row Amount" and "On Previous Row Total" ([#22346](https://github.com/frappe/erpnext/pull/22346)) -- UX date range field separated in from and to date fields. ([#21765](https://github.com/frappe/erpnext/pull/21765)) diff --git a/erpnext/change_log/v13/v13_0_0-beta_4.md b/erpnext/change_log/v13/v13_0_0-beta_4.md deleted file mode 100644 index b835cec211..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_4.md +++ /dev/null @@ -1,72 +0,0 @@ -### Version 13.0.0 Beta 4 Release Notes - -#### Features and Enhancements -- New and refreshed POS ([#20789](https://github.com/frappe/erpnext/pull/20789)) -- Introduced Dunning ([#22559](https://github.com/frappe/erpnext/pull/22559)) -- Taxjar Integration ([#21047](https://github.com/frappe/erpnext/pull/21047)) -- Patient Progress Page ([#22474](https://github.com/frappe/erpnext/pull/22474)) -- Provision to make RFQ against Opportunity ([#22765](https://github.com/frappe/erpnext/pull/22765)) -- Added form dashboards and refactored custom buttons in Education module ([#22727](https://github.com/frappe/erpnext/pull/22727)) -- Student Attendance and Leave Enhancements ([#22623](https://github.com/frappe/erpnext/pull/22623)) -- Recruitment analytics ([#21732](https://github.com/frappe/erpnext/pull/21732)) -- Add medical coding fields to Healthcare DocTypes ([#22501](https://github.com/frappe/erpnext/pull/22501)) -- Autofill Supplier pop-up when only 1 Supplier in RFQ ([#22512](https://github.com/frappe/erpnext/pull/22512)) -- Accounting entries for service item in Purchase receipt ([#22223](https://github.com/frappe/erpnext/pull/22223)) -- Enhancement in subscription ([#22263](https://github.com/frappe/erpnext/pull/22263)) -- Laboratory Module Enhancements ([#22416](https://github.com/frappe/erpnext/pull/22416)) -- Added columns to get complete analysis for material request ([#22607](https://github.com/frappe/erpnext/pull/22607)) -- Added all companies option in employee tree to view employee across all companies ([#22573](https://github.com/frappe/erpnext/pull/22573)) -- Email Group Option In Email Campaign ([#22731](https://github.com/frappe/erpnext/pull/22731)) -- Added range for age in stock ageing ([#22622](https://github.com/frappe/erpnext/pull/22622)) -- Refactored shopping cart ([#22617](https://github.com/frappe/erpnext/pull/22617)) - -#### Fixes: -- Enable show_configure_button when shopping cart is enabled ([#22468](https://github.com/frappe/erpnext/pull/22468)) -- Setup status indicators for Job Offer and Job Applicant (develop) ([#22445](https://github.com/frappe/erpnext/pull/22445)) -- Item-wise sales history report ([#22783](https://github.com/frappe/erpnext/pull/22783)) -- Setting filter for project in kanban board ([#22717](https://github.com/frappe/erpnext/pull/22717)) -- Dashboard For Timesheet ([#22750](https://github.com/frappe/erpnext/pull/22750)) -- Handle custom statuses for the pause SLA configuration ([#22349](https://github.com/frappe/erpnext/pull/22349)) -- Quality Feedback and Template ([#22571](https://github.com/frappe/erpnext/pull/22571)) -- Unable to change link from new lead to existing customer ([#22787](https://github.com/frappe/erpnext/pull/22787)) -- Job applicant fixes ([#22448](https://github.com/frappe/erpnext/pull/22448)) -- Move Issue List actions under 'Actions' dropdown (ux) ([#22710](https://github.com/frappe/erpnext/pull/22710)) -- Cost center should only show option of selected company ([#22598](https://github.com/frappe/erpnext/pull/22598)) -- Serial No Rename does not affect Stock Ledger Entry ([#22746](https://github.com/frappe/erpnext/pull/22746)) -- Descriptions not copied while creating Fees from Fee Structure ([#22792](https://github.com/frappe/erpnext/pull/22792)) -- Company filter for cost_center and expense_account in all sales and purchase transactions ([#22478](https://github.com/frappe/erpnext/pull/22478)) -- Arrangements of filters for reports accounts payable & receivable ([#22636](https://github.com/frappe/erpnext/pull/22636)) -- Update the project after task deletion so that the % completed shows correct value ([#22591](https://github.com/frappe/erpnext/pull/22591)) -- Block Invalid Serial No updates in Maintenance Schedule ([#22665](https://github.com/frappe/erpnext/pull/22665)) -- Fetch item price in sales invoice based on it's validity ([#22563](https://github.com/frappe/erpnext/pull/22563)) -- Add view ledger button for cancelled docs ([#22432](https://github.com/frappe/erpnext/pull/22432)) -- Allow creating SLA documents even if SLA tracking is not enabled ([#22608](https://github.com/frappe/erpnext/pull/22608)) -- Quotation list view blank if quotation_to field not set as a standard filter ([#22672](https://github.com/frappe/erpnext/pull/22672)) -- Salary deductions report fixes ([#22397](https://github.com/frappe/erpnext/pull/22397)) -22727)) -- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22631](https://github.com/frappe/erpnext/pull/22631)) -- Moved parent warehouse to top section also added a section break ([#22708](https://github.com/frappe/erpnext/pull/22708)) -- Skip Progress and Completed by fields on Task Duplication ([#22565](https://github.com/frappe/erpnext/pull/22565)) -- Incorrect stock after merging the items ([#22526](https://github.com/frappe/erpnext/pull/22526)) -- Letter head not found in opening invoice creation tool ([#22488](https://github.com/frappe/erpnext/pull/22488)) -- Cannot cancel asset and asset movement ([#22441](https://github.com/frappe/erpnext/pull/22441)) -- Fetch project-related info in Timesheet ([#22423](https://github.com/frappe/erpnext/pull/22423)) -- Currency symbol not showing as per company currency in stock balance report ([#22724](https://github.com/frappe/erpnext/pull/22724)) -- Add default cost center in payment reconciliation JV ([#22614](https://github.com/frappe/erpnext/pull/22614)) -- Stock Reconciliation Invalid Quantity for Batched Item ([#22726](https://github.com/frappe/erpnext/pull/22726)) -- Project link not set in accounts other than profit and loss accounts ([#22051](https://github.com/frappe/erpnext/pull/22051)) -- Buying price for non stock item in gross profit report ([#22616](https://github.com/frappe/erpnext/pull/22616)) -- Heatmap in Vehicle ([#22743](https://github.com/frappe/erpnext/pull/22743)) -- Added Project Field in Purchase Receipt for Stock Ledger Tagging ([#22666](https://github.com/frappe/erpnext/pull/22666)) -- Multi currency payment reconciliation ([#22738](https://github.com/frappe/erpnext/pull/22738)) -- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440)) -- Reset homepage to home after unchecking products page ([#22736](https://github.com/frappe/erpnext/pull/22736)) -- Add project filter in parent task field ([#22655](https://github.com/frappe/erpnext/pull/22655)) -- Generic Message in previous doc validation for buying and selling ([#22546](https://github.com/frappe/erpnext/pull/22546)) -- Cess amount in GSTR 3B report ([#22701](https://github.com/frappe/erpnext/pull/22701)) -- Expense claim outstanding while making payment entry ([#22735](https://github.com/frappe/erpnext/pull/22735)) -- Take parent cost center for child if no cost center at child in expense claim ([#22496](https://github.com/frappe/erpnext/pull/22496)) -- Consider company fiscal year for getting balance ([#22577](https://github.com/frappe/erpnext/pull/22577)) -- Pick List empty table and Serial-Batch items handling ([#22426](https://github.com/frappe/erpnext/pull/22426)) -- Show total row in print format of financial statement ([#22693](https://github.com/frappe/erpnext/pull/22693)) -- Set Root as Parent if no parent in new tree view node ([#22497](https://github.com/frappe/erpnext/pull/22497)) \ No newline at end of file diff --git a/erpnext/change_log/v13/v13_0_0-beta_5.md b/erpnext/change_log/v13/v13_0_0-beta_5.md deleted file mode 100644 index 8374c775fe..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_5.md +++ /dev/null @@ -1,89 +0,0 @@ -### Version 13.0.0 Beta 5 Release Notes - -#### Features and Enhancements -- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614)) -- Process Statement Of Accounts ([#22901](https://github.com/frappe/erpnext/pull/22901)) -- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014)) -- Material Request and Stock Entry Enhancement ([#22671](https://github.com/frappe/erpnext/pull/22671)) -- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641)) -- Appraisal form Enhancements ([#23500](https://github.com/frappe/erpnext/pull/23500)) -- Crm reports cleanup ([#22844](https://github.com/frappe/erpnext/pull/22844)) -- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445)) -- Quoted Item Comparison Report Enhancements v2 ([#23127](https://github.com/frappe/erpnext/pull/23127)) -- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170)) -- Added address template for luxembourg ([#23621](https://github.com/frappe/erpnext/pull/23621)) -- Provision to draft quotation from portal ([#23416](https://github.com/frappe/erpnext/pull/23416)) -- M-pesa integration ([#23439](https://github.com/frappe/erpnext/pull/23439)) -- Education Desk, Dashboard, and Onboarding ([#22825](https://github.com/frappe/erpnext/pull/22825)) -- Added search to support page ([#22447](https://github.com/frappe/erpnext/pull/22447)) -- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675)) -- Added POS Register report ([#23313](https://github.com/frappe/erpnext/pull/23313)) -- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309)) -- Enhancement in Loan Topups ([#23049](https://github.com/frappe/erpnext/pull/23049)) -- Inpatient Medication Order and Entry ([#23473](https://github.com/frappe/erpnext/pull/23473)) -- Option to print UOM after quantity ([#23263](https://github.com/frappe/erpnext/pull/23263)) -- Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323)) -- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557)) -- Therapy Plan Template ([#23558](https://github.com/frappe/erpnext/pull/23558)) -- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867)) -- Laboratory Module ([#22853](https://github.com/frappe/erpnext/pull/22853)) -- Shift management ([#22262](https://github.com/frappe/erpnext/pull/22262)) - -#### Fixes -- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725)) -- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001)) -- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217)) -- Loan Security shortfall calculation fixes ([#22866](https://github.com/frappe/erpnext/pull/22866)) -- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669)) -- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256)) -- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333)) -- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580)) -- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541)) -- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755)) -- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302)) -- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165)) -- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772)) -- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880)) -- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407)) -- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072)) -- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718)) -- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288)) -- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) -- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548)) -- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145)) -- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918)) -- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597)) -- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245)) -- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361)) -- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704)) -- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161)) -- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369)) -- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195)) -- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419)) -- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953)) -- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110)) -- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059)) -- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007)) -- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066)) -- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814)) -- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076)) -- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434)) -- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009)) -- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958)) -- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483)) -- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539)) -- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151)) -- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476)) -- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911)) -- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950)) -- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943)) -- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282)) -- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714)) -- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205)) -- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623)) -- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264)) -- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676)) -- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988)) -- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436)) -- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357)) -- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515)) diff --git a/erpnext/change_log/v13/v13_0_0-beta_6.md b/erpnext/change_log/v13/v13_0_0-beta_6.md deleted file mode 100644 index 4c6d9c28cf..0000000000 --- a/erpnext/change_log/v13/v13_0_0-beta_6.md +++ /dev/null @@ -1,151 +0,0 @@ -### Version 13.0.0 Beta 6 Release Notes - -#### Features and Enhancements - -- GST E-invoicing for India ([#23455](https://github.com/frappe/erpnext/pull/23455)) -- Multi-currency payroll ([#23519](https://github.com/frappe/erpnext/pull/23519)) -- Allow back-dated stock transactions and repost item costing via background job ([#24183](https://github.com/frappe/erpnext/pull/24183)) -- Introduced telephony feature using Twillio ([#24032](https://github.com/frappe/erpnext/pull/24032)) -- Shipment Doctype ([#22914](https://github.com/frappe/erpnext/pull/22914)) -- Leave policy assignment ([#23112](https://github.com/frappe/erpnext/pull/23112)) -- UAE VAT 201 Report ([#23447](https://github.com/frappe/erpnext/pull/23447)) -- Return tracking in PR/DN ([#22859](https://github.com/frappe/erpnext/pull/22859)) -- Close Production Plan ([#23728](https://github.com/frappe/erpnext/pull/23728)) -- Quality Inspection on Job Card ([#23964](https://github.com/frappe/erpnext/pull/23964)) -- Inpatient Medication Orders Script Report ([#23984](https://github.com/frappe/erpnext/pull/23984)) -- Leave type with partial payment ([#23173](https://github.com/frappe/erpnext/pull/23173)) -- Formula based Quality Inspection ([#23916](https://github.com/frappe/erpnext/pull/23916)) -- Link to Material Requests in Tools section for RFQ and Supplier Quotation ([#23429](https://github.com/frappe/erpnext/pull/23429)) -- Hide images & auto add item checkbox ([#24102](https://github.com/frappe/erpnext/pull/24102)) -- In reports get item details from Item instead of the transactions ([#24082](https://github.com/frappe/erpnext/pull/24082)) -- sales order status filter added for production plan ([#23805](https://github.com/frappe/erpnext/pull/23805)) -- Button to create Stock Entry for Drug Shortage ([#24012](https://github.com/frappe/erpnext/pull/24012)) -- Sync old shopify orders ([#23841](https://github.com/frappe/erpnext/pull/23841)) -- Added column cost center in Accounts Receivable report ([#23835](https://github.com/frappe/erpnext/pull/23835)) -- Added jinja templating in Contract Template ([#24046](https://github.com/frappe/erpnext/pull/24046)) -- Consider Holiday List in Student Leave Application and Attendance ([#23388](https://github.com/frappe/erpnext/pull/23388)) -- Make account number length configurable ([#23845](https://github.com/frappe/erpnext/pull/23845)) -- Add communication channel to communication medium ([#23793](https://github.com/frappe/erpnext/pull/23793)) - -#### Fixes - -- Loan disbursement amount validation ([#24000](https://github.com/frappe/erpnext/pull/24000)) -- Making company address read-only in delivery note ([#23890](https://github.com/frappe/erpnext/pull/23890)) -- BOM stock report color showing always red ([#23994](https://github.com/frappe/erpnext/pull/23994)) -- Added filter for customer field in Issue ([#24051](https://github.com/frappe/erpnext/pull/24051)) -- Added project link in timesheet form ([#23764](https://github.com/frappe/erpnext/pull/23764)) -- Update integrations desk page ([#23767](https://github.com/frappe/erpnext/pull/23767)) -- Place of supply change on address change ([#23941](https://github.com/frappe/erpnext/pull/23941)) -- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23672](https://github.com/frappe/erpnext/pull/23672)) -- Auto fetch serial nos with modified conversion factor ([#23854](https://github.com/frappe/erpnext/pull/23854)) -- Default cost center in item master not set in stock entry ([#23877](https://github.com/frappe/erpnext/pull/23877)) -- Incorrect de-link serial no and batch ([#23947](https://github.com/frappe/erpnext/pull/23947)) -- Accounting for internal transfer invoices within same company ([#24021](https://github.com/frappe/erpnext/pull/24021)) -- Multiple pricing rule with margin type as Percentage is not working ([#24205](https://github.com/frappe/erpnext/pull/24205)) -- Added Purchase Order to Global Search ([#24055](https://github.com/frappe/erpnext/pull/24055)) -- Cannot expand row in update items dialog ([#23839](https://github.com/frappe/erpnext/pull/23839)) -- Maintain stock can't be changed it there is product bundle ([#23989](https://github.com/frappe/erpnext/pull/23989)) -- SO to PO Mapping Issue ([#23820](https://github.com/frappe/erpnext/pull/23820)) -- Asset with value zero doesn't show up in fixed asset register ([#24091](https://github.com/frappe/erpnext/pull/24091)) -- Cannot save customer email & phone ([#23797](https://github.com/frappe/erpnext/pull/23797)) -- Incorrect balance value in stock balance report ([#24048](https://github.com/frappe/erpnext/pull/24048)) -- Payment Terms not fetched in Purchase Invoice from Purchase Receipt ([#23735](https://github.com/frappe/erpnext/pull/23735)) -- Fix for LMS Sign Up link ([#23743](https://github.com/frappe/erpnext/pull/23743)) -- Incorrect stock quantity if 'Allow Multiple Material Consumption… ([#24116](https://github.com/frappe/erpnext/pull/24116)) -- Added wrong absent days calculation in salary slip ([#23897](https://github.com/frappe/erpnext/pull/23897)) -- Purchase receipt to purchase invoice bill date mapping ([#23967](https://github.com/frappe/erpnext/pull/23967)) -- Overriding po ([#24022](https://github.com/frappe/erpnext/pull/24022)) -- Do not cancel reference document on Quality Inspection cancellation ([#24198](https://github.com/frappe/erpnext/pull/24198)) -- Get formatted value in 'taxes' print template ([#24035](https://github.com/frappe/erpnext/pull/24035)) -- Don't overrule Item Price via Pricing Rule Rate if 0 ([#23636](https://github.com/frappe/erpnext/pull/23636)) -- Job card error handling for operations field ([#23991](https://github.com/frappe/erpnext/pull/23991)) -- Validation for journal entry with 0 debit and credit values ([#23975](https://github.com/frappe/erpnext/pull/23975)) -- Check if customer exists in product listing ([#24030](https://github.com/frappe/erpnext/pull/24030)) -- Asset finance book posting date fix ([#23778](https://github.com/frappe/erpnext/pull/23778)) -- Same source and target tables in Status Updater's update query ([#24110](https://github.com/frappe/erpnext/pull/24110)) -- Asset finance book depreciation posting date fix ([#23833](https://github.com/frappe/erpnext/pull/23833)) -- Ignore exception during leave ledger creation from patch ([#24005](https://github.com/frappe/erpnext/pull/24005)) -- Added link of bank reconciliation and clearance in accounting desk page ([#23850](https://github.com/frappe/erpnext/pull/23850)) -- Sales invoice add button from sales order dashboard ([#24077](https://github.com/frappe/erpnext/pull/24077)) -- Incorrect calculation for consumed qty for subcontract item ([#23257](https://github.com/frappe/erpnext/pull/23257)) -- Incorrect required_qty in Production Planning Report ([#24074](https://github.com/frappe/erpnext/pull/24074)) -- Email digest user not found ([#23949](https://github.com/frappe/erpnext/pull/23949)) -- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24115](https://github.com/frappe/erpnext/pull/24115)) -- Added TDS Payable account number and an error message ([#24065](https://github.com/frappe/erpnext/pull/24065)) -- Override field_map for job card gantt ([#24155](https://github.com/frappe/erpnext/pull/24155)) -- Old shopify order syncing date ([#23990](https://github.com/frappe/erpnext/pull/23990)) -- Shipping chanrges not sync in erpnext from shopify ([#24114](https://github.com/frappe/erpnext/pull/24114)) -- GSTR B2C report ([#24039](https://github.com/frappe/erpnext/pull/24039)) -- Ignore cancelled entries in stock balance report ([#23757](https://github.com/frappe/erpnext/pull/23757)) -- Stock ageing report not working ([#23923](https://github.com/frappe/erpnext/pull/23923)) -- Incorrect assign to in Maintenance Schedule ([#23831](https://github.com/frappe/erpnext/pull/23831)) -- Improve UX of DATEV report ([#23892](https://github.com/frappe/erpnext/pull/23892)) -- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) -- dDouble exception in payroll ([#24078](https://github.com/frappe/erpnext/pull/24078)) -- Make asset dashboard charts public ([#23751](https://github.com/frappe/erpnext/pull/23751)) -- Don't copy terms and discount from SO to PO ([#23903](https://github.com/frappe/erpnext/pull/23903)) -- Ignore doctypes on company transaction delete ([#23864](https://github.com/frappe/erpnext/pull/23864)) -- Error handling in Upload Attendance ([#23907](https://github.com/frappe/erpnext/pull/23907)) -- Tax template update on customer address change ([#24160](https://github.com/frappe/erpnext/pull/24160)) -- Not able to save bom ([#23910](https://github.com/frappe/erpnext/pull/23910)) -- Enable Allow Auto Repeat for standard doctypes having auto_repeat field ([#23776](https://github.com/frappe/erpnext/pull/23776)) -- Place of Supply fix in Sales Invoices ([#23785](https://github.com/frappe/erpnext/pull/23785)) -- Opening invoices in GSTR-1 report ([#24117](https://github.com/frappe/erpnext/pull/24117)) -- Add check for allowing access to european region ([#23770](https://github.com/frappe/erpnext/pull/23770)) -- Partial serial no return issue ([#24208](https://github.com/frappe/erpnext/pull/24208)) -- Multiple pos issues ([#23347](https://github.com/frappe/erpnext/pull/23347)) -- Import taxjar globally in the taxjar_integration module ([#24027](https://github.com/frappe/erpnext/pull/24027)) -- Payroll attendance error ([#23887](https://github.com/frappe/erpnext/pull/23887)) -- Cannot add items to cart ([#23796](https://github.com/frappe/erpnext/pull/23796)) -- Show tax amount in base currencies ([#24069](https://github.com/frappe/erpnext/pull/24069)) -- Loan application link on creating loan ([#23937](https://github.com/frappe/erpnext/pull/23937)) -- Added shipment link in delivery note dashboard ([#24210](https://github.com/frappe/erpnext/pull/24210)) -- POS item search includes non stock items ([#23914](https://github.com/frappe/erpnext/pull/23914)) -- Paid amount in Sales Invoice POS return resets to 0 ([#24057](https://github.com/frappe/erpnext/pull/24057)) -- Remove check for exempt_from_sales_tax ([#23870](https://github.com/frappe/erpnext/pull/23870)) -- Fiscal year can be shorter than 12 months ([#23838](https://github.com/frappe/erpnext/pull/23838)) -- Loan repayment type option remove ([#23582](https://github.com/frappe/erpnext/pull/23582)) -- Make contract template editable ([#23891](https://github.com/frappe/erpnext/pull/23891)) -- Item wise tax calculation ([#23744](https://github.com/frappe/erpnext/pull/23744)) -- Enabling track changes for stock settings ([#23982](https://github.com/frappe/erpnext/pull/23982)) -- Added link of bank reconciliation and clearance in accounting desk page ([#23809](https://github.com/frappe/erpnext/pull/23809)) -- Location data on Asset to use command(make_demo) ([#23825](https://github.com/frappe/erpnext/pull/23825)) -- Handle Account and Item None not found in Opening Invoice Creation Tool ([#23559](https://github.com/frappe/erpnext/pull/23559)) -- List index out of range on including UOM ([#23814](https://github.com/frappe/erpnext/pull/23814)) -- Showing error for wrong filters. ([#23726](https://github.com/frappe/erpnext/pull/23726)) -- Multiple subcontracting issues ([#23662](https://github.com/frappe/erpnext/pull/23662)) -- Sequence id override with workstation column ([#23810](https://github.com/frappe/erpnext/pull/23810)) -- Replaced formatdate -> format_date ([#23849](https://github.com/frappe/erpnext/pull/23849)) -- Test Payment Based on Leave Application (Travis) ([#24044](https://github.com/frappe/erpnext/pull/24044)) -- Leave policy dashboard fix and roles ([#24170](https://github.com/frappe/erpnext/pull/24170)) -- Budget test cases ([#23801](https://github.com/frappe/erpnext/pull/23801)) -- Handle for custom field IFSC code in Bank remittance report. ([#23905](https://github.com/frappe/erpnext/pull/23905)) -- Scan barcode does not update barcode item field in sales order ([#24090](https://github.com/frappe/erpnext/pull/24090)) -- Item price duplicate checking ([#23408](https://github.com/frappe/erpnext/pull/23408)) -- Tax template update on supplier change for India ([#24060](https://github.com/frappe/erpnext/pull/24060)) -- Consumed qty logic for subcontracted raw materials ([#23314](https://github.com/frappe/erpnext/pull/23314)) -- Finance book not getting added in journal Entry of asset value adjustment ([#24100](https://github.com/frappe/erpnext/pull/24100)) -- Fixed home desk page ([#24075](https://github.com/frappe/erpnext/pull/24075)) -- po_detail field has no value for subcontracted stock entry ([#23777](https://github.com/frappe/erpnext/pull/23777)) -- Set proper state code in ewaybill JSON when GST category is SEZ ([#23953](https://github.com/frappe/erpnext/pull/23953)) -- Copying po no when mapping doc ([#23729](https://github.com/frappe/erpnext/pull/23729)) -- Duplicate items validation for POS Invoice when allow multiple items is disabled ([#23896](https://github.com/frappe/erpnext/pull/23896)) -- POS register shows cancelled documents ([#23747](https://github.com/frappe/erpnext/pull/23747)) -- Subscription test case ([#23763](https://github.com/frappe/erpnext/pull/23763)) -- BOM stock report color issue ([#23980](https://github.com/frappe/erpnext/pull/23980)) -- Handle the "no leave_allocation found" case ([#23922](https://github.com/frappe/erpnext/pull/23922)) -- Filters for tax templates ([#23998](https://github.com/frappe/erpnext/pull/23998)) -- Do not allow Company as accounting dimension ([#23749](https://github.com/frappe/erpnext/pull/23749)) -- Validation for duplicate Tax Category ([#23978](https://github.com/frappe/erpnext/pull/23978)) -- Therapy plan and session fixes ([#23817](https://github.com/frappe/erpnext/pull/23817)) -- Correcting description field in taxes and charges for accounts with account number + account name ([#23836](https://github.com/frappe/erpnext/pull/23836)) -- Pricing rule with transaction not working for additional product ([#24053](https://github.com/frappe/erpnext/pull/24053)) -- Keyerror 'sourced_by_supplier' ([#24038](https://github.com/frappe/erpnext/pull/24038)) -- Validation for membership ([#23934](https://github.com/frappe/erpnext/pull/23934)) -- Inpatient Medication Order and Entry fixes ([#23799](https://github.com/frappe/erpnext/pull/23799)) -- Avoid using SQL query to get fiscal year dates ([#24050](https://github.com/frappe/erpnext/pull/24050)) -- Auto Statewise gst tax template ([#23832](https://github.com/frappe/erpnext/pull/23832)) -- POS profile has no attr 'show_only_available_items' ([#23758](https://github.com/frappe/erpnext/pull/23758)) -- On save sequence id column override with workstation ([#23812](https://github.com/frappe/erpnext/pull/23812)) -- Multiple pricing rules are not working on selling side ([#22711](https://github.com/frappe/erpnext/pull/22711)) -- Salary slip popup error ([#24192](https://github.com/frappe/erpnext/pull/24192)) \ No newline at end of file diff --git a/erpnext/change_log/v13/v13_0_0.md b/erpnext/change_log/v13/v13_0_0.md new file mode 100644 index 0000000000..a6cebabab1 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0.md @@ -0,0 +1,471 @@ +# Version 13.0.0 Release Notes + +### Accounting +- [New and refreshed POS](https://github.com/frappe/erpnext/pull/20789) +- [GST E-invoicing for India](https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing) +- [Distributed Cost Center](https://docs.erpnext.com/docs/user/manual/en/accounts/distributed-cost-center) +- [Process Bulk Statement Of Accounts](https://docs.erpnext.com/docs/user/manual/en/accounts/process-statement-of-accounts) +- [More controlled deferred revenue booking](https://docs.erpnext.com/docs/user/manual/en/accounts/process-deferred-accounting) +- [Dunning](https://docs.erpnext.com/docs/user/manual/en/accounts/dunning) +- [Journal Entry Template](https://docs.erpnext.com/docs/user/manual/en/accounts/journal-entry-template) +- [POS Register report](https://github.com/frappe/erpnext/pull/23313) +- [UAE VAT 201 Report](https://github.com/frappe/erpnext/pull/23447) + + +### Loan Management +- [Loan Application](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-application) +- [Loan](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan) +- [Loan Security Pledge](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-security-pledge) +- [Loan Disbursement](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-disbursement) +- [Loan Repayment](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-repayment) +- [Loan Interest Accrual](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-interest-accrual) +- [Loan Write Off](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-write-off) + +### Healthcare +- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare) +- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type) +- [Laboratory Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/setup_laboratory) +- [Patient Progress Page](https://github.com/frappe/erpnext/pull/22474) +- [Inpatient Medication Order and Entry](https://docs.erpnext.com/docs/user/manual/en/healthcare/inpatient_medication_entry) +- [Therapy Plan Template](https://docs.erpnext.com/docs/user/manual/en/healthcare/therapy_plan) +- [Multi company support in Healthcare](https://github.com/frappe/erpnext/pull/21290) +- [Inpatient Medication Orders Script Report](https://github.com/frappe/erpnext/pull/23984) +- [Patient History Enhancements](https://github.com/frappe/erpnext/pull/24033) + + +### Stock +- [Putaway](https://docs.erpnext.com/docs/user/manual/en/stock/putaway-rule) +- [More accurate stock valuation in case of back-dated stock transactions](https://github.com/frappe/erpnext/pull/24183) +- [Repost item costing via background job](https://github.com/frappe/erpnext/pull/24183) +- [Item valuation for internal stock transfers](https://github.com/frappe/erpnext/pull/24200) +- [Multi currency in Landed Cost Voucher](https://github.com/frappe/erpnext/pull/24127) +- [Formula based Quality Inspection](https://docs.erpnext.com/docs/user/manual/en/stock/quality-inspection) +- [Value Based and Numeric Quality Inspection](https://github.com/frappe/erpnext/pull/24181) +- [Shipment](https://github.com/frappe/erpnext/pull/22914) +- [Return tracking in PR/DN](https://github.com/frappe/erpnext/pull/22859) + +### Manufacturing +- [Production forecasting using Exponential Smoothing method](https://docs.erpnext.com/docs/user/manual/en/manufacturing/reports/demand-driven-forecasting) +- [BOM Template](https://docs.erpnext.com/docs/user/manual/en/manufacturing/bill-of-materials#34-bom-template) +- [Downtime Entry](https://docs.erpnext.com/docs/user/manual/en/manufacturing/downtime-entry) +- [Quality Inspection on Job Card](https://github.com/frappe/erpnext/pull/23964) +- New Reports + - Production Planning Report ([#21763](https://github.com/frappe/erpnext/pull/21763)) + - BOM Operations Time ([#21763](https://github.com/frappe/erpnext/pull/21763)) + - Work Order Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Job card Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Downtime Analysis ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Quality Inspection ([#21430](https://github.com/frappe/erpnext/pull/21430)) + +### HR +- [Leave policy assignment](https://github.com/frappe/erpnext/pull/23112) +- [In and Out time in attendance](https://github.com/frappe/erpnext/pull/21547) +- [Shift management](https://docs.erpnext.com/docs/user/manual/en/human-resources/shift-management) +- [Recruitment analytics](https://github.com/frappe/erpnext/pull/21732) +- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062) +- [Leave type with partial payment](https://github.com/frappe/erpnext/pull/23173) +- New and enhanced reports + - Employee Analytics ([#21705](https://github.com/frappe/erpnext/pull/21705)) + - Employee Leave Balance ([#20754](https://github.com/frappe/erpnext/pull/20754)) + - Employee Leave Balance Summary ([#20754](https://github.com/frappe/erpnext/pull/20754)) + +### Payroll +- [Multi-currency payroll](https://github.com/frappe/erpnext/pull/23519) +- [Payroll based on attendance](https://github.com/frappe/erpnext/pull/21258) +- [Payroll based on employee cost center](https://github.com/frappe/erpnext/pull/21609) +- [Recurring Additional Salary](https://github.com/frappe/erpnext/pull/20936) +- [Compute Year to Date for Salary Slip components](https://github.com/frappe/erpnext/pull/24362) +- New Reports + - Income Tax Deductions + - Professional Tax Deductions + - Provident Fund Deductions + - Total Salary Payments Based on Payment Mode + - Salary Payments via ECS + +### CRM +- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post) +- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order) +- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280) + +### Selling +- [Batch wise item pricing](https://github.com/frappe/erpnext/pull/24470) +- [Refreshed shopping cart](https://github.com/frappe/erpnext/pull/22617) +- [Territory-wise Sales Report](https://github.com/frappe/erpnext/pull/20428) + +#### Buying +- [Multi UOM support in Request for Quotation](https://github.com/frappe/erpnext/pull/22249) +- [Provision to make RFQ against Opportunity](https://github.com/frappe/erpnext/pull/22765) +- [Item Rate in Stock UOM in purchase cycle](https://github.com/frappe/erpnext/pull/24315) +- New Reports + - Requested Items To Order ([#21611](https://github.com/frappe/erpnext/pull/21611)) + - Purchase Order Analysis ([#21611](https://github.com/frappe/erpnext/pull/21611)) + - Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323)) + +### Project +- [Project template with dependent tasks](https://github.com/frappe/erpnext/pull/24092) +- [Project Summary Report](https://github.com/frappe/erpnext/pull/21587) + +### Support +- [Help Articles on support portal](https://github.com/frappe/erpnext/pull/22194) +- [Issue Metrics and SLA Enhancements](https://github.com/frappe/erpnext/pull/21617) +- [Issue Summary Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports) +- [Issue Analytics Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports) + +### Non-Profits +- [80G Certificates and Donations](https://docs.erpnext.com/docs/user/manual/en/non_profit/tax_exemption_80g_certificate) + +#### Integrations +- [Woocommerce Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/woocommerce_integration) +- [Taxjar Integration](https://github.com/frappe/erpnext/pull/21047) +- [M-pesa Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/mpesa-integration) +- [Telephony feature using Twillio](https://github.com/frappe/erpnext/pull/24032) +- [Voice Call Settings](https://github.com/frappe/erpnext/pull/24126) + + +#### Other Enhancements and Fixes +- Accounting Dimensions in Budget Variance Report ([#19973](https://github.com/frappe/erpnext/pull/19973)) +- "Sync Now" option in Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602)) +- Custom Fields in POS ([#19876](https://github.com/frappe/erpnext/pull/19876)) +- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note) +- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) +- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912)) +- Territory tree in Customer Acquisition and Loyalty report ([#21668](https://github.com/frappe/erpnext/pull/21668)) +- Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier ([#20864](https://github.com/frappe/erpnext/pull/20864)) +- Gross Profit In Quotation ([#21795](https://github.com/frappe/erpnext/pull/21795)) +- Notify credit controller users for credit limit extension via Email ([#22213](https://github.com/frappe/erpnext/pull/22213)) +- Run MRP at parent level in the production plan and make material transfer based upon materials availability ([#21545](https://github.com/frappe/erpnext/pull/21545)) +- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675)) +- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867)) +- Consider Holiday List in Student Leave Application and Attendance ([#23388](https://github.com/frappe/erpnext/pull/23388)) +- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201)) +- Sales order status filter added for production plan ([#23805](https://github.com/frappe/erpnext/pull/23805)) +- Monthly attendance sheet report group by Department, Designation, Employee Grade and Branch ([#21331](https://github.com/frappe/erpnext/pull/21331)) +- Upload Attendance template now have pre-filled holiday status ([#20947](https://github.com/frappe/erpnext/pull/20947)) +- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398)) + +
+More + +- Fetch Items from BOM in Stock Entry([#19498](https://github.com/frappe/erpnext/pull/19498)) +- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557)) +- Close Production Plan ([#23728](https://github.com/frappe/erpnext/pull/23728)) +- Button to create Stock Entry for Drug Shortage ([#24012](https://github.com/frappe/erpnext/pull/24012)) +- Added column cost center in Accounts Receivable report ([#23835](https://github.com/frappe/erpnext/pull/23835)) +- Added jinja templating in Contract Template ([#24046](https://github.com/frappe/erpnext/pull/24046)) +- Make account number length configurable ([#23845](https://github.com/frappe/erpnext/pull/23845)) +- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614)) +- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014)) +- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445)) +- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile) +- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170)) +- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281)) +- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355)) +- Autofill Supplier pop-up when only 1 Supplier in RFQ ([#22512](https://github.com/frappe/erpnext/pull/22512)) +- Accounting entries for service item in Purchase receipt ([#22223](https://github.com/frappe/erpnext/pull/22223)) +- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309)) +- Added all companies option in employee tree to view employee across all companies ([#22573](https://github.com/frappe/erpnext/pull/22573)) +- Email Group Option In Email Campaign ([#22731](https://github.com/frappe/erpnext/pull/22731)) +- Stock Report Enhancements ([#21727](https://github.com/frappe/erpnext/pull/21727)) +- Added range for age in stock ageing ([#22622](https://github.com/frappe/erpnext/pull/22622)) +- Report Summary in Financial Statement([#20876](https://github.com/frappe/erpnext/pull/20876)) +- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641)) +- Nested Set filtering for Accounting Dimension +- Add/Remove Items from submitted Sales/Purchase Order +- Provision to edit Item Details from Marketplace +- Scan Barcode in Purchase Receipt +- Disable Rounded Totals Checkbox for Salary Slips in HR Settings + +- Renamed Loan Management to Loan on Desk Page ([#21877](https://github.com/frappe/erpnext/pull/21877)) +- Added Expense Approver field in Employee master ([#22244](https://github.com/frappe/erpnext/pull/22244)) +- Bill all hours by default on Timesheet ([#22155](https://github.com/frappe/erpnext/pull/22155)) +- Unable to cancel employee advance ([#22374](https://github.com/frappe/erpnext/pull/22374)) +- Status error in purchase invoice ([#22351](https://github.com/frappe/erpnext/pull/22351)) +- Item-wise sales and purchase register export ([#22184](https://github.com/frappe/erpnext/pull/22184)) +- Billing address in for Purchase documents ([#22233](https://github.com/frappe/erpnext/pull/22233)) +- Handle canceled entries in financial statements ([#22231](https://github.com/frappe/erpnext/pull/22231)) +- Default period start date and period end date for financial statements ([#22011](https://github.com/frappe/erpnext/pull/22011)) +- Update Packed Items via Update Items in Sales Order ([#22392](https://github.com/frappe/erpnext/pull/22392)) +- Hide delete company transactions button if not system manager ([#21839](https://github.com/frappe/erpnext/pull/21839)) +- Skipping total row for tree-view reports ([#22350](https://github.com/frappe/erpnext/pull/22350)) +- Cancelled entries in tds payable monthly report ([#22131](https://github.com/frappe/erpnext/pull/22131)) +- Inter-company Invoice currency for multicurrency transactions ([#21984](https://github.com/frappe/erpnext/pull/21984)) +- Filter batches based on item and warehouse in Pick List (develop) ([#21780](https://github.com/frappe/erpnext/pull/21780)) +- Set cost center in Expense Claim child based on parent (if missing) ([#22175](https://github.com/frappe/erpnext/pull/22175)) +- Item wise backdated stock entry posting for immutable ledger ([#22366](https://github.com/frappe/erpnext/pull/22366)) +- Shopping cart UI fixes ([#22137](https://github.com/frappe/erpnext/pull/22137)) +- Filter Leave Type based on allocation for a particular employee ([#22050](https://github.com/frappe/erpnext/pull/22050)) +- Party validation for inter-warehouse transaction ([#22186](https://github.com/frappe/erpnext/pull/22186)) +- Manufacturing dashboard and work order summary chart ([#21946](https://github.com/frappe/erpnext/pull/21946)) +- IP Admission and Discharge, Minor fixes ([#21817](https://github.com/frappe/erpnext/pull/21817)) +- Validation of Purchase Order against Material Request missing ([#22192](https://github.com/frappe/erpnext/pull/22192)) +- Staffing Plan validation ([#22379](https://github.com/frappe/erpnext/pull/22379)) +- Do not allow backdated stock transactions in previous fiscal year ([#21967](https://github.com/frappe/erpnext/pull/21967)) +- Employee Advance Return not working ([#21812](https://github.com/frappe/erpnext/pull/21812)) +- Added card for reports on education desk ([#21853](https://github.com/frappe/erpnext/pull/21853)) +- Refactored project summary report ([#21943](https://github.com/frappe/erpnext/pull/21943)) +- Revenue and Customer Count only in date range in Customer Acquitition Report ([#22210](https://github.com/frappe/erpnext/pull/22210)) +- Alternative item not working for subcontract ([#22386](https://github.com/frappe/erpnext/pull/22386)) +- Unable to create batched Item ([#22393](https://github.com/frappe/erpnext/pull/22393)) +- Filters for the manufacturing reports ([#21960](https://github.com/frappe/erpnext/pull/21960)) +- Raw material warehouse in Production Planning Report ([#21982](https://github.com/frappe/erpnext/pull/21982)) +- Allowed LWP leave types to select in Leave Application even if there is no allocation against them ([#22197](https://github.com/frappe/erpnext/pull/22197)) +- Report not working on parameter Grade ([#21951](https://github.com/frappe/erpnext/pull/21951)) +- Allow to enter Relieving date if employee status is Left ([#22242](https://github.com/frappe/erpnext/pull/22242)) +- Resetting lost reason in opportunity and quotation ([#22378](https://github.com/frappe/erpnext/pull/22378)) +- Filtering issues in opening invoice creation tool ([#21969](https://github.com/frappe/erpnext/pull/21969)) +- Set default reference Id for "On Previous Row Amount" and "On Previous Row Total" ([#22346](https://github.com/frappe/erpnext/pull/22346)) +- UX date range field separated in from and to date fields. ([#21765](https://github.com/frappe/erpnext/pull/21765)) +- Enable show_configure_button when shopping cart is enabled ([#22468](https://github.com/frappe/erpnext/pull/22468)) +- Setup status indicators for Job Offer and Job Applicant (develop) ([#22445](https://github.com/frappe/erpnext/pull/22445)) +- Item-wise sales history report ([#22783](https://github.com/frappe/erpnext/pull/22783)) +- Setting filter for project in kanban board ([#22717](https://github.com/frappe/erpnext/pull/22717)) +- Dashboard For Timesheet ([#22750](https://github.com/frappe/erpnext/pull/22750)) +- Handle custom statuses for the pause SLA configuration ([#22349](https://github.com/frappe/erpnext/pull/22349)) +- Quality Feedback and Template ([#22571](https://github.com/frappe/erpnext/pull/22571)) +- Unable to change link from new lead to existing customer ([#22787](https://github.com/frappe/erpnext/pull/22787)) +- Move Issue List actions under 'Actions' dropdown (ux) ([#22710](https://github.com/frappe/erpnext/pull/22710)) +- Cost center should only show option of selected company ([#22598](https://github.com/frappe/erpnext/pull/22598)) +- Serial No Rename does not affect Stock Ledger Entry ([#22746](https://github.com/frappe/erpnext/pull/22746)) +- Descriptions not copied while creating Fees from Fee Structure ([#22792](https://github.com/frappe/erpnext/pull/22792)) +- Company filter for cost_center and expense_account in all sales and purchase transactions ([#22478](https://github.com/frappe/erpnext/pull/22478)) +- Arrangements of filters for reports accounts payable & receivable ([#22636](https://github.com/frappe/erpnext/pull/22636)) +- Update the project after task deletion so that the % completed shows correct value ([#22591](https://github.com/frappe/erpnext/pull/22591)) +- Block Invalid Serial No updates in Maintenance Schedule ([#22665](https://github.com/frappe/erpnext/pull/22665)) +- Fetch item price in sales invoice based on it's validity ([#22563](https://github.com/frappe/erpnext/pull/22563)) +- Add view ledger button for cancelled docs ([#22432](https://github.com/frappe/erpnext/pull/22432)) +- Allow creating SLA documents even if SLA tracking is not enabled ([#22608](https://github.com/frappe/erpnext/pull/22608)) +- Quotation list view blank if quotation_to field not set as a standard filter ([#22672](https://github.com/frappe/erpnext/pull/22672)) +- Salary deductions report fixes ([#22397](https://github.com/frappe/erpnext/pull/22397)) +22727)) +- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22631](https://github.com/frappe/erpnext/pull/22631)) +- Moved parent warehouse to top section also added a section break ([#22708](https://github.com/frappe/erpnext/pull/22708)) +- Skip Progress and Completed by fields on Task Duplication ([#22565](https://github.com/frappe/erpnext/pull/22565)) +- Incorrect stock after merging the items ([#22526](https://github.com/frappe/erpnext/pull/22526)) +- Letter head not found in opening invoice creation tool ([#22488](https://github.com/frappe/erpnext/pull/22488)) +- Cannot cancel asset and asset movement ([#22441](https://github.com/frappe/erpnext/pull/22441)) +- Fetch project-related info in Timesheet ([#22423](https://github.com/frappe/erpnext/pull/22423)) +- Currency symbol not showing as per company currency in stock balance report ([#22724](https://github.com/frappe/erpnext/pull/22724)) +- Add default cost center in payment reconciliation JV ([#22614](https://github.com/frappe/erpnext/pull/22614)) +- Stock Reconciliation Invalid Quantity for Batched Item ([#22726](https://github.com/frappe/erpnext/pull/22726)) +- Project link not set in accounts other than profit and loss accounts ([#22051](https://github.com/frappe/erpnext/pull/22051)) +- Buying price for non stock item in gross profit report ([#22616](https://github.com/frappe/erpnext/pull/22616)) +- Multi currency payment reconciliation ([#22738](https://github.com/frappe/erpnext/pull/22738)) +- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440)) +- Reset homepage to home after unchecking products page ([#22736](https://github.com/frappe/erpnext/pull/22736)) +- Generic Message in previous doc validation for buying and selling ([#22546](https://github.com/frappe/erpnext/pull/22546)) +- Expense claim outstanding while making payment entry ([#22735](https://github.com/frappe/erpnext/pull/22735)) +- Take parent cost center for child if no cost center at child in expense claim ([#22496](https://github.com/frappe/erpnext/pull/22496)) +- Consider company fiscal year for getting balance ([#22577](https://github.com/frappe/erpnext/pull/22577)) +- Pick List empty table and Serial-Batch items handling ([#22426](https://github.com/frappe/erpnext/pull/22426)) +- Show total row in print format of financial statement ([#22693](https://github.com/frappe/erpnext/pull/22693)) +- Set Root as Parent if no parent in new tree view node ([#22497](https://github.com/frappe/erpnext/pull/22497)) +- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725)) +- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001)) +- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217)) +- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669)) +- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256)) +- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333)) +- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580)) +- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541)) +- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755)) +- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302)) +- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165)) +- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772)) +- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880)) +- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407)) +- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072)) +- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718)) +- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288)) +- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) +- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548)) +- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145)) +- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918)) +- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597)) +- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245)) +- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361)) +- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704)) +- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161)) +- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369)) +- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195)) +- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419)) +- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953)) +- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110)) +- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059)) +- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007)) +- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066)) +- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814)) +- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076)) +- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434)) +- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009)) +- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958)) +- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483)) +- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539)) +- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151)) +- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476)) +- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911)) +- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950)) +- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943)) +- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282)) +- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714)) +- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205)) +- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623)) +- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264)) +- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676)) +- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988)) +- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436)) +- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357)) +- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515)) +- Loan disbursement amount validation ([#24000](https://github.com/frappe/erpnext/pull/24000)) +- Making company address read-only in delivery note ([#23890](https://github.com/frappe/erpnext/pull/23890)) +- BOM stock report color showing always red ([#23994](https://github.com/frappe/erpnext/pull/23994)) +- Added filter for customer field in Issue ([#24051](https://github.com/frappe/erpnext/pull/24051)) +- Added project link in timesheet form ([#23764](https://github.com/frappe/erpnext/pull/23764)) +- Update integrations desk page ([#23767](https://github.com/frappe/erpnext/pull/23767)) +- Place of supply change on address change ([#23941](https://github.com/frappe/erpnext/pull/23941)) +- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23672](https://github.com/frappe/erpnext/pull/23672)) +- Auto fetch serial nos with modified conversion factor ([#23854](https://github.com/frappe/erpnext/pull/23854)) +- Default cost center in item master not set in stock entry ([#23877](https://github.com/frappe/erpnext/pull/23877)) +- Incorrect de-link serial no and batch ([#23947](https://github.com/frappe/erpnext/pull/23947)) +- Accounting for internal transfer invoices within same company ([#24021](https://github.com/frappe/erpnext/pull/24021)) +- Multiple pricing rule with margin type as Percentage is not working ([#24205](https://github.com/frappe/erpnext/pull/24205)) +- Added Purchase Order to Global Search ([#24055](https://github.com/frappe/erpnext/pull/24055)) +- Cannot expand row in update items dialog ([#23839](https://github.com/frappe/erpnext/pull/23839)) +- Maintain stock can't be changed it there is product bundle ([#23989](https://github.com/frappe/erpnext/pull/23989)) +- SO to PO Mapping Issue ([#23820](https://github.com/frappe/erpnext/pull/23820)) +- Asset with value zero doesn't show up in fixed asset register ([#24091](https://github.com/frappe/erpnext/pull/24091)) +- Cannot save customer email & phone ([#23797](https://github.com/frappe/erpnext/pull/23797)) +- Incorrect balance value in stock balance report ([#24048](https://github.com/frappe/erpnext/pull/24048)) +- Payment Terms not fetched in Purchase Invoice from Purchase Receipt ([#23735](https://github.com/frappe/erpnext/pull/23735)) +- Fix for LMS Sign Up link ([#23743](https://github.com/frappe/erpnext/pull/23743)) +- Incorrect stock quantity if 'Allow Multiple Material Consumption… ([#24116](https://github.com/frappe/erpnext/pull/24116)) +- Added wrong absent days calculation in salary slip ([#23897](https://github.com/frappe/erpnext/pull/23897)) +- Purchase receipt to purchase invoice bill date mapping ([#23967](https://github.com/frappe/erpnext/pull/23967)) +- Overriding po ([#24022](https://github.com/frappe/erpnext/pull/24022)) +- Do not cancel reference document on Quality Inspection cancellation ([#24198](https://github.com/frappe/erpnext/pull/24198)) +- Get formatted value in 'taxes' print template ([#24035](https://github.com/frappe/erpnext/pull/24035)) +- Don't overrule Item Price via Pricing Rule Rate if 0 ([#23636](https://github.com/frappe/erpnext/pull/23636)) +- Job card error handling for operations field ([#23991](https://github.com/frappe/erpnext/pull/23991)) +- Validation for journal entry with 0 debit and credit values ([#23975](https://github.com/frappe/erpnext/pull/23975)) +- Check if customer exists in product listing ([#24030](https://github.com/frappe/erpnext/pull/24030)) +- Asset finance book posting date fix ([#23778](https://github.com/frappe/erpnext/pull/23778)) +- Same source and target tables in Status Updater's update query ([#24110](https://github.com/frappe/erpnext/pull/24110)) +- Asset finance book depreciation posting date fix ([#23833](https://github.com/frappe/erpnext/pull/23833)) +- Ignore exception during leave ledger creation from patch ([#24005](https://github.com/frappe/erpnext/pull/24005)) +- Added link of bank reconciliation and clearance in accounting desk page ([#23850](https://github.com/frappe/erpnext/pull/23850)) +- Sales invoice add button from sales order dashboard ([#24077](https://github.com/frappe/erpnext/pull/24077)) +- Incorrect calculation for consumed qty for subcontract item ([#23257](https://github.com/frappe/erpnext/pull/23257)) +- Incorrect required_qty in Production Planning Report ([#24074](https://github.com/frappe/erpnext/pull/24074)) +- Email digest user not found ([#23949](https://github.com/frappe/erpnext/pull/23949)) +- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24115](https://github.com/frappe/erpnext/pull/24115)) +- Added TDS Payable account number and an error message ([#24065](https://github.com/frappe/erpnext/pull/24065)) +- Override field_map for job card gantt ([#24155](https://github.com/frappe/erpnext/pull/24155)) +- Old shopify order syncing date ([#23990](https://github.com/frappe/erpnext/pull/23990)) +- Shipping chanrges not sync in erpnext from shopify ([#24114](https://github.com/frappe/erpnext/pull/24114)) +- GSTR B2C report ([#24039](https://github.com/frappe/erpnext/pull/24039)) +- Ignore cancelled entries in stock balance report ([#23757](https://github.com/frappe/erpnext/pull/23757)) +- Stock ageing report not working ([#23923](https://github.com/frappe/erpnext/pull/23923)) +- Incorrect assign to in Maintenance Schedule ([#23831](https://github.com/frappe/erpnext/pull/23831)) +- Improve UX of DATEV report ([#23892](https://github.com/frappe/erpnext/pull/23892)) +- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) +- dDouble exception in payroll ([#24078](https://github.com/frappe/erpnext/pull/24078)) +- Make asset dashboard charts public ([#23751](https://github.com/frappe/erpnext/pull/23751)) +- Don't copy terms and discount from SO to PO ([#23903](https://github.com/frappe/erpnext/pull/23903)) +- Ignore doctypes on company transaction delete ([#23864](https://github.com/frappe/erpnext/pull/23864)) +- Error handling in Upload Attendance ([#23907](https://github.com/frappe/erpnext/pull/23907)) +- Tax template update on customer address change ([#24160](https://github.com/frappe/erpnext/pull/24160)) +- Not able to save bom ([#23910](https://github.com/frappe/erpnext/pull/23910)) +- Enable Allow Auto Repeat for standard doctypes having auto_repeat field ([#23776](https://github.com/frappe/erpnext/pull/23776)) +- Place of Supply fix in Sales Invoices ([#23785](https://github.com/frappe/erpnext/pull/23785)) +- Opening invoices in GSTR-1 report ([#24117](https://github.com/frappe/erpnext/pull/24117)) +- Partial serial no return issue ([#24208](https://github.com/frappe/erpnext/pull/24208)) +- Import taxjar globally in the taxjar_integration module ([#24027](https://github.com/frappe/erpnext/pull/24027)) +- Payroll attendance error ([#23887](https://github.com/frappe/erpnext/pull/23887)) +- Loan application link on creating loan ([#23937](https://github.com/frappe/erpnext/pull/23937)) +- POS item search includes non stock items ([#23914](https://github.com/frappe/erpnext/pull/23914)) +- Paid amount in Sales Invoice POS return resets to 0 ([#24057](https://github.com/frappe/erpnext/pull/24057)) +- Fiscal year can be shorter than 12 months ([#23838](https://github.com/frappe/erpnext/pull/23838)) +- Loan repayment type option remove ([#23582](https://github.com/frappe/erpnext/pull/23582)) +- Item wise tax calculation ([#23744](https://github.com/frappe/erpnext/pull/23744)) +- Enabling track changes for stock settings ([#23982](https://github.com/frappe/erpnext/pull/23982)) +- Added link of bank reconciliation and clearance in accounting desk page ([#23809](https://github.com/frappe/erpnext/pull/23809)) +- Location data on Asset to use command(make_demo) ([#23825](https://github.com/frappe/erpnext/pull/23825)) +- Handle Account and Item None not found in Opening Invoice Creation Tool ([#23559](https://github.com/frappe/erpnext/pull/23559)) +- Multiple subcontracting issues ([#23662](https://github.com/frappe/erpnext/pull/23662)) +- Sequence id override with workstation column ([#23810](https://github.com/frappe/erpnext/pull/23810)) +- Leave policy dashboard fix and roles ([#24170](https://github.com/frappe/erpnext/pull/24170)) +- Scan barcode does not update barcode item field in sales order ([#24090](https://github.com/frappe/erpnext/pull/24090)) +- Item price duplicate checking ([#23408](https://github.com/frappe/erpnext/pull/23408)) +- Tax template update on supplier change for India ([#24060](https://github.com/frappe/erpnext/pull/24060)) +- Consumed qty logic for subcontracted raw materials ([#23314](https://github.com/frappe/erpnext/pull/23314)) +- Finance book not getting added in journal Entry of asset value adjustment ([#24100](https://github.com/frappe/erpnext/pull/24100)) +- Set proper state code in ewaybill JSON when GST category is SEZ ([#23953](https://github.com/frappe/erpnext/pull/23953)) +- Copying po no when mapping doc ([#23729](https://github.com/frappe/erpnext/pull/23729)) +- Duplicate items validation for POS Invoice when allow multiple items is disabled ([#23896](https://github.com/frappe/erpnext/pull/23896)) +- Do not allow Company as accounting dimension ([#23749](https://github.com/frappe/erpnext/pull/23749)) +- Validation for duplicate Tax Category ([#23978](https://github.com/frappe/erpnext/pull/23978)) +- Therapy plan and session fixes ([#23817](https://github.com/frappe/erpnext/pull/23817)) +- Pricing rule with transaction not working for additional product ([#24053](https://github.com/frappe/erpnext/pull/24053)) +- Inpatient Medication Order and Entry fixes ([#23799](https://github.com/frappe/erpnext/pull/23799)) +- Avoid using SQL query to get fiscal year dates ([#24050](https://github.com/frappe/erpnext/pull/24050)) +- Auto Statewise gst tax template ([#23832](https://github.com/frappe/erpnext/pull/23832)) +- On save sequence id column override with workstation ([#23812](https://github.com/frappe/erpnext/pull/23812)) +- Multiple pricing rules are not working on selling side ([#22711](https://github.com/frappe/erpnext/pull/22711)) +- Salary slip popup error ([#24192](https://github.com/frappe/erpnext/pull/24192)) +- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204)) +- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424)) +- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119)) +- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339)) +- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191)) +- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338)) +- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346)) +- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019)) +- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358)) +- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324)) +- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437)) +- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252)) +- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226)) +- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329)) +- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130)) +- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474)) +- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146)) +- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164)) +- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375)) +- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207)) +- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376)) +- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349)) +- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296)) +- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265)) +- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397)) +- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272)) +- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402)) +- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517)) +- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220)) +- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354)) +- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294)) +- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063)) +- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356)) +- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378)) +- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072)) +- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332)) +- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515)) +- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322)) +- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197)) +- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297)) +- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944)) +- Fixed stock and account balance syncing ([#24644](https://github.com/frappe/erpnext/pull/24644)) +- Fixed incorrect stock ledger qty in the stock ledger report and bin ([#24649](https://github.com/frappe/erpnext/pull/24649)) +- Fixed Consolidated Financial Statement report ([#24580](https://github.com/frappe/erpnext/pull/24580)) +- Repost incompleted backdated transactions ([#24991](https://github.com/frappe/erpnext/pull/24991)) +- Unequal debit and credit issue on RCM Invoice ([#24838](https://github.com/frappe/erpnext/pull/24838)) +- Period list for exponential smoothing forecasting report ([#24983](https://github.com/frappe/erpnext/pull/24983)) +- POS Opening Entry with empty balance detail rows ([#24891](https://github.com/frappe/erpnext/pull/24891)) +- Use account_name only in consolidated report ([#24840](https://github.com/frappe/erpnext/pull/24840)) +- Validation of job card in stock entry ([#24882](https://github.com/frappe/erpnext/pull/24882)) +- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24918](https://github.com/frappe/erpnext/pull/24918)) +- TDS check getting checked after reload ([#24973](https://github.com/frappe/erpnext/pull/24973)) +- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900)) +- Allow zero valuation in stock reconciliation ([#24985](https://github.com/frappe/erpnext/pull/24985)) +- Simplified logic for additional salary ([#24907](https://github.com/frappe/erpnext/pull/24907)) +- Allow to select item code in batch naming ([#24825](https://github.com/frappe/erpnext/pull/24825)) +- Membership renewal validation (#24963) ([#24964](https://github.com/frappe/erpnext/pull/24964)) +
\ No newline at end of file From c5347c4a34434c4d313aad8e824cb7ddcc910150 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 1 Apr 2021 19:52:07 +0530 Subject: [PATCH 0413/1264] fix: get_route_options_for_new_doc for project --- erpnext/projects/doctype/project/project.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 077011ace0..c5265e23c0 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -18,8 +18,8 @@ frappe.ui.form.on("Project", { }; }, onload: function (frm) { - var so = frappe.meta.get_docfield("Project", "sales_order"); - so.get_route_options_for_new_doc = function (field) { + const so = frm.get_docfield("sales_order"); + so.get_route_options_for_new_doc = () => { if (frm.is_new()) return; return { "customer": frm.doc.customer, From c01b2caaa3eb144735f2519bb59c9848bdd944be Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:48:51 +0530 Subject: [PATCH 0414/1264] fix: serial no refresh issue (#25129) --- erpnext/public/js/controllers/transaction.js | 32 ++++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d2512e7f43..d1fc37930f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -737,28 +737,34 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.trigger("item_code", cdt, cdn); } else { - var valid_serial_nos = []; - var serialnos = []; // Replacing all occurences of comma with carriage return item.serial_no = item.serial_no.replace(/,/g, '\n'); - serialnos = item.serial_no.split("\n"); - for (var i = 0; i < serialnos.length; i++) { - if (serialnos[i] != "") { - valid_serial_nos.push(serialnos[i]); - } - } item.conversion_factor = item.conversion_factor || 1; - refresh_field("serial_no", item.name, item.parentfield); - if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { - frappe.model.set_value(item.doctype, item.name, - "qty", valid_serial_nos.length / item.conversion_factor); - frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); + if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { + setTimeout(() => { + me.update_qty(cdt, cdn); + }, 10000); } } } }, + update_qty: function(cdt, cdn) { + var valid_serial_nos = []; + var serialnos = []; + var item = frappe.get_doc(cdt, cdn); + serialnos = item.serial_no.split("\n"); + for (var i = 0; i < serialnos.length; i++) { + if (serialnos[i] != "") { + valid_serial_nos.push(serialnos[i]); + } + } + frappe.model.set_value(item.doctype, item.name, + "qty", valid_serial_nos.length / item.conversion_factor); + frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); + }, + validate: function() { this.calculate_taxes_and_totals(false); }, From f9b9298a6d248badfe1455223fa7d19b703c185b Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 1 Apr 2021 21:59:35 +0530 Subject: [PATCH 0415/1264] fix: reload doc in patch (#25144) --- .../v13_0/check_is_income_tax_component.py | 55 ++++++++++--------- .../v13_0/update_vehicle_no_reqd_condition.py | 1 + 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py index 9ad48e23b7..c92d52dcec 100644 --- a/erpnext/patches/v13_0/check_is_income_tax_component.py +++ b/erpnext/patches/v13_0/check_is_income_tax_component.py @@ -8,36 +8,39 @@ from erpnext.regional.india.setup import setup def execute(): - doctypes = ['salary_component', - 'Employee Tax Exemption Declaration', - 'Employee Tax Exemption Proof Submission', - 'Employee Tax Exemption Declaration Category', - 'Employee Tax Exemption Proof Submission Detail' - ] + doctypes = ['salary_component', + 'Employee Tax Exemption Declaration', + 'Employee Tax Exemption Proof Submission', + 'Employee Tax Exemption Declaration Category', + 'Employee Tax Exemption Proof Submission Detail', + 'gratuity_rule', + 'gratuity_rule_slab', + 'gratuity_applicable_component' + ] - for doctype in doctypes: - frappe.reload_doc('Payroll', 'doctype', doctype) + for doctype in doctypes: + frappe.reload_doc('Payroll', 'doctype', doctype) - reports = ['Professional Tax Deductions', 'Provident Fund Deductions'] - for report in reports: - frappe.reload_doc('Regional', 'Report', report) - frappe.reload_doc('Regional', 'Report', report) + reports = ['Professional Tax Deductions', 'Provident Fund Deductions'] + for report in reports: + frappe.reload_doc('Regional', 'Report', report) + frappe.reload_doc('Regional', 'Report', report) - if erpnext.get_region() == "India": - setup(patch=True) + if erpnext.get_region() == "India": + setup(patch=True) - if frappe.db.exists("Salary Component", "Income Tax"): - frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1) - if frappe.db.exists("Salary Component", "TDS"): - frappe.db.set_value("Salary Component", "TDS", "is_income_tax_component", 1) + if frappe.db.exists("Salary Component", "Income Tax"): + frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1) + if frappe.db.exists("Salary Component", "TDS"): + frappe.db.set_value("Salary Component", "TDS", "is_income_tax_component", 1) - components = frappe.db.sql("select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1) - for component in components: - frappe.db.set_value("Salary Component", component.name, "is_income_tax_component", 1) + components = frappe.db.sql("select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1) + for component in components: + frappe.db.set_value("Salary Component", component.name, "is_income_tax_component", 1) - if erpnext.get_region() == "India": - if frappe.db.exists("Salary Component", "Provident Fund"): - frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund") - if frappe.db.exists("Salary Component", "Professional Tax"): - frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax") \ No newline at end of file + if erpnext.get_region() == "India": + if frappe.db.exists("Salary Component", "Provident Fund"): + frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund") + if frappe.db.exists("Salary Component", "Professional Tax"): + frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax") \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py index c26cddbe4e..01a4ae04ad 100644 --- a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py +++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py @@ -1,6 +1,7 @@ import frappe def execute(): + frappe.reload_doc('custom', 'doctype', 'custom_field') company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: return From 87dea3923bce7cf5c452db47bf16811901e9bd37 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 1 Apr 2021 22:05:08 +0530 Subject: [PATCH 0416/1264] fix: Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List (#25106) * fix: picked qty from Stock Qty to Qty in DN * fix: suggested changes and added test * fix: sider changes * fix: sider changes --- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- .../stock/doctype/pick_list/test_pick_list.py | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 0da57b734b..7d206cb4a9 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -345,7 +345,7 @@ def create_delivery_note(source_name, target_doc=None): if dn_item: dn_item.warehouse = location.warehouse - dn_item.qty = location.picked_qty + dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1) dn_item.batch_no = location.batch_no dn_item.serial_no = location.serial_no diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 8ea7f89dc4..a762e9763e 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -9,6 +9,7 @@ test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \ import EmptyStockReconciliationItemsError @@ -291,6 +292,61 @@ class TestPickList(unittest.TestCase): self.assertEqual(pick_list.locations[1].qty, 5) self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name) + def test_pick_list_for_items_with_multiple_UOM(self): + purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10) + purchase_receipt.submit() + + sales_order = frappe.get_doc({ + 'doctype': 'Sales Order', + 'customer': '_Test Customer', + 'company': '_Test Company', + 'items': [{ + 'item_code': '_Test Item', + 'qty': 1, + 'conversion_factor': 5, + 'delivery_date': frappe.utils.today() + }, { + 'item_code': '_Test Item', + 'qty': 1, + 'conversion_factor': 1, + 'delivery_date': frappe.utils.today() + }], + }).insert() + sales_order.submit() + + pick_list = frappe.get_doc({ + 'doctype': 'Pick List', + 'company': '_Test Company', + 'customer': '_Test Customer', + 'items_based_on': 'Sales Order', + 'locations': [{ + 'item_code': '_Test Item', + 'qty': 1, + 'stock_qty': 5, + 'conversion_factor': 5, + 'sales_order': sales_order.name, + 'sales_order_item': sales_order.items[0].name , + }, { + 'item_code': '_Test Item', + 'qty': 1, + 'stock_qty': 1, + 'conversion_factor': 1, + 'sales_order': sales_order.name, + 'sales_order_item': sales_order.items[1].name , + }] + }) + pick_list.set_item_locations() + pick_list.submit() + + delivery_note = create_delivery_note(pick_list.name) + + self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty) + self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty) + self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor) + + pick_list.cancel() + sales_order.cancel() + purchase_receipt.cancel() # def test_pick_list_skips_items_in_expired_batch(self): # pass From e7b3047eca0722938900444582e1bf22d24e5b15 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 1 Apr 2021 22:59:28 +0530 Subject: [PATCH 0417/1264] chore: Bump version to v13.0.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 150033bae6..026c243c6d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-beta.14' +__version__ = '13.0.0' def get_default_company(user=None): '''Get default company for user''' From 9896907565e74e8957856eca32e9a0208221c30f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 2 Apr 2021 00:27:08 +0530 Subject: [PATCH 0418/1264] fix: incorrect incoming rate for the sales return --- .../controllers/sales_and_purchase_return.py | 17 ++++++++++++++++- erpnext/controllers/selling_controller.py | 6 ++++-- erpnext/stock/stock_ledger.py | 3 ++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index de61b35316..5f759b43bc 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ from frappe.model.meta import get_field_precision +from erpnext.stock.utils import get_incoming_rate from frappe.utils import flt, get_datetime, format_datetime class StockOverReturnError(frappe.ValidationError): pass @@ -389,10 +390,24 @@ def make_return_doc(doctype, source_name, target_doc=None): return doclist -def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None): +def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, + item_row=None, voucher_detail_no=None, sle=None): if not return_against: return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against") + if not return_against and voucher_type == 'Sales Invoice' and sle: + return get_incoming_rate({ + "item_code": sle.item_code, + "warehouse": sle.warehouse, + "posting_date": sle.get('posting_date'), + "posting_time": sle.get('posting_time'), + "qty": sle.actual_qty, + "serial_no": sle.get('serial_no'), + "company": sle.company, + "voucher_type": sle.voucher_type, + "voucher_no": sle.voucher_no + }, raise_error_if_no_rate=False) + return_against_item_field = get_return_against_item_fields(voucher_type) filters = get_filters(voucher_type, voucher_no, voucher_detail_no, diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index edc40c430a..54156f379c 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -311,14 +311,16 @@ class SellingController(StockController): items = self.get("items") + (self.get("packed_items") or []) for d in items: - if not cint(self.get("is_return")): + if not self.get("return_against"): # Get incoming rate based on original item cost based on valuation method + qty = flt(d.get('stock_qty') or d.get('actual_qty')) + d.incoming_rate = get_incoming_rate({ "item_code": d.item_code, "warehouse": d.warehouse, "posting_date": self.get('posting_date') or self.get('transaction_date'), "posting_time": self.get('posting_time') or nowtime(), - "qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')), + "qty": qty if cint(self.get("is_return")) else (-1 * qty), "serial_no": d.get('serial_no'), "company": self.company, "voucher_type": self.doctype, diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 121c51cf6a..df5f16fd41 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -372,7 +372,8 @@ class update_entries_after(object): elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"): if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"): from erpnext.controllers.sales_and_purchase_return import get_rate_for_return # don't move this import to top - rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no) + rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, + voucher_detail_no=sle.voucher_detail_no, sle = sle) else: if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): rate_field = "valuation_rate" From e41d48c7deeb228cb7d1bfd2604f1d0e48db8a80 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 2 Apr 2021 13:21:08 +0530 Subject: [PATCH 0419/1264] fix: disable auto naming of customer during import --- erpnext/selling/doctype/customer/customer.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 96b3fa4ccd..49ca9423e8 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -38,11 +38,19 @@ class Customer(TransactionBase): set_name_by_naming_series(self) def get_customer_name(self): - if frappe.db.get_value("Customer", self.customer_name): + + if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import: count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer where name like %s""", "%{0} - %".format(self.customer_name), as_list=1)[0][0] count = cint(count) + 1 - return "{0} - {1}".format(self.customer_name, cstr(count)) + + new_customer_name = "{0} - {1}".format(self.customer_name, cstr(count)) + + msgprint(_("Changed customer name to '{}' as '{}' already exists.") + .format(new_customer_name, self.customer_name), + title=_("Note"), indicator="yellow") + + return new_customer_name return self.customer_name From d27fc93eddf3d04fec977464d698c5ecec9c1e6b Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 2 Apr 2021 19:54:58 +0530 Subject: [PATCH 0420/1264] feat: Add additional stats to report summary --- ...ee_hours_utilisation_based_on_timesheet.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 19793917ce..c27efc96fb 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -143,8 +143,14 @@ class EmployeeHoursReport: return avg_utilisation = 0.0 + total_billed, total_non_billed = 0.0, 0.0 + total_untracked = 0.0 + for row in self.data: avg_utilisation += row['per_util'] + total_billed += row['billed_hours'] + total_non_billed += row['non_billed_hours'] + total_untracked += row['untracked_hours'] avg_utilisation /= len(self.data) avg_utilisation = flt(avg_utilisation, 2) @@ -156,6 +162,21 @@ class EmployeeHoursReport: 'indicator': 'Red' if avg_utilisation < THRESHOLD_PERCENTAGE else 'Green', 'label': _('Average Utilisation'), 'datatype': 'Percentage' + }, + { + 'value': total_billed, + 'label': _('Total Billed Hours'), + 'datatype': 'Float' + }, + { + 'value': total_non_billed, + 'label': _('Total Non-Billed Hours'), + 'datatype': 'Float' + }, + { + 'value': total_untracked, + 'label': _('Total Untracked Hours'), + 'datatype': 'Float' } ] From 40abd2a2e12b52a814dd414115e7b29e8729a0dd Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 2 Apr 2021 19:55:54 +0530 Subject: [PATCH 0421/1264] test: Add test case for summary data --- .../test_employee_util.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py index 7b310fa1fe..adf55afc14 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py @@ -68,7 +68,6 @@ class TestEmployeeUtilisation(unittest.TestCase): frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'") frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") - print(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") def test_utilisation_report_with_required_filters_only(self): filters = { @@ -144,4 +143,22 @@ class TestEmployeeUtilisation(unittest.TestCase): } ] - self.assertEqual(report[1], expected_data) \ No newline at end of file + self.assertEqual(report[1], expected_data) + + def test_report_summary_data(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03" + } + + report = execute(filters) + summary = report[4] + expected_summary_values = ['41.67%', 5.0, 10.0, 21.0] + + self.assertEqual(len(summary), 4) + + for i in range(4): + self.assertEqual( + summary[i]['value'], expected_summary_values[i] + ) From 87b2a513228f9b6a88f76adc27d7be00c855ad0d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 3 Apr 2021 12:28:34 +0530 Subject: [PATCH 0422/1264] fix: escape sql filters in credit report closes #24965 --- .../customer_credit_balance.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py index f396705460..6fb7666c2c 100644 --- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py +++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py @@ -57,18 +57,18 @@ def get_columns(customer_naming_type): return columns def get_details(filters): - conditions = "" + sql_query = """SELECT + c.name, c.customer_name, + ccl.bypass_credit_limit_check, + c.is_frozen, c.disabled + FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl + WHERE + c.name = ccl.parent + AND ccl.company = %(company)s""" + + # customer filter is optional. if filters.get("customer"): - conditions += " AND c.name = '" + filters.get("customer") + "'" + sql_query += " AND c.name = %(customer)s" - return frappe.db.sql("""SELECT - c.name, c.customer_name, - ccl.bypass_credit_limit_check, - c.is_frozen, c.disabled - FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl - WHERE - c.name = ccl.parent - AND ccl.company = '{0}' - {1} - """.format( filters.get("company"),conditions), as_dict=1) #nosec + return frappe.db.sql(sql_query, filters, as_dict=1) From 8b829711fc5feb91adbc235aebf3a2ff9b59ff43 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 3 Apr 2021 14:20:30 +0530 Subject: [PATCH 0423/1264] fix: incorrect batch picked in subcontracted purchase receipt --- erpnext/controllers/buying_controller.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 219d5295c3..b686dc026c 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -6,6 +6,7 @@ import frappe from frappe import _, msgprint from frappe.utils import flt,cint, cstr, getdate from six import iteritems +from collections import OrderedDict from erpnext.accounts.party import get_party_details from erpnext.stock.get_item_details import get_conversion_factor from erpnext.buying.utils import validate_for_items, update_last_purchase_rate @@ -391,10 +392,12 @@ class BuyingController(StockController): batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code, qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order) + for batch_data in batches_qty: qty = batch_data['qty'] raw_material.batch_no = batch_data['batch'] - self.append_raw_material_to_be_backflushed(item, raw_material, qty) + if qty > 0: + self.append_raw_material_to_be_backflushed(item, raw_material, qty) else: self.append_raw_material_to_be_backflushed(item, raw_material, qty) @@ -1056,7 +1059,7 @@ def get_transferred_batch_qty_map(purchase_order, fg_item): for batch_data in transferred_batches: key = ((batch_data.item_code, fg_item) if batch_data.subcontracted_item else (batch_data.item_code, purchase_order)) - transferred_batch_qty_map.setdefault(key, {}) + transferred_batch_qty_map.setdefault(key, OrderedDict()) transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty return transferred_batch_qty_map @@ -1109,8 +1112,14 @@ def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty if available_qty >= required_qty: available_batches.append({'batch': batch, 'qty': required_qty}) break - else: + elif available_qty != 0: available_batches.append({'batch': batch, 'qty': available_qty}) required_qty -= available_qty + for row in available_batches: + if backflushed_batches.get(row.get('batch'), 0) > 0: + backflushed_batches[row.get('batch')] += row.get('qty') + else: + backflushed_batches[row.get('batch')] = row.get('qty') + return available_batches From 36ff0748bfa019a5808de3b52f94db0c3e5fbfda Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 4 Apr 2021 18:27:01 +0530 Subject: [PATCH 0424/1264] fix: broken type check in stock ageing report --- erpnext/stock/report/stock_ageing/stock_ageing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index ff603fcfb3..623dc2ffd9 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -49,7 +49,7 @@ def get_average_age(fifo_queue, to_date): for batch in fifo_queue: batch_age = date_diff(to_date, batch[1]) - if type(batch[0]) in ['int', 'float']: + if isinstance(batch[0], (int, float)): age_qty += batch_age * batch[0] total_qty += batch[0] else: @@ -302,4 +302,4 @@ def add_column(range_columns, label, fieldname, fieldtype='Float', width=140): fieldname=fieldname, fieldtype=fieldtype, width=width - )) \ No newline at end of file + )) From ccb15de1c9e4c23487787b3f8be3bf3e8d7dfb86 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Mon, 5 Apr 2021 11:23:06 +0530 Subject: [PATCH 0425/1264] fix: Handle overtime edge case --- .../employee_hours_utilisation_based_on_timesheet.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index c27efc96fb..19cb1c911d 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -134,6 +134,11 @@ class EmployeeHoursReport: for emp, data in iteritems(self.stats_by_employee): data['total_hours'] = TOTAL_HOURS data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) + + # To handle overtime edge-case + if data['untracked_hours'] < 0: + data['untracked_hours'] = 0.0 + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) def generate_report_summary(self): From bb32f5a92a0f20dbd0f673c3b3beab69f47dd892 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Mon, 5 Apr 2021 19:32:10 +0530 Subject: [PATCH 0426/1264] fix: Rename utilisation to utilization --- .../__init__.py | 0 ...e_hours_utilization_based_on_timesheet.js} | 2 +- ...hours_utilization_based_on_timesheet.json} | 8 +++---- ...e_hours_utilization_based_on_timesheet.py} | 22 +++++++++---------- .../test_employee_util.py | 10 ++++----- 5 files changed, 21 insertions(+), 21 deletions(-) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet => employee_hours_utilization_based_on_timesheet}/__init__.py (100%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js => employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js} (93%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json => employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json} (63%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py => employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py} (92%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet => employee_hours_utilization_based_on_timesheet}/test_employee_util.py (92%) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py similarity index 100% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js similarity index 93% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js index e9b817ab67..2a5ea3655d 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Employee Hours Utilisation Based On Timesheet"] = { +frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = { "filters": [ { fieldname: "company", diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json similarity index 63% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json index 1b504e011d..5ff8186572 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json @@ -1,7 +1,7 @@ { "add_total_row": 0, "columns": [], - "creation": "2021-03-30 12:18:02.090630", + "creation": "2021-04-05 19:23:43.838623", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, @@ -9,14 +9,14 @@ "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2021-03-30 12:18:02.090630", + "modified": "2021-04-05 19:23:43.838623", "modified_by": "Administrator", "module": "Projects", - "name": "Employee Hours Utilisation Based On Timesheet", + "name": "Employee Hours Utilization Based On Timesheet", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "Timesheet", - "report_name": "Employee Hours Utilisation Based On Timesheet", + "report_name": "Employee Hours Utilization Based On Timesheet", "report_type": "Script Report", "roles": [] } \ No newline at end of file diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py similarity index 92% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 19cb1c911d..bc9dc7d095 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -11,7 +11,7 @@ def execute(filters=None): return EmployeeHoursReport(filters).run() class EmployeeHoursReport: - '''Employee Hours Utilisation Report Based On Timesheet''' + '''Employee Hours Utilization Report Based On Timesheet''' def __init__(self, filters=None): self.filters = frappe._dict(filters or {}) @@ -78,7 +78,7 @@ class EmployeeHoursReport: def generate_data(self): self.generate_filtered_time_logs() self.generate_stats_by_employee() - self.calculate_utilisations() + self.calculate_utilizations() self.data = [] @@ -88,7 +88,7 @@ class EmployeeHoursReport: row.update(data) self.data.append(row) - # Sort by descending order of percentage utilisation + # Sort by descending order of percentage utilization self.data.sort(key=lambda x: x['per_util'], reverse=True) def generate_filtered_time_logs(self): @@ -128,7 +128,7 @@ class EmployeeHoursReport: else: self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2) - def calculate_utilisations(self): + def calculate_utilizations(self): # (9.0) Will be fetched from HR settings TOTAL_HOURS = flt(9.0 * self.day_span, 2) for emp, data in iteritems(self.stats_by_employee): @@ -147,25 +147,25 @@ class EmployeeHoursReport: if not self.data: return - avg_utilisation = 0.0 + avg_utilization = 0.0 total_billed, total_non_billed = 0.0, 0.0 total_untracked = 0.0 for row in self.data: - avg_utilisation += row['per_util'] + avg_utilization += row['per_util'] total_billed += row['billed_hours'] total_non_billed += row['non_billed_hours'] total_untracked += row['untracked_hours'] - avg_utilisation /= len(self.data) - avg_utilisation = flt(avg_utilisation, 2) + avg_utilization /= len(self.data) + avg_utilization = flt(avg_utilization, 2) THRESHOLD_PERCENTAGE = 70.0 self.report_summary = [ { - 'value': f'{avg_utilisation}%', - 'indicator': 'Red' if avg_utilisation < THRESHOLD_PERCENTAGE else 'Green', - 'label': _('Average Utilisation'), + 'value': f'{avg_utilization}%', + 'indicator': 'Red' if avg_utilization < THRESHOLD_PERCENTAGE else 'Green', + 'label': _('Average Utilization'), 'datatype': 'Percentage' }, { diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py similarity index 92% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index adf55afc14..5306b922eb 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -4,11 +4,11 @@ import frappe from frappe.utils.make_random import get_random from frappe.utils import get_timestamp -from erpnext.projects.report.employee_hours_utilisation_based_on_timesheet.employee_hours_utilisation_based_on_timesheet import execute +from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import execute from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.project.test_project import make_project -class TestEmployeeUtilisation(unittest.TestCase): +class TestEmployeeUtilization(unittest.TestCase): @classmethod def setUpClass(cls): # Create test employee @@ -69,7 +69,7 @@ class TestEmployeeUtilisation(unittest.TestCase): frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'") frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") - def test_utilisation_report_with_required_filters_only(self): + def test_utilization_report_with_required_filters_only(self): filters = { "company": "_Test Company", "from_date": "2021-04-01", @@ -99,7 +99,7 @@ class TestEmployeeUtilisation(unittest.TestCase): self.assertEqual(report[1], expected_data) - def test_utilisation_report_for_single_employee(self): + def test_utilization_report_for_single_employee(self): filters = { "company": "_Test Company", "from_date": "2021-04-01", @@ -122,7 +122,7 @@ class TestEmployeeUtilisation(unittest.TestCase): self.assertEqual(report[1], expected_data) - def test_utilisation_report_for_project(self): + def test_utilization_report_for_project(self): filters = { "company": "_Test Company", "from_date": "2021-04-01", From 33e0c8ed191eb08eea2a2bc7f9544f2defed84fa Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 6 Apr 2021 11:40:35 +0530 Subject: [PATCH 0427/1264] test: Unique Employee IDs for tests --- .../employee_hours_utilization_based_on_timesheet.py | 2 +- .../test_employee_util.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index bc9dc7d095..2c78b3125d 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -23,7 +23,7 @@ class EmployeeHoursReport: def validate_dates(self): self.day_span = (self.to_date - self.from_date).days - if self.day_span < 0: + if self.day_span <= 0: frappe.throw(_('From Date must come before To Date')) def run(self): diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 5306b922eb..e01e9b1539 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -12,8 +12,8 @@ class TestEmployeeUtilization(unittest.TestCase): @classmethod def setUpClass(cls): # Create test employee - cls.test_emp1 = make_employee("test@example.com", "_Test Company") - cls.test_emp2 = make_employee("test1@example.com", "_Test Company") + cls.test_emp1 = make_employee("test1@employeeutil.com", "_Test Company") + cls.test_emp2 = make_employee("test2@employeeutil.com", "_Test Company") # Create test project cls.test_project = make_project({"project_name": "_Test Project"}) @@ -80,7 +80,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { - 'employee': 'EMP-00002', + 'employee': self.test_emp2, 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'total_hours': 18.0, @@ -88,7 +88,7 @@ class TestEmployeeUtilization(unittest.TestCase): 'per_util': 55.56 }, { - 'employee': 'EMP-00001', + 'employee': self.test_emp1, 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'total_hours': 18.0, @@ -111,7 +111,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { - 'employee': 'EMP-00001', + 'employee': self.test_emp1, 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'total_hours': 18.0, @@ -134,7 +134,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { - 'employee': 'EMP-00002', + 'employee': self.test_emp2, 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'total_hours': 18.0, From d2da7b673bf7a4525819451d429eb889c227b619 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 6 Apr 2021 11:44:54 +0530 Subject: [PATCH 0428/1264] fix: Sider Issues --- .../test_employee_util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index e01e9b1539..8b5d5be0e1 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -3,7 +3,6 @@ import unittest import frappe from frappe.utils.make_random import get_random -from frappe.utils import get_timestamp from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import execute from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.project.test_project import make_project @@ -161,4 +160,4 @@ class TestEmployeeUtilization(unittest.TestCase): for i in range(4): self.assertEqual( summary[i]['value'], expected_summary_values[i] - ) + ) From a93b514b2f433eb5f4c0ad3dd28e5f219cbb5ae4 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Tue, 6 Apr 2021 17:10:52 +0530 Subject: [PATCH 0429/1264] feat: create Quality Inspections from account and stock documents --- erpnext/controllers/accounts_controller.py | 32 +++++ erpnext/public/js/controllers/transaction.js | 133 ++++++++++++++++++- 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 36d399cf19..0c966566fd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1412,6 +1412,38 @@ def validate_and_delete_children(parent, data): for d in deleted_children: update_bin_on_delete(d, parent.doctype) + +@frappe.whitelist() +def make_quality_inspections(doctype, docname, items): + items = json.loads(items).get('items') + inspections = [] + + for item in items: + if item.get("sample_size") > item.get("qty"): + frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( + item_name=item.get("item_name"), + sample_size=item.get("sample_size"), + accepted_quantity=item.get("qty") + )) + + quality_inspection = frappe.get_doc({ + "doctype": "Quality Inspection", + "inspection_type": "Incoming", + "inspected_by": frappe.session.user, + "reference_type": doctype, + "reference_name": docname, + "item_code": item.get("item_code"), + "description": item.get("description"), + "sample_size": item.get("sample_size"), + "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, + "batch_no": item.get("batch_no") + }).insert() + quality_inspection.save() + inspections.append(quality_inspection) + + return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + + @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def check_doc_permissions(doc, perm_type='create'): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 21a20a7bce..da0c87dd85 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -261,11 +261,19 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) { return; } - var me = this; - var inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype) + + const me = this; + if (!this.frm.is_new() && this.frm.doc.docstatus === 0) { + this.frm.add_custom_button(__("Quality Inspection(s)"), () => { + me.make_quality_inspection(); + }, __("Create")); + this.frm.page.set_inner_btn_group_as_primary(__('Create')); + } + + const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype) ? "Incoming" : "Outgoing"; - var quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); + let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); quality_inspection_field.get_route_options_for_new_doc = function(row) { if(me.frm.is_new()) return; return { @@ -280,7 +288,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = locals[cdt][cdn]; return { filters: { docstatus: 1, @@ -1909,6 +1917,123 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); }, + make_quality_inspection: function () { + let data = []; + const fields = [ + { + label: "Items", + fieldtype: "Table", + fieldname: "items", + cannot_add_rows: true, + in_place_edit: true, + data: data, + get_data: () => { return data }, + fields: [ + { + fieldtype: "Data", + fieldname: "docname", + hidden: true + }, + { + fieldtype: "Read Only", + fieldname: "item_code", + label: __("Item Code"), + in_list_view: true + }, + { + fieldtype: "Read Only", + fieldname: "item_name", + label: __("Item Name"), + in_list_view: true + }, + { + fieldtype: "Float", + fieldname: "qty", + label: __("Accepted Quantity"), + in_list_view: true, + read_only: true + }, + { + fieldtype: "Float", + fieldname: "sample_size", + label: __("Sample Size"), + reqd: true, + in_list_view: true + }, + { + fieldtype: "Data", + fieldname: "description", + label: __("Description"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "serial_no", + label: __("Serial No"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "batch_no", + label: __("Batch No"), + hidden: true + } + ] + } + ]; + + const me = this; + const dialog = new frappe.ui.Dialog({ + title: __("Select Items for Quality Inspection"), + fields: fields, + primary_action: function () { + const data = dialog.get_values(); + frappe.call({ + method: "erpnext.controllers.accounts_controller.make_quality_inspections", + args: { + doctype: me.frm.doc.doctype, + docname: me.frm.doc.name, + items: data + }, + freeze: true, + callback: function (r) { + if (r.message) { + frappe.msgprint({ + message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), + indicator: "green" + }) + } + dialog.hide(); + } + }); + }, + primary_action_label: __("Create") + }); + + this.frm.doc.items.forEach(item => { + if (!item.quality_inspection) { + let dialog_items = dialog.fields_dict.items; + dialog_items.df.data.push({ + "docname": item.name, + "item_code": item.item_code, + "item_name": item.item_name, + "qty": item.qty, + "description": item.description, + "serial_no": item.serial_no, + "batch_no": item.batch_no + }); + dialog_items.grid.refresh(); + } + }) + + data = dialog.fields_dict.items.df.data; + if (!data.length) { + frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); + } else { + dialog.show(); + } + }, + get_method_for_payment: function(){ var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){ From aa809ceffef679fa8c12472f5dc51fb734c7438c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 7 Apr 2021 10:57:46 +0530 Subject: [PATCH 0430/1264] fix: Patch fixes for v13 upgrade (#25220) --- .../workspace/accounting/accounting.json | 10 ++++ erpnext/patches.txt | 1 + .../v13_0/create_uae_pos_invoice_fields.py | 4 ++ .../fix_non_unique_represents_company.py | 8 +++ ..._history_settings_for_standard_doctypes.py | 2 + erpnext/patches/v13_0/setup_uae_vat_fields.py | 4 ++ erpnext/regional/germany/setup.py | 15 +++++- erpnext/regional/india/setup.py | 13 ++--- erpnext/regional/report/datev/datev.json | 49 ++++++++----------- 9 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 erpnext/patches/v13_0/fix_non_unique_represents_company.py diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index fadb66535f..9ffa481c1c 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -443,6 +443,16 @@ "onboard": 0, "type": "Link" }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "UAE VAT 201", + "link_to": "UAE VAT 201", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 59058a4cf6..09d4b2290c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -761,3 +761,4 @@ erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields +erpnext.patches.v13_0.fix_non_unique_represents_company diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py index 48d5cb4cc8..59b2e49b26 100644 --- a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py +++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py @@ -11,4 +11,8 @@ def execute(): if not company: return + + frappe.reload_doc('accounts', 'doctype', 'pos_invoice') + frappe.reload_doc('accounts', 'doctype', 'pos_invoice_item') + make_custom_fields() \ No newline at end of file diff --git a/erpnext/patches/v13_0/fix_non_unique_represents_company.py b/erpnext/patches/v13_0/fix_non_unique_represents_company.py new file mode 100644 index 0000000000..61dc824dd4 --- /dev/null +++ b/erpnext/patches/v13_0/fix_non_unique_represents_company.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + frappe.db.sql(""" + update tabCustomer + set represents_company = NULL + where represents_company = '' + """) \ No newline at end of file diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py index de08aa26b3..7ec470c740 100644 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py @@ -6,6 +6,8 @@ def execute(): if "Healthcare" not in frappe.get_active_domains(): return + frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order") + frappe.reload_doc("healthcare", "doctype", "Therapy Session") frappe.reload_doc("healthcare", "doctype", "Patient History Settings") frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type") diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py index ee55bb8996..1830bab02b 100644 --- a/erpnext/patches/v13_0/setup_uae_vat_fields.py +++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py @@ -9,4 +9,8 @@ def execute(): if not company: return + frappe.reload_doc('regional', 'report', 'uae_vat_201') + frappe.reload_doc('regional', 'doctype', 'uae_vat_settings') + frappe.reload_doc('regional', 'doctype', 'uae_vat_account') + setup() diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index d6047e863c..ac1f543488 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -3,4 +3,17 @@ import frappe def setup(company=None, patch=True): - pass + add_custom_roles_for_reports() + + +def add_custom_roles_for_reports(): + """Add Access Control to UAE VAT 201.""" + if not frappe.db.get_value('Custom Role', dict(report='DATEV')): + frappe.get_doc(dict( + doctype='Custom Role', + report='DATEV', + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager') + ] + )).insert() \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index f7689cfa19..d573425678 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -12,14 +12,14 @@ from erpnext.accounts.utils import get_fiscal_year, FiscalYearError from frappe.utils import today def setup(company=None, patch=True): - setup_company_independent_fixtures() + setup_company_independent_fixtures(patch=patch) if not patch: make_fixtures(company) # TODO: for all countries -def setup_company_independent_fixtures(): +def setup_company_independent_fixtures(patch=False): make_custom_fields() - make_property_setters() + make_property_setters(patch=patch) add_permissions() add_custom_roles_for_reports() frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) @@ -112,10 +112,11 @@ def add_print_formats(): frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0) frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0) -def make_property_setters(): +def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters - make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') - make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + if not patch: + make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') + make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', diff --git a/erpnext/regional/report/datev/datev.json b/erpnext/regional/report/datev/datev.json index 80a866cbf5..94e3960ead 100644 --- a/erpnext/regional/report/datev/datev.json +++ b/erpnext/regional/report/datev/datev.json @@ -1,29 +1,22 @@ { - "add_total_row": 0, - "apply_user_permissions": 0, - "creation": "2019-04-24 08:45:16.650129", - "disabled": 0, - "icon": "octicon octicon-repo-pull", - "color": "#4CB944", - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "module": "Regional", - "name": "DATEV", - "owner": "Administrator", - "ref_doctype": "GL Entry", - "report_name": "DATEV", - "report_type": "Script Report", - "roles": [ - { - "role": "Accounts User" - }, - { - "role": "Accounts Manager" - }, - { - "role": "Auditor" - } - ] -} + "add_total_row": 0, + "columns": [], + "creation": "2019-04-24 08:45:16.650129", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-06 12:23:00.379517", + "modified_by": "Administrator", + "module": "Regional", + "name": "DATEV", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "DATEV", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file From 547fd31a37af08461e618d5585f7f7e6602b4be8 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 7 Apr 2021 11:33:40 +0550 Subject: [PATCH 0431/1264] bumped to version 13.0.1 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 026c243c6d..d2e58701c8 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0' +__version__ = '13.0.1' def get_default_company(user=None): '''Get default company for user''' From c39720db501c298c8993a4976984f71afc08eb6f Mon Sep 17 00:00:00 2001 From: Walstan Baptista <38958184+walstanb@users.noreply.github.com> Date: Wed, 7 Apr 2021 11:51:22 +0530 Subject: [PATCH 0432/1264] fix: frappe.whitelist for doc methods (#25231) --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index b520cdabdc..1a6a53438d 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -183,6 +183,7 @@ class PayrollEntry(Document): """, (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict) return ss_list + @frappe.whitelist() def submit_salary_slips(self): self.check_permission('write') ss_list = self.get_sal_slip_list(ss_status=0) @@ -405,6 +406,7 @@ class PayrollEntry(Document): self.update(get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date, self.company)) + @frappe.whitelist() def validate_employee_attendance(self): employees_to_mark_attendance = [] days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 From 09c7598a6782e53d90978b41f8e22e27dfc866d9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:03:33 +0200 Subject: [PATCH 0433/1264] fix: make account field non-mandatory --- .../doctype/party_account/party_account.json | 114 +++++------------- 1 file changed, 33 insertions(+), 81 deletions(-) diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json index aa32d95373..c9f15a6a47 100644 --- a/erpnext/accounts/doctype/party_account/party_account.json +++ b/erpnext/accounts/doctype/party_account/party_account.json @@ -1,87 +1,39 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-08-29 16:02:39.740505", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2014-08-29 16:02:39.740505", + "doctype": "DocType", + "editable_grid": 1, + "field_order": [ + "company", + "account" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account" } - ], - "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.348246", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Party Account", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-07 18:13:08.833822", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Party Account", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From 6b2e4f2b5d7555e14091f0908588ac5ebc360e64 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:03:59 +0200 Subject: [PATCH 0434/1264] feat: add custom field debtor_creditor_number to Party Account --- erpnext/regional/germany/setup.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index d6047e863c..6258c05459 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -1,6 +1,18 @@ import os import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def setup(company=None, patch=True): - pass + make_custom_fields() + + +def make_custom_fields(): + custom_fields = { + 'Party Account': [ + dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number', + fieldtype='Data', insert_after='account') + ] + } + + create_custom_fields(custom_fields) From b39608a02e389426d7da405a9d5467ec05514834 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:04:38 +0200 Subject: [PATCH 0435/1264] fix: handle encoding errors replace unknown characters by '?' --- erpnext/regional/germany/utils/datev/datev_csv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index f138a807bc..826d51f712 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -56,10 +56,10 @@ def get_datev_csv(data, filters, csv_class): ) if not six.PY2: - data = data.encode('latin_1') + data = data.encode('latin_1', errors='replace') header = get_header(filters, csv_class) - header = ';'.join(header).encode('latin_1') + header = ';'.join(header).encode('latin_1', errors='replace') # 1st Row: Header with meta data # 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here. From 4fe2d35b2e4ee548728f2592fb1cb322a2df2506 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:05:41 +0200 Subject: [PATCH 0436/1264] feat: more infos for transactions --- erpnext/regional/report/datev/datev.py | 45 +++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index cbc9478987..e1b0c54b2b 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -88,6 +88,32 @@ COLUMNS = [ "fieldtype": "Dynamic Link", "options": "Beleginfo - Art 2", "width": 150 + }, + { + "label": "Beleginfo - Art 3", + "fieldname": "Beleginfo - Art 3", + "fieldtype": "Link", + "options": "DocType", + "width": 100 + }, + { + "label": "Beleginfo - Inhalt 3", + "fieldname": "Beleginfo - Inhalt 3", + "fieldtype": "Dynamic Link", + "options": "Beleginfo - Art 3", + "width": 150 + } + ,{ + "label": "Beleginfo - Art 4", + "fieldname": "Beleginfo - Art 4", + "fieldtype": "Data", + "width": 100 + }, + { + "label": "Beleginfo - Inhalt 4", + "fieldname": "Beleginfo - Inhalt 4", + "fieldtype": "Data", + "width": 150 } ] @@ -169,7 +195,11 @@ def get_transactions(filters, as_dict=1): gl.voucher_type as 'Beleginfo - Art 1', gl.voucher_no as 'Beleginfo - Inhalt 1', gl.against_voucher_type as 'Beleginfo - Art 2', - gl.against_voucher as 'Beleginfo - Inhalt 2' + gl.against_voucher as 'Beleginfo - Inhalt 2', + gl.party_type as 'Beleginfo - Art 3', + gl.party as 'Beleginfo - Inhalt 3', + case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4', + par.debtor_creditor_number as 'Beleginfo - Inhalt 4' FROM `tabGL Entry` gl @@ -177,6 +207,19 @@ def get_transactions(filters, as_dict=1): left join `tabAccount` acc on gl.account = acc.name + left join `tabCustomer` cus + on gl.party_type = 'Customer' + and gl.party = cus.name + + left join `tabSupplier` sup + on gl.party_type = 'Supplier' + and gl.party = sup.name + + left join `tabParty Account` par + on par.parent = gl.party + and par.parenttype = gl.party_type + and par.company = %(company)s + WHERE gl.company = %(company)s AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) <= %(to_date)s From 94f293940c947873c5c78ca20c16c1fb2bad0e3e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:06:16 +0200 Subject: [PATCH 0437/1264] fix: better party export --- erpnext/regional/report/datev/datev.py | 106 +++++++++++++++---------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index e1b0c54b2b..42e4f96d1f 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -239,40 +239,56 @@ def get_customers(filters): return frappe.db.sql(""" SELECT - acc.account_number as 'Konto', - CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)', - CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', - CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', - CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', + par.debtor_creditor_number as 'Konto', + CASE cus.customer_type + WHEN 'Company' THEN cus.customer_name + ELSE null + END as 'Name (Adressatentyp Unternehmen)', + CASE cus.customer_type + WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name))) + ELSE null + END as 'Name (Adressatentyp natürl. Person)', + CASE cus.customer_type + WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1) + ELSE null + END as 'Vorname (Adressatentyp natürl. Person)', + CASE cus.customer_type + WHEN 'Individual' THEN '1' + WHEN 'Company' THEN '2' + ELSE '0' + END as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', UPPER(country.code) as 'Land', adr.address_line2 as 'Adresszusatz', - con.email_id as 'E-Mail', - coalesce(con.mobile_no, con.phone) as 'Telefon', + adr.email_id as 'E-Mail', + adr.phone as 'Telefon', + adr.fax as 'Fax', cus.website as 'Internet', cus.tax_id as 'Steuernummer' - FROM `tabParty Account` par + FROM `tabCustomer` cus - left join `tabAccount` acc - on acc.name = par.account + left join `tabParty Account` par + on par.parent = cus.name + and par.parenttype = 'Customer' + and par.company = %(company)s - left join `tabCustomer` cus - on cus.name = par.parent + left join `tabDynamic Link` dyn_adr + on dyn_adr.link_name = cus.name + and dyn_adr.link_doctype = 'Customer' + and dyn_adr.parenttype = 'Address' left join `tabAddress` adr - on adr.name = cus.customer_primary_address + on adr.name = dyn_adr.parent + and adr.is_primary_address = '1' left join `tabCountry` country on country.name = adr.country - left join `tabContact` con - on con.name = cus.customer_primary_contact - - WHERE par.company = %(company)s - AND par.parenttype = 'Customer'""", filters, as_dict=1) + WHERE adr.is_primary_address = '1' + """, filters, as_dict=1) def get_suppliers(filters): @@ -285,35 +301,48 @@ def get_suppliers(filters): return frappe.db.sql(""" SELECT - acc.account_number as 'Konto', - CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)', - CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', - CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', - CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', + par.debtor_creditor_number as 'Konto', + CASE sup.supplier_type + WHEN 'Company' THEN sup.supplier_name + ELSE null + END as 'Name (Adressatentyp Unternehmen)', + CASE sup.supplier_type + WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name))) + ELSE null + END as 'Name (Adressatentyp natürl. Person)', + CASE sup.supplier_type + WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1) + ELSE null + END as 'Vorname (Adressatentyp natürl. Person)', + CASE sup.supplier_type + WHEN 'Individual' THEN '1' + WHEN 'Company' THEN '2' + ELSE '0' + END as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', UPPER(country.code) as 'Land', adr.address_line2 as 'Adresszusatz', - con.email_id as 'E-Mail', - coalesce(con.mobile_no, con.phone) as 'Telefon', + adr.email_id as 'E-Mail', + adr.phone as 'Telefon', + adr.fax as 'Fax', sup.website as 'Internet', sup.tax_id as 'Steuernummer', case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis' - FROM `tabParty Account` par + FROM `tabSupplier` sup - left join `tabAccount` acc - on acc.name = par.account - - left join `tabSupplier` sup - on sup.name = par.parent + left join `tabParty Account` par + on par.parent = sup.name + and par.parenttype = 'Supplier' + and par.company = %(company)s left join `tabDynamic Link` dyn_adr on dyn_adr.link_name = sup.name and dyn_adr.link_doctype = 'Supplier' and dyn_adr.parenttype = 'Address' - + left join `tabAddress` adr on adr.name = dyn_adr.parent and adr.is_primary_address = '1' @@ -321,17 +350,8 @@ def get_suppliers(filters): left join `tabCountry` country on country.name = adr.country - left join `tabDynamic Link` dyn_con - on dyn_con.link_name = sup.name - and dyn_con.link_doctype = 'Supplier' - and dyn_con.parenttype = 'Contact' - - left join `tabContact` con - on con.name = dyn_con.parent - and con.is_primary_contact = '1' - - WHERE par.company = %(company)s - AND par.parenttype = 'Supplier'""", filters, as_dict=1) + WHERE adr.is_primary_address = '1' + """, filters, as_dict=1) def get_account_names(filters): From 368a6541e9471f53b88e00d585820f7a1a7ba1dd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:26:56 +0200 Subject: [PATCH 0438/1264] fix: Debtor/Creditor Number is not translatable --- erpnext/regional/germany/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index 6258c05459..83e4f97519 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -11,7 +11,7 @@ def make_custom_fields(): custom_fields = { 'Party Account': [ dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number', - fieldtype='Data', insert_after='account') + fieldtype='Data', insert_after='account', translatable=0) ] } From 03425071e7392673a9dfd22a4fb83294a2b19c0c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:31:00 +0200 Subject: [PATCH 0439/1264] fix: patch to add custom fields --- erpnext/patches.txt | 1 + .../patches/v13_0/germany_make_custom_fields.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 erpnext/patches/v13_0/germany_make_custom_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 16863142bc..df004abc48 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -764,3 +764,4 @@ erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.purchase_receipt_status +erpnext.patches.v13_0.germany_make_custom_fields diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py new file mode 100644 index 0000000000..cf1fe219dc --- /dev/null +++ b/erpnext/patches/v13_0/germany_make_custom_fields.py @@ -0,0 +1,14 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from erpnext.regional.germany.setup import make_custom_fields + +def execute(): + company_list = frappe.get_all('Company', filters = {'country': 'Germany'}) + if not company_list: + return + + make_custom_fields() From ed36fb2073de13459e09993ace93116322858a92 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:57:20 +0200 Subject: [PATCH 0440/1264] docs: doctring for patch --- erpnext/patches/v13_0/germany_make_custom_fields.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py index cf1fe219dc..41ab945eb1 100644 --- a/erpnext/patches/v13_0/germany_make_custom_fields.py +++ b/erpnext/patches/v13_0/germany_make_custom_fields.py @@ -6,7 +6,13 @@ from __future__ import unicode_literals import frappe from erpnext.regional.germany.setup import make_custom_fields + def execute(): + """Execute the make_custom_fields method for german companies. + + It is usually run once at setup of a new company. Since it's new, run it + once for existing companies as well. + """ company_list = frappe.get_all('Company', filters = {'country': 'Germany'}) if not company_list: return From c6e13ac218e3a95ccb3df25dfe7ebcd40c2ffdb2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:57:55 +0200 Subject: [PATCH 0441/1264] fix: patch to fill Debtor/Creditor Number --- erpnext/patches.txt | 1 + .../germany_fill_debtor_creditor_number.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index df004abc48..a03bd00735 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -765,3 +765,4 @@ execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.germany_make_custom_fields +erpnext.patches.v13_0.germany_fill_debtor_creditor_number diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py new file mode 100644 index 0000000000..8482730ec8 --- /dev/null +++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py @@ -0,0 +1,31 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from erpnext.regional.germany.setup import make_custom_fields + +def execute(): + """Move account number into the new custom field debtor_creditor_number. + + German companies used to use a dedicated payable/receivable account for + every party to mimick party accounts in the external accounting software + "DATEV". This is no longer necessary. The reference ID for DATEV will be + stored in a new custom field "debtor_creditor_number". + """ + company_list = frappe.get_all('Company', filters={'country': 'Germany'}) + + for company in company_list: + party_account_list = frappe.get_all('Party Account', filters={'company': company.name}, fields=['name', 'account', 'debtor_creditor_number']) + for party_account in party_account_list: + if (not party_account.account) or party_account.debtor_creditor_number: + # account empty or debtor_creditor_number already filled + continue + + account_number = frappe.db.get_value('Account', party_account.account, 'account_number') + if not account_number: + continue + + frappe.db.set_value('Party Account', party_account.name, 'debtor_creditor_number', account_number) + frappe.db.set_value('Party Account', party_account.name, 'account', '') From 3a12f1f1ae0a76321eda86c32c16ba3a224d177c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 17:53:48 +0200 Subject: [PATCH 0442/1264] fix: prettier error log --- erpnext/regional/report/datev/datev.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 42e4f96d1f..c81568bfa6 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -146,10 +146,8 @@ def validate(filters): validate_fiscal_year(from_date, to_date, company) if not frappe.db.exists('DATEV Settings', filters.get('company')): - frappe.log_error(_('Please create {} for Company {}.').format( - '
{}'.format(_('DATEV Settings')), - frappe.bold(filters.get('company')) - )) + msg = 'Please create DATEV Settings for Company {}'.format(filters.get('company')) + frappe.log_error(msg, title='DATEV Settings missing') return False return True From 0a45fc8c58927c3f0810d0124b4e3bdf580234a9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 18:25:17 +0200 Subject: [PATCH 0443/1264] fix: remove unused import --- erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py index 8482730ec8..11e1e9b3b9 100644 --- a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py +++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from erpnext.regional.germany.setup import make_custom_fields + def execute(): """Move account number into the new custom field debtor_creditor_number. From e66cf0aa444d33eaebf72314e4a77590793100f7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 18:26:45 +0200 Subject: [PATCH 0444/1264] fix: hanging indent --- erpnext/regional/report/datev/datev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index c81568bfa6..1a215031c1 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -102,8 +102,8 @@ COLUMNS = [ "fieldtype": "Dynamic Link", "options": "Beleginfo - Art 3", "width": 150 - } - ,{ + }, + { "label": "Beleginfo - Art 4", "fieldname": "Beleginfo - Art 4", "fieldtype": "Data", From e7813b69c4633aea6d476c286106280c5b0f94a7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 9 Apr 2021 11:30:50 +0530 Subject: [PATCH 0445/1264] fix: condition for setting agreement status --- erpnext/support/doctype/issue/issue.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 767a8a6a29..b068363f06 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -7,7 +7,7 @@ import json from frappe import _ from frappe import utils from frappe.model.document import Document -from frappe.utils import now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds +from frappe.utils import cint, now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds from datetime import datetime, timedelta from frappe.model.mapper import get_mapped_doc from frappe.utils.user import is_website_user @@ -128,8 +128,8 @@ class Issue(Document): def update_agreement_status(self): if self.service_level_agreement and self.agreement_status == "Ongoing": - if frappe.db.get_value("Issue", self.name, "response_by_variance") < 0 or \ - frappe.db.get_value("Issue", self.name, "resolution_by_variance") < 0: + if cint(frappe.db.get_value("Issue", self.name, "response_by_variance")) < 0 or \ + cint(frappe.db.get_value("Issue", self.name, "resolution_by_variance")) < 0: self.agreement_status = "Failed" else: From 032246dc2bf772102a876cedd814b7616efd3b34 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 9 Apr 2021 12:08:24 +0530 Subject: [PATCH 0446/1264] feat(italy): add document type field for e-invoicing --- erpnext/patches.txt | 1 + ...document_type_field_for_italy_einvoicing.py | 18 ++++++++++++++++++ erpnext/regional/italy/setup.py | 3 +++ erpnext/regional/italy/utils.py | 11 ++++++----- 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 16863142bc..010963a868 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -764,3 +764,4 @@ erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.purchase_receipt_status +erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing \ No newline at end of file diff --git a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py new file mode 100644 index 0000000000..4d649dd0f0 --- /dev/null +++ b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'Italy'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='type_of_document', label='Type of Document', + fieldtype='Select', insert_after='customer_fiscal_code', + options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'), + ] + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index a1f5bb9836..7db2f6b0f8 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -139,6 +139,9 @@ def make_custom_fields(update=True): dict(fieldname='customer_fiscal_code', label='Customer Fiscal Code', fieldtype='Data', insert_after='cb_e_invoicing_reference', read_only=1, fetch_from="customer.fiscal_code"), + dict(fieldname='type_of_document', label='Type of Document', + fieldtype='Select', insert_after='customer_fiscal_code', + options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'), ], 'Purchase Invoice Item': invoice_item_fields, 'Sales Order Item': invoice_item_fields, diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 08573cddcd..ba1aeafc3e 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -57,11 +57,12 @@ def prepare_invoice(invoice, progressive_number): invoice.company_address_data = company_address #Set invoice type - if invoice.is_return and invoice.return_against: - invoice.type_of_document = "TD04" #Credit Note (Nota di Credito) - invoice.return_against_unamended = get_unamended_name(frappe.get_doc("Sales Invoice", invoice.return_against)) - else: - invoice.type_of_document = "TD01" #Sales Invoice (Fattura) + if not invoice.type_of_document: + if invoice.is_return and invoice.return_against: + invoice.type_of_document = "TD04" #Credit Note (Nota di Credito) + invoice.return_against_unamended = get_unamended_name(frappe.get_doc("Sales Invoice", invoice.return_against)) + else: + invoice.type_of_document = "TD01" #Sales Invoice (Fattura) #set customer information invoice.customer_data = frappe.get_doc("Customer", invoice.customer) From 31b5dfe9eed3de15effd1998f0c004896652af39 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 9 Apr 2021 16:52:14 +0530 Subject: [PATCH 0447/1264] feat: Dimension-wise Accounts Balance Report --- .../__init__.py | 0 .../dimension_wise_accounts_balance_report.js | 81 +++++++ ...imension_wise_accounts_balance_report.json | 22 ++ .../dimension_wise_accounts_balance_report.py | 205 ++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js new file mode 100644 index 0000000000..6a0394861b --- /dev/null +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js @@ -0,0 +1,81 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.require("assets/erpnext/js/financial_statements.js", function() { + frappe.query_reports["Dimension-wise Accounts Balance Report"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1, + "on_change": function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + from_date: fy.year_start_date, + to_date: fy.year_end_date + }); + }); + } + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_start_date"), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_end_date"), + }, + { + "fieldname": "finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + }, + { + "fieldname": "dimension", + "label": __("Select Dimension"), + "fieldtype": "Select", + "options": get_accounting_dimension_options(), + "reqd": 1, + }, + ], + "formatter": erpnext.financial_statements.formatter, + "tree": true, + "name_field": "account", + "parent_field": "parent_account", + "initial_depth": 3 + } + +}); + +function get_accounting_dimension_options() { + let options =["", "Cost Center", "Project"]; + frappe.db.get_list('Accounting Dimension', + {fields:['document_type']}).then((res) => { + res.forEach((dimension) => { + options.push(dimension.document_type); + }); + }); + return options +} diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json new file mode 100644 index 0000000000..6141944f9d --- /dev/null +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json @@ -0,0 +1,22 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-04-09 16:48:59.548018", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-09 16:48:59.548018", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Dimension-wise Accounts Balance Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "Dimension-wise Accounts Balance Report", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py new file mode 100644 index 0000000000..9769a458dd --- /dev/null +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -0,0 +1,205 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, erpnext +from frappe import _ +from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint) + +from erpnext.accounts.report.financial_statements import get_accounts, filter_accounts, get_appropriate_currency, get_fiscal_year_data, validate_fiscal_year, filter_out_zero_value_rows +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children +from erpnext.accounts.report.trial_balance.trial_balance import validate_filters, get_opening_balances, calculate_values + +from six import itervalues + +def execute(filters=None): + validate_filters(filters) + dimension_items_list = get_dimension_items_list(filters.dimension, filters.company) + + if not dimension_items_list: + return [], [] + + dimension_items_list = [''.join(d) for d in dimension_items_list] + columns = get_columns(dimension_items_list) + data = get_data(filters, dimension_items_list) + + return columns, data + +def get_data(filters, dimension_items_list): + company_currency = erpnext.get_company_currency(filters.company) + acc = frappe.db.sql(""" + select + name, account_number, parent_account, lft, rgt, root_type, + report_type, account_name, include_in_gross, account_type, is_group + from + `tabAccount` + where + company=%s + order by lft""", (filters.company), as_dict=True) + + if not acc: + return None + + accounts, accounts_by_name, parent_children_map = filter_accounts(acc) + + min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount` + where company=%s""", (filters.company))[0] + + account = frappe.db.sql_list("""select name from `tabAccount` + where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company)) + + gl_entries_by_account = {} + set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account) + format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list) + accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list) + out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list) + out = filter_out_zero_value_rows(out, parent_children_map) + + return out + +def set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account): + for item in dimension_items_list: + condition = get_condition(filters.from_date, item, filters.dimension) + if account: + condition += " and account in ({})"\ + .format(", ".join([frappe.db.escape(d) for d in account])) + + gl_filters = { + "company": filters.get("company"), + "from_date": filters.get("from_date"), + "to_date": filters.get("to_date"), + "finance_book": cstr(filters.get("finance_book")) + } + + gl_filters['item'] = ''.join(item) + + if filters.get("include_default_book_entries"): + gl_filters["company_fb"] = frappe.db.get_value("Company", + filters.company, 'default_finance_book') + + for key, value in filters.items(): + if value: + gl_filters.update({ + key: value + }) + + gl_entries = frappe.db.sql(""" + select + posting_date, account, debit, credit, is_opening, fiscal_year, + debit_in_account_currency, credit_in_account_currency, account_currency + from + `tabGL Entry` + where + company=%(company)s + {condition} + and posting_date <= %(to_date)s + and is_cancelled = 0 + order by account, posting_date""".format( + condition=condition), + gl_filters, as_dict=True) #nosec + + for entry in gl_entries: + entry['dimension_item'] = ''.join(item) + gl_entries_by_account.setdefault(entry.account, []).append(entry) + +def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list): + + for entries in itervalues(gl_entries_by_account): + for entry in entries: + d = accounts_by_name.get(entry.account) + if not d: + frappe.msgprint( + _("Could not retrieve information for {0}.").format(entry.account), title="Error", + raise_exception=1 + ) + for item in dimension_items_list: + if item == entry.dimension_item: + x = flt(entry.debit) - flt(entry.credit) + d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit) + +def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list): + data = [] + + for d in accounts: + has_value = False + row = { + "account": d.name, + "parent_account": d.parent_account, + "indent": d.indent, + "from_date": filters.from_date, + "to_date": filters.to_date, + "currency": company_currency, + "account_name": ('{} - {}'.format(d.account_number, d.account_name) + if d.account_number else d.account_name) + } + + for item in dimension_items_list: + row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3) + + if abs(row[frappe.scrub(item)]) >= 0.005: + # ignore zero values + has_value = True + + row["has_value"] = has_value + data.append(row) + + return data + +def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list): + """accumulate children's values in parent accounts""" + for d in reversed(accounts): + if d.parent_account: + for item in dimension_items_list: + accounts_by_name[d.parent_account][frappe.scrub(item)] = \ + accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0) + +def get_condition(from_date, item, dimension): + conditions = [] + + if from_date: + conditions.append("posting_date >= %(from_date)s") + if dimension: + if dimension not in ['Cost Center', 'Project']: + if dimension in ['Customer', 'Supplier']: + dimension = 'Party' + else: + dimension = 'Voucher No' + txt = "{0} = %(item)s".format(frappe.scrub(dimension)) + conditions.append(txt) + + return " and {}".format(" and ".join(conditions)) if conditions else "" + +def get_dimension_items_list(dimension, company): + meta = frappe.get_meta(dimension, cached=False) + fieldnames = [d.fieldname for d in meta.get("fields")] + filters = {} + if 'company' in fieldnames: + filters['company'] = company + return frappe.get_all(dimension, filters, as_list=True) + +def get_columns(dimension_items_list, accumulated_values=1, company=None): + columns = [{ + "fieldname": "account", + "label": _("Account"), + "fieldtype": "Link", + "options": "Account", + "width": 300 + }] + if company: + columns.append({ + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "hidden": 1 + }) + for item in dimension_items_list: + columns.append({ + "fieldname": frappe.scrub(item), + "label": item, + "fieldtype": "Currency", + "options": "currency", + "width": 150 + }) + + return columns From 5451bbf7d2aecd5da454e8ee591abf5cb6f38d20 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 9 Apr 2021 17:14:37 +0530 Subject: [PATCH 0448/1264] fix: condition for SLA status banner If the document is NOT in the closed state and fulfilled then there won't be any banner. This bug was introduced while fixing a bug related to buttons, so separated out code for showing buttons and showing banners. --- erpnext/support/doctype/issue/issue.js | 102 +++++++++++++------------ 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 9fe12f9490..ecc9fcfe82 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -48,44 +48,62 @@ frappe.ui.form.on("Issue", { } }, - refresh: function (frm) { - if (frm.doc.status !== "Closed") { - if (frm.doc.service_level_agreement && frm.doc.agreement_status === "Ongoing") { - frappe.call({ - "method": "frappe.client.get", - args: { - doctype: "Service Level Agreement", - name: frm.doc.service_level_agreement - }, - callback: function(data) { - let statuses = data.message.pause_sla_on; - const hold_statuses = []; - $.each(statuses, (_i, entry) => { - hold_statuses.push(entry.status); - }); - if (hold_statuses.includes(frm.doc.status)) { - frm.dashboard.clear_headline(); - let message = {"indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)])}; - frm.dashboard.set_headline_alert( - '
' + - '
' + - ''+ message.msg +' ' + - '
' + - '
' - ); - } else { - set_time_to_resolve_and_response(frm); - } - } - }); - } + refresh: function(frm) { - frm.add_custom_button(__("Close"), function () { + // alert messages + if (frm.doc.status !== "Closed" && frm.doc.service_level_agreement + && frm.doc.agreement_status === "Ongoing") { + frappe.call({ + "method": "frappe.client.get", + args: { + doctype: "Service Level Agreement", + name: frm.doc.service_level_agreement + }, + callback: function(data) { + let statuses = data.message.pause_sla_on; + const hold_statuses = []; + $.each(statuses, (_i, entry) => { + hold_statuses.push(entry.status); + }); + if (hold_statuses.includes(frm.doc.status)) { + frm.dashboard.clear_headline(); + let message = { "indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)]) }; + frm.dashboard.set_headline_alert( + '
' + + '
' + + '' + message.msg + ' ' + + '
' + + '
' + ); + } else { + set_time_to_resolve_and_response(frm); + } + } + }); + } else if (frm.doc.service_level_agreement) { + frm.dashboard.clear_headline(); + + let agreement_status = (frm.doc.agreement_status == "Fulfilled") ? + { "indicator": "green", "msg": "Service Level Agreement has been fulfilled" } : + { "indicator": "red", "msg": "Service Level Agreement Failed" }; + + frm.dashboard.set_headline_alert( + '
' + + '
' + + ' ' + + '
' + + '
' + ); + } + + // buttons + if (frm.doc.status !== "Closed") { + frm.add_custom_button(__("Close"), function() { frm.set_value("status", "Closed"); frm.save(); }); - frm.add_custom_button(__("Task"), function () { + frm.add_custom_button(__("Task"), function() { frappe.model.open_mapped_doc({ method: "erpnext.support.doctype.issue.issue.make_task", frm: frm @@ -93,23 +111,7 @@ frappe.ui.form.on("Issue", { }, __("Create")); } else { - if (frm.doc.service_level_agreement) { - frm.dashboard.clear_headline(); - - let agreement_status = (frm.doc.agreement_status == "Fulfilled") ? - {"indicator": "green", "msg": "Service Level Agreement has been fulfilled"} : - {"indicator": "red", "msg": "Service Level Agreement Failed"}; - - frm.dashboard.set_headline_alert( - '
' + - '
' + - ' ' + - '
' + - '
' - ); - } - - frm.add_custom_button(__("Reopen"), function () { + frm.add_custom_button(__("Reopen"), function() { frm.set_value("status", "Open"); frm.save(); }); From c42318ec24fdecda9d5cdc0a8ca8f63e9be83a30 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 9 Apr 2021 17:44:30 +0530 Subject: [PATCH 0449/1264] chores: clean up --- .../dimension_wise_accounts_balance_report.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py index 9769a458dd..7f38770a32 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -4,11 +4,10 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint) +from frappe.utils import (flt, cstr) -from erpnext.accounts.report.financial_statements import get_accounts, filter_accounts, get_appropriate_currency, get_fiscal_year_data, validate_fiscal_year, filter_out_zero_value_rows -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children -from erpnext.accounts.report.trial_balance.trial_balance import validate_filters, get_opening_balances, calculate_values +from erpnext.accounts.report.financial_statements import filter_accounts, filter_out_zero_value_rows +from erpnext.accounts.report.trial_balance.trial_balance import validate_filters from six import itervalues @@ -114,7 +113,6 @@ def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_l ) for item in dimension_items_list: if item == entry.dimension_item: - x = flt(entry.debit) - flt(entry.credit) d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit) def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list): From 986452107bd26c15f2b487a2ca83ed26845a8aff Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 9 Apr 2021 18:15:51 +0530 Subject: [PATCH 0450/1264] fix: Lead Source's module (#24583) * fix: Lead Source's module * fix: patch fix --- .../doctype/lead_source/__init__.py | 0 .../crm/doctype/lead_source/lead_source.js | 8 ++ .../crm/doctype/lead_source/lead_source.json | 62 +++++++++ .../doctype/lead_source/lead_source.py | 4 +- .../doctype/lead_source/test_lead_source.py | 6 +- erpnext/patches/v7_1/update_lead_source.py | 2 +- .../doctype/lead_source/lead_source.js | 8 -- .../doctype/lead_source/lead_source.json | 131 ------------------ 8 files changed, 75 insertions(+), 146 deletions(-) rename erpnext/{selling => crm}/doctype/lead_source/__init__.py (100%) create mode 100644 erpnext/crm/doctype/lead_source/lead_source.js create mode 100644 erpnext/crm/doctype/lead_source/lead_source.json rename erpnext/{selling => crm}/doctype/lead_source/lead_source.py (71%) rename erpnext/{selling => crm}/doctype/lead_source/test_lead_source.py (52%) delete mode 100644 erpnext/selling/doctype/lead_source/lead_source.js delete mode 100644 erpnext/selling/doctype/lead_source/lead_source.json diff --git a/erpnext/selling/doctype/lead_source/__init__.py b/erpnext/crm/doctype/lead_source/__init__.py similarity index 100% rename from erpnext/selling/doctype/lead_source/__init__.py rename to erpnext/crm/doctype/lead_source/__init__.py diff --git a/erpnext/crm/doctype/lead_source/lead_source.js b/erpnext/crm/doctype/lead_source/lead_source.js new file mode 100644 index 0000000000..3cbe649209 --- /dev/null +++ b/erpnext/crm/doctype/lead_source/lead_source.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Lead Source', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/crm/doctype/lead_source/lead_source.json b/erpnext/crm/doctype/lead_source/lead_source.json new file mode 100644 index 0000000000..723c6d993d --- /dev/null +++ b/erpnext/crm/doctype/lead_source/lead_source.json @@ -0,0 +1,62 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:source_name", + "creation": "2016-09-16 01:47:47.382372", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "source_name", + "details" + ], + "fields": [ + { + "fieldname": "source_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Source Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "details", + "fieldtype": "Text Editor", + "label": "Details" + } + ], + "links": [], + "modified": "2021-02-08 12:51:48.971517", + "modified_by": "Administrator", + "module": "CRM", + "name": "Lead Source", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/selling/doctype/lead_source/lead_source.py b/erpnext/crm/doctype/lead_source/lead_source.py similarity index 71% rename from erpnext/selling/doctype/lead_source/lead_source.py rename to erpnext/crm/doctype/lead_source/lead_source.py index d2d7558621..5c64fb8b4a 100644 --- a/erpnext/selling/doctype/lead_source/lead_source.py +++ b/erpnext/crm/doctype/lead_source/lead_source.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals -import frappe +# import frappe from frappe.model.document import Document class LeadSource(Document): diff --git a/erpnext/selling/doctype/lead_source/test_lead_source.py b/erpnext/crm/doctype/lead_source/test_lead_source.py similarity index 52% rename from erpnext/selling/doctype/lead_source/test_lead_source.py rename to erpnext/crm/doctype/lead_source/test_lead_source.py index 42df18f181..b5bc6490cf 100644 --- a/erpnext/selling/doctype/lead_source/test_lead_source.py +++ b/erpnext/crm/doctype/lead_source/test_lead_source.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals -import frappe +# import frappe import unittest -# test_records = frappe.get_test_records('Lead Source') - class TestLeadSource(unittest.TestCase): pass diff --git a/erpnext/patches/v7_1/update_lead_source.py b/erpnext/patches/v7_1/update_lead_source.py index 517e66c4bc..a2a48a62e1 100644 --- a/erpnext/patches/v7_1/update_lead_source.py +++ b/erpnext/patches/v7_1/update_lead_source.py @@ -5,7 +5,7 @@ from frappe import _ def execute(): from erpnext.setup.setup_wizard.operations.install_fixtures import default_lead_sources - frappe.reload_doc('selling', 'doctype', 'lead_source') + frappe.reload_doc('crm', 'doctype', 'lead_source') frappe.local.lang = frappe.db.get_default("lang") or 'en' diff --git a/erpnext/selling/doctype/lead_source/lead_source.js b/erpnext/selling/doctype/lead_source/lead_source.js deleted file mode 100644 index 6af6a4f648..0000000000 --- a/erpnext/selling/doctype/lead_source/lead_source.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Lead Source', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/selling/doctype/lead_source/lead_source.json b/erpnext/selling/doctype/lead_source/lead_source.json deleted file mode 100644 index 373e83af9c..0000000000 --- a/erpnext/selling/doctype/lead_source/lead_source.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 1, - "autoname": "field:source_name", - "beta": 0, - "creation": "2016-09-16 01:47:47.382372", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "source_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Source Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "details", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - } - ], - "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": "2020-09-16 02:03:01.441622", - "modified_by": "Administrator", - "module": "Selling", - "name": "Lead Source", - "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": "Sales Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 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 User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 -} From 110f28f869c1a82be5fc4f4cd13c6c2d6bf34720 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 9 Apr 2021 20:01:47 +0530 Subject: [PATCH 0451/1264] Company field editable and is readonly after saving. --- erpnext/stock/doctype/warehouse/warehouse.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index bddb114c9d..9b9093261c 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.json @@ -70,6 +70,7 @@ "oldfieldname": "company", "oldfieldtype": "Link", "options": "Company", + "read_only_depends_on": "eval: !doc.__islocal", "remember_last_selected_value": 1, "reqd": 1, "search_index": 1 @@ -244,7 +245,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2021-02-16 17:21:52.380098", + "modified": "2021-04-09 19:54:56.263965", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse", From 1358443cdbaa9924069242f7d191e625bf19ecff Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 26 Mar 2021 14:11:50 +0530 Subject: [PATCH 0452/1264] fix: patch --- erpnext/controllers/stock_controller.py | 3 +-- .../v13_0/item_reposting_for_incorrect_sl_and_gl.py | 13 ++++++++++--- .../repost_item_valuation/repost_item_valuation.py | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index f352bae30e..20499579ca 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -406,8 +406,7 @@ class StockController(AccountsController): def set_rate_of_stock_uom(self): if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]: for d in self.get("items"): - if d.conversion_factor: - d.stock_uom_rate = d.rate / d.conversion_factor + d.stock_uom_rate = d.rate / (d.conversion_factor or 1) def validate_internal_transfer(self): if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \ diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index d968e1fb76..abde65f5cd 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -20,9 +20,11 @@ def execute(): frappe.clear_cache() frappe.flags.warehouse_account_map = {} + company_wise_data = {} + data = frappe.db.sql(''' SELECT - name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time + name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time, company FROM `tabStock Ledger Entry` WHERE @@ -36,6 +38,9 @@ def execute(): total_sle = len(data) i = 0 for d in data: + if d.company not in company_wise_data: + company_wise_data[d.company] = True + update_entries_after({ "item_code": d.item_code, "warehouse": d.warehouse, @@ -53,8 +58,10 @@ def execute(): print("Reposting General Ledger Entries...") - for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): - update_gl_entries_after(posting_date, posting_time, company=row.name) + if data: + for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): + if company_wise_data.get(row.name): + update_gl_entries_after(posting_date, posting_time, company=row.name) frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index f8cfdf83e7..e68360266f 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -124,7 +124,7 @@ def repost_entries(): return for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): - check_if_stock_and_account_balance_synced(today(), d.company) + check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): date = add_to_date(today(), hours=-12) From b9aa775f3ba17dd9d763510a158b758a44ba8c56 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 27 Mar 2021 18:54:41 +0530 Subject: [PATCH 0453/1264] fix: pricing_rule test cases --- .../doctype/pricing_rule/test_pricing_rule.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index f28cee7c5a..ef9aad562d 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -328,6 +328,21 @@ class TestPricingRule(unittest.TestCase): self.assertEquals(item.discount_amount, 110) self.assertEquals(item.rate, 990) + def test_pricing_rule_with_margin_and_discount_amount(self): + frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') + make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10, + rate_or_discount="Discount Amount", discount_amount=110) + si = create_sales_invoice(do_not_save=True) + si.items[0].price_list_rate = 1000 + si.payment_schedule = [] + si.insert(ignore_permissions=True) + + item = si.items[0] + self.assertEquals(item.margin_rate_or_amount, 10) + self.assertEquals(item.rate_with_margin, 1100) + self.assertEquals(item.discount_amount, 110) + self.assertEquals(item.rate, 990) + def test_pricing_rule_for_product_discount_on_same_item(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') test_record = { @@ -560,6 +575,7 @@ def make_pricing_rule(**args): "margin_rate_or_amount": args.margin_rate_or_amount or 0.0, "condition": args.condition or '', "priority": 1, + "discount_amount": args.discount_amount or 0.0, "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 }) From 1db8c5174949a176c6894fccd29d4576eebeb2ce Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 27 Mar 2021 19:02:07 +0530 Subject: [PATCH 0454/1264] fix: changed logic --- .../v13_0/item_reposting_for_incorrect_sl_and_gl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index abde65f5cd..021bb72cae 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -20,7 +20,7 @@ def execute(): frappe.clear_cache() frappe.flags.warehouse_account_map = {} - company_wise_data = {} + company_list = [] data = frappe.db.sql(''' SELECT @@ -38,8 +38,8 @@ def execute(): total_sle = len(data) i = 0 for d in data: - if d.company not in company_wise_data: - company_wise_data[d.company] = True + if d.company not in company_list: + company_list.append(d.company) update_entries_after({ "item_code": d.item_code, @@ -60,7 +60,7 @@ def execute(): if data: for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): - if company_wise_data.get(row.name): + if row.name in company_list: update_gl_entries_after(posting_date, posting_time, company=row.name) frappe.db.auto_commit_on_many_writes = 0 From cb6494876fe780c1900174a9f11957bbe8c88a8d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 27 Mar 2021 22:21:41 +0530 Subject: [PATCH 0455/1264] feat: purchase receipt creation from purchase invoice --- .../purchase_invoice/purchase_invoice.js | 30 +++++ .../purchase_invoice/purchase_invoice.json | 12 +- .../purchase_invoice/purchase_invoice.py | 38 +++++++ .../purchase_invoice_item.json | 3 +- .../billed_items_to_be_received/__init__.py | 0 .../billed_items_to_be_received.js | 29 +++++ .../billed_items_to_be_received.json | 39 +++++++ .../billed_items_to_be_received.py | 107 ++++++++++++++++++ .../purchase_receipt/purchase_receipt.js | 28 +++++ .../purchase_receipt/purchase_receipt.py | 17 ++- .../purchase_receipt_item.json | 24 +++- 11 files changed, 320 insertions(+), 7 deletions(-) create mode 100644 erpnext/accounts/report/billed_items_to_be_received/__init__.py create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 66a8e206a8..73475a1131 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -523,6 +523,28 @@ frappe.ui.form.on("Purchase Invoice", { } }, + refresh: function(frm) { + frm.events.add_custom_buttons(frm); + }, + + add_custom_buttons: function(frm) { + if (frm.doc.per_received < 100) { + frm.add_custom_button(__('Purchase Receipt'), () => { + frm.events.make_purchase_receipt(frm); + }, __('Create')); + } + + if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) { + frm.add_custom_button(__('Purchase Receipt'), () => { + frappe.route_options = { + 'purchase_invoice': frm.doc.name + } + + frappe.set_route("List", "Purchase Receipt", "List") + }, __('View')); + } + }, + onload: function(frm) { if(frm.doc.__onload && frm.is_new()) { if(frm.doc.supplier) { @@ -548,5 +570,13 @@ frappe.ui.form.on("Purchase Invoice", { update_stock: function(frm) { hide_fields(frm.doc); frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false); + }, + + make_purchase_receipt: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt", + frm: frm, + freeze_message: __("Creating Purchase Receipt ...") + }) } }) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 739bd671f7..5c37eda8d4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -163,7 +163,8 @@ "to_date", "column_break_114", "auto_repeat", - "update_auto_repeat_reference" + "update_auto_repeat_reference", + "per_received" ], "fields": [ { @@ -1364,6 +1365,15 @@ "print_hide": 1, "print_width": "50px", "width": "50px" + }, + { + "fieldname": "per_received", + "fieldtype": "Percent", + "hidden": 1, + "label": "Per Received", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-file-text", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5c4e32e493..7d008507a2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1207,3 +1207,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None): def on_doctype_update(): frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"]) + +@frappe.whitelist() +def make_purchase_receipt(source_name, target_doc=None): + def update_item(obj, target, source_parent): + target.qty = flt(obj.qty) - flt(obj.received_qty) + target.received_qty = flt(obj.qty) - flt(obj.received_qty) + target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor) + target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) + target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \ + flt(obj.rate) * flt(source_parent.conversion_rate) + + doc = get_mapped_doc("Purchase Invoice", source_name, { + "Purchase Invoice": { + "doctype": "Purchase Receipt", + "validation": { + "docstatus": ["=", 1], + } + }, + "Purchase Invoice Item": { + "doctype": "Purchase Receipt Item", + "field_map": { + "name": "purchase_invoice_item", + "parent": "purchase_invoice", + "bom": "bom", + "purchase_order": "purchase_order", + "po_detail": "purchase_order_item", + "material_request": "material_request", + "material_request_item": "material_request_item" + }, + "postprocess": update_item, + "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) + }, + "Purchase Taxes and Charges": { + "doctype": "Purchase Taxes and Charges" + } + }, target_doc) + + return doc 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 96ad0fd785..10e1c73ea9 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -607,6 +607,7 @@ "oldfieldname": "purchase_order", "oldfieldtype": "Link", "options": "Purchase Order", + "print_hide": 1, "read_only": 1, "search_index": 1 }, @@ -853,7 +854,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 00:59:52.614805", + "modified": "2021-03-30 09:02:39.256602", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js new file mode 100644 index 0000000000..e1fccb6e72 --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js @@ -0,0 +1,29 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports['Billed Items To Be Received'] = { + 'filters': [ + { + 'label': __('Company'), + 'fieldname': 'company', + 'fieldtype': 'Link', + 'options': 'Company', + 'reqd': 1, + 'default': frappe.defaults.get_default('Company') + }, + { + 'label': __('As on Date'), + 'fieldname': 'posting_date', + 'fieldtype': 'Date', + 'reqd': 1, + 'default': get_today() + }, + { + 'label': __('Purchase Invoice'), + 'fieldname': 'purchase_invoice', + 'fieldtype': 'Link', + 'options': 'Purchase Invoice' + } + ] +}; diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json new file mode 100644 index 0000000000..de09b33c96 --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json @@ -0,0 +1,39 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-30 09:35:38.683028", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-03-31 08:48:30.944429", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Billed Items To Be Received", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "Purchase Invoice", + "report_name": "Billed Items To Be Received", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Purchase User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py new file mode 100644 index 0000000000..2ce5d50edf --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py @@ -0,0 +1,107 @@ +# 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): + data = get_data(filters) or [] + columns = get_columns() + + return columns, data + +def get_data(report_filters): + filters = get_report_filters(report_filters) + fields = get_report_fields() + + return frappe.get_all('Purchase Invoice', + fields= fields, filters=filters) + +def get_report_filters(report_filters): + filters = [['Purchase Invoice','company','=',report_filters.get('company')], + ['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1], + ['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]] + + if report_filters.get('purchase_invoice'): + filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]]) + + return filters + +def get_report_fields(): + fields = [] + for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']: + fields.append('`tabPurchase Invoice`.`{}`'.format(p_field)) + + for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']: + fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field)) + + return fields + +def get_columns(): + return [ + { + 'label': _('Purchase Invoice'), + 'fieldname': 'name', + 'fieldtype': 'Link', + 'options': 'Purchase Invoice', + 'width': 170 + }, + { + 'label': _('Supplier'), + 'fieldname': 'supplier', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'width': 120 + }, + { + 'label': _('Posting Date'), + 'fieldname': 'posting_date', + 'fieldtype': 'Date', + 'width': 100 + }, + { + 'label': _('Item Code'), + 'fieldname': 'item_code', + 'fieldtype': 'Link', + 'options': 'Item', + 'width': 100 + }, + { + 'label': _('Item Name'), + 'fieldname': 'item_name', + 'fieldtype': 'Data', + 'width': 100 + }, + { + 'label': _('UOM'), + 'fieldname': 'uom', + 'fieldtype': 'Link', + 'options': 'UOM', + 'width': 100 + }, + { + 'label': _('Invoiced Qty'), + 'fieldname': 'qty', + 'fieldtype': 'Float', + 'width': 100 + }, + { + 'label': _('Received Qty'), + 'fieldname': 'received_qty', + 'fieldtype': 'Float', + 'width': 100 + }, + { + 'label': _('Rate'), + 'fieldname': 'rate', + 'fieldtype': 'Currency', + 'width': 100 + }, + { + 'label': _('Amount'), + 'fieldname': 'amount', + 'fieldtype': 'Currency', + 'width': 100 + } + ] \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 57cc3504a9..f9abea6453 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -73,6 +73,34 @@ frappe.ui.form.on("Purchase Receipt", { }) }, __('Create')); } + + frm.events.add_custom_buttons(frm); + }, + + add_custom_buttons: function(frm) { + if (frm.doc.docstatus == 0) { + frm.add_custom_button(__('Purchase Invoice'), function () { + if (!frm.doc.supplier) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Supplier") + }); + } + erpnext.utils.map_current_doc({ + method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt", + source_doctype: "Purchase Invoice", + target: frm, + setters: { + supplier: frm.doc.supplier, + }, + get_query_filters: { + docstatus: 1, + per_received: ["<", 100], + company: frm.doc.company + } + }) + }, __("Get Items From")); + } }, company: function(frm) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 5d7597b2db..b4cf659bf3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -53,7 +53,20 @@ class PurchaseReceipt(BuyingController): 'target_ref_field': 'stock_qty', 'source_field': 'stock_qty', 'percent_join_field': 'material_request' + }, + { + 'source_dt': 'Purchase Receipt Item', + 'target_dt': 'Purchase Invoice Item', + 'join_field': 'purchase_invoice_item', + 'target_field': 'received_qty', + 'target_parent_dt': 'Purchase Invoice', + 'target_parent_field': 'per_received', + 'target_ref_field': 'qty', + 'source_field': 'received_qty', + 'percent_join_field': 'purchase_invoice', + 'overflow_type': 'receipt' }] + if cint(self.is_return): self.status_updater.extend([ { @@ -513,7 +526,9 @@ class PurchaseReceipt(BuyingController): def update_billing_status(self, update_modified=True): updated_pr = [self.name] for d in self.get("items"): - if d.purchase_order_item: + if d.purchase_invoice and d.purchase_invoice_item: + d.db_set('billed_amt', d.amount, update_modified=update_modified) + elif d.purchase_order_item: updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified) for pr in set(updated_pr): diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index efe3642d23..82cc98e7f7 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -72,16 +72,18 @@ "warehouse", "rejected_warehouse", "from_warehouse", - "purchase_order", "material_request", + "purchase_order", + "purchase_invoice", "column_break_40", "is_fixed_asset", "asset_location", "asset_category", "schedule_date", "quality_inspection", - "purchase_order_item", "material_request_item", + "purchase_order_item", + "purchase_invoice_item", "purchase_receipt_item", "delivery_note_item", "putaway_rule", @@ -937,7 +939,21 @@ "fieldname": "base_rate_with_margin", "fieldtype": "Currency", "label": "Rate With Margin (Company Currency)", - "options": "Company:company:default_currency", + "options": "Company:company:default_currency" + }, + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "label": "Purchase Invoice", + "options": "Purchase Invoice", + "read_only": 1 + }, + { + "fieldname": "purchase_invoice_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Invoice Item", + "no_copy": 1, "print_hide": 1, "read_only": 1 } @@ -945,7 +961,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 00:59:14.360847", + "modified": "2021-03-29 04:17:00.336298", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From a5d062453ecf6266d4e420e343bea88f1e54d96c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 8 Apr 2021 22:20:10 +0530 Subject: [PATCH 0456/1264] feat: added test case --- .../purchase_invoice/purchase_invoice.py | 2 +- .../purchase_order/test_purchase_order.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7d008507a2..83e9f7583e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1218,7 +1218,7 @@ def make_purchase_receipt(source_name, target_doc=None): target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \ flt(obj.rate) * flt(source_parent.conversion_rate) - doc = get_mapped_doc("Purchase Invoice", source_name, { + doc = get_mapped_doc("Purchase Invoice", source_name, { "Purchase Invoice": { "doctype": "Purchase Receipt", "validation": { diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3c4f908ee4..42f4472f29 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -435,6 +435,35 @@ class TestPurchaseOrder(unittest.TestCase): po.load_from_db() self.assertEqual(po.get("items")[0].received_qty, 5) + def test_purchase_order_invoice_receipt_workflow(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt + + po = create_purchase_order() + pi = make_pi_from_po(po.name) + + pi.submit() + + pr = make_purchase_receipt(pi.name) + pr.submit() + + pi.load_from_db() + + self.assertEquals(pi.per_received, 100.00) + self.assertEquals(pi.items[0].qty, pi.items[0].received_qty) + + po.load_from_db() + + self.assertEquals(po.per_received, 100.00) + self.assertEquals(po.per_billed, 100.00) + + pr.cancel() + + pi.load_from_db() + pi.cancel() + + po.load_from_db() + po.cancel() + def test_make_purchase_invoice(self): po = create_purchase_order(do_not_submit=True) From 37d6c19a347a833c6863bd14108815fe4497563a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Apr 2021 18:33:26 +0530 Subject: [PATCH 0457/1264] fix: Consider paid repayment entries in subsequent loan repayments --- .../loan_management/doctype/loan/test_loan.py | 74 ++++++++++++------- .../doctype/loan_repayment/loan_repayment.py | 28 +++++-- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 4b9a89486a..57f98869cc 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -523,33 +523,7 @@ class TestLoan(unittest.TestCase): self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0)) def test_penalty(self): - pledge = [{ - "loan_security": "Test Security 1", - "qty": 4000.00 - }] - - loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) - create_pledge(loan_application) - - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') - loan.submit() - - self.assertEquals(loan.loan_amount, 1000000) - - first_date = '2019-10-01' - last_date = '2019-10-30' - - make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) - process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - - amounts = calculate_amounts(loan.name, add_days(last_date, 1)) - paid_amount = amounts['interest_amount']/2 - - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), - paid_amount) - - repayment_entry.submit() - + loan = create_loan_scenario_for_penalty(self) # 30 days - grace period penalty_days = 30 - 4 penalty_applicable_amount = flt(amounts['interest_amount']/2) @@ -559,8 +533,28 @@ class TestLoan(unittest.TestCase): calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount') + self.assertEquals(loan.loan_amount, 1000000) self.assertEquals(calculated_penalty_amount, penalty_amount) + def test_penalty_repayment(self): + loan = create_loan_scenario_for_penalty(self) + amounts = calculate_amounts(loan.name, '2019-11-30 00:00:00') + + first_penalty = 10000 + second_penalty = amounts['penalty_amount'] - 10000 + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:00', 10000) + repayment_entry.submit() + + amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01') + self.assertEquals(amounts['penalty_amount'], second_penalty) + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty) + repayment_entry.submit() + + amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02') + self.assertEquals(amounts['penalty_amount'], 0) + def test_loan_write_off_limit(self): pledge = [{ "loan_security": "Test Security 1", @@ -651,6 +645,32 @@ class TestLoan(unittest.TestCase): amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0) +def create_loan_scenario_for_penalty(doc): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', doc.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + loan = create_demand_loan(doc.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + first_date = '2019-10-01' + last_date = '2019-10-30' + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + amounts = calculate_amounts(loan.name, add_days(last_date, 1)) + paid_amount = amounts['interest_amount']/2 + + repayment_entry = create_repayment_entry(loan.name, doc.applicant2, add_days(last_date, 5), + paid_amount) + + repayment_entry.submit() + + return loan def create_loan_accounts(): if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"): diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index a88e183ead..2a0221beb1 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -75,10 +75,6 @@ class LoanRepayment(AccountsController): if not self.amount_paid: frappe.throw(_("Amount paid cannot be zero")) - if not self.shortfall_amount and self.amount_paid < self.penalty_amount: - msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount) - frappe.throw(msg) - def book_unaccrued_interest(self): precision = cint(frappe.db.get_default("currency_precision")) or 2 if self.total_interest_paid > self.interest_payable: @@ -327,6 +323,18 @@ def get_accrued_interest_entries(against_loan): return unpaid_accrued_entries +def get_penalty_details(against_loan): + penalty_details = frappe.db.sql(""" + SELECT posting_date, (penalty_amount - total_penalty_paid) as pending_penalty_amount + FROM `tabLoan Repayment` where posting_date >= (SELECT MAX(posting_date) from `tabLoan Repayment` + where against_loan = %s) and docstatus = 1 and against_loan = %s + """, (against_loan, against_loan)) + + if penalty_details: + return penalty_details[0][0], flt(penalty_details[0][1]) + else: + return None, 0 + # This function returns the amounts that are payable at the time of loan repayment based on posting date # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable @@ -337,6 +345,7 @@ def get_amounts(amounts, against_loan, posting_date): loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type) accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name) + computed_penalty_date, pending_penalty_amount = get_penalty_details(against_loan) pending_accrual_entries = {} total_pending_interest = 0 @@ -351,8 +360,13 @@ def get_amounts(amounts, against_loan, posting_date): # and if no_of_late days are positive then penalty is levied due_date = add_days(entry.posting_date, 1) - no_of_late_days = date_diff(posting_date, - add_days(due_date, loan_type_details.grace_period_in_days)) + 1 + due_date_after_grace_period = add_days(due_date, loan_type_details.grace_period_in_days) + + # Consider one day after already calculated penalty + if computed_penalty_date and getdate(computed_penalty_date) >= due_date_after_grace_period: + due_date_after_grace_period = add_days(computed_penalty_date, 1) + + no_of_late_days = date_diff(posting_date, due_date_after_grace_period) + 1 if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary) and entry.accrual_type == 'Regular': penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days) @@ -390,7 +404,7 @@ def get_amounts(amounts, against_loan, posting_date): amounts["pending_principal_amount"] = flt(pending_principal_amount, precision) amounts["payable_principal_amount"] = flt(payable_principal_amount, precision) amounts["interest_amount"] = flt(total_pending_interest, precision) - amounts["penalty_amount"] = flt(penalty_amount, precision) + amounts["penalty_amount"] = flt(penalty_amount + pending_penalty_amount, precision) amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision) amounts["pending_accrual_entries"] = pending_accrual_entries amounts["unaccrued_interest"] = flt(unaccrued_interest, precision) From f38a653a08006a97b360bf864dedd9173fbc4ca3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Apr 2021 18:34:51 +0530 Subject: [PATCH 0458/1264] fix: Show exact error message in datetime --- .../loan_management/doctype/loan_repayment/loan_repayment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 834c98cb3e..baa45a4704 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -75,7 +75,7 @@ class LoanRepayment(AccountsController): "docstatus": 1, "against_loan": self.against_loan}, 'posting_date') if future_repayment_date: - frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date))) + frappe.throw("Repayment already made till date {0}".format(get_datetime(future_repayment_date))) def validate_amount(self): precision = cint(frappe.db.get_default("currency_precision")) or 2 From b58590251b4537c9c1a927312f3ba1e633e8e746 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Apr 2021 19:55:36 +0530 Subject: [PATCH 0459/1264] fix: minor improvements and tests --- .../loan_management/doctype/loan/loan.json | 9 ++++++- .../loan_management/doctype/loan/test_loan.py | 6 ++--- .../loan_disbursement/loan_disbursement.json | 25 ++++++++++++++++++- .../loan_repayment/loan_repayment.json | 6 +++-- .../doctype/loan_repayment/loan_repayment.py | 12 +++++++-- .../loan_security_unpledge.py | 8 ++++-- 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index acf09f5c03..4f8ceb0de8 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -23,6 +23,7 @@ "rate_of_interest", "is_secured_loan", "disbursement_date", + "closure_date", "disbursed_amount", "column_break_11", "maximum_loan_amount", @@ -348,12 +349,18 @@ "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "closure_date", + "fieldtype": "Date", + "label": "Closure Date", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-24 12:27:23.208240", + "modified": "2021-04-10 09:28:21.946972", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 57f98869cc..6f8da3166f 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -523,7 +523,7 @@ class TestLoan(unittest.TestCase): self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0)) def test_penalty(self): - loan = create_loan_scenario_for_penalty(self) + loan, amounts = create_loan_scenario_for_penalty(self) # 30 days - grace period penalty_days = 30 - 4 penalty_applicable_amount = flt(amounts['interest_amount']/2) @@ -537,7 +537,7 @@ class TestLoan(unittest.TestCase): self.assertEquals(calculated_penalty_amount, penalty_amount) def test_penalty_repayment(self): - loan = create_loan_scenario_for_penalty(self) + loan, dummy = create_loan_scenario_for_penalty(self) amounts = calculate_amounts(loan.name, '2019-11-30 00:00:00') first_penalty = 10000 @@ -670,7 +670,7 @@ def create_loan_scenario_for_penalty(doc): repayment_entry.submit() - return loan + return loan, amounts def create_loan_accounts(): if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"): diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json index cd5df4d3cd..662c626b8d 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json @@ -20,6 +20,10 @@ "cost_center", "customer_details_section", "bank_account", + "disbursement_references_section", + "reference_date", + "column_break_17", + "reference_number", "amended_from" ], "fields": [ @@ -126,12 +130,31 @@ { "fieldname": "column_break_8", "fieldtype": "Column Break" + }, + { + "fieldname": "disbursement_references_section", + "fieldtype": "Section Break", + "label": "Disbursement References" + }, + { + "fieldname": "reference_date", + "fieldtype": "Date", + "label": "Reference Date" + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "label": "Reference Number" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-06 10:04:30.882322", + "modified": "2021-04-10 10:03:41.502210", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Disbursement", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 86ea59dc24..8fbf233be5 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -239,14 +239,16 @@ { "fieldname": "total_penalty_paid", "fieldtype": "Currency", + "hidden": 1, "label": "Total Penalty Paid", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-05 13:45:19.137896", + "modified": "2021-04-10 10:00:31.859076", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index baa45a4704..728eadf22a 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -227,6 +227,14 @@ class LoanRepayment(AccountsController): gle_map = [] loan_details = frappe.get_doc("Loan", self.against_loan) + if self.shortfall_amount and self.amount_paid > self.shortfall_amount: + remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount, + self.against_loan) + elif self.shortfall_amount: + remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount) + else: + remarks = _("Repayment against Loan: ") + self.against_loan + if self.total_penalty_paid: gle_map.append( self.get_gl_dict({ @@ -267,7 +275,7 @@ class LoanRepayment(AccountsController): "debit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Repayment against Loan: ") + self.against_loan, + "remarks": remarks, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date) }) @@ -283,7 +291,7 @@ class LoanRepayment(AccountsController): "credit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Repayment against Loan: ") + self.against_loan, + "remarks": remarks, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date) }) diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index c4c2d68378..b24dc2f7c2 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import get_datetime, flt +from frappe.utils import get_datetime, flt, getdate import json from six import iteritems from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price @@ -113,7 +113,11 @@ class LoanSecurityUnpledge(Document): pledged_qty += qty if not pledged_qty: - frappe.db.set_value('Loan', self.loan, 'status', 'Closed') + frappe.db.set_value('Loan', self.loan, + { + 'status': 'Closed', + 'closure_date': getdate() + }) @frappe.whitelist() def get_pledged_security_qty(loan): From d235fc75ad573525ee7dc022acb468e82aa22674 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Apr 2021 10:23:06 +0530 Subject: [PATCH 0460/1264] fix: remove nonexistent method from pick list (#25279) This method is merged in calculate_rate_and_amount() during repost item valuation refactoring. Ref: https://github.com/nabinhait/erpnext/commit/e10f0a5a6cd344b0e2ea2b65a992b37e9a4594d6#diff-a160e3a8907dcdf28ce7728bb7dd45914ad8ab033489ea3de4c80fb1c4ca7fe9 --- erpnext/stock/doctype/pick_list/pick_list.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 755fa615bb..6ab68e292a 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -379,7 +379,6 @@ def create_stock_entry(pick_list): else: stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry) - stock_entry.set_incoming_rate() stock_entry.set_actual_qty() stock_entry.calculate_rate_and_amount() From 207166fe8ba5f22ec07acc9c8f91e1f2493bbf05 Mon Sep 17 00:00:00 2001 From: Anoop <3326959+akurungadam@users.noreply.github.com> Date: Mon, 12 Apr 2021 10:25:34 +0530 Subject: [PATCH 0461/1264] fix(patch): copy Lab Test / Template renamed field values (#25276) reload_doc doctypes introduced since v11 --- .../v13_0/healthcare_lab_module_rename_doctypes.py | 13 +++++++++++++ ...atient_history_settings_for_standard_doctypes.py | 1 + 2 files changed, 14 insertions(+) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index 5920bf1f70..a78f802574 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -18,6 +18,7 @@ def execute(): for old_dt, new_dt in doctypes.items(): if not frappe.db.table_exists(new_dt) and frappe.db.table_exists(old_dt): + frappe.reload_doc('healthcare', 'doctype', frappe.scrub(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) @@ -36,6 +37,18 @@ def execute(): SET parentfield = %(parentfield)s """.format(doctype), {'parentfield': parentfield}) + # copy renamed child table fields (fields were already renamed in old doctype json, hence sql) + frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""") + frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""") + frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""") + frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""") + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") + frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""") + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""") + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""") + # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') if frappe.db.has_column('Lab Test', 'special_toggle'): diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py index 2d3b096915..5316c01170 100644 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py @@ -8,6 +8,7 @@ def execute(): frappe.reload_doc("healthcare", "doctype", "Therapy Session") frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order") + frappe.reload_doc("healthcare", "doctype", "Clinical Procedure") frappe.reload_doc("healthcare", "doctype", "Patient History Settings") frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type") From b8a270074e402c707e31537f072f2a637ce0d441 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Apr 2021 10:28:05 +0530 Subject: [PATCH 0462/1264] fix(pos): cannot set qty to less than zero (#25258) --- erpnext/selling/page/point_of_sale/pos_controller.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 9e3c9a5656..8adf5bf747 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -279,11 +279,6 @@ erpnext.PointOfSale.Controller = class { 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, From 9f73bd80401ed7e97bbba04f4d51b15f0d7e2581 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 12 Apr 2021 10:31:36 +0530 Subject: [PATCH 0463/1264] fix: don't delete mode of payment account details while deleting company transactions (#25217) --- erpnext/setup/doctype/company/delete_company_transactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 0df4c87f51..933ed3cf32 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -27,7 +27,7 @@ def delete_company_transactions(company_name): if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", "Party Account", "Employee", "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template", "POS Profile", "BOM", - "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", + "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account", "Item Default", "Customer", "Supplier", "GST Account"): delete_for_doctype(doctype, company_name) From c36e48a869a45f27072e724a4738b6dc7f820125 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 12 Apr 2021 10:55:43 +0530 Subject: [PATCH 0464/1264] fix: GST on freight charge in e-invoicing (#25000) * fix: GST on freight charge in e-invocing * fix: Add patch for taxable value field * fix: Handle discounts on net total * fix: Handle all types of discounts for e-invoicing * fix: Absolute taxable values * fix: Use correct tax amount --- erpnext/hooks.py | 3 +- erpnext/patches.txt | 1 + .../v12_0/create_taxable_value_field.py | 18 +++++++ erpnext/regional/india/e_invoice/utils.py | 47 +++++++++++++++---- erpnext/regional/india/setup.py | 5 +- erpnext/regional/india/utils.py | 47 ++++++++++++++++++- 6 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 erpnext/patches/v12_0/create_taxable_value_field.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2e26fd2a9b..98d5966264 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,7 +262,8 @@ doc_events = { ], "on_trash": "erpnext.regional.check_deletion_permission", "validate": [ - "erpnext.regional.india.utils.validate_document_name" + "erpnext.regional.india.utils.validate_document_name", + "erpnext.regional.india.utils.update_taxable_values" ] }, "Purchase Invoice": { diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bf25db3ea5..d6176fd6f6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -763,5 +763,6 @@ erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record +erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status diff --git a/erpnext/patches/v12_0/create_taxable_value_field.py b/erpnext/patches/v12_0/create_taxable_value_field.py new file mode 100644 index 0000000000..a0c9fcf4cb --- /dev/null +++ b/erpnext/patches/v12_0/create_taxable_value_field.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice Item': [ + dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + ] + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 3dd1b36fb6..5f59d64132 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -171,10 +171,15 @@ def get_item_list(invoice): item.description = sanitize_for_json(d.item_name) item.qty = abs(item.qty) - item.discount_amount = 0 - item.unit_rate = abs(item.base_net_amount / item.qty) - item.gross_amount = abs(item.base_net_amount) - item.taxable_value = abs(item.base_net_amount) + + if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: + item.discount_amount = abs(item.base_amount - item.base_net_amount) + else: + item.discount_amount = 0 + + item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty) + item.gross_amount = abs(item.taxable_value) + item.discount_amount + item.taxable_value = abs(item.taxable_value) item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None @@ -211,7 +216,7 @@ def update_item_taxes(invoice, item): item_tax_rate = item_tax_detail[0] # item tax amount excluding discount amount - item_tax_amount = (item_tax_rate / 100) * item.base_net_amount + item_tax_amount = (item_tax_rate / 100) * item.taxable_value if t.account_head in gst_accounts.cess_account: item_tax_amount_after_discount = item_tax_detail[1] @@ -232,10 +237,14 @@ def get_invoice_value_details(invoice): invoice_value_details = frappe._dict(dict()) if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: - invoice_value_details.base_total = abs(invoice.base_total) - invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount) + # Discount already applied on net total which means on items + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) + invoice_value_details.invoice_discount_amt = 0 + elif invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount: + invoice_value_details.invoice_discount_amt = invoice.base_discount_amount + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) else: - invoice_value_details.base_total = abs(invoice.base_net_total) + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) # since tax already considers discount amount invoice_value_details.invoice_discount_amt = 0 @@ -256,7 +265,11 @@ def update_invoice_taxes(invoice, invoice_value_details): invoice_value_details.total_igst_amt = 0 invoice_value_details.total_cess_amt = 0 invoice_value_details.total_other_charges = 0 + considered_rows = [] + for t in invoice.taxes: + tax_amount = t.base_tax_amount if (invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount) \ + else t.base_tax_amount_after_discount_amount if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: # using after discount amt since item also uses after discount amt for cess calc @@ -264,12 +277,26 @@ def update_invoice_taxes(invoice, invoice_value_details): for tax_type in ['igst', 'cgst', 'sgst']: if t.account_head in gst_accounts[f'{tax_type}_account']: - invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount) + + invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount) + update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows) else: - invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_other_charges += abs(tax_amount) return invoice_value_details +def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows): + prev_row_id = cint(tax_row.row_id) - 1 + if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows: + if tax_row.charge_type == 'On Previous Row Amount': + amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount + invoice_value_details.total_other_charges -= abs(amount) + considered_rows.append(prev_row_id) + if tax_row.charge_type == 'On Previous Row Total': + amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total + invoice_value_details.total_other_charges -= abs(amount) + considered_rows.append(prev_row_id) + def get_payment_details(invoice): payee_name = invoice.company mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments]) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 7c25ce12f5..bf0712f0a5 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -127,6 +127,9 @@ def make_custom_fields(update=True): is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST', fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt', print_hide=1) + taxable_value = dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) purchase_invoice_gst_category = [ dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', @@ -460,7 +463,7 @@ def make_custom_fields(update=True): 'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], - 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 3637de438c..082a32f3ff 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe, re, json from frappe import _ import erpnext -from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate +from frappe.utils import cstr, flt, cint, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate 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 @@ -832,3 +832,48 @@ def get_regional_round_off_accounts(company, account_list): account_list.extend(gst_account_list) return account_list + +def update_taxable_values(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'India': + return + + gst_accounts = get_gst_accounts(doc.company) + + # Only considering sgst account to avoid inflating taxable value + gst_account_list = gst_accounts.get('sgst_account', []) + gst_accounts.get('sgst_account', []) \ + + gst_accounts.get('igst_account', []) + + additional_taxes = 0 + total_charges = 0 + item_count = 0 + considered_rows = [] + + for tax in doc.get('taxes'): + prev_row_id = cint(tax.row_id) - 1 + if tax.account_head in gst_account_list and prev_row_id not in considered_rows: + if tax.charge_type == 'On Previous Row Amount': + additional_taxes += doc.get('taxes')[prev_row_id].tax_amount_after_discount_amount + considered_rows.append(prev_row_id) + if tax.charge_type == 'On Previous Row Total': + additional_taxes += doc.get('taxes')[prev_row_id].base_total - doc.base_net_total + considered_rows.append(prev_row_id) + + for item in doc.get('items'): + if doc.apply_discount_on == 'Grand Total' and doc.discount_amount: + proportionate_value = item.base_amount if doc.base_total else item.qty + total_value = doc.base_total if doc.base_total else doc.total_qty + else: + proportionate_value = item.base_net_amount if doc.base_net_total else item.qty + total_value = doc.base_net_total if doc.base_net_total else doc.total_qty + + applicable_charges = flt(flt(proportionate_value * (flt(additional_taxes) / flt(total_value)), + item.precision('taxable_value'))) + item.taxable_value = applicable_charges + proportionate_value + total_charges += applicable_charges + item_count += 1 + + if total_charges != additional_taxes: + diff = additional_taxes - total_charges + doc.get('items')[item_count - 1].taxable_value += diff From 5f3d7f547c2c0cadca23d007f296c370ef37a98c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 12 Apr 2021 10:56:47 +0530 Subject: [PATCH 0465/1264] fix: Item wise tax rate for consolidated POS invoice (#25029) * fix: Item wise tax rate for consolidated POS invoice * fix: Do not alter item wise taxes for consolidated invoices * fix: Add test case * fix: Update * fix: Set opening stock for test item * fix: Add valuation rate for opening stock --- .../pos_invoice_merge_log.py | 34 +++++++++++-- .../test_pos_invoice_merge_log.py | 48 +++++++++++++++++++ erpnext/controllers/taxes_and_totals.py | 13 +++-- erpnext/stock/doctype/item/test_records.json | 2 + 4 files changed, 88 insertions(+), 9 deletions(-) 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 index 40f77b4088..2f5ee01baf 100644 --- 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 @@ -12,6 +12,7 @@ from frappe.utils.background_jobs import enqueue from frappe.model.mapper import map_doc, map_child_doc from frappe.utils.scheduler import is_scheduler_inactive from frappe.core.page.background_jobs.background_jobs import get_info +import json from six import iteritems @@ -131,12 +132,14 @@ class POSInvoiceMergeLog(Document): if t.account_head == tax.account_head and t.cost_center == tax.cost_center: t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount) t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount) + update_item_wise_tax_detail(t, tax) found = True if not found: tax.charge_type = 'Actual' tax.included_in_print_rate = 0 tax.tax_amount = tax.tax_amount_after_discount_amount tax.base_tax_amount = tax.base_tax_amount_after_discount_amount + tax.item_wise_tax_detail = tax.item_wise_tax_detail taxes.append(tax) for payment in doc.get('payments'): @@ -172,7 +175,7 @@ class POSInvoiceMergeLog(Document): sales_invoice.posting_date = getdate(nowdate()) return sales_invoice - + def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''): for doc in invoice_docs: doc.load_from_db() @@ -187,6 +190,26 @@ class POSInvoiceMergeLog(Document): si.flags.ignore_validate = True si.cancel() +def update_item_wise_tax_detail(consolidate_tax_row, tax_row): + consolidated_tax_detail = json.loads(consolidate_tax_row.item_wise_tax_detail) + tax_row_detail = json.loads(tax_row.item_wise_tax_detail) + + if not consolidated_tax_detail: + consolidated_tax_detail = {} + + for item_code, tax_data in tax_row_detail.items(): + if consolidated_tax_detail.get(item_code): + consolidated_tax_data = consolidated_tax_detail.get(item_code) + consolidated_tax_detail.update({ + item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]] + }) + else: + consolidated_tax_detail.update({ + item_code: [tax_data[0], tax_data[1]] + }) + + consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(',', ':')) + def get_all_unconsolidated_invoices(): filters = { 'consolidated_invoice': [ 'in', [ '', None ]], @@ -214,7 +237,7 @@ def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): if len(invoices) >= 5 and closing_entry: closing_entry.set_status(update=True, status='Queued') - enqueue_job(create_merge_logs, invoice_by_customer, closing_entry) + enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: create_merge_logs(invoice_by_customer, closing_entry) @@ -227,7 +250,7 @@ def unconsolidate_pos_invoices(closing_entry): if len(merge_logs) >= 5: closing_entry.set_status(update=True, status='Queued') - enqueue_job(cancel_merge_logs, merge_logs, closing_entry) + enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) @@ -241,7 +264,7 @@ def create_merge_logs(invoice_by_customer, closing_entry={}): merge_log.set('pos_invoices', invoices) merge_log.save(ignore_permissions=True) merge_log.submit() - + if closing_entry: closing_entry.set_status(update=True, status='Submitted') closing_entry.update_opening_entry() @@ -256,7 +279,7 @@ def cancel_merge_logs(merge_logs, closing_entry={}): closing_entry.set_status(update=True, status='Cancelled') closing_entry.update_opening_entry(for_cancel=True) -def enqueue_job(job, invoice_by_customer, closing_entry): +def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None): check_scheduler_status() job_name = closing_entry.get("name") @@ -269,6 +292,7 @@ def enqueue_job(job, invoice_by_customer, closing_entry): job_name=job_name, closing_entry=closing_entry, invoice_by_customer=invoice_by_customer, + merge_logs=merge_logs, now=frappe.conf.developer_mode or frappe.flags.in_test ) 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 index d880caa3c7..040a815fab 100644 --- 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 @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe import unittest +import json 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 consolidate_pos_invoices @@ -99,4 +100,51 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Invoice`") + def test_consolidated_invoice_item_taxes(self): + frappe.db.sql("delete from `tabPOS Invoice`") + + try: + inv = create_pos_invoice(qty=1, rate=100, do_not_save=True) + + 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": 9 + }) + inv.insert() + inv.submit() + + inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True) + inv2.get('items')[0].item_code = '_Test Item 2' + inv2.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": 5 + }) + inv2.insert() + inv2.submit() + + consolidate_pos_invoices() + inv.load_from_db() + + consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice) + item_wise_tax_detail = json.loads(consolidated_invoice.get('taxes')[0].item_wise_tax_detail) + + tax_rate, amount = item_wise_tax_detail.get('_Test Item') + self.assertEqual(tax_rate, 9) + self.assertEqual(amount, 9) + + tax_rate2, amount2 = item_wise_tax_detail.get('_Test Item 2') + self.assertEqual(tax_rate2, 5) + self.assertEqual(amount2, 5) + finally: + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 5f73c55836..7653a5dfdc 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -147,7 +147,9 @@ class calculate_taxes_and_totals(object): validate_taxes_and_charges(tax) validate_inclusive_tax(tax, self.doc) - tax.item_wise_tax_detail = {} + if not self.doc.get('is_consolidated'): + tax.item_wise_tax_detail = {} + tax_fields = ["total", "tax_amount_after_discount_amount", "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] @@ -338,7 +340,9 @@ class calculate_taxes_and_totals(object): current_tax_amount = tax_rate * item.qty current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount) - self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) + + if not self.doc.get("is_consolidated"): + self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount @@ -440,8 +444,9 @@ class calculate_taxes_and_totals(object): self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) def _cleanup(self): - for tax in self.doc.get("taxes"): - tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) + if not self.doc.get('is_consolidated'): + for tax in self.doc.get("taxes"): + tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) def set_discount_amount(self): if self.doc.additional_discount_percentage: diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index c1f20a47b7..6cec85288f 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -59,6 +59,8 @@ "show_in_website": 1, "website_warehouse": "_Test Warehouse - _TC", "gst_hsn_code": "999800", + "opening_stock": 10, + "valuation_rate": 100, "item_defaults": [{ "company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC", From 42f4a00c885b03166a092abe5073d3441ae53a88 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 12 Apr 2021 12:01:20 +0530 Subject: [PATCH 0466/1264] fix: declare data before assigining --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index b361c0c345..26d60389f6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -916,7 +916,7 @@ frappe.ui.form.on('Sales Invoice Timesheet', { }, callback: function(r, rt) { if(r.message){ - data = r.message; + let data = r.message; frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours); frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount); frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail); From 49336b8065e498fc2ae260ec3414c792dfa24585 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Apr 2021 12:17:50 +0530 Subject: [PATCH 0467/1264] fix: consolidated sales invoice posting date (#25119) --- .../pos_invoice_merge_log/pos_invoice_merge_log.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 index 2f5ee01baf..6d2cffcf68 100644 --- 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 @@ -79,8 +79,11 @@ class POSInvoiceMergeLog(Document): sales_invoice = self.merge_pos_invoice_into(sales_invoice, data) sales_invoice.is_consolidated = 1 + sales_invoice.set_posting_time = 1 + sales_invoice.posting_date = getdate(self.posting_date) sales_invoice.save() sales_invoice.submit() + self.consolidated_invoice = sales_invoice.name return sales_invoice.name @@ -92,10 +95,13 @@ class POSInvoiceMergeLog(Document): credit_note = self.merge_pos_invoice_into(credit_note, data) credit_note.is_consolidated = 1 + credit_note.set_posting_time = 1 + credit_note.posting_date = getdate(self.posting_date) # 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 @@ -171,8 +177,6 @@ class POSInvoiceMergeLog(Document): 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 @@ -257,7 +261,7 @@ def unconsolidate_pos_invoices(closing_entry): def create_merge_logs(invoice_by_customer, closing_entry={}): for customer, invoices in iteritems(invoice_by_customer): merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(nowdate()) + merge_log.posting_date = getdate(closing_entry.get('posting_date')) merge_log.customer = customer merge_log.pos_closing_entry = closing_entry.get('name', None) From 719e0c1e60995e7efc06dfe7891b26174349ce95 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Apr 2021 14:37:43 +0530 Subject: [PATCH 0468/1264] fix: Taxxable value including Freight and Forwarding charges in GSTR-1 Report --- erpnext/regional/report/gstr_1/gstr_1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 62faa30e3f..75076231c0 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -199,7 +199,7 @@ class Gstr1Report(object): self.item_tax_rate = frappe._dict() items = frappe.db.sql(""" - select item_code, parent, base_net_amount, item_tax_rate + select item_code, parent, taxable_value, item_tax_rate from `tab%s Item` where parent in (%s) """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) @@ -207,7 +207,7 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum(i.get('base_net_amount', 0) for i in items + sum(i.get('taxable_value', 0) for i in items if i.item_code == d.item_code and i.parent == d.parent)) item_tax_rate = {} From faca47831780c5cbcee303a2b2a1032c78ab653a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 12 Apr 2021 15:40:27 +0530 Subject: [PATCH 0469/1264] fix: payment amount showing in foreign currency --- .../accounts/doctype/payment_schedule/payment_schedule.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e362566af0..39eda331db 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -65,7 +65,6 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", - "options": "currency", "reqd": 1 }, { @@ -150,7 +149,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-15 21:03:12.540546", + "modified": "2021-04-12 15:39:47.363609", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From 2339de7333514f1b45ec942fe822bccbf63b7cc4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 12 Apr 2021 16:09:55 +0530 Subject: [PATCH 0470/1264] fix: update scheduler check tim --- .../doctype/repost_item_valuation/repost_item_valuation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index e68360266f..3f83780569 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -127,7 +127,7 @@ def repost_entries(): check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): - date = add_to_date(today(), hours=-12) + date = add_to_date(today(), hours=-3) return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 From bda4c5cc5298c0a5833cd423ee2156f7a15dd198 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 12 Apr 2021 16:09:55 +0530 Subject: [PATCH 0471/1264] fix: update scheduler check tim --- .../doctype/repost_item_valuation/repost_item_valuation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index a75db1ac86..a9556514f5 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -126,7 +126,7 @@ def repost_entries(): check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): - date = add_to_date(today(), hours=-12) + date = add_to_date(today(), hours=-3) return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 From bffe933c226bb5d0587ca55e265cebecf8e8d8ee Mon Sep 17 00:00:00 2001 From: Andy Zhu Date: Mon, 12 Apr 2021 22:49:26 +1200 Subject: [PATCH 0472/1264] Fix: attempts to add overrides function Overrides parent_fieldname in outer form to all child rows's fieldname --- erpnext/public/js/utils.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index e7c7d11b0d..8fe38d91ea 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -291,17 +291,16 @@ $.extend(erpnext.utils, { return options[0]; } }, - copy_parent_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) { - var d = locals[dt][dn]; - if(d[parent_fieldname]){ - var cl = doc[table_fieldname] || []; - for(var i = 0; i < cl.length; i++) { + overrides_parent_value_in_all_rows: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) { + let d = locals[dt][dn]; + if(doc[parent_fieldname]){ + let cl = doc[table_fieldname] || []; + for(let i = 0; i < cl.length; i++) { cl[i][fieldname] = doc[parent_fieldname]; } + frappe.refresh_field(table_fieldname); } - refresh_field(table_fieldname); }, - create_new_doc: function (doctype, update_fields) { frappe.model.with_doctype(doctype, function() { var new_doc = frappe.model.get_new_doc(doctype); From 9320316462e1fdb96843209e3e01fb8179db924e Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Apr 2021 17:55:46 +0530 Subject: [PATCH 0473/1264] feat(india): bulk e-invoice generation (#24969) --- .../sales_invoice/regional/india_list.js | 147 ++++- erpnext/patches.txt | 5 +- .../add_company_link_to_einvoice_settings.py | 16 + .../v12_0/add_einvoice_status_field.py | 69 ++ ...add_einvoice_summary_report_permissions.py | 18 + .../update_vehicle_no_reqd_condition.py | 0 .../e_invoice_settings.json | 10 +- .../e_invoice_user/e_invoice_user.json | 11 +- erpnext/regional/india/e_invoice/einvoice.js | 53 +- erpnext/regional/india/e_invoice/utils.py | 590 +++++++++++++----- erpnext/regional/india/setup.py | 32 +- erpnext/regional/india/utils.py | 13 +- .../report/e_invoice_summary/__init__.py | 0 .../e_invoice_summary/e_invoice_summary.js | 55 ++ .../e_invoice_summary/e_invoice_summary.json | 28 + .../e_invoice_summary/e_invoice_summary.py | 106 ++++ 16 files changed, 925 insertions(+), 228 deletions(-) create mode 100644 erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py create mode 100644 erpnext/patches/v12_0/add_einvoice_status_field.py create mode 100644 erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py rename erpnext/patches/{v13_0 => v12_0}/update_vehicle_no_reqd_condition.py (100%) create mode 100644 erpnext/regional/report/e_invoice_summary/__init__.py create mode 100644 erpnext/regional/report/e_invoice_summary/e_invoice_summary.js create mode 100644 erpnext/regional/report/e_invoice_summary/e_invoice_summary.json create mode 100644 erpnext/regional/report/e_invoice_summary/e_invoice_summary.py diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js index 3e1c5228ea..ada665a0ca 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js @@ -1,14 +1,14 @@ var globalOnload = frappe.listview_settings['Sales Invoice'].onload; -frappe.listview_settings['Sales Invoice'].onload = function (doclist) { +frappe.listview_settings['Sales Invoice'].onload = function (list_view) { // Provision in case onload event is added to sales_invoice.js in future if (globalOnload) { - globalOnload(doclist); + globalOnload(list_view); } const action = () => { - const selected_docs = doclist.get_checked_items(); - const docnames = doclist.get_checked_items(true); + const selected_docs = list_view.get_checked_items(); + const docnames = list_view.get_checked_items(true); for (let doc of selected_docs) { if (doc.docstatus !== 1) { @@ -19,7 +19,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) { frappe.call({ method: 'erpnext.regional.india.utils.generate_ewb_json', args: { - 'dt': doclist.doctype, + 'dt': list_view.doctype, 'dn': docnames }, callback: function(r) { @@ -35,5 +35,140 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) { }); }; - doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false); + list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false); + + const generate_irns = () => { + const docnames = list_view.get_checked_items(true); + if (docnames && docnames.length) { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices', + args: { docnames }, + freeze: true, + freeze_message: __('Generating E-Invoices...') + }); + } else { + frappe.msgprint({ + message: __('Please select at least one sales invoice to generate IRN'), + title: __('No Invoice Selected'), + indicator: 'red' + }); + } + }; + + const cancel_irns = () => { + const docnames = list_view.get_checked_items(true); + + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irns', + args: { + doctype: list_view.doctype, + docnames, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + freeze_message: __('Cancelling E-Invoices...'), + }); + d.hide(); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + let einvoicing_enabled = false; + frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => { + einvoicing_enabled = enabled; + }); + + list_view.$result.on("change", "input[type=checkbox]", () => { + if (einvoicing_enabled) { + const docnames = list_view.get_checked_items(true); + // show/hide e-invoicing actions when no sales invoices are checked + if (docnames && docnames.length) { + // prevent adding actions twice if e-invoicing action group already exists + if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) { + list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing')); + list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing')); + } + } else { + list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing')); + list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing')); + } + } + }); + + frappe.realtime.on("bulk_einvoice_generation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices generated successfully', [invoices.length]), + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to generate IRNs for following ${failures.length} sales invoices: +
    + ${failures.map(d => `
  • ${d.docname}
  • `).join('')} +
+ `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + }); + + frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices cancelled successfully', [invoices.length]), + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to cancel IRNs for following ${failures.length} sales invoices: +
    + ${failures.map(d => `
  • ${d.docname}
  • `).join('')} +
+ `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + }); }; \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d6176fd6f6..7c842fcfa9 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -756,12 +756,15 @@ erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes -erpnext.patches.v13_0.update_vehicle_no_reqd_condition +erpnext.patches.v12_0.update_vehicle_no_reqd_condition +erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17 +erpnext.patches.v12_0.add_einvoice_summary_report_permissions erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') +erpnext.patches.v12_0.add_company_link_to_einvoice_settings erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py new file mode 100644 index 0000000000..fe284e814c --- /dev/null +++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company or not frappe.db.count('E Invoice User'): + return + + frappe.reload_doc("regional", "doctype", "e_invoice_user") + for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']): + company_name = frappe.db.sql(""" + select dl.link_name from `tabAddress` a, `tabDynamic Link` dl + where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company' + """, (creds.get('gstin'))) + if company_name[0]: + frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0]) \ No newline at end of file diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py new file mode 100644 index 0000000000..387e88588d --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_status_field.py @@ -0,0 +1,69 @@ +from __future__ import unicode_literals +import json +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # move hidden einvoice fields to a different section + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), + + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + + if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'): + frappe.db.sql(''' + UPDATE `tabSales Invoice` SET einvoice_status = 'Pending' + WHERE + posting_date >= '2021-04-01' + AND ifnull(irn, '') = '' + AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '') + AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export') + ''') + + # set appropriate statuses + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated' + WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''') + + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled' + WHERE ifnull(irn_cancelled, 0) = 1''') + + # set correct acknowledgement in e-invoices + einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice']) + + if einvoices: + for inv in einvoices: + signed_einvoice = inv.get('signed_einvoice') + if signed_einvoice: + signed_einvoice = json.loads(signed_einvoice) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py new file mode 100644 index 0000000000..bf8f566d32 --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + if frappe.db.exists('Report', 'E-Invoice Summary') and \ + not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')): + frappe.get_doc(dict( + doctype='Custom Role', + report='E-Invoice Summary', + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager') + ] + )).insert() \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py similarity index 100% rename from erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py rename to erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index db8bda75bf..68ed3391d0 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -8,6 +8,7 @@ "enable", "section_break_2", "sandbox_mode", + "applicable_from", "credentials", "auth_token", "token_expiry" @@ -48,12 +49,19 @@ "fieldname": "sandbox_mode", "fieldtype": "Check", "label": "Sandbox Mode" + }, + { + "fieldname": "applicable_from", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Applicable From", + "reqd": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-13 12:04:49.449199", + "modified": "2021-03-30 12:26:25.538294", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json index dd9d99773a..a65b1ca7ca 100644 --- a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json @@ -5,6 +5,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "company", "gstin", "username", "password" @@ -30,12 +31,20 @@ "in_list_view": 1, "label": "Password", "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-22 15:10:53.466205", + "modified": "2021-03-22 12:16:56.365616", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice User", diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 7cd64f2fc0..c1a222adb4 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -1,12 +1,13 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.ui.form.on(doctype, { async refresh(frm) { - const einvoicing_enabled = await frappe.db.get_single_value("E Invoice Settings", "enable"); - const supply_type = frm.doc.gst_category; - const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); - const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin; + const res = await frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility', + args: { doc: frm.doc } + }); + const invoice_eligible = res.message; - if (cint(einvoicing_enabled) == 0 || !valid_supply_type || company_transaction) return; + if (!invoice_eligible) return; const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; @@ -109,45 +110,25 @@ erpnext.setup_einvoice_actions = (doctype) => { } if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { - const fields = [ - { - "label": "Reason", - "fieldname": "reason", - "fieldtype": "Select", - "reqd": 1, - "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", - "fieldname": "remark", - "fieldtype": "Data", - "reqd": 1 - } - ]; const action = () => { - const d = new frappe.ui.Dialog({ - title: __('Cancel E-Way Bill'), - fields: fields, + let message = __('Cancellation of e-way bill is currently not supported. '); + message += '

'; + message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); + + frappe.msgprint({ + title: __('Update E-Way Bill Cancelled Status?'), + message: message, + indicator: 'orange', primary_action: function() { - const data = d.get_values(); frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', - args: { - doctype, - docname: name, - eway_bill: ewaybill, - reason: data.reason.split('-')[0], - remark: data.remark - }, + args: { doctype, docname: name }, freeze: true, - callback: () => frm.reload_doc() || d.hide(), - error: () => d.hide() + callback: () => frm.reload_doc() }); }, - primary_action_label: __('Submit') + primary_action_label: __('Yes') }); - d.show(); }; add_custom_button(__("Cancel E-Way Bill"), action); } diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 5f59d64132..fb1f46493f 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -15,18 +15,43 @@ import traceback import io from frappe import _, bold from pyqrcode import create as qrcreate +from frappe.utils.background_jobs import enqueue +from frappe.utils.scheduler import is_scheduler_inactive +from frappe.core.page.background_jobs.background_jobs import get_info from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form +from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form, getdate, time_diff_in_hours + +@frappe.whitelist() +def validate_eligibility(doc): + if isinstance(doc, six.string_types): + doc = json.loads(doc) + + invalid_doctype = doc.get('doctype') != 'Sales Invoice' + if invalid_doctype: + return False + + einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable')) + if not einvoicing_enabled: + return False + + einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' + if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): + return False -def validate_einvoice_fields(doc): - einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) - invalid_doctype = doc.doctype != 'Sales Invoice' invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied: + if invalid_supply_type or company_transaction or no_taxes_applied: + return False + + return True + +def validate_einvoice_fields(doc): + invoice_eligible = validate_eligibility(doc) + + if not invoice_eligible: return if doc.docstatus == 0 and doc._action == 'save': @@ -35,6 +60,8 @@ def validate_einvoice_fields(doc): if len(doc.name) > 16: raise_document_name_too_long_error() + doc.einvoice_status = 'Pending' + elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) @@ -76,6 +103,9 @@ def get_transaction_details(invoice): )) def get_doc_details(invoice): + if getdate(invoice.posting_date) < getdate('2021-01-01'): + frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed')) + invoice_type = 'CRN' if invoice.is_return else 'INV' invoice_name = invoice.name @@ -87,56 +117,39 @@ def get_doc_details(invoice): invoice_date=invoice_date )) -def get_party_details(address_name, company_address=None, billing_address=None, shipping_address=None): - d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - - if ((not d.gstin and not shipping_address) - or not d.city - or not d.pincode - or not d.address_title - or not d.address_line1 - or not d.gst_state_number): +def validate_address_fields(address, is_shipping_address): + if ((not address.gstin and not is_shipping_address) + or not address.city + or not address.pincode + or not address.address_title + or not address.address_line1 + or not address.gst_state_number): frappe.throw( - msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format( - get_link_to_form('Address', address_name) - ), + msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name), title=_('Missing Address Fields') ) - if d.gst_state_number == 97: +def get_party_details(address_name, is_shipping_address=False): + addr = frappe.get_doc('Address', address_name) + + validate_address_fields(addr, is_shipping_address) + + if addr.gst_state_number == 97: # according to einvoice standard - pincode = 999999 + addr.pincode = 999999 party_address_details = frappe._dict(dict( - legal_name=sanitize_for_json(d.address_title), - location=sanitize_for_json(d.city), - pincode=d.pincode, - state_code=d.gst_state_number, - address_line1=sanitize_for_json(d.address_line1), - address_line2=sanitize_for_json(d.address_line2) + legal_name=sanitize_for_json(addr.address_title), + location=sanitize_for_json(addr.city), + pincode=addr.pincode, gstin=addr.gstin, + state_code=addr.gst_state_number, + address_line1=sanitize_for_json(addr.address_line1), + address_line2=sanitize_for_json(addr.address_line2) )) - if d.gstin: - party_address_details.gstin = d.gstin + return party_address_details -def get_gstin_details(gstin): - if not hasattr(frappe.local, 'gstin_cache'): - frappe.local.gstin_cache = {} - - key = gstin - details = frappe.local.gstin_cache.get(key) - if details: - return details - - details = frappe.cache().hget('gstin_cache', key) - if details: - frappe.local.gstin_cache[key] = details - return details - - if not details: - return GSPConnector.get_gstin_details(gstin) - def get_overseas_address_details(address_name): address_title, address_line1, address_line2, city = frappe.db.get_value( 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city'] @@ -212,7 +225,7 @@ def update_item_taxes(invoice, item): is_applicable = t.tax_amount and t.account_head in gst_accounts_list if is_applicable: # this contains item wise tax rate & tax amount (incl. discount) - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name) item_tax_rate = item_tax_detail[0] # item tax amount excluding discount amount @@ -230,6 +243,9 @@ def update_item_taxes(invoice, item): if t.account_head in gst_accounts[f'{tax_type}_account']: item.tax_rate += item_tax_rate item[f'{tax_type}_amount'] += abs(item_tax_amount) + else: + # TODO: other charges per item + pass return item @@ -309,6 +325,10 @@ def get_payment_details(invoice): )) def get_return_doc_reference(invoice): + if not invoice.return_against: + frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.') + .format(frappe.bold('Return Against')), title=_('Missing Field')) + invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') return frappe._dict(dict( invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') @@ -316,7 +336,11 @@ def get_return_doc_reference(invoice): def get_eway_bill_details(invoice): if invoice.is_return: - frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed')) + frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'), + title=_('Invalid Fields')) + + if not invoice.distance: + frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field')) mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } @@ -334,9 +358,15 @@ def get_eway_bill_details(invoice): def validate_mandatory_fields(invoice): if not invoice.company_address: - frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields')) + frappe.throw( + _('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'), + title=_('Missing Fields') + ) if not invoice.customer_address: - frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields')) + frappe.throw( + _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'), + title=_('Missing Fields') + ) if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): frappe.throw( _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), @@ -348,6 +378,39 @@ def validate_mandatory_fields(invoice): title=_('Missing Fields') ) +def validate_totals(einvoice): + item_list = einvoice['ItemList'] + value_details = einvoice['ValDtls'] + + total_item_ass_value = 0 + total_item_cgst_value = 0 + total_item_sgst_value = 0 + total_item_igst_value = 0 + total_item_value = 0 + for item in item_list: + total_item_ass_value += flt(item['AssAmt']) + total_item_cgst_value += flt(item['CgstAmt']) + total_item_sgst_value += flt(item['SgstAmt']) + total_item_igst_value += flt(item['IgstAmt']) + total_item_value += flt(item['TotItemVal']) + + if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1: + frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx)) + + if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: + frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) + + if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1: + frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) + + calculated_invoice_value = \ + flt(value_details['AssVal']) + flt(value_details['CgstVal']) \ + + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \ + + flt(value_details['OthChrg']) - flt(value_details['Discount']) + + if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1: + frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.')) + def make_einvoice(invoice): validate_mandatory_fields(invoice) @@ -357,12 +420,12 @@ def make_einvoice(invoice): item_list = get_item_list(invoice) doc_details = get_doc_details(invoice) invoice_value_details = get_invoice_value_details(invoice) - seller_details = get_party_details(invoice.company_address, company_address=1) + seller_details = get_party_details(invoice.company_address) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) else: - buyer_details = get_party_details(invoice.customer_address, billing_address=1) + buyer_details = get_party_details(invoice.customer_address) place_of_supply = get_place_of_supply(invoice, invoice.doctype) if place_of_supply: place_of_supply = place_of_supply.split('-')[0] @@ -370,20 +433,23 @@ def make_einvoice(invoice): place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2] buyer_details.update(dict(place_of_supply=place_of_supply)) + seller_details.update(dict(legal_name=invoice.company)) + buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer)) + shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: if invoice.gst_category == 'Overseas': shipping_details = get_overseas_address_details(invoice.shipping_address_name) else: - shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=1) + shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True) if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) - if invoice.is_return and invoice.return_against: + if invoice.is_return: prev_doc_details = get_return_doc_reference(invoice) - if invoice.transporter: + if invoice.transporter and flt(invoice.distance) and not invoice.is_return: eway_bill_details = get_eway_bill_details(invoice) # not yet implemented @@ -396,18 +462,70 @@ def make_einvoice(invoice): period_details=period_details, prev_doc_details=prev_doc_details, export_details=export_details, eway_bill_details=eway_bill_details ) - einvoice = safe_json_load(einvoice) - validations = json.loads(read_json('einv_validation')) - errors = validate_einvoice(validations, einvoice) - if errors: - message = "\n".join([ - "E Invoice: ", json.dumps(einvoice, indent=4), - "-" * 50, - "Errors: ", json.dumps(errors, indent=4) - ]) - frappe.log_error(title="E Invoice Validation Failed", message=message) - frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1) + try: + einvoice = safe_json_load(einvoice) + einvoice = santize_einvoice_fields(einvoice) + validate_totals(einvoice) + + except Exception: + log_error(einvoice) + link_to_error_list = 'Error Log' + frappe.throw( + _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format( + invoice.name, link_to_error_list), + title=_('E Invoice Creation Failed') + ) + + return einvoice + +def log_error(data=None): + if not isinstance(data, dict): + data = json.loads(data) + + seperator = "--" * 50 + err_tb = traceback.format_exc() + err_msg = str(sys.exc_info()[1]) + data = json.dumps(data, indent=4) + + message = "\n".join([ + "Error", err_msg, seperator, + "Data:", data, seperator, + "Exception:", err_tb + ]) + frappe.log_error(title=_('E Invoice Request Failed'), message=message) + +def santize_einvoice_fields(einvoice): + int_fields = ["Pin","Distance","CrDay"] + float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",] + copy = einvoice.copy() + for key, value in copy.items(): + if isinstance(value, list): + for idx, d in enumerate(value): + santized_dict = santize_einvoice_fields(d) + if santized_dict: + einvoice[key][idx] = santized_dict + else: + einvoice[key].pop(idx) + + if not einvoice[key]: + einvoice.pop(key, None) + + elif isinstance(value, dict): + santized_dict = santize_einvoice_fields(value) + if santized_dict: + einvoice[key] = santized_dict + else: + einvoice.pop(key, None) + + elif not value or value == "None": + einvoice.pop(key, None) + + elif key in float_fields: + einvoice[key] = flt(value, 2) + + elif key in int_fields: + einvoice[key] = cint(value) return einvoice @@ -423,72 +541,22 @@ def safe_json_load(json_string): snippet = json_string[start:end] frappe.throw(_("Error in input data. Please check for any special characters near following input:
{}").format(snippet)) -def validate_einvoice(validations, einvoice, errors=None): - if errors is None: - errors = [] - for fieldname, field_validation in validations.items(): - value = einvoice.get(fieldname, None) - if not value or value == "None": - # remove keys with empty values - einvoice.pop(fieldname, None) - continue - - value_type = field_validation.get("type").lower() - if value_type in ['object', 'array']: - child_validations = field_validation.get('properties') - - if isinstance(value, list): - for d in value: - validate_einvoice(child_validations, d, errors) - if not d: - # remove empty dicts - einvoice.pop(fieldname, None) - else: - validate_einvoice(child_validations, value, errors) - if not value: - # remove empty dicts - einvoice.pop(fieldname, None) - continue - - # convert to int or str - if value_type == 'string': - einvoice[fieldname] = str(value) - elif value_type == 'number': - is_integer = '.' not in str(field_validation.get('maximum')) - precision = 3 if '.999' in str(field_validation.get('maximum')) else 2 - einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value) - value = einvoice[fieldname] - - max_length = field_validation.get('maxLength') - minimum = flt(field_validation.get('minimum')) - maximum = flt(field_validation.get('maximum')) - pattern_str = field_validation.get('pattern') - pattern = re.compile(pattern_str or '') - - label = field_validation.get('description') or fieldname - - if value_type == 'string' and len(value) > max_length: - errors.append(_('{} should not exceed {} characters').format(label, max_length)) - if value_type == 'number' and (value > maximum or value < minimum): - errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum)) - if pattern_str and not pattern.match(value): - errors.append(field_validation.get('validationMsg')) - - return errors - -class RequestFailed(Exception): pass +class RequestFailed(Exception): + pass +class CancellationNotAllowed(Exception): + pass class GSPConnector(): def __init__(self, doctype=None, docname=None): - self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') - sandbox_mode = self.e_invoice_settings.sandbox_mode + self.doctype = doctype + self.docname = docname - self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None - self.credentials = self.get_credentials() + self.set_invoice() + self.set_credentials() # authenticate url is same for sandbox & live self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token' - self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test' + self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test' self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' @@ -497,15 +565,26 @@ class GSPConnector(): self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB' self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' - def get_credentials(self): + def set_invoice(self): + self.invoice = None + if self.doctype and self.docname: + self.invoice = frappe.get_cached_doc(self.doctype, self.docname) + + def set_credentials(self): + self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') + + if not self.e_invoice_settings.enable: + frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) + if self.invoice: gstin = self.get_seller_gstin() - if not self.e_invoice_settings.enable: - frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) - credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin) + credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin] + if credentials_for_gstin: + self.credentials = credentials_for_gstin[0] + else: + frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings')) else: - credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None - return credentials + self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None def get_seller_gstin(self): gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin') @@ -556,7 +635,7 @@ class GSPConnector(): self.e_invoice_settings.reload() except Exception: - self.log_error(res) + log_error(res) self.raise_error(True) def get_headers(self): @@ -578,14 +657,14 @@ class GSPConnector(): if res.get('success'): return res.get('result') else: - self.log_error(res) + log_error(res) raise RequestFailed except RequestFailed: self.raise_error() except Exception: - self.log_error() + log_error() self.raise_error(True) @staticmethod @@ -603,12 +682,13 @@ class GSPConnector(): return details def generate_irn(self): - headers = self.get_headers() - einvoice = make_einvoice(self.invoice) - data = json.dumps(einvoice, indent=4) - + data = {} try: + headers = self.get_headers() + einvoice = make_einvoice(self.invoice) + data = json.dumps(einvoice, indent=4) res = self.make_request('post', self.generate_irn_url, headers, data) + if res.get('success'): self.set_einvoice_data(res.get('result')) @@ -628,12 +708,36 @@ class GSPConnector(): except RequestFailed: errors = self.sanitize_error_message(res.get('message')) + self.set_failed_status(errors=errors) self.raise_error(errors=errors) - except Exception: - self.log_error(data) + except Exception as e: + self.set_failed_status(errors=str(e)) + log_error(data) self.raise_error(True) + @staticmethod + def bulk_generate_irn(invoices): + gsp_connector = GSPConnector() + gsp_connector.doctype = 'Sales Invoice' + + failed = [] + + for invoice in invoices: + try: + gsp_connector.docname = invoice + gsp_connector.set_invoice() + gsp_connector.set_credentials() + gsp_connector.generate_irn() + + except Exception as e: + failed.append({ + 'docname': invoice, + 'message': str(e) + }) + + return failed + def get_irn_details(self, irn): headers = self.get_headers() @@ -650,21 +754,30 @@ class GSPConnector(): self.raise_error(errors=errors) except Exception: - self.log_error() + log_error() self.raise_error(True) def cancel_irn(self, irn, reason, remark): - headers = self.get_headers() - data = json.dumps({ - 'Irn': irn, - 'Cnlrsn': reason, - 'Cnlrem': remark - }, indent=4) - + data, res = {}, {} try: + # validate cancellation + if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24: + frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed) + if not irn: + frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed) + + headers = self.get_headers() + data = json.dumps({ + 'Irn': irn, + 'Cnlrsn': reason, + 'Cnlrem': remark + }, indent=4) + res = self.make_request('post', self.cancel_irn_url, headers, data) - if res.get('success'): + if res.get('success') or '9999' in res.get('message'): self.invoice.irn_cancelled = 1 + self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else "" + self.invoice.einvoice_status = 'Cancelled' self.invoice.flags.updater_reference = { 'doctype': self.invoice.doctype, 'docname': self.invoice.name, @@ -677,12 +790,41 @@ class GSPConnector(): except RequestFailed: errors = self.sanitize_error_message(res.get('message')) + self.set_failed_status(errors=errors) self.raise_error(errors=errors) - except Exception: - self.log_error(data) + except CancellationNotAllowed as e: + self.set_failed_status(errors=str(e)) + self.raise_error(errors=str(e)) + + except Exception as e: + self.set_failed_status(errors=str(e)) + log_error(data) self.raise_error(True) + @staticmethod + def bulk_cancel_irn(invoices, reason, remark): + gsp_connector = GSPConnector() + gsp_connector.doctype = 'Sales Invoice' + + failed = [] + + for invoice in invoices: + try: + gsp_connector.docname = invoice + gsp_connector.set_invoice() + gsp_connector.set_credentials() + irn = gsp_connector.invoice.irn + gsp_connector.cancel_irn(irn, reason, remark) + + except Exception as e: + failed.append({ + 'docname': invoice, + 'message': str(e) + }) + + return failed + def generate_eway_bill(self, **kwargs): args = frappe._dict(kwargs) @@ -721,7 +863,7 @@ class GSPConnector(): self.raise_error(errors=errors) except Exception: - self.log_error(data) + log_error(data) self.raise_error(True) def cancel_eway_bill(self, eway_bill, reason, remark): @@ -753,7 +895,7 @@ class GSPConnector(): self.raise_error(errors=errors) except Exception: - self.log_error(data) + log_error(data) self.raise_error(True) def sanitize_error_message(self, message): @@ -768,6 +910,9 @@ class GSPConnector(): ] then we trim down the message by looping over errors ''' + if not message: + return [] + errors = re.findall(': [^:]+', message) for idx, e in enumerate(errors): # remove colons @@ -779,22 +924,6 @@ class GSPConnector(): return errors - def log_error(self, data={}): - if not isinstance(data, dict): - data = json.loads(data) - - seperator = "--" * 50 - err_tb = traceback.format_exc() - err_msg = str(sys.exc_info()[1]) - data = json.dumps(data, indent=4) - - message = "\n".join([ - "Error", err_msg, seperator, - "Data:", data, seperator, - "Exception:", err_tb - ]) - frappe.log_error(title=_('E Invoice Request Failed'), message=message) - def raise_error(self, raise_exception=False, errors=[]): title = _('E Invoice Request Failed') if errors: @@ -817,7 +946,10 @@ class GSPConnector(): self.invoice.ack_no = res.get('AckNo') self.invoice.ack_date = res.get('AckDt') self.invoice.signed_einvoice = dec_signed_invoice + self.invoice.ack_no = res.get('AckNo') + self.invoice.ack_date = res.get('AckDt') self.invoice.signed_qr_code = res.get('SignedQRCode') + self.invoice.einvoice_status = 'Generated' self.attach_qrcode_image() @@ -854,6 +986,17 @@ class GSPConnector(): self.invoice.flags.ignore_validate = True self.invoice.save() + def set_failed_status(self, errors=None): + frappe.db.rollback() + self.invoice.einvoice_status = 'Failed' + self.invoice.failure_description = self.get_failure_message(errors) if errors else "" + self.update_invoice() + frappe.db.commit() + + def get_failure_message(self, errors): + if isinstance(errors, list): + errors = ', '.join(errors) + return errors def sanitize_for_json(string): """Escape JSON specific characters from a string.""" @@ -883,5 +1026,114 @@ def generate_eway_bill(doctype, docname, **kwargs): @frappe.whitelist() def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.cancel_eway_bill(eway_bill, reason, remark) + # TODO: uncomment when eway_bill api from Adequare is enabled + # gsp_connector = GSPConnector(doctype, docname) + # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) + + # update cancelled status only, to be able to cancel irn next + frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) + +@frappe.whitelist() +def generate_einvoices(docnames): + docnames = json.loads(docnames) or [] + + if len(docnames) < 10: + failures = GSPConnector.bulk_generate_irn(docnames) + frappe.local.message_log = [] + + if failures: + show_bulk_action_failure_message(failures) + + success = len(docnames) - len(failures) + frappe.msgprint( + _('{} e-invoices generated successfully').format(success), + title=_('Bulk E-Invoice Generation Complete') + ) + + else: + enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames) + +def schedule_bulk_generate_irn(docnames): + failures = GSPConnector.bulk_generate_irn(docnames) + frappe.local.message_log = [] + + frappe.publish_realtime("bulk_einvoice_generation_complete", { + "user": frappe.session.user, + "failures": failures, + "invoices": docnames + }) + +def show_bulk_action_failure_message(failures): + for doc in failures: + docname = '{0}'.format(doc.get('docname')) + message = doc.get('message').replace("'", '"') + if message[0] == '[': + errors = json.loads(message) + error_list = ''.join(['
  • {}
  • '.format(err) for err in errors]) + message = '''{} has following errors:
    +
      {}
    '''.format(docname, error_list) + else: + message = '{} - {}'.format(docname, message) + + frappe.msgprint( + message, + title=_('Bulk E-Invoice Generation Complete'), + indicator='red' + ) + +@frappe.whitelist() +def cancel_irns(docnames, reason, remark): + docnames = json.loads(docnames) or [] + + if len(docnames) < 10: + failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) + frappe.local.message_log = [] + + if failures: + show_bulk_action_failure_message(failures) + + success = len(docnames) - len(failures) + frappe.msgprint( + _('{} e-invoices cancelled successfully').format(success), + title=_('Bulk E-Invoice Cancellation Complete') + ) + else: + enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark) + +def schedule_bulk_cancel_irn(docnames, reason, remark): + failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) + frappe.local.message_log = [] + + frappe.publish_realtime("bulk_einvoice_cancellation_complete", { + "user": frappe.session.user, + "failures": failures, + "invoices": docnames + }) + +def enqueue_bulk_action(job, **kwargs): + check_scheduler_status() + + enqueue( + job, + **kwargs, + queue="long", + timeout=10000, + event="processing_bulk_einvoice_action", + now=frappe.conf.developer_mode or frappe.flags.in_test, + ) + + if job == schedule_bulk_generate_irn: + msg = _('E-Invoices will be generated in a background process.') + else: + msg = _('E-Invoices will be cancelled in a background process.') + + frappe.msgprint(msg, alert=1) + +def check_scheduler_status(): + if is_scheduler_inactive() and not frappe.flags.in_test: + frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive")) + +def job_already_enqueued(job_name): + enqueued_jobs = [d.get("job_name") for d in get_info()] + if job_name in enqueued_jobs: + return True \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index bf0712f0a5..ec58fd2f33 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -51,7 +51,7 @@ def create_hsn_codes(data, code_field): def add_custom_roles_for_reports(): for report_name in ('GST Sales Register', 'GST Purchase Register', - 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'): + 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'): if not frappe.db.get_value('Custom Role', dict(report=report_name)): frappe.get_doc(dict( @@ -418,21 +418,37 @@ def make_custom_fields(update=True): dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), - dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), - - dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), - dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), - dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) ] custom_fields = { diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 082a32f3ff..7709ddf42c 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -41,24 +41,25 @@ def validate_gstin_for_india(doc, method): return if len(doc.gstin) != 15: - frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters.")) + frappe.throw(_("A GSTIN must have 15 characters."), title=_("Invalid GSTIN")) if gst_category and gst_category == 'UIN Holders': if not GSTIN_UIN_FORMAT.match(doc.gstin): - frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers")) + frappe.throw(_("The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers"), + title=_("Invalid GSTIN")) else: if not GSTIN_FORMAT.match(doc.gstin): - frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.")) + frappe.throw(_("The input you've entered doesn't match the format of GSTIN."), title=_("Invalid GSTIN")) validate_gstin_check_digit(doc.gstin) set_gst_state_and_state_number(doc) if not doc.gst_state: - frappe.throw(_("Please Enter GST state")) + frappe.throw(_("Please enter GST state"), title=_("Invalid State")) if doc.gst_state_number != doc.gstin[:2]: - frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") - .format(doc.gst_state_number)) + frappe.throw(_("First 2 digits of GSTIN should match with State number {0}.") + .format(doc.gst_state_number), title=_("Invalid GSTIN")) def validate_pan_for_india(doc, method): if doc.get('country') != 'India' or not doc.pan: diff --git a/erpnext/regional/report/e_invoice_summary/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js new file mode 100644 index 0000000000..4713217d83 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js @@ -0,0 +1,55 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["E-Invoice Summary"] = { + "filters": [ + { + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "fieldname": "company", + "label": __("Company"), + "default": frappe.defaults.get_user_default("Company"), + }, + { + "fieldtype": "Link", + "options": "Customer", + "fieldname": "customer", + "label": __("Customer") + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "from_date", + "label": __("From Date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "to_date", + "label": __("To Date"), + "default": frappe.datetime.get_today(), + }, + { + "fieldtype": "Select", + "fieldname": "status", + "label": __("Status"), + "options": "\nPending\nGenerated\nCancelled\nFailed" + } + ], + + "formatter": function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "einvoice_status" && value) { + if (value == 'Pending') value = `${value}`; + else if (value == 'Generated') value = `${value}`; + else if (value == 'Cancelled') value = `${value}`; + else if (value == 'Failed') value = `${value}`; + } + + return value; + } +}; diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json new file mode 100644 index 0000000000..4deb073a53 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json @@ -0,0 +1,28 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-12 11:23:37.312294", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "Logo", + "modified": "2021-03-12 12:36:48.689413", + "modified_by": "Administrator", + "module": "Regional", + "name": "E-Invoice Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Sales Invoice", + "report_name": "E-Invoice Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Administrator" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py new file mode 100644 index 0000000000..47acf291a3 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py @@ -0,0 +1,106 @@ +# 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): + validate_filters(filters) + + columns = get_columns() + data = get_data(filters) + + return columns, data + +def validate_filters(filters={}): + filters = frappe._dict(filters) + + if not filters.company: + frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter')) + if filters.company: + # validate if company has e-invoicing enabled + pass + if not filters.from_date or not filters.to_date: + frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter')) + if filters.from_date > filters.to_date: + frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter')) + +def get_data(filters={}): + query_filters = { + 'posting_date': ['between', [filters.from_date, filters.to_date]], + 'einvoice_status': ['is', 'set'], + 'company': filters.company + } + if filters.customer: + query_filters['customer'] = filters.customer + if filters.status: + query_filters['einvoice_status'] = filters.status + + data = frappe.get_all( + 'Sales Invoice', + filters=query_filters, + fields=[d.get('fieldname') for d in get_columns()] + ) + + return data + +def get_columns(): + return [ + { + "fieldtype": "Date", + "fieldname": "posting_date", + "label": _("Posting Date"), + "width": 0 + }, + { + "fieldtype": "Link", + "fieldname": "name", + "label": _("Sales Invoice"), + "options": "Sales Invoice", + "width": 140 + }, + { + "fieldtype": "Data", + "fieldname": "einvoice_status", + "label": _("Status"), + "width": 100 + }, + { + "fieldtype": "Link", + "fieldname": "customer", + "options": "Customer", + "label": _("Customer") + }, + { + "fieldtype": "Check", + "fieldname": "is_return", + "label": _("Is Return"), + "width": 85 + }, + { + "fieldtype": "Data", + "fieldname": "ack_no", + "label": "Ack. No.", + "width": 145 + }, + { + "fieldtype": "Data", + "fieldname": "ack_date", + "label": "Ack. Date", + "width": 165 + }, + { + "fieldtype": "Data", + "fieldname": "irn", + "label": _("IRN No."), + "width": 250 + }, + { + "fieldtype": "Currency", + "options": "Company:company:default_currency", + "fieldname": "base_grand_total", + "label": _("Grand Total"), + "width": 120 + } + ] \ No newline at end of file From a19c98255f89d40d6defdf356a72c7e3853a96d8 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Apr 2021 19:02:28 +0530 Subject: [PATCH 0474/1264] fix: patch condition (#25301) --- erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py index fe284e814c..3b560fd43a 100644 --- a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py +++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py @@ -12,5 +12,5 @@ def execute(): select dl.link_name from `tabAddress` a, `tabDynamic Link` dl where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company' """, (creds.get('gstin'))) - if company_name[0]: + if company_name and len(company_name) == 1: frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0]) \ No newline at end of file From c65751d97f262637535251225bfca089a2390f10 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Apr 2021 19:45:34 +0530 Subject: [PATCH 0475/1264] test: ignore floating point errors in test_bom --- erpnext/manufacturing/doctype/bom/test_bom.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index cd61d2a31d..7108338dab 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -93,15 +93,15 @@ class TestBOM(unittest.TestCase): base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate")) base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate")) - # test amounts in selected currency - self.assertEqual(bom.operating_cost, op_cost) - self.assertEqual(bom.raw_material_cost, raw_material_cost) - self.assertEqual(bom.total_cost, raw_material_cost + op_cost) + # test amounts in selected currency, almostEqual checks for 7 digits by default + self.assertAlmostEqual(bom.operating_cost, op_cost) + self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost) + self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost) # test amounts in selected currency - self.assertEqual(bom.base_operating_cost, base_op_cost) - self.assertEqual(bom.base_raw_material_cost, base_raw_material_cost) - self.assertEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + self.assertAlmostEqual(bom.base_operating_cost, base_op_cost) + self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) + self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) From 6a014d12c1594cfccb4c2713cba7f5936010e2cb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Apr 2021 20:21:27 +0530 Subject: [PATCH 0476/1264] fix(stock_ledger): round off values near to zero --- erpnext/stock/stock_ledger.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 121c51cf6a..a9e2f77490 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -603,7 +603,7 @@ class update_entries_after(object): batch = self.wh_data.stock_queue[index] if qty_to_pop >= batch[0]: # consume current batch - qty_to_pop = qty_to_pop - batch[0] + qty_to_pop = _round_off_if_near_zero(qty_to_pop - batch[0]) self.wh_data.stock_queue.pop(index) if not self.wh_data.stock_queue and qty_to_pop: # stock finished, qty still remains to be withdrawn @@ -617,8 +617,8 @@ class update_entries_after(object): batch[0] = batch[0] - qty_to_pop qty_to_pop = 0 - stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue)) - stock_qty = sum((flt(batch[0]) for batch in self.wh_data.stock_queue)) + stock_value = _round_off_if_near_zero(sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue))) + stock_qty = _round_off_if_near_zero(sum((flt(batch[0]) for batch in self.wh_data.stock_queue))) if stock_qty: self.wh_data.valuation_rate = stock_value / flt(stock_qty) @@ -857,3 +857,12 @@ def get_future_sle_with_negative_qty(args): order by timestamp(posting_date, posting_time) asc limit 1 """, args, as_dict=1) + +def _round_off_if_near_zero(number: float, precision: int = 6) -> float: + """ Rounds off the number to zero only if number is close to zero for decimal + specified in precision. Precision defaults to 6. + """ + if flt(number) < (1.0 / (10**precision)): + return 0 + + return flt(number) From 20140425c820b0dd861116f87f456dced07f2c2d Mon Sep 17 00:00:00 2001 From: Abdullah Abouzekry <1107178+abdouz@users.noreply.github.com> Date: Tue, 13 Apr 2021 08:06:39 +0200 Subject: [PATCH 0477/1264] fix: sales order not saving due type mismatch in promo scheme (#24748) (#25222) --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index c676abd4c6..b91a7a5bd2 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -471,7 +471,7 @@ def apply_pricing_rule_on_transaction(doc): if not d.get(pr_field): continue - if d.validate_applied_rule and doc.get(field) < d.get(pr_field): + if d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field): frappe.msgprint(_("User has not applied rule on the invoice {0}") .format(doc.name)) else: From 65f25c27b340f9d9e370e882e474ff656d8c8848 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 13 Apr 2021 11:58:16 +0530 Subject: [PATCH 0478/1264] fix(minor): fix dynamically changing grid properties --- erpnext/accounts/doctype/bank/bank.js | 5 +- .../doctype/journal_entry/journal_entry.js | 38 ++++------ .../payment_reconciliation.js | 5 +- .../doctype/pos_settings/pos_settings.js | 7 +- .../purchase_invoice/purchase_invoice.js | 9 --- .../doctype/sales_invoice/sales_invoice.js | 3 - .../patient_assessment/patient_assessment.js | 8 ++- .../doctype/therapy_plan/therapy_plan.js | 10 ++- .../manufacturing/doctype/routing/routing.js | 9 ++- .../salary_structure/salary_structure.js | 11 ++- .../products_settings/products_settings.js | 10 +-- erpnext/public/js/controllers/accounts.js | 71 ------------------- .../setup/doctype/item_group/item_group.js | 12 ++-- .../item_variant_settings.js | 5 +- .../doctype/packing_slip/packing_slip.js | 15 ---- .../purchase_receipt/purchase_receipt.js | 7 -- .../service_level_agreement.js | 6 +- 17 files changed, 69 insertions(+), 162 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 49b2b186c4..8c0abc2447 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -42,10 +42,7 @@ let add_fields_to_mapping_table = function (frm) { }); }); - frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field", - frm.doc.name).options = options; - - frm.fields_dict.bank_transaction_mapping.grid.refresh(); + frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property('frm.fields_dict.bank_transaction_mapping.grid', 'options', options); }; erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 37b03f3f0e..411bd3d6fc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -327,18 +327,16 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ }, setup_balance_formatter: function() { - var me = this; - $.each(["balance", "party_balance"], function(i, field) { - var df = frappe.meta.get_docfield("Journal Entry Account", field, me.frm.doc.name); - df.formatter = function(value, df, options, doc) { - var currency = frappe.meta.get_field_currency(df, doc); - var dr_or_cr = value ? ('') : ""; - return "
    " - + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) - + " " + dr_or_cr - + "
    "; - } - }) + const formatter = function(value, df, options, doc) { + var currency = frappe.meta.get_field_currency(df, doc); + var dr_or_cr = value ? ('') : ""; + return "
    " + + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) + + " " + dr_or_cr + + "
    "; + } + this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter); + this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter); }, reference_name: function(doc, cdt, cdn) { @@ -431,15 +429,6 @@ cur_frm.cscript.validate = function(doc,cdt,cdn) { cur_frm.cscript.update_totals(doc); } -cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){ - if(doc.select_print_heading){ - // print heading - cur_frm.pformat.print_heading = doc.select_print_heading; - } - else - cur_frm.pformat.print_heading = __("Journal Entry"); -} - frappe.ui.form.on("Journal Entry Account", { party: function(frm, cdt, cdn) { var d = frappe.get_doc(cdt, cdn); @@ -511,8 +500,11 @@ $.extend(erpnext.journal_entry, { }; $.each(field_label_map, function (fieldname, label) { - var df = frappe.meta.get_docfield("Journal Entry Account", fieldname, frm.doc.name); - df.label = frm.doc.multi_currency ? (label + " in Account Currency") : label; + frm.fields_dict.accounts.grid.update_docfield_property( + fieldname, + 'label', + frm.doc.multi_currency ? (label + " in Account Currency") : label + ); }) }, diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 6b07197ec1..b693863fb2 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -234,8 +234,9 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }); if (invoices) { - frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number", - me.frm.doc.name).options = "\n" + invoices.join("\n"); + this.frm.fields_dict.payment.grid.update_docfield_property( + 'invoice_number', options, "\n" + invoices.join("\n") + ) $.each(me.frm.doc.payments || [], function(i, p) { if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null; diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index 8890d59403..4037a927fa 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -16,8 +16,13 @@ frappe.ui.form.on('POS Settings', { } }); - frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields); + frm.fields_dict.invoice_fields.grid.update_docfield_property( + 'fieldname', + 'options', + [""].concat(fields) + ); }); + } }); diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 66a8e206a8..e61cde8fd0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -496,15 +496,6 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc, } } -cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){ - if(doc.select_print_heading){ - // print heading - cur_frm.pformat.print_heading = doc.select_print_heading; - } - else - cur_frm.pformat.print_heading = __("Purchase Invoice"); -} - frappe.ui.form.on("Purchase Invoice", { setup: function(frm) { frm.custom_make_buttons = { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 26d60389f6..8a42d9e13c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -1,9 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -// print heading -cur_frm.pformat.print_heading = 'Invoice'; - {% include 'erpnext/selling/sales_common.js' %}; frappe.provide("erpnext.accounts"); diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js index c7074e88d5..f28d32c22c 100644 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js @@ -39,11 +39,13 @@ frappe.ui.form.on('Patient Assessment', { }, set_score_range: function(frm) { - let options = []; + let options = ['']; for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) { options.push(i); } - frappe.meta.get_docfield('Patient Assessment Sheet', 'score', frm.doc.name).options = [''].concat(options); + frm.fields_dict.assessment_sheet.grid.update_docfield_property( + 'score', 'options', options + ); }, calculate_total_score: function(frm, cdt, cdn) { @@ -83,4 +85,4 @@ frappe.ui.form.on('Patient Assessment Sheet', { score: function(frm, cdt, cdn) { frm.events.calculate_total_score(frm, cdt, cdn); } -}); \ No newline at end of file +}); diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js index d1f72d625b..3bad65c312 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js @@ -58,8 +58,12 @@ frappe.ui.form.on('Therapy Plan', { } if (frm.doc.therapy_plan_template) { - frappe.meta.get_docfield('Therapy Plan Detail', 'therapy_type', frm.doc.name).read_only = 1; - frappe.meta.get_docfield('Therapy Plan Detail', 'no_of_sessions', frm.doc.name).read_only = 1; + frm.fields_dict.therapy_plan_details.grid.update_docfield_property( + 'therapy_type', 'read_only', 1, + ); + frm.fields_dict.therapy_plan_details.grid.update_docfield_property( + 'no_of_sessions', 'read_only', 1 + ); } }, @@ -126,4 +130,4 @@ frappe.ui.form.on('Therapy Plan Detail', { frm.set_value('total_sessions', total); refresh_field('total_sessions'); } -}); \ No newline at end of file +}); diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js index 9b1a8ca670..032c9cd9a2 100644 --- a/erpnext/manufacturing/doctype/routing/routing.js +++ b/erpnext/manufacturing/doctype/routing/routing.js @@ -11,10 +11,9 @@ frappe.ui.form.on('Routing', { }, display_sequence_id_column: function(frm) { - frappe.meta.get_docfield("BOM Operation", "sequence_id", - frm.doc.name).in_list_view = true; - - frm.fields_dict.operations.grid.refresh(); + frm.fields_dict.operations.grid.update_docfield_property( + 'sequence_id', 'in_list_view', 1 + ); }, calculate_operating_cost: function(frm, child) { @@ -69,4 +68,4 @@ frappe.ui.form.on('BOM Operation', { const d = locals[cdt][cdn]; frm.events.calculate_operating_cost(frm, d); } -}); \ No newline at end of file +}); diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index 6aa1387363..21e9d81225 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -111,12 +111,17 @@ frappe.ui.form.on('Salary Structure', { frappe.set_route('Form', 'Salary Structure Assignment', doc.name); }); frm.add_custom_button(__("Assign to Employees"),function () { - frm.trigger('assign_to_employees') - }) + frm.trigger('assign_to_employees') + }) } + + // set columns read-only let fields_read_only = ["is_tax_applicable", "is_flexible_benefit", "variable_based_on_taxable_salary"]; fields_read_only.forEach(function(field) { - frappe.meta.get_docfield("Salary Detail", field, frm.doc.name).read_only = 1; + frm.fields_dict.earnings.grid.update_docfield_property( + field, 'read_only', 1); + frm.fields_dict.deductions.grid.update_docfield_property( + field, 'read_only', 1); }); frm.trigger('set_earning_deduction_component'); }, diff --git a/erpnext/portal/doctype/products_settings/products_settings.js b/erpnext/portal/doctype/products_settings/products_settings.js index b68b5d7aa8..2f8b037164 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.js +++ b/erpnext/portal/doctype/products_settings/products_settings.js @@ -10,10 +10,12 @@ frappe.ui.form.on('Products Settings', { df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden ).map(df => ({ label: df.label, value: df.fieldname })); - const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname); - field.fieldtype = 'Select'; - field.options = valid_fields; - frm.fields_dict.filter_fields.grid.refresh(); + frm.fields_dict.filter_fields.grid.update_docfield_property( + 'fieldname', 'fieldtype', 'Select' + ); + frm.fields_dict.filter_fields.grid.update_docfield_property( + 'fieldname', 'options', valid_fields + ); }); } }); diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 649eb454ac..ceeecb28a2 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -276,74 +276,3 @@ erpnext.taxes.set_conditional_mandatory_rate_or_amount = function(grid_row) { } } } - - -// For customizing print -cur_frm.pformat.total = function(doc) { return ''; } -cur_frm.pformat.discount_amount = function(doc) { return ''; } -cur_frm.pformat.grand_total = function(doc) { return ''; } -cur_frm.pformat.rounded_total = function(doc) { return ''; } -cur_frm.pformat.in_words = function(doc) { return ''; } - -cur_frm.pformat.taxes= function(doc){ - //function to make row of table - var make_row = function(title, val, bold, is_negative) { - var bstart = ''; var bend = ''; - return '' + (bold?bstart:'') + title + (bold?bend:'') + '' - + '' + (is_negative ? '- ' : '') - + format_currency(val, doc.currency) + ''; - } - - function print_hide(fieldname) { - var doc_field = frappe.meta.get_docfield(doc.doctype, fieldname, doc.name); - return doc_field.print_hide; - } - - out =''; - if (!doc.print_without_amount) { - var cl = doc.taxes || []; - - // outer table - var out='
    '; - - // main table - - out +=''; - - if(!print_hide('total')) { - out += make_row('Total', doc.total, 1); - } - - // Discount Amount on net total - if(!print_hide('discount_amount') && doc.apply_discount_on == "Net Total" && doc.discount_amount) - out += make_row('Discount Amount', doc.discount_amount, 0, 1); - - // add rows - if(cl.length){ - for(var i=0;i'; - } - out += '
    '; - } - return out; -} \ No newline at end of file diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index 1413cb2862..885d874720 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -61,7 +61,7 @@ frappe.ui.form.on("Item Group", { frappe.set_route("List", "Item", {"item_group": frm.doc.name}); }); } - + frappe.model.with_doctype('Item', () => { const item_meta = frappe.get_meta('Item'); @@ -69,10 +69,12 @@ frappe.ui.form.on("Item Group", { df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden ).map(df => ({ label: df.label, value: df.fieldname })); - const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname); - field.fieldtype = 'Select'; - field.options = valid_fields; - frm.fields_dict.filter_fields.grid.refresh(); + frm.fields_dict.filter_fields.grid.update_docfield_property( + 'fieldname', 'fieldtype', 'Select' + ); + frm.fields_dict.filter_fields.grid.update_docfield_property( + 'fieldname', 'options', valid_fields + ); }); }, diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js index 24f7e31a0c..e8fb34732f 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js @@ -15,8 +15,9 @@ frappe.ui.form.on('Item Variant Settings', { } }); - const child = frappe.meta.get_docfield("Variant Field", "field_name", frm.doc.name); - child.options = allow_fields; + frm.fields_dict.fields.grid.update_docfield_property( + 'field_name', 'options', allow_fields + ); }); } }); diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.js b/erpnext/stock/doctype/packing_slip/packing_slip.js index bd14e5f616..40d46852d0 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.js +++ b/erpnext/stock/doctype/packing_slip/packing_slip.js @@ -110,19 +110,4 @@ cur_frm.cscript.calc_net_total_pkg = function(doc, ps_detail) { refresh_many(['net_weight_pkg', 'net_weight_uom', 'gross_weight_uom', 'gross_weight_pkg']); } -var make_row = function(title,val,bold){ - var bstart = ''; var bend = ''; - return ''+(bold?bstart:'')+title+(bold?bend:'')+'' - +''+ val +'' - +'' -} - -cur_frm.pformat.net_weight_pkg= function(doc){ - return '' + make_row('Net Weight', doc.net_weight_pkg) + '
    ' -} - -cur_frm.pformat.gross_weight_pkg= function(doc){ - return '' + make_row('Gross Weight', doc.gross_weight_pkg) + '
    ' -} - // TODO: validate gross weight field diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 57cc3504a9..4d1a514c6b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -248,13 +248,6 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc, } } -cur_frm.cscript.select_print_heading = function(doc, cdt, cdn) { - if(doc.select_print_heading) - cur_frm.pformat.print_heading = doc.select_print_heading; - else - cur_frm.pformat.print_heading = "Purchase Receipt"; -} - cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) { return { filters: [ diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js index 5346195a39..00060b9530 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js @@ -10,7 +10,9 @@ frappe.ui.form.on('Service Level Agreement', { let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options; statuses = statuses.split('\n'); allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status)); - frappe.meta.get_docfield('Pause SLA On Status', 'status', frm.doc.name).options = [''].concat(allow_statuses); + frm.fields_dict.pause_sla_on.grid.update_docfield_property( + 'status', 'options', [''].concat(allow_statuses) + ); }); } -}); \ No newline at end of file +}); From 3c58fd4d5e3384482cbca1c82a270397a00eb688 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 13 Apr 2021 12:05:10 +0530 Subject: [PATCH 0479/1264] fix(minor): fix dynamically changing grid properties --- erpnext/accounts/doctype/bank/bank.js | 4 +++- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- erpnext/accounts/doctype/pos_settings/pos_settings.js | 4 +--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 8c0abc2447..059e1d3158 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -42,7 +42,9 @@ let add_fields_to_mapping_table = function (frm) { }); }); - frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property('frm.fields_dict.bank_transaction_mapping.grid', 'options', options); + frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property( + 'bank_transaction_field', 'options', options + ); }; erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index b693863fb2..a27659b8f7 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -235,7 +235,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext if (invoices) { this.frm.fields_dict.payment.grid.update_docfield_property( - 'invoice_number', options, "\n" + invoices.join("\n") + 'invoice_number', 'options', "\n" + invoices.join("\n") ) $.each(me.frm.doc.payments || [], function(i, p) { diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index 4037a927fa..3625393a80 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -17,9 +17,7 @@ frappe.ui.form.on('POS Settings', { }); frm.fields_dict.invoice_fields.grid.update_docfield_property( - 'fieldname', - 'options', - [""].concat(fields) + 'fieldname', 'options', [""].concat(fields) ); }); From ff935b45860cc26e43abef045373753a41c0c864 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 13 Apr 2021 12:09:36 +0530 Subject: [PATCH 0480/1264] fix(minor): fix dynamically changing grid properties --- erpnext/healthcare/doctype/therapy_plan/therapy_plan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js index 3bad65c312..42e231dc66 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js @@ -59,7 +59,7 @@ frappe.ui.form.on('Therapy Plan', { if (frm.doc.therapy_plan_template) { frm.fields_dict.therapy_plan_details.grid.update_docfield_property( - 'therapy_type', 'read_only', 1, + 'therapy_type', 'read_only', 1 ); frm.fields_dict.therapy_plan_details.grid.update_docfield_property( 'no_of_sessions', 'read_only', 1 From 82d0a1a13e510b1031bb4cca6316575adbf65f31 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 13 Apr 2021 12:35:42 +0530 Subject: [PATCH 0481/1264] style: Add missing semicolons - & make formatting consistent --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 2 +- .../payment_reconciliation/payment_reconciliation.js | 2 +- .../payroll/doctype/salary_structure/salary_structure.js | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 411bd3d6fc..d76641dc9b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -334,7 +334,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) + " " + dr_or_cr + "
    "; - } + }; this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter); this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter); }, diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index a27659b8f7..08103184d5 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -236,7 +236,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext if (invoices) { this.frm.fields_dict.payment.grid.update_docfield_property( 'invoice_number', 'options', "\n" + invoices.join("\n") - ) + ); $.each(me.frm.doc.payments || [], function(i, p) { if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null; diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index 21e9d81225..b539b1b8a9 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -119,9 +119,11 @@ frappe.ui.form.on('Salary Structure', { let fields_read_only = ["is_tax_applicable", "is_flexible_benefit", "variable_based_on_taxable_salary"]; fields_read_only.forEach(function(field) { frm.fields_dict.earnings.grid.update_docfield_property( - field, 'read_only', 1); + field, 'read_only', 1 + ); frm.fields_dict.deductions.grid.update_docfield_property( - field, 'read_only', 1); + field, 'read_only', 1 + ); }); frm.trigger('set_earning_deduction_component'); }, From 10c61376e36257ac2b6ba4ee838f987a17e511ab Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 13 Apr 2021 15:46:01 +0530 Subject: [PATCH 0482/1264] fix: Backport missing commits to develop branch (#25305) --- erpnext/__init__.py | 2 +- .../journal_entry_account.json | 2 +- .../doctype/pos_invoice/pos_invoice.py | 1 - .../doctype/pos_profile/pos_profile.py | 5 +- .../doctype/pos_profile/test_pos_profile.py | 16 +- .../process_statement_of_accounts.js | 2 +- .../process_statement_of_accounts.py | 6 +- .../purchase_invoice/purchase_invoice.json | 2 +- .../doctype/sales_invoice/sales_invoice.py | 4 + .../sales_invoice/test_sales_invoice.py | 4 +- .../workspace/accounting/accounting.json | 10 + erpnext/change_log/v13/v13_0_0.md | 471 ++++++++++++++++++ erpnext/controllers/taxes_and_totals.py | 4 +- .../controllers/website_list_for_contact.py | 2 +- .../shopify_settings/test_shopify_settings.py | 2 +- erpnext/hr/doctype/attendance/attendance.py | 3 +- .../employee_advance/employee_advance.json | 2 +- .../leave_encashment/leave_encashment.json | 2 +- .../recruitment_analytics.py | 2 +- .../doctype/job_card/job_card.py | 1 + .../manufacturing/manufacturing.json | 4 +- erpnext/patches.txt | 3 +- .../v12_0/update_vehicle_no_reqd_condition.py | 1 + .../v13_0/check_is_income_tax_component.py | 55 +- .../v13_0/create_uae_pos_invoice_fields.py | 4 + .../fix_non_unique_represents_company.py | 8 + ..._history_settings_for_standard_doctypes.py | 2 +- erpnext/patches/v13_0/setup_uae_vat_fields.py | 8 +- .../additional_salary/additional_salary.json | 2 +- .../employee_benefit_application.json | 2 +- .../employee_benefit_claim.json | 2 +- .../employee_incentive.json | 2 +- .../employee_tax_exemption_declaration.json | 2 +- ...ployee_tax_exemption_proof_submission.json | 2 +- .../income_tax_slab/income_tax_slab.json | 2 +- .../doctype/payroll_entry/payroll_entry.py | 1 + .../retention_bonus/retention_bonus.json | 2 +- .../doctype/salary_slip/salary_slip.js | 1 + .../doctype/salary_slip/salary_slip.json | 2 +- .../doctype/salary_slip/salary_slip.py | 10 +- .../doctype/salary_slip/test_salary_slip.py | 2 +- .../salary_structure/test_salary_structure.py | 8 +- .../salary_structure_assignment.json | 2 +- .../create_payroll_period.json | 2 +- .../payroll_settings/payroll_settings.json | 10 +- erpnext/public/js/website_theme.js | 14 + erpnext/regional/germany/setup.py | 15 +- erpnext/regional/india/e_invoice/einvoice.js | 6 +- erpnext/regional/india/e_invoice/utils.py | 2 - erpnext/regional/india/setup.py | 13 +- erpnext/regional/india/test_utils.py | 8 +- erpnext/regional/india/utils.py | 1 + erpnext/regional/report/datev/datev.json | 49 +- .../selling/doctype/customer/customer.json | 3 +- .../page/point_of_sale/pos_item_cart.js | 1 - .../page/point_of_sale/pos_item_details.js | 1 - .../point_of_sale/pos_past_order_summary.js | 8 + .../selling/page/point_of_sale/pos_payment.js | 35 ++ erpnext/shopping_cart/cart.py | 4 +- .../stock/doctype/stock_entry/stock_entry.js | 1 - .../test_stock_ledger_entry.py | 8 +- .../generators/item/item_add_to_cart.html | 2 +- .../templates/includes/transaction_row.html | 6 +- erpnext/templates/pages/order.html | 31 +- erpnext/utilities/transaction_base.py | 1 - 65 files changed, 739 insertions(+), 152 deletions(-) create mode 100644 erpnext/change_log/v13/v13_0_0.md create mode 100644 erpnext/patches/v13_0/fix_non_unique_represents_company.py create mode 100644 erpnext/public/js/website_theme.js diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 199a183e47..d2e58701c8 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-dev' +__version__ = '13.0.1' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 774159d691..a89fefde07 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -280,7 +280,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-06-24 14:06:54.833738", + "modified": "2020-06-26 14:06:54.833738", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 832fb8069a..e614459252 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -108,7 +108,6 @@ class POSInvoice(SalesInvoice): filters = { "item_code": d.item_code, "warehouse": d.warehouse } if d.batch_no: filters["batch_no"] = d.batch_no - reserved_serial_nos = get_pos_reserved_serial_nos(filters) serial_nos = get_serial_nos(d.serial_no) invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos] diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index ee76bba750..cf7ed26d27 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -62,14 +62,15 @@ class POSProfile(Document): if len(default_mode) > 1: frappe.throw(_("You can only select one mode of payment as default")) - + invalid_modes = [] for d in self.payments: account = frappe.db.get_value( - "Mode of Payment Account", + "Mode of Payment Account", {"parent": d.mode_of_payment, "company": self.company}, "default_account" ) + if not account: invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment)) diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 62dc1fcb20..0033965700 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -92,11 +92,21 @@ def make_pos_profile(**args): "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC" }) - payments = [{ + mode_of_payment = frappe.get_doc("Mode of Payment", "Cash") + company = args.company or "_Test Company" + default_account = args.income_account or "Sales - _TC" + + if not frappe.db.get_value("Mode of Payment Account", {"company": company, "parent": "Cash"}): + mode_of_payment.append("accounts", { + "company": company, + "default_account": default_account + }) + mode_of_payment.save() + + pos_profile.append("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/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js index 7425132c46..6dc46430e0 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js @@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { frm.refresh_field('customers'); } else{ - frappe.msgprint('No Customers found with selected options.'); + frappe.throw('No Customers found with selected options.'); } } } diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index d50e4a8af9..43fbb0600a 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -126,9 +126,11 @@ def get_customers_based_on_sales_person(sales_person): sales_person_records = frappe._dict() for d in records: sales_person_records.setdefault(d.parenttype, set()).add(d.parent) - customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ + if sales_person_records.get('Customer'): + return frappe.get_list('Customer', fields=['name', 'email_id'], \ filters=[['name', 'in', list(sales_person_records['Customer'])]]) - return customers + else: + return [] def get_recipients_and_cc(customer, doc): recipients = [] diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 739bd671f7..2d5760b505 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1370,7 +1370,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-03-30 21:45:58.334107", + "modified": "2021-03-30 22:45:58.334107", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 21d550a4d3..3c91dccaa7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -24,6 +24,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from frappe.model.utils import get_fetch_values from frappe.contacts.doctype.address.address import get_address_display +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.healthcare.utils import manage_invoice_submit_cancel @@ -211,6 +212,9 @@ class SalesInvoice(SellingController): # this sequence because outstanding may get -ve self.make_gl_entries() + if self.update_stock == 1: + self.repost_future_sle_and_gle() + if self.update_stock == 1: self.repost_future_sle_and_gle() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f09cc5af96..4a6f9d1d6a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1166,10 +1166,12 @@ class TestSalesInvoice(unittest.TestCase): def test_create_so_with_margin(self): si = create_sales_invoice(item_code="_Test Item", qty=1, do_not_submit=True) - price_list_rate = 100 + price_list_rate = flt(100) * flt(si.plc_conversion_rate) si.items[0].price_list_rate = price_list_rate si.items[0].margin_type = 'Percentage' si.items[0].margin_rate_or_amount = 25 + si.items[0].discount_amount = 0.0 + si.items[0].discount_percentage = 0.0 si.save() self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate)) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index fadb66535f..9ffa481c1c 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -443,6 +443,16 @@ "onboard": 0, "type": "Link" }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "UAE VAT 201", + "link_to": "UAE VAT 201", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, diff --git a/erpnext/change_log/v13/v13_0_0.md b/erpnext/change_log/v13/v13_0_0.md new file mode 100644 index 0000000000..a6cebabab1 --- /dev/null +++ b/erpnext/change_log/v13/v13_0_0.md @@ -0,0 +1,471 @@ +# Version 13.0.0 Release Notes + +### Accounting +- [New and refreshed POS](https://github.com/frappe/erpnext/pull/20789) +- [GST E-invoicing for India](https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing) +- [Distributed Cost Center](https://docs.erpnext.com/docs/user/manual/en/accounts/distributed-cost-center) +- [Process Bulk Statement Of Accounts](https://docs.erpnext.com/docs/user/manual/en/accounts/process-statement-of-accounts) +- [More controlled deferred revenue booking](https://docs.erpnext.com/docs/user/manual/en/accounts/process-deferred-accounting) +- [Dunning](https://docs.erpnext.com/docs/user/manual/en/accounts/dunning) +- [Journal Entry Template](https://docs.erpnext.com/docs/user/manual/en/accounts/journal-entry-template) +- [POS Register report](https://github.com/frappe/erpnext/pull/23313) +- [UAE VAT 201 Report](https://github.com/frappe/erpnext/pull/23447) + + +### Loan Management +- [Loan Application](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-application) +- [Loan](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan) +- [Loan Security Pledge](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-security-pledge) +- [Loan Disbursement](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-disbursement) +- [Loan Repayment](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-repayment) +- [Loan Interest Accrual](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-interest-accrual) +- [Loan Write Off](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-write-off) + +### Healthcare +- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare) +- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type) +- [Laboratory Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/setup_laboratory) +- [Patient Progress Page](https://github.com/frappe/erpnext/pull/22474) +- [Inpatient Medication Order and Entry](https://docs.erpnext.com/docs/user/manual/en/healthcare/inpatient_medication_entry) +- [Therapy Plan Template](https://docs.erpnext.com/docs/user/manual/en/healthcare/therapy_plan) +- [Multi company support in Healthcare](https://github.com/frappe/erpnext/pull/21290) +- [Inpatient Medication Orders Script Report](https://github.com/frappe/erpnext/pull/23984) +- [Patient History Enhancements](https://github.com/frappe/erpnext/pull/24033) + + +### Stock +- [Putaway](https://docs.erpnext.com/docs/user/manual/en/stock/putaway-rule) +- [More accurate stock valuation in case of back-dated stock transactions](https://github.com/frappe/erpnext/pull/24183) +- [Repost item costing via background job](https://github.com/frappe/erpnext/pull/24183) +- [Item valuation for internal stock transfers](https://github.com/frappe/erpnext/pull/24200) +- [Multi currency in Landed Cost Voucher](https://github.com/frappe/erpnext/pull/24127) +- [Formula based Quality Inspection](https://docs.erpnext.com/docs/user/manual/en/stock/quality-inspection) +- [Value Based and Numeric Quality Inspection](https://github.com/frappe/erpnext/pull/24181) +- [Shipment](https://github.com/frappe/erpnext/pull/22914) +- [Return tracking in PR/DN](https://github.com/frappe/erpnext/pull/22859) + +### Manufacturing +- [Production forecasting using Exponential Smoothing method](https://docs.erpnext.com/docs/user/manual/en/manufacturing/reports/demand-driven-forecasting) +- [BOM Template](https://docs.erpnext.com/docs/user/manual/en/manufacturing/bill-of-materials#34-bom-template) +- [Downtime Entry](https://docs.erpnext.com/docs/user/manual/en/manufacturing/downtime-entry) +- [Quality Inspection on Job Card](https://github.com/frappe/erpnext/pull/23964) +- New Reports + - Production Planning Report ([#21763](https://github.com/frappe/erpnext/pull/21763)) + - BOM Operations Time ([#21763](https://github.com/frappe/erpnext/pull/21763)) + - Work Order Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Job card Summary ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Downtime Analysis ([#21430](https://github.com/frappe/erpnext/pull/21430)) + - Quality Inspection ([#21430](https://github.com/frappe/erpnext/pull/21430)) + +### HR +- [Leave policy assignment](https://github.com/frappe/erpnext/pull/23112) +- [In and Out time in attendance](https://github.com/frappe/erpnext/pull/21547) +- [Shift management](https://docs.erpnext.com/docs/user/manual/en/human-resources/shift-management) +- [Recruitment analytics](https://github.com/frappe/erpnext/pull/21732) +- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062) +- [Leave type with partial payment](https://github.com/frappe/erpnext/pull/23173) +- New and enhanced reports + - Employee Analytics ([#21705](https://github.com/frappe/erpnext/pull/21705)) + - Employee Leave Balance ([#20754](https://github.com/frappe/erpnext/pull/20754)) + - Employee Leave Balance Summary ([#20754](https://github.com/frappe/erpnext/pull/20754)) + +### Payroll +- [Multi-currency payroll](https://github.com/frappe/erpnext/pull/23519) +- [Payroll based on attendance](https://github.com/frappe/erpnext/pull/21258) +- [Payroll based on employee cost center](https://github.com/frappe/erpnext/pull/21609) +- [Recurring Additional Salary](https://github.com/frappe/erpnext/pull/20936) +- [Compute Year to Date for Salary Slip components](https://github.com/frappe/erpnext/pull/24362) +- New Reports + - Income Tax Deductions + - Professional Tax Deductions + - Provident Fund Deductions + - Total Salary Payments Based on Payment Mode + - Salary Payments via ECS + +### CRM +- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post) +- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order) +- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280) + +### Selling +- [Batch wise item pricing](https://github.com/frappe/erpnext/pull/24470) +- [Refreshed shopping cart](https://github.com/frappe/erpnext/pull/22617) +- [Territory-wise Sales Report](https://github.com/frappe/erpnext/pull/20428) + +#### Buying +- [Multi UOM support in Request for Quotation](https://github.com/frappe/erpnext/pull/22249) +- [Provision to make RFQ against Opportunity](https://github.com/frappe/erpnext/pull/22765) +- [Item Rate in Stock UOM in purchase cycle](https://github.com/frappe/erpnext/pull/24315) +- New Reports + - Requested Items To Order ([#21611](https://github.com/frappe/erpnext/pull/21611)) + - Purchase Order Analysis ([#21611](https://github.com/frappe/erpnext/pull/21611)) + - Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323)) + +### Project +- [Project template with dependent tasks](https://github.com/frappe/erpnext/pull/24092) +- [Project Summary Report](https://github.com/frappe/erpnext/pull/21587) + +### Support +- [Help Articles on support portal](https://github.com/frappe/erpnext/pull/22194) +- [Issue Metrics and SLA Enhancements](https://github.com/frappe/erpnext/pull/21617) +- [Issue Summary Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports) +- [Issue Analytics Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports) + +### Non-Profits +- [80G Certificates and Donations](https://docs.erpnext.com/docs/user/manual/en/non_profit/tax_exemption_80g_certificate) + +#### Integrations +- [Woocommerce Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/woocommerce_integration) +- [Taxjar Integration](https://github.com/frappe/erpnext/pull/21047) +- [M-pesa Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/mpesa-integration) +- [Telephony feature using Twillio](https://github.com/frappe/erpnext/pull/24032) +- [Voice Call Settings](https://github.com/frappe/erpnext/pull/24126) + + +#### Other Enhancements and Fixes +- Accounting Dimensions in Budget Variance Report ([#19973](https://github.com/frappe/erpnext/pull/19973)) +- "Sync Now" option in Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602)) +- Custom Fields in POS ([#19876](https://github.com/frappe/erpnext/pull/19876)) +- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note) +- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports) +- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912)) +- Territory tree in Customer Acquisition and Loyalty report ([#21668](https://github.com/frappe/erpnext/pull/21668)) +- Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier ([#20864](https://github.com/frappe/erpnext/pull/20864)) +- Gross Profit In Quotation ([#21795](https://github.com/frappe/erpnext/pull/21795)) +- Notify credit controller users for credit limit extension via Email ([#22213](https://github.com/frappe/erpnext/pull/22213)) +- Run MRP at parent level in the production plan and make material transfer based upon materials availability ([#21545](https://github.com/frappe/erpnext/pull/21545)) +- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675)) +- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867)) +- Consider Holiday List in Student Leave Application and Attendance ([#23388](https://github.com/frappe/erpnext/pull/23388)) +- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201)) +- Sales order status filter added for production plan ([#23805](https://github.com/frappe/erpnext/pull/23805)) +- Monthly attendance sheet report group by Department, Designation, Employee Grade and Branch ([#21331](https://github.com/frappe/erpnext/pull/21331)) +- Upload Attendance template now have pre-filled holiday status ([#20947](https://github.com/frappe/erpnext/pull/20947)) +- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398)) + +
    +More + +- Fetch Items from BOM in Stock Entry([#19498](https://github.com/frappe/erpnext/pull/19498)) +- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557)) +- Close Production Plan ([#23728](https://github.com/frappe/erpnext/pull/23728)) +- Button to create Stock Entry for Drug Shortage ([#24012](https://github.com/frappe/erpnext/pull/24012)) +- Added column cost center in Accounts Receivable report ([#23835](https://github.com/frappe/erpnext/pull/23835)) +- Added jinja templating in Contract Template ([#24046](https://github.com/frappe/erpnext/pull/24046)) +- Make account number length configurable ([#23845](https://github.com/frappe/erpnext/pull/23845)) +- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614)) +- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014)) +- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445)) +- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile) +- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170)) +- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281)) +- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355)) +- Autofill Supplier pop-up when only 1 Supplier in RFQ ([#22512](https://github.com/frappe/erpnext/pull/22512)) +- Accounting entries for service item in Purchase receipt ([#22223](https://github.com/frappe/erpnext/pull/22223)) +- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309)) +- Added all companies option in employee tree to view employee across all companies ([#22573](https://github.com/frappe/erpnext/pull/22573)) +- Email Group Option In Email Campaign ([#22731](https://github.com/frappe/erpnext/pull/22731)) +- Stock Report Enhancements ([#21727](https://github.com/frappe/erpnext/pull/21727)) +- Added range for age in stock ageing ([#22622](https://github.com/frappe/erpnext/pull/22622)) +- Report Summary in Financial Statement([#20876](https://github.com/frappe/erpnext/pull/20876)) +- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641)) +- Nested Set filtering for Accounting Dimension +- Add/Remove Items from submitted Sales/Purchase Order +- Provision to edit Item Details from Marketplace +- Scan Barcode in Purchase Receipt +- Disable Rounded Totals Checkbox for Salary Slips in HR Settings + +- Renamed Loan Management to Loan on Desk Page ([#21877](https://github.com/frappe/erpnext/pull/21877)) +- Added Expense Approver field in Employee master ([#22244](https://github.com/frappe/erpnext/pull/22244)) +- Bill all hours by default on Timesheet ([#22155](https://github.com/frappe/erpnext/pull/22155)) +- Unable to cancel employee advance ([#22374](https://github.com/frappe/erpnext/pull/22374)) +- Status error in purchase invoice ([#22351](https://github.com/frappe/erpnext/pull/22351)) +- Item-wise sales and purchase register export ([#22184](https://github.com/frappe/erpnext/pull/22184)) +- Billing address in for Purchase documents ([#22233](https://github.com/frappe/erpnext/pull/22233)) +- Handle canceled entries in financial statements ([#22231](https://github.com/frappe/erpnext/pull/22231)) +- Default period start date and period end date for financial statements ([#22011](https://github.com/frappe/erpnext/pull/22011)) +- Update Packed Items via Update Items in Sales Order ([#22392](https://github.com/frappe/erpnext/pull/22392)) +- Hide delete company transactions button if not system manager ([#21839](https://github.com/frappe/erpnext/pull/21839)) +- Skipping total row for tree-view reports ([#22350](https://github.com/frappe/erpnext/pull/22350)) +- Cancelled entries in tds payable monthly report ([#22131](https://github.com/frappe/erpnext/pull/22131)) +- Inter-company Invoice currency for multicurrency transactions ([#21984](https://github.com/frappe/erpnext/pull/21984)) +- Filter batches based on item and warehouse in Pick List (develop) ([#21780](https://github.com/frappe/erpnext/pull/21780)) +- Set cost center in Expense Claim child based on parent (if missing) ([#22175](https://github.com/frappe/erpnext/pull/22175)) +- Item wise backdated stock entry posting for immutable ledger ([#22366](https://github.com/frappe/erpnext/pull/22366)) +- Shopping cart UI fixes ([#22137](https://github.com/frappe/erpnext/pull/22137)) +- Filter Leave Type based on allocation for a particular employee ([#22050](https://github.com/frappe/erpnext/pull/22050)) +- Party validation for inter-warehouse transaction ([#22186](https://github.com/frappe/erpnext/pull/22186)) +- Manufacturing dashboard and work order summary chart ([#21946](https://github.com/frappe/erpnext/pull/21946)) +- IP Admission and Discharge, Minor fixes ([#21817](https://github.com/frappe/erpnext/pull/21817)) +- Validation of Purchase Order against Material Request missing ([#22192](https://github.com/frappe/erpnext/pull/22192)) +- Staffing Plan validation ([#22379](https://github.com/frappe/erpnext/pull/22379)) +- Do not allow backdated stock transactions in previous fiscal year ([#21967](https://github.com/frappe/erpnext/pull/21967)) +- Employee Advance Return not working ([#21812](https://github.com/frappe/erpnext/pull/21812)) +- Added card for reports on education desk ([#21853](https://github.com/frappe/erpnext/pull/21853)) +- Refactored project summary report ([#21943](https://github.com/frappe/erpnext/pull/21943)) +- Revenue and Customer Count only in date range in Customer Acquitition Report ([#22210](https://github.com/frappe/erpnext/pull/22210)) +- Alternative item not working for subcontract ([#22386](https://github.com/frappe/erpnext/pull/22386)) +- Unable to create batched Item ([#22393](https://github.com/frappe/erpnext/pull/22393)) +- Filters for the manufacturing reports ([#21960](https://github.com/frappe/erpnext/pull/21960)) +- Raw material warehouse in Production Planning Report ([#21982](https://github.com/frappe/erpnext/pull/21982)) +- Allowed LWP leave types to select in Leave Application even if there is no allocation against them ([#22197](https://github.com/frappe/erpnext/pull/22197)) +- Report not working on parameter Grade ([#21951](https://github.com/frappe/erpnext/pull/21951)) +- Allow to enter Relieving date if employee status is Left ([#22242](https://github.com/frappe/erpnext/pull/22242)) +- Resetting lost reason in opportunity and quotation ([#22378](https://github.com/frappe/erpnext/pull/22378)) +- Filtering issues in opening invoice creation tool ([#21969](https://github.com/frappe/erpnext/pull/21969)) +- Set default reference Id for "On Previous Row Amount" and "On Previous Row Total" ([#22346](https://github.com/frappe/erpnext/pull/22346)) +- UX date range field separated in from and to date fields. ([#21765](https://github.com/frappe/erpnext/pull/21765)) +- Enable show_configure_button when shopping cart is enabled ([#22468](https://github.com/frappe/erpnext/pull/22468)) +- Setup status indicators for Job Offer and Job Applicant (develop) ([#22445](https://github.com/frappe/erpnext/pull/22445)) +- Item-wise sales history report ([#22783](https://github.com/frappe/erpnext/pull/22783)) +- Setting filter for project in kanban board ([#22717](https://github.com/frappe/erpnext/pull/22717)) +- Dashboard For Timesheet ([#22750](https://github.com/frappe/erpnext/pull/22750)) +- Handle custom statuses for the pause SLA configuration ([#22349](https://github.com/frappe/erpnext/pull/22349)) +- Quality Feedback and Template ([#22571](https://github.com/frappe/erpnext/pull/22571)) +- Unable to change link from new lead to existing customer ([#22787](https://github.com/frappe/erpnext/pull/22787)) +- Move Issue List actions under 'Actions' dropdown (ux) ([#22710](https://github.com/frappe/erpnext/pull/22710)) +- Cost center should only show option of selected company ([#22598](https://github.com/frappe/erpnext/pull/22598)) +- Serial No Rename does not affect Stock Ledger Entry ([#22746](https://github.com/frappe/erpnext/pull/22746)) +- Descriptions not copied while creating Fees from Fee Structure ([#22792](https://github.com/frappe/erpnext/pull/22792)) +- Company filter for cost_center and expense_account in all sales and purchase transactions ([#22478](https://github.com/frappe/erpnext/pull/22478)) +- Arrangements of filters for reports accounts payable & receivable ([#22636](https://github.com/frappe/erpnext/pull/22636)) +- Update the project after task deletion so that the % completed shows correct value ([#22591](https://github.com/frappe/erpnext/pull/22591)) +- Block Invalid Serial No updates in Maintenance Schedule ([#22665](https://github.com/frappe/erpnext/pull/22665)) +- Fetch item price in sales invoice based on it's validity ([#22563](https://github.com/frappe/erpnext/pull/22563)) +- Add view ledger button for cancelled docs ([#22432](https://github.com/frappe/erpnext/pull/22432)) +- Allow creating SLA documents even if SLA tracking is not enabled ([#22608](https://github.com/frappe/erpnext/pull/22608)) +- Quotation list view blank if quotation_to field not set as a standard filter ([#22672](https://github.com/frappe/erpnext/pull/22672)) +- Salary deductions report fixes ([#22397](https://github.com/frappe/erpnext/pull/22397)) +22727)) +- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22631](https://github.com/frappe/erpnext/pull/22631)) +- Moved parent warehouse to top section also added a section break ([#22708](https://github.com/frappe/erpnext/pull/22708)) +- Skip Progress and Completed by fields on Task Duplication ([#22565](https://github.com/frappe/erpnext/pull/22565)) +- Incorrect stock after merging the items ([#22526](https://github.com/frappe/erpnext/pull/22526)) +- Letter head not found in opening invoice creation tool ([#22488](https://github.com/frappe/erpnext/pull/22488)) +- Cannot cancel asset and asset movement ([#22441](https://github.com/frappe/erpnext/pull/22441)) +- Fetch project-related info in Timesheet ([#22423](https://github.com/frappe/erpnext/pull/22423)) +- Currency symbol not showing as per company currency in stock balance report ([#22724](https://github.com/frappe/erpnext/pull/22724)) +- Add default cost center in payment reconciliation JV ([#22614](https://github.com/frappe/erpnext/pull/22614)) +- Stock Reconciliation Invalid Quantity for Batched Item ([#22726](https://github.com/frappe/erpnext/pull/22726)) +- Project link not set in accounts other than profit and loss accounts ([#22051](https://github.com/frappe/erpnext/pull/22051)) +- Buying price for non stock item in gross profit report ([#22616](https://github.com/frappe/erpnext/pull/22616)) +- Multi currency payment reconciliation ([#22738](https://github.com/frappe/erpnext/pull/22738)) +- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440)) +- Reset homepage to home after unchecking products page ([#22736](https://github.com/frappe/erpnext/pull/22736)) +- Generic Message in previous doc validation for buying and selling ([#22546](https://github.com/frappe/erpnext/pull/22546)) +- Expense claim outstanding while making payment entry ([#22735](https://github.com/frappe/erpnext/pull/22735)) +- Take parent cost center for child if no cost center at child in expense claim ([#22496](https://github.com/frappe/erpnext/pull/22496)) +- Consider company fiscal year for getting balance ([#22577](https://github.com/frappe/erpnext/pull/22577)) +- Pick List empty table and Serial-Batch items handling ([#22426](https://github.com/frappe/erpnext/pull/22426)) +- Show total row in print format of financial statement ([#22693](https://github.com/frappe/erpnext/pull/22693)) +- Set Root as Parent if no parent in new tree view node ([#22497](https://github.com/frappe/erpnext/pull/22497)) +- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725)) +- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001)) +- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217)) +- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669)) +- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256)) +- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333)) +- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580)) +- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541)) +- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755)) +- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302)) +- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165)) +- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772)) +- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880)) +- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407)) +- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072)) +- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718)) +- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288)) +- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) +- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548)) +- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145)) +- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918)) +- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597)) +- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245)) +- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361)) +- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704)) +- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161)) +- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369)) +- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195)) +- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419)) +- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953)) +- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110)) +- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059)) +- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007)) +- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066)) +- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814)) +- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076)) +- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434)) +- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009)) +- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958)) +- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483)) +- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539)) +- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151)) +- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476)) +- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911)) +- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950)) +- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943)) +- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282)) +- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714)) +- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205)) +- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623)) +- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264)) +- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676)) +- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988)) +- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436)) +- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357)) +- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515)) +- Loan disbursement amount validation ([#24000](https://github.com/frappe/erpnext/pull/24000)) +- Making company address read-only in delivery note ([#23890](https://github.com/frappe/erpnext/pull/23890)) +- BOM stock report color showing always red ([#23994](https://github.com/frappe/erpnext/pull/23994)) +- Added filter for customer field in Issue ([#24051](https://github.com/frappe/erpnext/pull/24051)) +- Added project link in timesheet form ([#23764](https://github.com/frappe/erpnext/pull/23764)) +- Update integrations desk page ([#23767](https://github.com/frappe/erpnext/pull/23767)) +- Place of supply change on address change ([#23941](https://github.com/frappe/erpnext/pull/23941)) +- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23672](https://github.com/frappe/erpnext/pull/23672)) +- Auto fetch serial nos with modified conversion factor ([#23854](https://github.com/frappe/erpnext/pull/23854)) +- Default cost center in item master not set in stock entry ([#23877](https://github.com/frappe/erpnext/pull/23877)) +- Incorrect de-link serial no and batch ([#23947](https://github.com/frappe/erpnext/pull/23947)) +- Accounting for internal transfer invoices within same company ([#24021](https://github.com/frappe/erpnext/pull/24021)) +- Multiple pricing rule with margin type as Percentage is not working ([#24205](https://github.com/frappe/erpnext/pull/24205)) +- Added Purchase Order to Global Search ([#24055](https://github.com/frappe/erpnext/pull/24055)) +- Cannot expand row in update items dialog ([#23839](https://github.com/frappe/erpnext/pull/23839)) +- Maintain stock can't be changed it there is product bundle ([#23989](https://github.com/frappe/erpnext/pull/23989)) +- SO to PO Mapping Issue ([#23820](https://github.com/frappe/erpnext/pull/23820)) +- Asset with value zero doesn't show up in fixed asset register ([#24091](https://github.com/frappe/erpnext/pull/24091)) +- Cannot save customer email & phone ([#23797](https://github.com/frappe/erpnext/pull/23797)) +- Incorrect balance value in stock balance report ([#24048](https://github.com/frappe/erpnext/pull/24048)) +- Payment Terms not fetched in Purchase Invoice from Purchase Receipt ([#23735](https://github.com/frappe/erpnext/pull/23735)) +- Fix for LMS Sign Up link ([#23743](https://github.com/frappe/erpnext/pull/23743)) +- Incorrect stock quantity if 'Allow Multiple Material Consumption… ([#24116](https://github.com/frappe/erpnext/pull/24116)) +- Added wrong absent days calculation in salary slip ([#23897](https://github.com/frappe/erpnext/pull/23897)) +- Purchase receipt to purchase invoice bill date mapping ([#23967](https://github.com/frappe/erpnext/pull/23967)) +- Overriding po ([#24022](https://github.com/frappe/erpnext/pull/24022)) +- Do not cancel reference document on Quality Inspection cancellation ([#24198](https://github.com/frappe/erpnext/pull/24198)) +- Get formatted value in 'taxes' print template ([#24035](https://github.com/frappe/erpnext/pull/24035)) +- Don't overrule Item Price via Pricing Rule Rate if 0 ([#23636](https://github.com/frappe/erpnext/pull/23636)) +- Job card error handling for operations field ([#23991](https://github.com/frappe/erpnext/pull/23991)) +- Validation for journal entry with 0 debit and credit values ([#23975](https://github.com/frappe/erpnext/pull/23975)) +- Check if customer exists in product listing ([#24030](https://github.com/frappe/erpnext/pull/24030)) +- Asset finance book posting date fix ([#23778](https://github.com/frappe/erpnext/pull/23778)) +- Same source and target tables in Status Updater's update query ([#24110](https://github.com/frappe/erpnext/pull/24110)) +- Asset finance book depreciation posting date fix ([#23833](https://github.com/frappe/erpnext/pull/23833)) +- Ignore exception during leave ledger creation from patch ([#24005](https://github.com/frappe/erpnext/pull/24005)) +- Added link of bank reconciliation and clearance in accounting desk page ([#23850](https://github.com/frappe/erpnext/pull/23850)) +- Sales invoice add button from sales order dashboard ([#24077](https://github.com/frappe/erpnext/pull/24077)) +- Incorrect calculation for consumed qty for subcontract item ([#23257](https://github.com/frappe/erpnext/pull/23257)) +- Incorrect required_qty in Production Planning Report ([#24074](https://github.com/frappe/erpnext/pull/24074)) +- Email digest user not found ([#23949](https://github.com/frappe/erpnext/pull/23949)) +- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24115](https://github.com/frappe/erpnext/pull/24115)) +- Added TDS Payable account number and an error message ([#24065](https://github.com/frappe/erpnext/pull/24065)) +- Override field_map for job card gantt ([#24155](https://github.com/frappe/erpnext/pull/24155)) +- Old shopify order syncing date ([#23990](https://github.com/frappe/erpnext/pull/23990)) +- Shipping chanrges not sync in erpnext from shopify ([#24114](https://github.com/frappe/erpnext/pull/24114)) +- GSTR B2C report ([#24039](https://github.com/frappe/erpnext/pull/24039)) +- Ignore cancelled entries in stock balance report ([#23757](https://github.com/frappe/erpnext/pull/23757)) +- Stock ageing report not working ([#23923](https://github.com/frappe/erpnext/pull/23923)) +- Incorrect assign to in Maintenance Schedule ([#23831](https://github.com/frappe/erpnext/pull/23831)) +- Improve UX of DATEV report ([#23892](https://github.com/frappe/erpnext/pull/23892)) +- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765)) +- dDouble exception in payroll ([#24078](https://github.com/frappe/erpnext/pull/24078)) +- Make asset dashboard charts public ([#23751](https://github.com/frappe/erpnext/pull/23751)) +- Don't copy terms and discount from SO to PO ([#23903](https://github.com/frappe/erpnext/pull/23903)) +- Ignore doctypes on company transaction delete ([#23864](https://github.com/frappe/erpnext/pull/23864)) +- Error handling in Upload Attendance ([#23907](https://github.com/frappe/erpnext/pull/23907)) +- Tax template update on customer address change ([#24160](https://github.com/frappe/erpnext/pull/24160)) +- Not able to save bom ([#23910](https://github.com/frappe/erpnext/pull/23910)) +- Enable Allow Auto Repeat for standard doctypes having auto_repeat field ([#23776](https://github.com/frappe/erpnext/pull/23776)) +- Place of Supply fix in Sales Invoices ([#23785](https://github.com/frappe/erpnext/pull/23785)) +- Opening invoices in GSTR-1 report ([#24117](https://github.com/frappe/erpnext/pull/24117)) +- Partial serial no return issue ([#24208](https://github.com/frappe/erpnext/pull/24208)) +- Import taxjar globally in the taxjar_integration module ([#24027](https://github.com/frappe/erpnext/pull/24027)) +- Payroll attendance error ([#23887](https://github.com/frappe/erpnext/pull/23887)) +- Loan application link on creating loan ([#23937](https://github.com/frappe/erpnext/pull/23937)) +- POS item search includes non stock items ([#23914](https://github.com/frappe/erpnext/pull/23914)) +- Paid amount in Sales Invoice POS return resets to 0 ([#24057](https://github.com/frappe/erpnext/pull/24057)) +- Fiscal year can be shorter than 12 months ([#23838](https://github.com/frappe/erpnext/pull/23838)) +- Loan repayment type option remove ([#23582](https://github.com/frappe/erpnext/pull/23582)) +- Item wise tax calculation ([#23744](https://github.com/frappe/erpnext/pull/23744)) +- Enabling track changes for stock settings ([#23982](https://github.com/frappe/erpnext/pull/23982)) +- Added link of bank reconciliation and clearance in accounting desk page ([#23809](https://github.com/frappe/erpnext/pull/23809)) +- Location data on Asset to use command(make_demo) ([#23825](https://github.com/frappe/erpnext/pull/23825)) +- Handle Account and Item None not found in Opening Invoice Creation Tool ([#23559](https://github.com/frappe/erpnext/pull/23559)) +- Multiple subcontracting issues ([#23662](https://github.com/frappe/erpnext/pull/23662)) +- Sequence id override with workstation column ([#23810](https://github.com/frappe/erpnext/pull/23810)) +- Leave policy dashboard fix and roles ([#24170](https://github.com/frappe/erpnext/pull/24170)) +- Scan barcode does not update barcode item field in sales order ([#24090](https://github.com/frappe/erpnext/pull/24090)) +- Item price duplicate checking ([#23408](https://github.com/frappe/erpnext/pull/23408)) +- Tax template update on supplier change for India ([#24060](https://github.com/frappe/erpnext/pull/24060)) +- Consumed qty logic for subcontracted raw materials ([#23314](https://github.com/frappe/erpnext/pull/23314)) +- Finance book not getting added in journal Entry of asset value adjustment ([#24100](https://github.com/frappe/erpnext/pull/24100)) +- Set proper state code in ewaybill JSON when GST category is SEZ ([#23953](https://github.com/frappe/erpnext/pull/23953)) +- Copying po no when mapping doc ([#23729](https://github.com/frappe/erpnext/pull/23729)) +- Duplicate items validation for POS Invoice when allow multiple items is disabled ([#23896](https://github.com/frappe/erpnext/pull/23896)) +- Do not allow Company as accounting dimension ([#23749](https://github.com/frappe/erpnext/pull/23749)) +- Validation for duplicate Tax Category ([#23978](https://github.com/frappe/erpnext/pull/23978)) +- Therapy plan and session fixes ([#23817](https://github.com/frappe/erpnext/pull/23817)) +- Pricing rule with transaction not working for additional product ([#24053](https://github.com/frappe/erpnext/pull/24053)) +- Inpatient Medication Order and Entry fixes ([#23799](https://github.com/frappe/erpnext/pull/23799)) +- Avoid using SQL query to get fiscal year dates ([#24050](https://github.com/frappe/erpnext/pull/24050)) +- Auto Statewise gst tax template ([#23832](https://github.com/frappe/erpnext/pull/23832)) +- On save sequence id column override with workstation ([#23812](https://github.com/frappe/erpnext/pull/23812)) +- Multiple pricing rules are not working on selling side ([#22711](https://github.com/frappe/erpnext/pull/22711)) +- Salary slip popup error ([#24192](https://github.com/frappe/erpnext/pull/24192)) +- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204)) +- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424)) +- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119)) +- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339)) +- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191)) +- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338)) +- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346)) +- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019)) +- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358)) +- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324)) +- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437)) +- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252)) +- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226)) +- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329)) +- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130)) +- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474)) +- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146)) +- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164)) +- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375)) +- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207)) +- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376)) +- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349)) +- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296)) +- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265)) +- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397)) +- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272)) +- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402)) +- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517)) +- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220)) +- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354)) +- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294)) +- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063)) +- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356)) +- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378)) +- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072)) +- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332)) +- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515)) +- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322)) +- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197)) +- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297)) +- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944)) +- Fixed stock and account balance syncing ([#24644](https://github.com/frappe/erpnext/pull/24644)) +- Fixed incorrect stock ledger qty in the stock ledger report and bin ([#24649](https://github.com/frappe/erpnext/pull/24649)) +- Fixed Consolidated Financial Statement report ([#24580](https://github.com/frappe/erpnext/pull/24580)) +- Repost incompleted backdated transactions ([#24991](https://github.com/frappe/erpnext/pull/24991)) +- Unequal debit and credit issue on RCM Invoice ([#24838](https://github.com/frappe/erpnext/pull/24838)) +- Period list for exponential smoothing forecasting report ([#24983](https://github.com/frappe/erpnext/pull/24983)) +- POS Opening Entry with empty balance detail rows ([#24891](https://github.com/frappe/erpnext/pull/24891)) +- Use account_name only in consolidated report ([#24840](https://github.com/frappe/erpnext/pull/24840)) +- Validation of job card in stock entry ([#24882](https://github.com/frappe/erpnext/pull/24882)) +- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24918](https://github.com/frappe/erpnext/pull/24918)) +- TDS check getting checked after reload ([#24973](https://github.com/frappe/erpnext/pull/24973)) +- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900)) +- Allow zero valuation in stock reconciliation ([#24985](https://github.com/frappe/erpnext/pull/24985)) +- Simplified logic for additional salary ([#24907](https://github.com/frappe/erpnext/pull/24907)) +- Allow to select item code in batch naming ([#24825](https://github.com/frappe/erpnext/pull/24825)) +- Membership renewal validation (#24963) ([#24964](https://github.com/frappe/erpnext/pull/24964)) +
    \ No newline at end of file diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 7653a5dfdc..1ae7310747 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -113,10 +113,12 @@ class calculate_taxes_and_totals(object): item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) + if item.discount_amount and not item.discount_percentage: - item.rate -= item.discount_amount + item.rate = item.rate_with_margin - item.discount_amount else: item.discount_amount = item.rate_with_margin - item.rate + elif flt(item.price_list_rate) > 0: item.discount_amount = item.price_list_rate - item.rate elif flt(item.price_list_rate) > 0 and not item.discount_amount: diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index 801c405732..ecf041efd1 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -25,7 +25,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p if not filters: filters = [] - if doctype in ['Supplier Quotation', 'Purchase Invoice', 'Quotation']: + if doctype in ['Supplier Quotation', 'Purchase Invoice']: filters.append((doctype, 'docstatus', '<', 2)) else: filters.append((doctype, 'docstatus', '=', 1)) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py index 5f471ab2e7..6bec301b8e 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py @@ -22,7 +22,7 @@ class ShopifySettings(unittest.TestCase): frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1) # use the fixture data - import_doc(frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json")) + import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json")) frappe.reload_doctype("Customer") frappe.reload_doctype("Sales Order") diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 18a4fe53c4..f3b8a799b3 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -35,7 +35,8 @@ class Attendance(Document): and docstatus != 2 """, (self.employee, getdate(self.attendance_date), self.name)) if res: - frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee)) + frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format( + frappe.bold(self.employee), frappe.bold(self.attendance_date))) def check_leave_record(self): leave_record = frappe.db.sql(""" diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index 04f98d1441..ea25aa720a 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -200,7 +200,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 14:42:47.321368", + "modified": "2021-03-31 22:31:53.746659", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json index dcb587407d..1f6c03f7b6 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json @@ -154,7 +154,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 14:45:27.948207", + "modified": "2021-03-31 22:32:55.492327", "modified_by": "Administrator", "module": "HR", "name": "Leave Encashment", diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py index f5fece8049..303c829eb6 100644 --- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py @@ -187,4 +187,4 @@ def get_job_offer(ja_list): else: ja_joff_map[offer.job_applicant].append(offer) - return ja_joff_map \ No newline at end of file + return ja_joff_map diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 92074c6288..fb26062566 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -433,6 +433,7 @@ def make_material_request(source_name, target_doc=None): def make_stock_entry(source_name, target_doc=None): def update_item(obj, target, source_parent): target.t_warehouse = source_parent.wip_warehouse + target.conversion_factor = 1 def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json index 7b5747e393..7317152565 100644 --- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json @@ -19,7 +19,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:56.197563", + "modified": "2020-06-29 20:25:36.899106", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -53,4 +53,4 @@ "subtitle": "Products, Raw Materials, BOM, Work Order, and more.", "success_message": "Manufacturing module is all set up!", "title": "Let's Set Up the Manufacturing Module." -} \ No newline at end of file +} diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 76d3c415ab..14f1ab84d6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -769,4 +769,5 @@ erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status -erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing \ No newline at end of file +erpnext.patches.v13_0.fix_non_unique_represents_company +erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing diff --git a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py index c26cddbe4e..01a4ae04ad 100644 --- a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py +++ b/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py @@ -1,6 +1,7 @@ import frappe def execute(): + frappe.reload_doc('custom', 'doctype', 'custom_field') company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: return diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py index 9ad48e23b7..c92d52dcec 100644 --- a/erpnext/patches/v13_0/check_is_income_tax_component.py +++ b/erpnext/patches/v13_0/check_is_income_tax_component.py @@ -8,36 +8,39 @@ from erpnext.regional.india.setup import setup def execute(): - doctypes = ['salary_component', - 'Employee Tax Exemption Declaration', - 'Employee Tax Exemption Proof Submission', - 'Employee Tax Exemption Declaration Category', - 'Employee Tax Exemption Proof Submission Detail' - ] + doctypes = ['salary_component', + 'Employee Tax Exemption Declaration', + 'Employee Tax Exemption Proof Submission', + 'Employee Tax Exemption Declaration Category', + 'Employee Tax Exemption Proof Submission Detail', + 'gratuity_rule', + 'gratuity_rule_slab', + 'gratuity_applicable_component' + ] - for doctype in doctypes: - frappe.reload_doc('Payroll', 'doctype', doctype) + for doctype in doctypes: + frappe.reload_doc('Payroll', 'doctype', doctype) - reports = ['Professional Tax Deductions', 'Provident Fund Deductions'] - for report in reports: - frappe.reload_doc('Regional', 'Report', report) - frappe.reload_doc('Regional', 'Report', report) + reports = ['Professional Tax Deductions', 'Provident Fund Deductions'] + for report in reports: + frappe.reload_doc('Regional', 'Report', report) + frappe.reload_doc('Regional', 'Report', report) - if erpnext.get_region() == "India": - setup(patch=True) + if erpnext.get_region() == "India": + setup(patch=True) - if frappe.db.exists("Salary Component", "Income Tax"): - frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1) - if frappe.db.exists("Salary Component", "TDS"): - frappe.db.set_value("Salary Component", "TDS", "is_income_tax_component", 1) + if frappe.db.exists("Salary Component", "Income Tax"): + frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1) + if frappe.db.exists("Salary Component", "TDS"): + frappe.db.set_value("Salary Component", "TDS", "is_income_tax_component", 1) - components = frappe.db.sql("select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1) - for component in components: - frappe.db.set_value("Salary Component", component.name, "is_income_tax_component", 1) + components = frappe.db.sql("select name from `tabSalary Component` where variable_based_on_taxable_salary = 1", as_dict=1) + for component in components: + frappe.db.set_value("Salary Component", component.name, "is_income_tax_component", 1) - if erpnext.get_region() == "India": - if frappe.db.exists("Salary Component", "Provident Fund"): - frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund") - if frappe.db.exists("Salary Component", "Professional Tax"): - frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax") \ No newline at end of file + if erpnext.get_region() == "India": + if frappe.db.exists("Salary Component", "Provident Fund"): + frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund") + if frappe.db.exists("Salary Component", "Professional Tax"): + frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax") \ No newline at end of file diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py index 48d5cb4cc8..59b2e49b26 100644 --- a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py +++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py @@ -11,4 +11,8 @@ def execute(): if not company: return + + frappe.reload_doc('accounts', 'doctype', 'pos_invoice') + frappe.reload_doc('accounts', 'doctype', 'pos_invoice_item') + make_custom_fields() \ No newline at end of file diff --git a/erpnext/patches/v13_0/fix_non_unique_represents_company.py b/erpnext/patches/v13_0/fix_non_unique_represents_company.py new file mode 100644 index 0000000000..61dc824dd4 --- /dev/null +++ b/erpnext/patches/v13_0/fix_non_unique_represents_company.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + frappe.db.sql(""" + update tabCustomer + set represents_company = NULL + where represents_company = '' + """) \ No newline at end of file diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py index 5316c01170..d927524a3c 100644 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py @@ -6,8 +6,8 @@ def execute(): if "Healthcare" not in frappe.get_active_domains(): return - frappe.reload_doc("healthcare", "doctype", "Therapy Session") frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order") + frappe.reload_doc("healthcare", "doctype", "Therapy Session") frappe.reload_doc("healthcare", "doctype", "Clinical Procedure") frappe.reload_doc("healthcare", "doctype", "Patient History Settings") frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py index d7a5c682df..1830bab02b 100644 --- a/erpnext/patches/v13_0/setup_uae_vat_fields.py +++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py @@ -2,11 +2,15 @@ # License: GNU General Public License v3. See license.txt import frappe -from erpnext.regional.united_arab_emirates.setup import setup +from erpnext.regional.united_arab_emirates.setup import setup def execute(): company = frappe.get_all('Company', filters = {'country': 'United Arab Emirates'}) if not company: return - setup() \ No newline at end of file + frappe.reload_doc('regional', 'report', 'uae_vat_201') + frappe.reload_doc('regional', 'doctype', 'uae_vat_settings') + frappe.reload_doc('regional', 'doctype', 'uae_vat_account') + + setup() diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json index 61ae7e4c2f..5e17a5cbb7 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.json +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json @@ -175,7 +175,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 14:45:48.566756", + "modified": "2021-03-31 22:33:59.098532", "modified_by": "Administrator", "module": "Payroll", "name": "Additional Salary", diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json index c6f764ccdb..83326975b0 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json @@ -147,7 +147,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 14:46:22.465521", + "modified": "2021-03-31 22:35:08.940087", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Benefit Application", diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json index e331b7af93..b3bac01818 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json @@ -144,7 +144,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 15:51:51.489269", + "modified": "2021-03-31 22:37:21.024625", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Benefit Claim", diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json index 51346c6c7d..0d10b2c19a 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json @@ -94,7 +94,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 14:48:00.919839", + "modified": "2021-03-31 22:38:20.332316", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Incentive", diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json index 873bf887bf..b247d266ae 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json @@ -119,7 +119,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 20:41:57.387749", + "modified": "2021-03-31 22:39:59.237361", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Tax Exemption Declaration", diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json index f32202a3bd..77b107ef4a 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json @@ -142,7 +142,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 20:48:32.639885", + "modified": "2021-03-31 22:41:13.723339", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Tax Exemption Proof Submission", diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json index c343a44326..5a7de37bec 100644 --- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json +++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json @@ -104,7 +104,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 20:53:33.323712", + "modified": "2021-03-31 22:42:08.139520", "modified_by": "Administrator", "module": "Payroll", "name": "Income Tax Slab", diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index fde2e0776e..4c9469e277 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -567,6 +567,7 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True): if publish_progress: frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)), title = _("Creating Salary Slips...")) + else: salary_slips_not_created.append(emp) diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json index cd563bc404..7ea6210c7a 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json @@ -105,7 +105,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 14:50:29.401020", + "modified": "2021-03-31 22:43:28.363644", "modified_by": "Administrator", "module": "Payroll", "name": "Retention Bonus", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index e3993fae3a..a0ddd39ca2 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -119,6 +119,7 @@ frappe.ui.form.on("Salary Slip", { frm.set_df_property('exchange_rate', 'hidden', 1); frm.set_df_property("exchange_rate", "description", "" ); } + } } }, diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index ec5607602d..42a0f290cb 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -631,7 +631,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-03-31 15:39:28.817166", + "modified": "2021-03-31 22:44:09.772331", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index f6d4c7b855..afdf081ac8 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -598,10 +598,10 @@ class SalarySlip(TransactionBase): continue if ( - not d.additional_salary - and (not additional_salary or additional_salary.overwrite) - or additional_salary - and additional_salary.name == d.additional_salary + (not d.additional_salary + and (not additional_salary or additional_salary.overwrite)) + or (additional_salary + and additional_salary.name == d.additional_salary) ): component_row = d break @@ -611,7 +611,7 @@ class SalarySlip(TransactionBase): self.set(component_type, [ d for d in self.get(component_type) if d.salary_component != component_data.salary_component - or d.additional_salary and additional_salary.name != d.additional_salary + or (d.additional_salary and additional_salary.name != d.additional_salary) or d == component_row ]) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 7672695653..01e4170d31 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -312,7 +312,7 @@ class TestSalarySlip(unittest.TestCase): frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'") create_salary_slips_for_payroll_period(applicant, salary_structure.name, - payroll_period, deduct_random=False) + payroll_period, deduct_random=False, num=6) salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name': 'test_ytd@salary.com'}, order_by = 'posting_date') diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index f2fb558a14..36387f23df 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -164,7 +164,13 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non salary_structure_assignment.employee = employee salary_structure_assignment.base = 50000 salary_structure_assignment.variable = 5000 - salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1) + + if getdate(nowdate()).day == 1: + date = from_date or nowdate() + else: + date = from_date or add_days(nowdate(), -1) + + salary_structure_assignment.from_date = date salary_structure_assignment.salary_structure = salary_structure salary_structure_assignment.currency = currency salary_structure_assignment.payroll_payable_account = get_payable_account(company) diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json index 50fabedb42..c8b98e5aaf 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json @@ -145,7 +145,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 15:49:36.361253", + "modified": "2021-03-31 22:44:46.267974", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Structure Assignment", diff --git a/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json b/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json index 4bae67546c..b1a7cc2734 100644 --- a/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json +++ b/erpnext/payroll/onboarding_step/create_payroll_period/create_payroll_period.json @@ -8,7 +8,7 @@ "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-06-01 11:53:54.553947", + "modified": "2020-06-29 11:53:54.553947", "modified_by": "Administrator", "name": "Create Payroll Period", "owner": "Administrator", diff --git a/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json b/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json index 946b8c8707..a7cf7bf988 100644 --- a/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/onboarding_step/payroll_settings/payroll_settings.json @@ -1,19 +1,19 @@ { - "action": "Go to Page", + "action": "Update Settings", "creation": "2020-06-04 16:34:29.664917", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_mandatory": 0, - "is_single": 0, + "is_single": 1, "is_skipped": 0, - "modified": "2020-06-04 16:34:29.664917", + "modified": "2020-06-29 16:34:29.664917", "modified_by": "Administrator", "name": "Payroll Settings", "owner": "Administrator", - "path": "#Form/Payroll Settings", + "reference_document": "Payroll Settings", "show_full_form": 0, "title": "Payroll Settings", - "validate_action": 1 + "validate_action": 0 } \ No newline at end of file diff --git a/erpnext/public/js/website_theme.js b/erpnext/public/js/website_theme.js new file mode 100644 index 0000000000..0009cacf61 --- /dev/null +++ b/erpnext/public/js/website_theme.js @@ -0,0 +1,14 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.ui.form.on('Website Theme', { + validate(frm) { + let theme_scss = frm.doc.theme_scss; + if (theme_scss && theme_scss.includes('frappe/public/scss/website') + && !theme_scss.includes('erpnext/public/scss/website') + ) { + frm.set_value('theme_scss', + `${frm.doc.theme_scss}\n@import "erpnext/public/scss/website";`); + } + } +}); diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index d6047e863c..ac1f543488 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -3,4 +3,17 @@ import frappe def setup(company=None, patch=True): - pass + add_custom_roles_for_reports() + + +def add_custom_roles_for_reports(): + """Add Access Control to UAE VAT 201.""" + if not frappe.db.get_value('Custom Role', dict(report='DATEV')): + frappe.get_doc(dict( + doctype='Custom Role', + report='DATEV', + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager') + ] + )).insert() \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index c1a222adb4..8d682beec3 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -46,7 +46,7 @@ erpnext.setup_einvoice_actions = (doctype) => { "default": "1-Duplicate", "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - { + { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", @@ -61,7 +61,7 @@ erpnext.setup_einvoice_actions = (doctype) => { const data = d.get_values(); frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', - args: { + args: { doctype, docname: name, irn: irn, @@ -235,7 +235,7 @@ const get_preview_dialog = (frm, action) => { title: __("Preview"), size: "large", fields: [ - { + { "label": "Preview", "fieldname": "preview_html", "fieldtype": "HTML" diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index fb1f46493f..605f4e1613 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -666,7 +666,6 @@ class GSPConnector(): except Exception: log_error() self.raise_error(True) - @staticmethod def get_gstin_details(gstin): '''fetch and cache GSTIN details''' @@ -959,7 +958,6 @@ class GSPConnector(): 'label': _('IRN Generated') } self.update_invoice() - def attach_qrcode_image(self): qrcode = self.invoice.signed_qr_code doctype = self.invoice.doctype diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index ec58fd2f33..9ded8dab5b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -12,14 +12,14 @@ from erpnext.accounts.utils import get_fiscal_year, FiscalYearError from frappe.utils import today def setup(company=None, patch=True): - setup_company_independent_fixtures() + setup_company_independent_fixtures(patch=patch) if not patch: make_fixtures(company) # TODO: for all countries -def setup_company_independent_fixtures(): +def setup_company_independent_fixtures(patch=False): make_custom_fields() - make_property_setters() + make_property_setters(patch=patch) add_permissions() add_custom_roles_for_reports() frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) @@ -112,10 +112,11 @@ def add_print_formats(): frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0) frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0) -def make_property_setters(): +def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters - make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') - make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + if not patch: + make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') + make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', diff --git a/erpnext/regional/india/test_utils.py b/erpnext/regional/india/test_utils.py index 7ce27f6cf5..a16f56c704 100644 --- a/erpnext/regional/india/test_utils.py +++ b/erpnext/regional/india/test_utils.py @@ -12,14 +12,14 @@ class TestIndiaUtils(unittest.TestCase): mock_get_cached.return_value = "India" # mock country posting_date = "2021-05-01" - invalid_names = [ "SI$1231", "012345678901234567", "SI 2020 05", - "SI.2020.0001", "PI2021 - 001" ] + invalid_names = ["SI$1231", "012345678901234567", "SI 2020 05", + "SI.2020.0001", "PI2021 - 001"] for name in invalid_names: doc = frappe._dict(name=name, posting_date=posting_date) self.assertRaises(frappe.ValidationError, validate_document_name, doc) - valid_names = [ "012345678901236", "SI/2020/0001", "SI/2020-0001", - "2020-PI-0001", "PI2020-0001" ] + valid_names = ["012345678901236", "SI/2020/0001", "SI/2020-0001", + "2020-PI-0001", "PI2020-0001"] for name in valid_names: doc = frappe._dict(name=name, posting_date=posting_date) try: diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 7709ddf42c..6338056698 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -155,6 +155,7 @@ def set_place_of_supply(doc, method=None): def validate_document_name(doc, method=None): """Validate GST invoice number requirements.""" + country = frappe.get_cached_value("Company", doc.company, "country") # Date was chosen as start of next FY to avoid irritating current users. diff --git a/erpnext/regional/report/datev/datev.json b/erpnext/regional/report/datev/datev.json index 80a866cbf5..94e3960ead 100644 --- a/erpnext/regional/report/datev/datev.json +++ b/erpnext/regional/report/datev/datev.json @@ -1,29 +1,22 @@ { - "add_total_row": 0, - "apply_user_permissions": 0, - "creation": "2019-04-24 08:45:16.650129", - "disabled": 0, - "icon": "octicon octicon-repo-pull", - "color": "#4CB944", - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "module": "Regional", - "name": "DATEV", - "owner": "Administrator", - "ref_doctype": "GL Entry", - "report_name": "DATEV", - "report_type": "Script Report", - "roles": [ - { - "role": "Accounts User" - }, - { - "role": "Accounts Manager" - }, - { - "role": "Auditor" - } - ] -} + "add_total_row": 0, + "columns": [], + "creation": "2019-04-24 08:45:16.650129", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-06 12:23:00.379517", + "modified_by": "Administrator", + "module": "Regional", + "name": "DATEV", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "DATEV", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 7d5e84df52..cd94ee101a 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -212,7 +212,8 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Represents Company", - "options": "Company" + "options": "Company", + "unique": 1 }, { "depends_on": "represents_company", diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 9ab9eefa30..11a63b3d4a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -7,7 +7,6 @@ erpnext.PointOfSale.ItemCart = class { this.allowed_customer_groups = settings.customer_groups; this.allow_rate_change = settings.allow_rate_change; this.allow_discount_change = settings.allow_discount_change; - this.init_component(); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index cb0a0103e0..32a4556766 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -201,7 +201,6 @@ erpnext.PointOfSale.ItemDetails = class { 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); }); 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 index b10a9e33c5..a5a739cff9 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -176,6 +176,14 @@ erpnext.PointOfSale.PastOrderSummary = class { this.show_summary_placeholder(); }); + this.$summary_container.on('click', '.delete-btn', () => { + this.events.delete_order(this.doc.name); + this.show_summary_placeholder(); + // 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); diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 22a279d463..600f160490 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -252,6 +252,41 @@ erpnext.PointOfSale.Payment = class { } } + setup_listener_for_payments() { + frappe.realtime.on("process_phone_payment", (data) => { + const doc = this.events.get_frm().doc; + const { response, amount, success, failure_message } = data; + let message, title; + + if (success) { + title = __("Payment Received"); + if (amount >= doc.grand_total) { + frappe.dom.unfreeze(); + message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]); + this.events.submit_invoice(); + cur_frm.reload_doc(); + + } else { + message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]); + } + } else if (failure_message) { + message = failure_message; + title = __("Payment Failed"); + } + + frappe.msgprint({ "message": message, "title": title }); + }); + } + + auto_set_remaining_amount() { + const doc = this.events.get_frm().doc; + const remaining_amount = doc.grand_total - doc.paid_amount; + const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined; + if (!current_value && remaining_amount > 0 && this.selected_mode) { + this.selected_mode.set_value(remaining_amount); + } + } + attach_shortcuts() { const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`); diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 681d161edc..8515db3300 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -112,9 +112,7 @@ def place_order(): def request_for_quotation(): quotation = _get_cart_quotation() quotation.flags.ignore_permissions = True - quotation.save() - if not get_shopping_cart_settings().save_quotations_as_draft: - quotation.submit() + quotation.submit() return quotation.name @frappe.whitelist() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 42627f91f6..ef7d54ac96 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -558,7 +558,6 @@ frappe.ui.form.on('Stock Entry', { }) ); } - for (let i in frm.doc.items) { let item = frm.doc.items[i]; diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 349d8ae679..3296f5ba4a 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -34,7 +34,7 @@ class TestStockLedgerEntry(unittest.TestCase): qty=50, rate=100, company=company, - expense_account = "Stock Adjustment - _TC", + expense_account = "Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-10', posting_time='14:00' ) @@ -46,7 +46,7 @@ class TestStockLedgerEntry(unittest.TestCase): qty=10, rate=200, company=company, - expense_account = "Stock Adjustment - _TC", + expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-20', posting_time='14:00' ) @@ -58,7 +58,7 @@ class TestStockLedgerEntry(unittest.TestCase): target="Finished Goods - _TC", company=company, qty=10, - expense_account="Stock Adjustment - _TC", + expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-30', posting_time='14:00' ) @@ -90,7 +90,7 @@ class TestStockLedgerEntry(unittest.TestCase): qty=50, rate=150, company=company, - expense_account = "Stock Adjustment - _TC", + expense_account ="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC", posting_date='2020-04-12', posting_time='14:00' ) diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index f5adbf01e3..167c848eff 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -11,7 +11,7 @@ ({{ product_info.price.formatted_price }} / {{ product_info.uom }})
    {% else %} - {{ _("Unit of Measurement") }} : {{ product_info.uom }} + {{ _("UOM") }} : {{ product_info.uom }} {% endif %} {% if cart_settings.show_stock_availability %} diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index 930d0c2613..383413103e 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -14,11 +14,7 @@
    - {% if doc.doctype == "Quotation" and not doc.docstatus %} - {{ _("Pending") }} - {% else %} - {{ doc.get_formatted("grand_total") }} - {% endif %} + {{ doc.get_formatted("grand_total") }}
    Link diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 07dd676e77..28faea8f4f 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -12,21 +12,22 @@ {% endblock %} {% block header_actions %} - + + {% endblock %} {% block page_content %} diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index c8ae73365b..b575855aed 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -7,7 +7,6 @@ import frappe.share from frappe import _ from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_datetime, get_link_to_form, date_diff, nowdate from erpnext.controllers.status_updater import StatusUpdater -from erpnext.accounts.utils import get_fiscal_year from six import string_types From dbb76a7d0080ce0bab5e4e087c2923872b9d4949 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 13 Apr 2021 18:49:03 +0530 Subject: [PATCH 0483/1264] fix(e-invoicing): validations & tax calculation fixes (#25314) * fix: GST on freight charge in e-invoicing * feat(india): bulk e-invoice generation (#24969) * fix: cannot fetch e invoice settings * fix: patch condition (#25301) * fix: patch condition * fix: except einvoice loading error seperately * fix: json.loads error Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../sales_invoice/regional/india_list.js | 147 +++- erpnext/hooks.py | 5 +- erpnext/patches.txt | 6 +- .../add_company_link_to_einvoice_settings.py | 16 + .../v12_0/add_einvoice_status_field.py | 69 ++ ...add_einvoice_summary_report_permissions.py | 18 + .../v12_0/create_taxable_value_field.py | 18 + .../update_vehicle_no_reqd_condition.py | 0 .../e_invoice_settings.json | 10 +- .../e_invoice_user/e_invoice_user.json | 11 +- erpnext/regional/india/e_invoice/einvoice.js | 54 +- erpnext/regional/india/e_invoice/utils.py | 648 +++++++++++++----- erpnext/regional/india/setup.py | 37 +- erpnext/regional/india/utils.py | 60 +- .../report/e_invoice_summary/__init__.py | 0 .../e_invoice_summary/e_invoice_summary.js | 55 ++ .../e_invoice_summary/e_invoice_summary.json | 28 + .../e_invoice_summary/e_invoice_summary.py | 106 +++ 18 files changed, 1046 insertions(+), 242 deletions(-) create mode 100644 erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py create mode 100644 erpnext/patches/v12_0/add_einvoice_status_field.py create mode 100644 erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py create mode 100644 erpnext/patches/v12_0/create_taxable_value_field.py rename erpnext/patches/{v13_0 => v12_0}/update_vehicle_no_reqd_condition.py (100%) create mode 100644 erpnext/regional/report/e_invoice_summary/__init__.py create mode 100644 erpnext/regional/report/e_invoice_summary/e_invoice_summary.js create mode 100644 erpnext/regional/report/e_invoice_summary/e_invoice_summary.json create mode 100644 erpnext/regional/report/e_invoice_summary/e_invoice_summary.py diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js index 3e1c5228ea..ada665a0ca 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js @@ -1,14 +1,14 @@ var globalOnload = frappe.listview_settings['Sales Invoice'].onload; -frappe.listview_settings['Sales Invoice'].onload = function (doclist) { +frappe.listview_settings['Sales Invoice'].onload = function (list_view) { // Provision in case onload event is added to sales_invoice.js in future if (globalOnload) { - globalOnload(doclist); + globalOnload(list_view); } const action = () => { - const selected_docs = doclist.get_checked_items(); - const docnames = doclist.get_checked_items(true); + const selected_docs = list_view.get_checked_items(); + const docnames = list_view.get_checked_items(true); for (let doc of selected_docs) { if (doc.docstatus !== 1) { @@ -19,7 +19,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) { frappe.call({ method: 'erpnext.regional.india.utils.generate_ewb_json', args: { - 'dt': doclist.doctype, + 'dt': list_view.doctype, 'dn': docnames }, callback: function(r) { @@ -35,5 +35,140 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) { }); }; - doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false); + list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false); + + const generate_irns = () => { + const docnames = list_view.get_checked_items(true); + if (docnames && docnames.length) { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices', + args: { docnames }, + freeze: true, + freeze_message: __('Generating E-Invoices...') + }); + } else { + frappe.msgprint({ + message: __('Please select at least one sales invoice to generate IRN'), + title: __('No Invoice Selected'), + indicator: 'red' + }); + } + }; + + const cancel_irns = () => { + const docnames = list_view.get_checked_items(true); + + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irns', + args: { + doctype: list_view.doctype, + docnames, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + freeze_message: __('Cancelling E-Invoices...'), + }); + d.hide(); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + let einvoicing_enabled = false; + frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => { + einvoicing_enabled = enabled; + }); + + list_view.$result.on("change", "input[type=checkbox]", () => { + if (einvoicing_enabled) { + const docnames = list_view.get_checked_items(true); + // show/hide e-invoicing actions when no sales invoices are checked + if (docnames && docnames.length) { + // prevent adding actions twice if e-invoicing action group already exists + if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) { + list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing')); + list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing')); + } + } else { + list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing')); + list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing')); + } + } + }); + + frappe.realtime.on("bulk_einvoice_generation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices generated successfully', [invoices.length]), + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to generate IRNs for following ${failures.length} sales invoices: +
      + ${failures.map(d => `
    • ${d.docname}
    • `).join('')} +
    + `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + }); + + frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices cancelled successfully', [invoices.length]), + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to cancel IRNs for following ${failures.length} sales invoices: +
      + ${failures.map(d => `
    • ${d.docname}
    • `).join('')} +
    + `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + }); }; \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9b9a0dae2e..5d091ddfbc 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -256,7 +256,10 @@ doc_events = { "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" ], - "on_trash": "erpnext.regional.check_deletion_permission" + "on_trash": "erpnext.regional.check_deletion_permission", + "validate": [ + "erpnext.regional.india.utils.update_taxable_values" + ] }, "Purchase Invoice": { "validate": [ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 09d4b2290c..6d5b4264fb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -756,9 +756,13 @@ erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes -erpnext.patches.v13_0.update_vehicle_no_reqd_condition +erpnext.patches.v12_0.update_vehicle_no_reqd_condition +erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17 +erpnext.patches.v12_0.add_einvoice_summary_report_permissions erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields erpnext.patches.v13_0.fix_non_unique_represents_company +erpnext.patches.v12_0.create_taxable_value_field +erpnext.patches.v12_0.add_company_link_to_einvoice_settings diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py new file mode 100644 index 0000000000..b6bd5fa311 --- /dev/null +++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company or not frappe.db.count('E Invoice User'): + return + + frappe.reload_doc("regional", "doctype", "e_invoice_user") + for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']): + company_name = frappe.db.sql(""" + select dl.link_name from `tabAddress` a, `tabDynamic Link` dl + where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company' + """, (creds.get('gstin'))) + if company_name and len(company_name) > 0: + frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0]) \ No newline at end of file diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py new file mode 100644 index 0000000000..387e88588d --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_status_field.py @@ -0,0 +1,69 @@ +from __future__ import unicode_literals +import json +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # move hidden einvoice fields to a different section + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), + + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + + if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'): + frappe.db.sql(''' + UPDATE `tabSales Invoice` SET einvoice_status = 'Pending' + WHERE + posting_date >= '2021-04-01' + AND ifnull(irn, '') = '' + AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '') + AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export') + ''') + + # set appropriate statuses + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated' + WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''') + + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled' + WHERE ifnull(irn_cancelled, 0) = 1''') + + # set correct acknowledgement in e-invoices + einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice']) + + if einvoices: + for inv in einvoices: + signed_einvoice = inv.get('signed_einvoice') + if signed_einvoice: + signed_einvoice = json.loads(signed_einvoice) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py new file mode 100644 index 0000000000..bf8f566d32 --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + if frappe.db.exists('Report', 'E-Invoice Summary') and \ + not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')): + frappe.get_doc(dict( + doctype='Custom Role', + report='E-Invoice Summary', + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager') + ] + )).insert() \ No newline at end of file diff --git a/erpnext/patches/v12_0/create_taxable_value_field.py b/erpnext/patches/v12_0/create_taxable_value_field.py new file mode 100644 index 0000000000..a0c9fcf4cb --- /dev/null +++ b/erpnext/patches/v12_0/create_taxable_value_field.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice Item': [ + dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + ] + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py similarity index 100% rename from erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py rename to erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index db8bda75bf..68ed3391d0 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -8,6 +8,7 @@ "enable", "section_break_2", "sandbox_mode", + "applicable_from", "credentials", "auth_token", "token_expiry" @@ -48,12 +49,19 @@ "fieldname": "sandbox_mode", "fieldtype": "Check", "label": "Sandbox Mode" + }, + { + "fieldname": "applicable_from", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Applicable From", + "reqd": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-13 12:04:49.449199", + "modified": "2021-03-30 12:26:25.538294", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json index dd9d99773a..a65b1ca7ca 100644 --- a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json @@ -5,6 +5,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "company", "gstin", "username", "password" @@ -30,12 +31,20 @@ "in_list_view": 1, "label": "Password", "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-22 15:10:53.466205", + "modified": "2021-03-22 12:16:56.365616", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice User", diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 48dc70638f..8d682beec3 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -1,13 +1,13 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.ui.form.on(doctype, { async refresh(frm) { - const { message } = await frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); - const einvoicing_enabled = cint(message.enable); - const supply_type = frm.doc.gst_category; - const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); - const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin; + const res = await frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility', + args: { doc: frm.doc } + }); + const invoice_eligible = res.message; - if (cint(einvoicing_enabled) == 0 || !valid_supply_type || company_transaction) return; + if (!invoice_eligible) return; const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; @@ -110,45 +110,25 @@ erpnext.setup_einvoice_actions = (doctype) => { } if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { - const fields = [ - { - "label": "Reason", - "fieldname": "reason", - "fieldtype": "Select", - "reqd": 1, - "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", - "fieldname": "remark", - "fieldtype": "Data", - "reqd": 1 - } - ]; const action = () => { - const d = new frappe.ui.Dialog({ - title: __('Cancel E-Way Bill'), - fields: fields, + let message = __('Cancellation of e-way bill is currently not supported. '); + message += '

    '; + message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); + + frappe.msgprint({ + title: __('Update E-Way Bill Cancelled Status?'), + message: message, + indicator: 'orange', primary_action: function() { - const data = d.get_values(); frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', - args: { - doctype, - docname: name, - eway_bill: ewaybill, - reason: data.reason.split('-')[0], - remark: data.remark - }, + args: { doctype, docname: name }, freeze: true, - callback: () => frm.reload_doc() || d.hide(), - error: () => d.hide() + callback: () => frm.reload_doc() }); }, - primary_action_label: __('Submit') + primary_action_label: __('Yes') }); - d.show(); }; add_custom_button(__("Cancel E-Way Bill"), action); } diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 6267635f60..baec796b75 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -15,18 +15,43 @@ import traceback import io from frappe import _, bold from pyqrcode import create as qrcreate +from frappe.utils.background_jobs import enqueue +from frappe.utils.scheduler import is_scheduler_inactive +from frappe.core.page.background_jobs.background_jobs import get_info from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form +from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form, getdate, time_diff_in_hours + +@frappe.whitelist() +def validate_eligibility(doc): + if isinstance(doc, six.string_types): + doc = json.loads(doc) + + invalid_doctype = doc.get('doctype') != 'Sales Invoice' + if invalid_doctype: + return False + + einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable')) + if not einvoicing_enabled: + return False + + einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' + if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): + return False -def validate_einvoice_fields(doc): - einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) - invalid_doctype = doc.doctype != 'Sales Invoice' invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - - if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied: + + if invalid_supply_type or company_transaction or no_taxes_applied: + return False + + return True + +def validate_einvoice_fields(doc): + invoice_eligible = validate_eligibility(doc) + + if not invoice_eligible: return if doc.docstatus == 0 and doc._action == 'save': @@ -35,6 +60,8 @@ def validate_einvoice_fields(doc): if len(doc.name) > 16: raise_document_name_too_long_error() + doc.einvoice_status = 'Pending' + elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) @@ -76,6 +103,9 @@ def get_transaction_details(invoice): )) def get_doc_details(invoice): + if getdate(invoice.posting_date) < getdate('2021-01-01'): + frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed')) + invoice_type = 'CRN' if invoice.is_return else 'INV' invoice_name = invoice.name @@ -87,53 +117,38 @@ def get_doc_details(invoice): invoice_date=invoice_date )) -def get_party_details(address_name): - d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - - if (not d.gstin - or not d.city - or not d.pincode - or not d.address_title - or not d.address_line1 - or not d.gst_state_number): +def validate_address_fields(address, is_shipping_address): + if ((not address.gstin and not is_shipping_address) + or not address.city + or not address.pincode + or not address.address_title + or not address.address_line1 + or not address.gst_state_number): frappe.throw( - msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format( - get_link_to_form('Address', address_name) - ), + msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name), title=_('Missing Address Fields') ) - if d.gst_state_number == 97: - # according to einvoice standard - pincode = 999999 +def get_party_details(address_name, is_shipping_address=False): + addr = frappe.get_doc('Address', address_name) + + validate_address_fields(addr, is_shipping_address) - return frappe._dict(dict( - gstin=d.gstin, - legal_name=sanitize_for_json(d.address_title), - location=sanitize_for_json(d.city), - pincode=d.pincode, - state_code=d.gst_state_number, - address_line1=sanitize_for_json(d.address_line1), - address_line2=sanitize_for_json(d.address_line2) + if addr.gst_state_number == 97: + # according to einvoice standard + addr.pincode = 999999 + + party_address_details = frappe._dict(dict( + legal_name=sanitize_for_json(addr.address_title), + location=sanitize_for_json(addr.city), + pincode=addr.pincode, gstin=addr.gstin, + state_code=addr.gst_state_number, + address_line1=sanitize_for_json(addr.address_line1), + address_line2=sanitize_for_json(addr.address_line2) )) -def get_gstin_details(gstin): - if not hasattr(frappe.local, 'gstin_cache'): - frappe.local.gstin_cache = {} - - key = gstin - details = frappe.local.gstin_cache.get(key) - if details: - return details - - details = frappe.cache().hget('gstin_cache', key) - if details: - frappe.local.gstin_cache[key] = details - return details - - if not details: - return GSPConnector.get_gstin_details(gstin) + return party_address_details def get_overseas_address_details(address_name): address_title, address_line1, address_line2, city = frappe.db.get_value( @@ -169,10 +184,15 @@ def get_item_list(invoice): item.description = sanitize_for_json(d.item_name) item.qty = abs(item.qty) - item.discount_amount = 0 - item.unit_rate = abs(item.base_net_amount / item.qty) - item.gross_amount = abs(item.base_net_amount) - item.taxable_value = abs(item.base_net_amount) + + if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: + item.discount_amount = abs(item.base_amount - item.base_net_amount) + else: + item.discount_amount = 0 + + item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty) + item.gross_amount = abs(item.taxable_value) + item.discount_amount + item.taxable_value = abs(item.taxable_value) item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None @@ -205,11 +225,11 @@ def update_item_taxes(invoice, item): is_applicable = t.tax_amount and t.account_head in gst_accounts_list if is_applicable: # this contains item wise tax rate & tax amount (incl. discount) - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name) item_tax_rate = item_tax_detail[0] # item tax amount excluding discount amount - item_tax_amount = (item_tax_rate / 100) * item.base_net_amount + item_tax_amount = (item_tax_rate / 100) * item.taxable_value if t.account_head in gst_accounts.cess_account: item_tax_amount_after_discount = item_tax_detail[1] @@ -223,6 +243,9 @@ def update_item_taxes(invoice, item): if t.account_head in gst_accounts[f'{tax_type}_account']: item.tax_rate += item_tax_rate item[f'{tax_type}_amount'] += abs(item_tax_amount) + else: + # TODO: other charges per item + pass return item @@ -230,10 +253,14 @@ def get_invoice_value_details(invoice): invoice_value_details = frappe._dict(dict()) if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: - invoice_value_details.base_total = abs(invoice.base_total) - invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount) + # Discount already applied on net total which means on items + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) + invoice_value_details.invoice_discount_amt = 0 + elif invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount: + invoice_value_details.invoice_discount_amt = invoice.base_discount_amount + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) else: - invoice_value_details.base_total = abs(invoice.base_net_total) + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) # since tax already considers discount amount invoice_value_details.invoice_discount_amt = 0 @@ -254,7 +281,11 @@ def update_invoice_taxes(invoice, invoice_value_details): invoice_value_details.total_igst_amt = 0 invoice_value_details.total_cess_amt = 0 invoice_value_details.total_other_charges = 0 + considered_rows = [] + for t in invoice.taxes: + tax_amount = t.base_tax_amount if (invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount) \ + else t.base_tax_amount_after_discount_amount if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: # using after discount amt since item also uses after discount amt for cess calc @@ -262,12 +293,26 @@ def update_invoice_taxes(invoice, invoice_value_details): for tax_type in ['igst', 'cgst', 'sgst']: if t.account_head in gst_accounts[f'{tax_type}_account']: - invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount) + + invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount) + update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows) else: - invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_other_charges += abs(tax_amount) return invoice_value_details +def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows): + prev_row_id = cint(tax_row.row_id) - 1 + if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows: + if tax_row.charge_type == 'On Previous Row Amount': + amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount + invoice_value_details.total_other_charges -= abs(amount) + considered_rows.append(prev_row_id) + if tax_row.charge_type == 'On Previous Row Total': + amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total + invoice_value_details.total_other_charges -= abs(amount) + considered_rows.append(prev_row_id) + def get_payment_details(invoice): payee_name = invoice.company mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments]) @@ -280,6 +325,10 @@ def get_payment_details(invoice): )) def get_return_doc_reference(invoice): + if not invoice.return_against: + frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.') + .format(frappe.bold('Return Against')), title=_('Missing Field')) + invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') return frappe._dict(dict( invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') @@ -287,7 +336,11 @@ def get_return_doc_reference(invoice): def get_eway_bill_details(invoice): if invoice.is_return: - frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed')) + frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'), + title=_('Invalid Fields')) + + if not invoice.distance: + frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field')) mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } @@ -305,9 +358,15 @@ def get_eway_bill_details(invoice): def validate_mandatory_fields(invoice): if not invoice.company_address: - frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields')) + frappe.throw( + _('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'), + title=_('Missing Fields') + ) if not invoice.customer_address: - frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields')) + frappe.throw( + _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'), + title=_('Missing Fields') + ) if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): frappe.throw( _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), @@ -319,6 +378,39 @@ def validate_mandatory_fields(invoice): title=_('Missing Fields') ) +def validate_totals(einvoice): + item_list = einvoice['ItemList'] + value_details = einvoice['ValDtls'] + + total_item_ass_value = 0 + total_item_cgst_value = 0 + total_item_sgst_value = 0 + total_item_igst_value = 0 + total_item_value = 0 + for item in item_list: + total_item_ass_value += flt(item['AssAmt']) + total_item_cgst_value += flt(item['CgstAmt']) + total_item_sgst_value += flt(item['SgstAmt']) + total_item_igst_value += flt(item['IgstAmt']) + total_item_value += flt(item['TotItemVal']) + + if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1: + frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx)) + + if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: + frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) + + if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1: + frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) + + calculated_invoice_value = \ + flt(value_details['AssVal']) + flt(value_details['CgstVal']) \ + + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \ + + flt(value_details['OthChrg']) - flt(value_details['Discount']) + + if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1: + frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.')) + def make_einvoice(invoice): validate_mandatory_fields(invoice) @@ -334,24 +426,30 @@ def make_einvoice(invoice): buyer_details = get_overseas_address_details(invoice.customer_address) else: buyer_details = get_party_details(invoice.customer_address) - place_of_supply = get_place_of_supply(invoice, invoice.doctype) or sanitize_for_json(invoice.billing_address_gstin) - place_of_supply = place_of_supply[:2] + place_of_supply = get_place_of_supply(invoice, invoice.doctype) + if place_of_supply: + place_of_supply = place_of_supply.split('-')[0] + else: + place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2] buyer_details.update(dict(place_of_supply=place_of_supply)) + seller_details.update(dict(legal_name=invoice.company)) + buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer)) + shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: if invoice.gst_category == 'Overseas': shipping_details = get_overseas_address_details(invoice.shipping_address_name) else: - shipping_details = get_party_details(invoice.shipping_address_name) + shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True) if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) - if invoice.is_return and invoice.return_against: + if invoice.is_return: prev_doc_details = get_return_doc_reference(invoice) - if invoice.transporter: + if invoice.transporter and flt(invoice.distance) and not invoice.is_return: eway_bill_details = get_eway_bill_details(invoice) # not yet implemented @@ -364,18 +462,73 @@ def make_einvoice(invoice): period_details=period_details, prev_doc_details=prev_doc_details, export_details=export_details, eway_bill_details=eway_bill_details ) - einvoice = safe_json_load(einvoice) - validations = json.loads(read_json('einv_validation')) - errors = validate_einvoice(validations, einvoice) - if errors: - message = "\n".join([ - "E Invoice: ", json.dumps(einvoice, indent=4), - "-" * 50, - "Errors: ", json.dumps(errors, indent=4) - ]) - frappe.log_error(title="E Invoice Validation Failed", message=message) - frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1) + try: + einvoice = safe_json_load(einvoice) + einvoice = santize_einvoice_fields(einvoice) + except Exception: + show_link_to_error_log(invoice, einvoice) + + validate_totals(einvoice) + + return einvoice + +def show_link_to_error_log(invoice, einvoice): + err_log = log_error(einvoice) + link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log') + frappe.throw( + _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format( + invoice.name, link_to_error_log), + title=_('E Invoice Creation Failed') + ) + +def log_error(data=None): + if isinstance(data, six.string_types): + data = json.loads(data) + + seperator = "--" * 50 + err_tb = traceback.format_exc() + err_msg = str(sys.exc_info()[1]) + data = json.dumps(data, indent=4) + + message = "\n".join([ + "Error", err_msg, seperator, + "Data:", data, seperator, + "Exception:", err_tb + ]) + frappe.log_error(title=_('E Invoice Request Failed'), message=message) + +def santize_einvoice_fields(einvoice): + int_fields = ["Pin","Distance","CrDay"] + float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",] + copy = einvoice.copy() + for key, value in copy.items(): + if isinstance(value, list): + for idx, d in enumerate(value): + santized_dict = santize_einvoice_fields(d) + if santized_dict: + einvoice[key][idx] = santized_dict + else: + einvoice[key].pop(idx) + + if not einvoice[key]: + einvoice.pop(key, None) + + elif isinstance(value, dict): + santized_dict = santize_einvoice_fields(value) + if santized_dict: + einvoice[key] = santized_dict + else: + einvoice.pop(key, None) + + elif not value or value == "None": + einvoice.pop(key, None) + + elif key in float_fields: + einvoice[key] = flt(value, 2) + + elif key in int_fields: + einvoice[key] = cint(value) return einvoice @@ -391,70 +544,22 @@ def safe_json_load(json_string): snippet = json_string[start:end] frappe.throw(_("Error in input data. Please check for any special characters near following input:
    {}").format(snippet)) -def validate_einvoice(validations, einvoice, errors=[]): - for fieldname, field_validation in validations.items(): - value = einvoice.get(fieldname, None) - if not value or value == "None": - # remove keys with empty values - einvoice.pop(fieldname, None) - continue - - value_type = field_validation.get("type").lower() - if value_type in ['object', 'array']: - child_validations = field_validation.get('properties') - - if isinstance(value, list): - for d in value: - validate_einvoice(child_validations, d, errors) - if not d: - # remove empty dicts - einvoice.pop(fieldname, None) - else: - validate_einvoice(child_validations, value, errors) - if not value: - # remove empty dicts - einvoice.pop(fieldname, None) - continue - - # convert to int or str - if value_type == 'string': - einvoice[fieldname] = str(value) - elif value_type == 'number': - is_integer = '.' not in str(field_validation.get('maximum')) - precision = 3 if '.999' in str(field_validation.get('maximum')) else 2 - einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value) - value = einvoice[fieldname] - - max_length = field_validation.get('maxLength') - minimum = flt(field_validation.get('minimum')) - maximum = flt(field_validation.get('maximum')) - pattern_str = field_validation.get('pattern') - pattern = re.compile(pattern_str or '') - - label = field_validation.get('description') or fieldname - - if value_type == 'string' and len(value) > max_length: - errors.append(_('{} should not exceed {} characters').format(label, max_length)) - if value_type == 'number' and (value > maximum or value < minimum): - errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum)) - if pattern_str and not pattern.match(value): - errors.append(field_validation.get('validationMsg')) - - return errors - -class RequestFailed(Exception): pass +class RequestFailed(Exception): + pass +class CancellationNotAllowed(Exception): + pass class GSPConnector(): def __init__(self, doctype=None, docname=None): - self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') - sandbox_mode = self.e_invoice_settings.sandbox_mode + self.doctype = doctype + self.docname = docname - self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None - self.credentials = self.get_credentials() + self.set_invoice() + self.set_credentials() # authenticate url is same for sandbox & live self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token' - self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test' + self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test' self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' @@ -463,18 +568,29 @@ class GSPConnector(): self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB' self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' - def get_credentials(self): + def set_invoice(self): + self.invoice = None + if self.doctype and self.docname: + self.invoice = frappe.get_cached_doc(self.doctype, self.docname) + + def set_credentials(self): + self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') + + if not self.e_invoice_settings.enable: + frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) + if self.invoice: gstin = self.get_seller_gstin() - if not self.e_invoice_settings.enable: - frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) - credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin) + credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin] + if credentials_for_gstin: + self.credentials = credentials_for_gstin[0] + else: + frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings')) else: - credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None - return credentials + self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None def get_seller_gstin(self): - gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin') + gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin') if not gstin: frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.')) return gstin @@ -522,7 +638,7 @@ class GSPConnector(): self.e_invoice_settings.reload() except Exception: - self.log_error(res) + log_error(res) self.raise_error(True) def get_headers(self): @@ -544,14 +660,14 @@ class GSPConnector(): if res.get('success'): return res.get('result') else: - self.log_error(res) + log_error(res) raise RequestFailed except RequestFailed: self.raise_error() except Exception: - self.log_error() + log_error() self.raise_error(True) @staticmethod def get_gstin_details(gstin): @@ -568,12 +684,13 @@ class GSPConnector(): return details def generate_irn(self): - headers = self.get_headers() - einvoice = make_einvoice(self.invoice) - data = json.dumps(einvoice, indent=4) - + data = {} try: + headers = self.get_headers() + einvoice = make_einvoice(self.invoice) + data = json.dumps(einvoice, indent=4) res = self.make_request('post', self.generate_irn_url, headers, data) + if res.get('success'): self.set_einvoice_data(res.get('result')) @@ -593,12 +710,36 @@ class GSPConnector(): except RequestFailed: errors = self.sanitize_error_message(res.get('message')) + self.set_failed_status(errors=errors) self.raise_error(errors=errors) - except Exception: - self.log_error(data) + except Exception as e: + self.set_failed_status(errors=str(e)) + log_error(data) self.raise_error(True) + @staticmethod + def bulk_generate_irn(invoices): + gsp_connector = GSPConnector() + gsp_connector.doctype = 'Sales Invoice' + + failed = [] + + for invoice in invoices: + try: + gsp_connector.docname = invoice + gsp_connector.set_invoice() + gsp_connector.set_credentials() + gsp_connector.generate_irn() + + except Exception as e: + failed.append({ + 'docname': invoice, + 'message': str(e) + }) + + return failed + def get_irn_details(self, irn): headers = self.get_headers() @@ -615,21 +756,30 @@ class GSPConnector(): self.raise_error(errors=errors) except Exception: - self.log_error() + log_error() self.raise_error(True) def cancel_irn(self, irn, reason, remark): - headers = self.get_headers() - data = json.dumps({ - 'Irn': irn, - 'Cnlrsn': reason, - 'Cnlrem': remark - }, indent=4) - + data, res = {}, {} try: + # validate cancellation + if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24: + frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed) + if not irn: + frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed) + + headers = self.get_headers() + data = json.dumps({ + 'Irn': irn, + 'Cnlrsn': reason, + 'Cnlrem': remark + }, indent=4) + res = self.make_request('post', self.cancel_irn_url, headers, data) - if res.get('success'): + if res.get('success') or '9999' in res.get('message'): self.invoice.irn_cancelled = 1 + self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else "" + self.invoice.einvoice_status = 'Cancelled' self.invoice.flags.updater_reference = { 'doctype': self.invoice.doctype, 'docname': self.invoice.name, @@ -642,11 +792,41 @@ class GSPConnector(): except RequestFailed: errors = self.sanitize_error_message(res.get('message')) + self.set_failed_status(errors=errors) self.raise_error(errors=errors) - except Exception: - self.log_error(data) + except CancellationNotAllowed as e: + self.set_failed_status(errors=str(e)) + self.raise_error(errors=str(e)) + + except Exception as e: + self.set_failed_status(errors=str(e)) + log_error(data) self.raise_error(True) + + @staticmethod + def bulk_cancel_irn(invoices, reason, remark): + gsp_connector = GSPConnector() + gsp_connector.doctype = 'Sales Invoice' + + failed = [] + + for invoice in invoices: + try: + gsp_connector.docname = invoice + gsp_connector.set_invoice() + gsp_connector.set_credentials() + irn = gsp_connector.invoice.irn + gsp_connector.cancel_irn(irn, reason, remark) + + except Exception as e: + failed.append({ + 'docname': invoice, + 'message': str(e) + }) + + return failed + def generate_eway_bill(self, **kwargs): args = frappe._dict(kwargs) @@ -685,7 +865,7 @@ class GSPConnector(): self.raise_error(errors=errors) except Exception: - self.log_error(data) + log_error(data) self.raise_error(True) def cancel_eway_bill(self, eway_bill, reason, remark): @@ -717,7 +897,7 @@ class GSPConnector(): self.raise_error(errors=errors) except Exception: - self.log_error(data) + log_error(data) self.raise_error(True) def sanitize_error_message(self, message): @@ -732,6 +912,9 @@ class GSPConnector(): ] then we trim down the message by looping over errors ''' + if not message: + return [] + errors = re.findall(': [^:]+', message) for idx, e in enumerate(errors): # remove colons @@ -743,22 +926,6 @@ class GSPConnector(): return errors - def log_error(self, data={}): - if not isinstance(data, dict): - data = json.loads(data) - - seperator = "--" * 50 - err_tb = traceback.format_exc() - err_msg = str(sys.exc_info()[1]) - data = json.dumps(data, indent=4) - - message = "\n".join([ - "Error", err_msg, seperator, - "Data:", data, seperator, - "Exception:", err_tb - ]) - frappe.log_error(title=_('E Invoice Request Failed'), message=message) - def raise_error(self, raise_exception=False, errors=[]): title = _('E Invoice Request Failed') if errors: @@ -779,7 +946,10 @@ class GSPConnector(): self.invoice.irn = res.get('Irn') self.invoice.ewaybill = res.get('EwbNo') self.invoice.signed_einvoice = dec_signed_invoice + self.invoice.ack_no = res.get('AckNo') + self.invoice.ack_date = res.get('AckDt') self.invoice.signed_qr_code = res.get('SignedQRCode') + self.invoice.einvoice_status = 'Generated' self.attach_qrcode_image() @@ -815,6 +985,17 @@ class GSPConnector(): self.invoice.flags.ignore_validate = True self.invoice.save() + def set_failed_status(self, errors=None): + frappe.db.rollback() + self.invoice.einvoice_status = 'Failed' + self.invoice.failure_description = self.get_failure_message(errors) if errors else "" + self.update_invoice() + frappe.db.commit() + + def get_failure_message(self, errors): + if isinstance(errors, list): + errors = ', '.join(errors) + return errors def sanitize_for_json(string): """Escape JSON specific characters from a string.""" @@ -844,5 +1025,114 @@ def generate_eway_bill(doctype, docname, **kwargs): @frappe.whitelist() def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.cancel_eway_bill(eway_bill, reason, remark) + # TODO: uncomment when eway_bill api from Adequare is enabled + # gsp_connector = GSPConnector(doctype, docname) + # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) + + # update cancelled status only, to be able to cancel irn next + frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) + +@frappe.whitelist() +def generate_einvoices(docnames): + docnames = json.loads(docnames) or [] + + if len(docnames) < 10: + failures = GSPConnector.bulk_generate_irn(docnames) + frappe.local.message_log = [] + + if failures: + show_bulk_action_failure_message(failures) + + success = len(docnames) - len(failures) + frappe.msgprint( + _('{} e-invoices generated successfully').format(success), + title=_('Bulk E-Invoice Generation Complete') + ) + + else: + enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames) + +def schedule_bulk_generate_irn(docnames): + failures = GSPConnector.bulk_generate_irn(docnames) + frappe.local.message_log = [] + + frappe.publish_realtime("bulk_einvoice_generation_complete", { + "user": frappe.session.user, + "failures": failures, + "invoices": docnames + }) + +def show_bulk_action_failure_message(failures): + for doc in failures: + docname = '{0}'.format(doc.get('docname')) + message = doc.get('message').replace("'", '"') + if message[0] == '[': + errors = json.loads(message) + error_list = ''.join(['
  • {}
  • '.format(err) for err in errors]) + message = '''{} has following errors:
    +
      {}
    '''.format(docname, error_list) + else: + message = '{} - {}'.format(docname, message) + + frappe.msgprint( + message, + title=_('Bulk E-Invoice Generation Complete'), + indicator='red' + ) + +@frappe.whitelist() +def cancel_irns(docnames, reason, remark): + docnames = json.loads(docnames) or [] + + if len(docnames) < 10: + failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) + frappe.local.message_log = [] + + if failures: + show_bulk_action_failure_message(failures) + + success = len(docnames) - len(failures) + frappe.msgprint( + _('{} e-invoices cancelled successfully').format(success), + title=_('Bulk E-Invoice Cancellation Complete') + ) + else: + enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark) + +def schedule_bulk_cancel_irn(docnames, reason, remark): + failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) + frappe.local.message_log = [] + + frappe.publish_realtime("bulk_einvoice_cancellation_complete", { + "user": frappe.session.user, + "failures": failures, + "invoices": docnames + }) + +def enqueue_bulk_action(job, **kwargs): + check_scheduler_status() + + enqueue( + job, + **kwargs, + queue="long", + timeout=10000, + event="processing_bulk_einvoice_action", + now=frappe.conf.developer_mode or frappe.flags.in_test, + ) + + if job == schedule_bulk_generate_irn: + msg = _('E-Invoices will be generated in a background process.') + else: + msg = _('E-Invoices will be cancelled in a background process.') + + frappe.msgprint(msg, alert=1) + +def check_scheduler_status(): + if is_scheduler_inactive() and not frappe.flags.in_test: + frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive")) + +def job_already_enqueued(job_name): + enqueued_jobs = [d.get("job_name") for d in get_info()] + if job_name in enqueued_jobs: + return True \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index d573425678..3440202a2b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -51,7 +51,7 @@ def create_hsn_codes(data, code_field): def add_custom_roles_for_reports(): for report_name in ('GST Sales Register', 'GST Purchase Register', - 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'): + 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'): if not frappe.db.get_value('Custom Role', dict(report=report_name)): frappe.get_doc(dict( @@ -128,6 +128,9 @@ def make_custom_fields(update=True): is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST', fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt', print_hide=1) + taxable_value = dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) purchase_invoice_gst_category = [ dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', @@ -409,21 +412,37 @@ def make_custom_fields(update=True): dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), - dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), - - dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), - dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), - dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) ] custom_fields = { @@ -454,7 +473,7 @@ def make_custom_fields(update=True): 'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], - 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index ddcedd5e4f..9d23369fc4 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe, re, json from frappe import _ import erpnext -from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate +from frappe.utils import cstr, flt, cint, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate 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 @@ -41,24 +41,25 @@ def validate_gstin_for_india(doc, method): return if len(doc.gstin) != 15: - frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters.")) + frappe.throw(_("A GSTIN must have 15 characters."), title=_("Invalid GSTIN")) if gst_category and gst_category == 'UIN Holders': if not GSTIN_UIN_FORMAT.match(doc.gstin): - frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers")) + frappe.throw(_("The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers"), + title=_("Invalid GSTIN")) else: if not GSTIN_FORMAT.match(doc.gstin): - frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.")) + frappe.throw(_("The input you've entered doesn't match the format of GSTIN."), title=_("Invalid GSTIN")) validate_gstin_check_digit(doc.gstin) set_gst_state_and_state_number(doc) if not doc.gst_state: - frappe.throw(_("Please Enter GST state")) + frappe.throw(_("Please enter GST state"), title=_("Invalid State")) if doc.gst_state_number != doc.gstin[:2]: - frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") - .format(doc.gst_state_number)) + frappe.throw(_("First 2 digits of GSTIN should match with State number {0}.") + .format(doc.gst_state_number), title=_("Invalid GSTIN")) def validate_pan_for_india(doc, method): if doc.get('country') != 'India' or not doc.pan: @@ -830,3 +831,48 @@ def get_regional_round_off_accounts(company, account_list): account_list.extend(gst_account_list) return account_list + +def update_taxable_values(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'India': + return + + gst_accounts = get_gst_accounts(doc.company) + + # Only considering sgst account to avoid inflating taxable value + gst_account_list = gst_accounts.get('sgst_account', []) + gst_accounts.get('sgst_account', []) \ + + gst_accounts.get('igst_account', []) + + additional_taxes = 0 + total_charges = 0 + item_count = 0 + considered_rows = [] + + for tax in doc.get('taxes'): + prev_row_id = cint(tax.row_id) - 1 + if tax.account_head in gst_account_list and prev_row_id not in considered_rows: + if tax.charge_type == 'On Previous Row Amount': + additional_taxes += doc.get('taxes')[prev_row_id].tax_amount_after_discount_amount + considered_rows.append(prev_row_id) + if tax.charge_type == 'On Previous Row Total': + additional_taxes += doc.get('taxes')[prev_row_id].base_total - doc.base_net_total + considered_rows.append(prev_row_id) + + for item in doc.get('items'): + if doc.apply_discount_on == 'Grand Total' and doc.discount_amount: + proportionate_value = item.base_amount if doc.base_total else item.qty + total_value = doc.base_total if doc.base_total else doc.total_qty + else: + proportionate_value = item.base_net_amount if doc.base_net_total else item.qty + total_value = doc.base_net_total if doc.base_net_total else doc.total_qty + + applicable_charges = flt(flt(proportionate_value * (flt(additional_taxes) / flt(total_value)), + item.precision('taxable_value'))) + item.taxable_value = applicable_charges + proportionate_value + total_charges += applicable_charges + item_count += 1 + + if total_charges != additional_taxes: + diff = additional_taxes - total_charges + doc.get('items')[item_count - 1].taxable_value += diff diff --git a/erpnext/regional/report/e_invoice_summary/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js new file mode 100644 index 0000000000..4713217d83 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js @@ -0,0 +1,55 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["E-Invoice Summary"] = { + "filters": [ + { + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "fieldname": "company", + "label": __("Company"), + "default": frappe.defaults.get_user_default("Company"), + }, + { + "fieldtype": "Link", + "options": "Customer", + "fieldname": "customer", + "label": __("Customer") + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "from_date", + "label": __("From Date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "to_date", + "label": __("To Date"), + "default": frappe.datetime.get_today(), + }, + { + "fieldtype": "Select", + "fieldname": "status", + "label": __("Status"), + "options": "\nPending\nGenerated\nCancelled\nFailed" + } + ], + + "formatter": function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "einvoice_status" && value) { + if (value == 'Pending') value = `${value}`; + else if (value == 'Generated') value = `${value}`; + else if (value == 'Cancelled') value = `${value}`; + else if (value == 'Failed') value = `${value}`; + } + + return value; + } +}; diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json new file mode 100644 index 0000000000..4deb073a53 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json @@ -0,0 +1,28 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-12 11:23:37.312294", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "Logo", + "modified": "2021-03-12 12:36:48.689413", + "modified_by": "Administrator", + "module": "Regional", + "name": "E-Invoice Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Sales Invoice", + "report_name": "E-Invoice Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Administrator" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py new file mode 100644 index 0000000000..47acf291a3 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py @@ -0,0 +1,106 @@ +# 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): + validate_filters(filters) + + columns = get_columns() + data = get_data(filters) + + return columns, data + +def validate_filters(filters={}): + filters = frappe._dict(filters) + + if not filters.company: + frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter')) + if filters.company: + # validate if company has e-invoicing enabled + pass + if not filters.from_date or not filters.to_date: + frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter')) + if filters.from_date > filters.to_date: + frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter')) + +def get_data(filters={}): + query_filters = { + 'posting_date': ['between', [filters.from_date, filters.to_date]], + 'einvoice_status': ['is', 'set'], + 'company': filters.company + } + if filters.customer: + query_filters['customer'] = filters.customer + if filters.status: + query_filters['einvoice_status'] = filters.status + + data = frappe.get_all( + 'Sales Invoice', + filters=query_filters, + fields=[d.get('fieldname') for d in get_columns()] + ) + + return data + +def get_columns(): + return [ + { + "fieldtype": "Date", + "fieldname": "posting_date", + "label": _("Posting Date"), + "width": 0 + }, + { + "fieldtype": "Link", + "fieldname": "name", + "label": _("Sales Invoice"), + "options": "Sales Invoice", + "width": 140 + }, + { + "fieldtype": "Data", + "fieldname": "einvoice_status", + "label": _("Status"), + "width": 100 + }, + { + "fieldtype": "Link", + "fieldname": "customer", + "options": "Customer", + "label": _("Customer") + }, + { + "fieldtype": "Check", + "fieldname": "is_return", + "label": _("Is Return"), + "width": 85 + }, + { + "fieldtype": "Data", + "fieldname": "ack_no", + "label": "Ack. No.", + "width": 145 + }, + { + "fieldtype": "Data", + "fieldname": "ack_date", + "label": "Ack. Date", + "width": 165 + }, + { + "fieldtype": "Data", + "fieldname": "irn", + "label": _("IRN No."), + "width": 250 + }, + { + "fieldtype": "Currency", + "options": "Company:company:default_currency", + "fieldname": "base_grand_total", + "label": _("Grand Total"), + "width": 120 + } + ] \ No newline at end of file From 03635fb0c4805fda016c4b6a4cacd10473fdd863 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 18 Jan 2021 15:06:35 +0530 Subject: [PATCH 0484/1264] feat: ESS User --- erpnext/patches.txt | 1 + .../v13_0/make_non_standard_user_type.py | 8 +++ erpnext/setup/install.py | 69 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 erpnext/patches/v13_0/make_non_standard_user_type.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 14f1ab84d6..108495613e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -771,3 +771,4 @@ erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing +erpnext.patches.v13_0.make_non_standard_user_type #12-03-2020 diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py new file mode 100644 index 0000000000..e2388f114c --- /dev/null +++ b/erpnext/patches/v13_0/make_non_standard_user_type.py @@ -0,0 +1,8 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +from erpnext.setup.install import add_non_standard_user_types + +def execute(): + add_non_standard_user_types() \ No newline at end of file diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 82f191d0b7..b269c5e0b2 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -8,9 +8,11 @@ from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import D from .default_success_action import get_default_success_action from frappe import _ from frappe.utils import cint +from frappe.installer import update_site_config from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.custom.doctype.custom_field.custom_field import create_custom_field from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules +from six import iteritems default_mail_footer = """
    Sent via ERPNext
    """ @@ -29,6 +31,7 @@ def after_install(): add_company_to_session_defaults() add_standard_navbar_items() add_app_name() + add_non_standard_user_types() frappe.db.commit() @@ -164,3 +167,69 @@ def add_standard_navbar_items(): def add_app_name(): frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') + +def add_non_standard_user_types(): + user_types = get_user_types_data() + + user_type_limit = {} + for user_type, data in iteritems(user_types): + user_type_limit.setdefault(frappe.scrub(user_type), 10) + + update_site_config('user_type_doctype_limit', user_type_limit) + + for user_type, data in iteritems(user_types): + create_custom_role(data) + create_user_type(user_type, data) + +def get_user_types_data(): + return { + 'ESS User': { + 'role': 'ESS User', + 'apply_user_permission_on': 'Employee', + 'user_id_field': 'user_id', + 'doctypes': { + 'Salary Slip': ['read'], + 'Employee': ['read', 'write'], + 'Timesheet': ['read', 'write', 'create'], + 'Expense Claim': ['read', 'write', 'create'], + 'Leave Application': ['read', 'write', 'create'], + 'Attendance Request': ['read', 'write', 'create'], + 'Compensatory Leave Request': ['read', 'write', 'create'], + 'Employee Tax Exemption Declaration': ['read', 'write', 'create'], + 'Employee Tax Exemption Proof Submission': ['read', 'write', 'create'], + } + } + } + +def create_custom_role(data): + if data.get('role') and not frappe.db.exists('Role', data.get('role')): + frappe.get_doc({ + 'doctype': 'Role', + 'role_name': data.get('role'), + 'desk_access': 1, + 'is_custom': 1 + }).insert(ignore_permissions=True) + +def create_user_type(user_type, data): + if frappe.db.exists('User Type', user_type): + doc = frappe.get_cached_doc('User Type', user_type) + doc.user_doctypes = [] + else: + doc = frappe.new_doc('User Type') + doc.update({ + 'name': user_type, + 'role': data.get('role'), + 'user_id_field': data.get('user_id_field'), + 'apply_user_permission_on': data.get('apply_user_permission_on') + }) + + create_role_permissions_for_doctype(doc, data) + doc.save(ignore_permissions=True) + +def create_role_permissions_for_doctype(doc, data): + for doctype, perms in iteritems(data.get('doctypes')): + args = {'document_type': doctype} + for perm in perms: + args[perm] = 1 + + doc.append('user_doctypes', args) From 610ccd4c0322c40fa11dec8e8bbaccbd834d3220 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 16 Mar 2021 00:39:10 +0530 Subject: [PATCH 0485/1264] fix: modified permission --- erpnext/patches.txt | 2 +- erpnext/setup/install.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 108495613e..44ec1ff37c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -771,4 +771,4 @@ erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing -erpnext.patches.v13_0.make_non_standard_user_type #12-03-2020 +erpnext.patches.v13_0.make_non_standard_user_type #31-03-2020 diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index b269c5e0b2..09cba8b4d0 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -183,20 +183,20 @@ def add_non_standard_user_types(): def get_user_types_data(): return { - 'ESS User': { - 'role': 'ESS User', + 'Employee Self Service': { + 'role': 'Employee Self Service', 'apply_user_permission_on': 'Employee', 'user_id_field': 'user_id', 'doctypes': { 'Salary Slip': ['read'], 'Employee': ['read', 'write'], - 'Timesheet': ['read', 'write', 'create'], - 'Expense Claim': ['read', 'write', 'create'], - 'Leave Application': ['read', 'write', 'create'], - 'Attendance Request': ['read', 'write', 'create'], - 'Compensatory Leave Request': ['read', 'write', 'create'], - 'Employee Tax Exemption Declaration': ['read', 'write', 'create'], - 'Employee Tax Exemption Proof Submission': ['read', 'write', 'create'], + 'Expense Claim': ['read', 'write', 'create', 'delete'], + 'Leave Application': ['read', 'write', 'create', 'delete'], + 'Attendance Request': ['read', 'write', 'create', 'delete'], + 'Compensatory Leave Request': ['read', 'write', 'create', 'delete'], + 'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'], + 'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'], + 'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'] } } } From a8f78fabfd46cc42309cc6b8b414afc0739b6595 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 13 Apr 2021 18:43:57 +0530 Subject: [PATCH 0486/1264] fix: patch failing while migrating from v7 to v13 --- erpnext/hooks.py | 2 ++ erpnext/patches.txt | 2 +- .../patches/v13_0/make_non_standard_user_type.py | 16 ++++++++++++++++ erpnext/setup/install.py | 12 ++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 98d5966264..bb6cd8bdc2 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -307,6 +307,8 @@ auto_cancel_exempted_doctypes= [ "Inpatient Medication Entry" ] +after_migrate = ["erpnext.setup.install.update_select_perm_after_install"] + scheduler_events = { "cron": { "0/30 * * * *": [ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 44ec1ff37c..7098a24043 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -771,4 +771,4 @@ erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing -erpnext.patches.v13_0.make_non_standard_user_type #31-03-2020 +erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 \ No newline at end of file diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py index e2388f114c..a9d7883d40 100644 --- a/erpnext/patches/v13_0/make_non_standard_user_type.py +++ b/erpnext/patches/v13_0/make_non_standard_user_type.py @@ -2,7 +2,23 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import frappe +from six import iteritems from erpnext.setup.install import add_non_standard_user_types def execute(): + doctype_dict = { + 'projects': ['Timesheet'], + 'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'], + 'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request'] + } + + for module, doctypes in iteritems(doctype_dict): + for doctype in doctypes: + frappe.reload_doc(module, 'doctype', doctype) + + + frappe.flags.ignore_select_perm = True + frappe.flags.update_select_perm_after_migrate = True + add_non_standard_user_types() \ No newline at end of file diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 09cba8b4d0..c7220cbc07 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -233,3 +233,15 @@ def create_role_permissions_for_doctype(doc, data): args[perm] = 1 doc.append('user_doctypes', args) + +def update_select_perm_after_install(): + if not frappe.flags.update_select_perm_after_migrate: + return + + frappe.flags.ignore_select_perm = False + for row in frappe.get_all('User Type', filters= {'is_standard': 0}): + print('Updating user type :- ', row.name) + doc = frappe.get_doc('User Type', row.name) + doc.save() + + frappe.flags.update_select_perm_after_migrate = False From ce6c3b5b748a1c46620832e2b51d085b2ef59bf5 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 13 Apr 2021 20:55:52 +0530 Subject: [PATCH 0487/1264] fix: incorrect incoming rate for the sales return (#25306) --- .../controllers/sales_and_purchase_return.py | 17 ++++++++++++++++- erpnext/controllers/selling_controller.py | 6 ++++-- erpnext/stock/stock_ledger.py | 3 ++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index de61b35316..5f759b43bc 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ from frappe.model.meta import get_field_precision +from erpnext.stock.utils import get_incoming_rate from frappe.utils import flt, get_datetime, format_datetime class StockOverReturnError(frappe.ValidationError): pass @@ -389,10 +390,24 @@ def make_return_doc(doctype, source_name, target_doc=None): return doclist -def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None): +def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, + item_row=None, voucher_detail_no=None, sle=None): if not return_against: return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against") + if not return_against and voucher_type == 'Sales Invoice' and sle: + return get_incoming_rate({ + "item_code": sle.item_code, + "warehouse": sle.warehouse, + "posting_date": sle.get('posting_date'), + "posting_time": sle.get('posting_time'), + "qty": sle.actual_qty, + "serial_no": sle.get('serial_no'), + "company": sle.company, + "voucher_type": sle.voucher_type, + "voucher_no": sle.voucher_no + }, raise_error_if_no_rate=False) + return_against_item_field = get_return_against_item_fields(voucher_type) filters = get_filters(voucher_type, voucher_no, voucher_detail_no, diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index fb52c1f6ca..6d921625b7 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -311,14 +311,16 @@ class SellingController(StockController): items = self.get("items") + (self.get("packed_items") or []) for d in items: - if not cint(self.get("is_return")): + if not self.get("return_against"): # Get incoming rate based on original item cost based on valuation method + qty = flt(d.get('stock_qty') or d.get('actual_qty')) + d.incoming_rate = get_incoming_rate({ "item_code": d.item_code, "warehouse": d.warehouse, "posting_date": self.get('posting_date') or self.get('transaction_date'), "posting_time": self.get('posting_time') or nowtime(), - "qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')), + "qty": qty if cint(self.get("is_return")) else (-1 * qty), "serial_no": d.get('serial_no'), "company": self.company, "voucher_type": self.doctype, diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 121c51cf6a..df5f16fd41 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -372,7 +372,8 @@ class update_entries_after(object): elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"): if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"): from erpnext.controllers.sales_and_purchase_return import get_rate_for_return # don't move this import to top - rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no) + rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, + voucher_detail_no=sle.voucher_detail_no, sle = sle) else: if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): rate_field = "valuation_rate" From f68205468c9443a061e66fd0166a28923dd07a2d Mon Sep 17 00:00:00 2001 From: m1ngaa <77512195+m1ngaa@users.noreply.github.com> Date: Wed, 14 Apr 2021 05:50:31 +0800 Subject: [PATCH 0488/1264] Delete accounts (an empty file) This file has no purpose, and must've been included as a tiny mistake..? --- erpnext/accounts/accounts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/accounts/accounts diff --git a/erpnext/accounts/accounts b/erpnext/accounts/accounts deleted file mode 100644 index e69de29bb2..0000000000 From b71742084208aa3ab2a7441d8f04817558267bf4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 14 Apr 2021 10:43:45 +0530 Subject: [PATCH 0489/1264] fix: Role to override maintain same rate check in transactions (#25193) * feat: configurable action if the same purchase/selling rate is not maintained * fix: Role to override maintain same rate check in transactions Co-authored-by: Rucha Mahabal --- .../buying_settings/buying_settings.json | 21 ++++++++++++++++- .../selling_settings/selling_settings.json | 21 ++++++++++++++++- erpnext/utilities/transaction_base.py | 23 +++++++++++-------- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 248cb9a8a0..630a1dc8cd 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -13,6 +13,8 @@ "po_required", "pr_required", "maintain_same_rate", + "maintain_same_rate_action", + "role_to_override_stop_action", "allow_multiple_items", "subcontract", "backflush_raw_materials_of_subcontract_based_on", @@ -89,6 +91,23 @@ { "fieldname": "column_break_11", "fieldtype": "Column Break" + }, + { + "default": "Stop", + "depends_on": "maintain_same_rate", + "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.", + "fieldname": "maintain_same_rate_action", + "fieldtype": "Select", + "label": "Action If Same Rate is Not Maintained", + "mandatory_depends_on": "maintain_same_rate", + "options": "Stop\nWarn" + }, + { + "depends_on": "eval:doc.maintain_same_rate_action == 'Stop'", + "fieldname": "role_to_override_stop_action", + "fieldtype": "Link", + "label": "Role Allowed to Override Stop Action", + "options": "Role" } ], "icon": "fa fa-cog", @@ -96,7 +115,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-02 17:34:04.190677", + "modified": "2021-04-04 20:01:44.087066", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 2104c0131c..f01934b7e6 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -18,6 +18,8 @@ "dn_required", "sales_update_frequency", "maintain_same_sales_rate", + "maintain_same_rate_action", + "role_to_override_stop_action", "editable_price_list_rate", "allow_multiple_items", "allow_against_multiple_purchase_orders", @@ -133,6 +135,23 @@ "fieldname": "hide_tax_id", "fieldtype": "Check", "label": "Hide Customer's Tax ID from Sales Transactions" + }, + { + "default": "Stop", + "depends_on": "maintain_same_sales_rate", + "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.", + "fieldname": "maintain_same_rate_action", + "fieldtype": "Select", + "label": "Action If Same Rate is Not Maintained", + "mandatory_depends_on": "maintain_same_sales_rate", + "options": "Stop\nWarn" + }, + { + "depends_on": "eval: doc.maintain_same_rate_action == 'Stop'", + "fieldname": "role_to_override_stop_action", + "fieldtype": "Link", + "label": "Role Allowed to Override Stop Action", + "options": "Role" } ], "icon": "fa fa-cog", @@ -140,7 +159,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-02 17:35:53.603607", + "modified": "2021-04-04 20:18:12.814624", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index b575855aed..f99da58e46 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -120,11 +120,11 @@ class TransactionBase(StatusUpdater): buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"] if self.doctype in buying_doctypes: - to_disable = "Maintain same rate throughout Purchase cycle" - settings_page = "Buying Settings" + action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action") + settings_doc = "Buying Settings" else: - to_disable = "Maintain same rate throughout Sales cycle" - settings_page = "Selling Settings" + action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action") + settings_doc = "Selling Settings" for ref_dt, ref_dn_field, ref_link_field in ref_details: for d in self.get("items"): @@ -132,11 +132,16 @@ class TransactionBase(StatusUpdater): ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate") if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01: - frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ") - .format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate)) - frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.") - .format(frappe.bold(_(to_disable)), - get_link_to_form(settings_page, settings_page, frappe.bold(settings_page)))) + if action == "Stop": + role_allowed_to_override = frappe.db.get_single_value(settings_doc, 'role_to_override_stop_action') + + if role_allowed_to_override not in frappe.get_roles(): + frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format( + d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate)) + else: + frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format( + d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate), title=_("Warning"), indicator="orange") + def get_link_filters(self, for_doctype): if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype): From b67058869a737614a8126b21fcba1b3b399d72c3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 14 Apr 2021 11:20:27 +0530 Subject: [PATCH 0490/1264] fix: Round off final tax amount instead of current tax amount (#25188) * fix: Round off final tax amount instead of current tax amount * fix: Syntax error * fix: Indentation * fix: Round Off taxes order execution Co-authored-by: Nabin Hait --- erpnext/controllers/taxes_and_totals.py | 26 +++++++++-------- .../public/js/controllers/taxes_and_totals.js | 28 +++++++++++-------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 1ae7310747..9fae49482d 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -291,10 +291,13 @@ class calculate_taxes_and_totals(object): # set precision in the last item iteration if n == len(self.doc.get("items")) - 1: self.round_off_totals(tax) + self._set_in_company_currency(tax, + ["tax_amount", "tax_amount_after_discount_amount"]) + + self.round_off_base_values(tax) self.set_cumulative_total(i, tax) - self._set_in_company_currency(tax, - ["total", "tax_amount", "tax_amount_after_discount_amount"]) + self._set_in_company_currency(tax, ["total"]) # adjust Discount Amount loss in last tax iteration if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ @@ -341,20 +344,11 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty - current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount) - if not self.doc.get("is_consolidated"): self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount - def get_final_current_tax_amount(self, tax, current_tax_amount): - # Some countries need individual tax components to be rounded - # Handeled via regional doctypess - if tax.account_head in frappe.flags.round_off_applicable_accounts: - current_tax_amount = round(current_tax_amount, 0) - return current_tax_amount - def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount): # store tax breakup for each item key = item.item_code or item.item_name @@ -365,10 +359,20 @@ class calculate_taxes_and_totals(object): tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)] def round_off_totals(self, tax): + if tax.account_head in frappe.flags.round_off_applicable_accounts: + tax.tax_amount = round(tax.tax_amount, 0) + tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0) + tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount")) tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount")) + def round_off_base_values(self, tax): + # Round off to nearest integer based on regional settings + if tax.account_head in frappe.flags.round_off_applicable_accounts: + tax.base_tax_amount = round(tax.base_tax_amount, 0) + tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0) + def manipulate_grand_total_for_inclusive_tax(self): # if fully inclusive taxes and diff if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]): diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 3a3ee3858b..2e133bed2e 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -323,12 +323,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ // set precision in the last item iteration if (n == me.frm.doc["items"].length - 1) { me.round_off_totals(tax); + me.set_in_company_currency(tax, + ["tax_amount", "tax_amount_after_discount_amount"]); + + me.round_off_base_values(tax); // in tax.total, accumulate grand total for each item me.set_cumulative_total(i, tax); - me.set_in_company_currency(tax, - ["total", "tax_amount", "tax_amount_after_discount_amount"]); + me.set_in_company_currency(tax, ["total"]); // adjust Discount Amount loss in last tax iteration if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied @@ -393,20 +396,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ current_tax_amount = tax_rate * item.qty; } - current_tax_amount = this.get_final_tax_amount(tax, current_tax_amount); this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); return current_tax_amount; }, - get_final_tax_amount: function(tax, current_tax_amount) { - if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) { - current_tax_amount = Math.round(current_tax_amount); - } - - return current_tax_amount; - }, - set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) { // store tax breakup for each item let tax_detail = tax.item_wise_tax_detail; @@ -420,10 +414,22 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, round_off_totals: function(tax) { + if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) { + tax.tax_amount= Math.round(tax.tax_amount); + tax.tax_amount_after_discount_amount = Math.round(tax.tax_amount_after_discount_amount); + } + tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax)); tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, precision("tax_amount", tax)); }, + round_off_base_values: function(tax) { + if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) { + tax.base_tax_amount= Math.round(tax.base_tax_amount); + tax.base_tax_amount_after_discount_amount = Math.round(tax.base_tax_amount_after_discount_amount); + } + }, + manipulate_grand_total_for_inclusive_tax: function() { var me = this; // if fully inclusive taxes and diff From e2dc102bc609028cd2d4214b7b89f6d7946a9a80 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 14 Apr 2021 11:21:11 +0530 Subject: [PATCH 0491/1264] fix: Zero amount completed delivery notes being shown in Sales Invocie get items (#25317) --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 81f0ad3fed..c0c13153de 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -325,7 +325,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, and status not in ("Stopped", "Closed") %(fcond)s and ( (`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100) - or `tabDelivery Note`.grand_total = 0 + or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100) or ( `tabDelivery Note`.is_return = 1 and return_against in (select name from `tabDelivery Note` where per_billed < 100) From ece4c16d8f498ffcc354f1b002f55c5e64dc98ce Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 14 Apr 2021 12:53:13 +0530 Subject: [PATCH 0492/1264] chore: Added change log --- erpnext/change_log/v13/v13.0.2.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 erpnext/change_log/v13/v13.0.2.md diff --git a/erpnext/change_log/v13/v13.0.2.md b/erpnext/change_log/v13/v13.0.2.md new file mode 100644 index 0000000000..2bfbdfcc5d --- /dev/null +++ b/erpnext/change_log/v13/v13.0.2.md @@ -0,0 +1,7 @@ +## Version 13.0.2 Release Notes + +### Fixes +- fix: frappe.whitelist for doc methods ([#25231](https://github.com/frappe/erpnext/pull/25231)) +- fix: incorrect incoming rate for the sales return ([#25306](https://github.com/frappe/erpnext/pull/25306)) +- fix(e-invoicing): validations & tax calculation fixes ([#25314](https://github.com/frappe/erpnext/pull/25314)) +- fix: update scheduler check time ([#25295](https://github.com/frappe/erpnext/pull/25295)) \ No newline at end of file From 88b3c525339638c263977b28377b799d60c27fa5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 14 Apr 2021 13:13:53 +0550 Subject: [PATCH 0493/1264] bumped to version 13.0.2 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index d2e58701c8..1a5a0fa275 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.1' +__version__ = '13.0.2' def get_default_company(user=None): '''Get default company for user''' From 7f8b95efe8c6c16cf50faf85480b8f97f8eda089 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 14 Apr 2021 14:12:03 +0530 Subject: [PATCH 0494/1264] fix: move QI logic to stock module --- erpnext/controllers/accounts_controller.py | 31 ------------ erpnext/controllers/stock_controller.py | 53 ++++++++++++++++---- erpnext/public/js/controllers/transaction.js | 2 +- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0c966566fd..ea0b495c10 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1413,37 +1413,6 @@ def validate_and_delete_children(parent, data): update_bin_on_delete(d, parent.doctype) -@frappe.whitelist() -def make_quality_inspections(doctype, docname, items): - items = json.loads(items).get('items') - inspections = [] - - for item in items: - if item.get("sample_size") > item.get("qty"): - frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( - item_name=item.get("item_name"), - sample_size=item.get("sample_size"), - accepted_quantity=item.get("qty") - )) - - quality_inspection = frappe.get_doc({ - "doctype": "Quality Inspection", - "inspection_type": "Incoming", - "inspected_by": frappe.session.user, - "reference_type": doctype, - "reference_name": docname, - "item_code": item.get("item_code"), - "description": item.get("description"), - "sample_size": item.get("sample_size"), - "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, - "batch_no": item.get("batch_no") - }).insert() - quality_inspection.save() - inspections.append(quality_inspection) - - return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] - - @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def check_doc_permissions(doc, perm_type='create'): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index f352bae30e..7f8da516aa 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1,17 +1,21 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate -from frappe import _ -import frappe.defaults +import json from collections import defaultdict -from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced + +import frappe +import frappe.defaults +from frappe import _ +from frappe.utils import cint, cstr, flt, get_link_to_form, getdate + +import erpnext from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map +from erpnext.accounts.utils import check_if_stock_and_account_balance_synced, get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController -from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock import get_warehouse_account_map +from erpnext.stock.stock_ledger import get_valuation_rate + class QualityInspectionRequiredError(frappe.ValidationError): pass class QualityInspectionRejectedError(frappe.ValidationError): pass @@ -190,7 +194,6 @@ class StockController(AccountsController): if hasattr(self, "items"): item_doclist = self.get("items") elif self.doctype == "Stock Reconciliation": - import json item_doclist = [] data = json.loads(self.reconciliation_json) for row in data[data.index(self.head_row)+1:]: @@ -320,7 +323,7 @@ class StockController(AccountsController): return serialized_items def validate_warehouse(self): - from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse + from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company warehouses = list(set([d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)])) @@ -501,6 +504,38 @@ class StockController(AccountsController): check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) + +@frappe.whitelist() +def make_quality_inspections(doctype, docname, items): + items = json.loads(items).get('items') + inspections = [] + + for item in items: + if item.get("sample_size") > item.get("qty"): + frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( + item_name=item.get("item_name"), + sample_size=item.get("sample_size"), + accepted_quantity=item.get("qty") + )) + + quality_inspection = frappe.get_doc({ + "doctype": "Quality Inspection", + "inspection_type": "Incoming", + "inspected_by": frappe.session.user, + "reference_type": doctype, + "reference_name": docname, + "item_code": item.get("item_code"), + "description": item.get("description"), + "sample_size": item.get("sample_size"), + "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, + "batch_no": item.get("batch_no") + }).insert() + quality_inspection.save() + inspections.append(quality_inspection) + + return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + + def is_reposting_pending(): return frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index da0c87dd85..0e0a87a8b5 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1989,7 +1989,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ primary_action: function () { const data = dialog.get_values(); frappe.call({ - method: "erpnext.controllers.accounts_controller.make_quality_inspections", + method: "erpnext.controllers.stock_controller.make_quality_inspections", args: { doctype: me.frm.doc.doctype, docname: me.frm.doc.name, From 03f711e3a2c6ce12fad80a4f375728a077a8a958 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 14 Apr 2021 14:41:55 +0530 Subject: [PATCH 0495/1264] style: sider issues --- erpnext/public/js/controllers/transaction.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0e0a87a8b5..478b21b441 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1927,7 +1927,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ cannot_add_rows: true, in_place_edit: true, data: data, - get_data: () => { return data }, + get_data: () => { + return data; + }, fields: [ { fieldtype: "Data", @@ -2001,7 +2003,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.msgprint({ message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), indicator: "green" - }) + }); } dialog.hide(); } @@ -2024,7 +2026,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); dialog_items.grid.refresh(); } - }) + }); data = dialog.fields_dict.items.df.data; if (!data.length) { From 8798f3080837d3f7e4ebf1e41801aa112f2e064e Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Apr 2021 15:05:33 +0530 Subject: [PATCH 0496/1264] fix: pos print receipt (#25328) --- .../selling/page/point_of_sale/pos_past_order_summary.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index a5a739cff9..acf4eb371f 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -204,11 +204,11 @@ erpnext.PointOfSale.PastOrderSummary = class { print_receipt() { const frm = this.events.get_frm(); frappe.utils.print( - frm.doctype, - frm.docname, + this.doc.doctype, + this.doc.name, frm.pos_print_format, - frm.doc.letter_head, - frm.doc.language || frappe.boot.lang + this.doc.letter_head, + this.doc.language || frappe.boot.lang ); } From d4fd1038dc73f2f5321cb28a42d5d8ac14731f15 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Apr 2021 15:07:10 +0530 Subject: [PATCH 0497/1264] fix: pos print receipt (#25329) From b54cc7c7f569171bf2e8cd77e92bad5a39274eef Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Apr 2021 15:09:11 +0530 Subject: [PATCH 0498/1264] fix: pos print receipt (#25330) --- .../selling/page/point_of_sale/pos_past_order_summary.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index a5a739cff9..acf4eb371f 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -204,11 +204,11 @@ erpnext.PointOfSale.PastOrderSummary = class { print_receipt() { const frm = this.events.get_frm(); frappe.utils.print( - frm.doctype, - frm.docname, + this.doc.doctype, + this.doc.name, frm.pos_print_format, - frm.doc.letter_head, - frm.doc.language || frappe.boot.lang + this.doc.letter_head, + this.doc.language || frappe.boot.lang ); } From c4565651ff79e6880086830341494e4b77d1e3a7 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Wed, 14 Apr 2021 17:11:36 +0530 Subject: [PATCH 0499/1264] fix: Let Administrator delete company transactions (#25300) Co-authored-by: Nabin Hait --- erpnext/setup/doctype/company/delete_company_transactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 933ed3cf32..8367a257ea 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -15,7 +15,7 @@ def delete_company_transactions(company_name): frappe.only_for("System Manager") doc = frappe.get_doc("Company", company_name) - if frappe.session.user != doc.owner: + if frappe.session.user != doc.owner and frappe.session.user != 'Administrator': frappe.throw(_("Transactions can only be deleted by the creator of the Company"), frappe.PermissionError) From 24e45163d5fa7a86ffd26639258fc1a24ccb5329 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 14 Apr 2021 18:53:15 +0530 Subject: [PATCH 0500/1264] fix: Sider --- erpnext/public/js/utils.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 8fe38d91ea..fd98f17ac1 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -292,10 +292,9 @@ $.extend(erpnext.utils, { } }, overrides_parent_value_in_all_rows: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) { - let d = locals[dt][dn]; - if(doc[parent_fieldname]){ + if (doc[parent_fieldname]) { let cl = doc[table_fieldname] || []; - for(let i = 0; i < cl.length; i++) { + for (let i = 0; i < cl.length; i++) { cl[i][fieldname] = doc[parent_fieldname]; } frappe.refresh_field(table_fieldname); From 593071bd53c65ebbe3ebde477cd6283b73d9f32c Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 15 Apr 2021 14:12:11 +0530 Subject: [PATCH 0501/1264] fix: currency symbol in bank transaction list view (#25336) --- .../doctype/bank_transaction/bank_transaction.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 69ee4971cd..88aa7ef8b5 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -175,22 +175,24 @@ }, { "fieldname": "deposit", - "oldfieldname": "debit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Deposit" + "label": "Deposit", + "oldfieldname": "debit", + "options": "currency" }, { "fieldname": "withdrawal", - "oldfieldname": "credit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Withdrawal" + "label": "Withdrawal", + "oldfieldname": "credit", + "options": "currency" } ], "is_submittable": 1, "links": [], - "modified": "2020-12-30 19:40:54.221070", + "modified": "2021-04-14 17:31:58.963529", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", From c50fbc689737800f2cb1e78786f31f2dbedd3ba9 Mon Sep 17 00:00:00 2001 From: Sun Howwrongbum Date: Thu, 15 Apr 2021 14:52:13 +0530 Subject: [PATCH 0502/1264] fix: list lookup with undefined variable (#25210) Co-authored-by: Ankush Menat --- erpnext/public/js/utils/serial_no_batch_selector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index d49a8138fb..3333d569a7 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -353,9 +353,9 @@ erpnext.SerialNoBatchSelector = Class.extend({ return row.on_grid_fields_dict.batch_no.get_value(); } }); - if (selected_batches.includes(val)) { + if (selected_batches.includes(batch_no)) { this.set_value(""); - frappe.throw(__('Batch {0} already selected.', [val])); + frappe.throw(__('Batch {0} already selected.', [batch_no])); } if (me.warehouse_details.name) { From 1ac471e04ff995ad0591b68280ecf1c7939fe432 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 15 Apr 2021 16:48:01 +0530 Subject: [PATCH 0503/1264] feat: generate schedule is also triggered on save. --- .../doctype/maintenance_schedule/maintenance_schedule.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 0aefe19c8d..9acb6c29ae 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -33,7 +33,7 @@ class MaintenanceSchedule(TransactionBase): count = count + 1 child.sales_person = d.sales_person - self.save() + def on_submit(self): if not self.get('schedules'): @@ -169,9 +169,12 @@ class MaintenanceSchedule(TransactionBase): self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() + self.generate_schedule() def on_update(self): frappe.db.set(self, 'status', 'Draft') + + def update_amc_date(self, serial_nos, amc_expiry_date=None): for serial_no in serial_nos: From 39b1cd827a5e3ba04c29189426e5f85f7fe77daf Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 15 Apr 2021 18:54:29 +0530 Subject: [PATCH 0504/1264] fix: Additional Salary component amount not getting set (#25355) --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 539f2c56d3..afdf081ac8 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -633,6 +633,8 @@ class SalarySlip(TransactionBase): if additional_salary: component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date else: From 56c428663ffcda075cff985a719a86b0dc87bf2c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 15 Apr 2021 18:54:38 +0530 Subject: [PATCH 0505/1264] fix: Additional Salary component amount not getting set (#25356) --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 539f2c56d3..afdf081ac8 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -633,6 +633,8 @@ class SalarySlip(TransactionBase): if additional_salary: component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date else: From 2c802720c30c254c0b87e69af4af32d2509bc494 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 15 Apr 2021 16:52:23 +0530 Subject: [PATCH 0506/1264] feat: Automated setting end_date based on periodicity, no of visits and improved ux. --- .../maintenance_schedule.js | 71 ++- .../maintenance_schedule_item.json | 551 +++++------------- 2 files changed, 184 insertions(+), 438 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index ddbcdfde57..d954d905d9 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -2,9 +2,8 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.maintenance"); - frappe.ui.form.on('Maintenance Schedule', { - setup: function(frm) { + setup: function (frm) { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); @@ -12,30 +11,30 @@ frappe.ui.form.on('Maintenance Schedule', { frm.add_fetch('item_code', 'item_name', 'item_name'); frm.add_fetch('item_code', 'description', 'description'); }, - onload: function(frm) { + onload: function (frm) { if (!frm.doc.status) { - frm.set_value({status:'Draft'}); + frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { - frm.set_value({transaction_date: frappe.datetime.get_today()}); + frm.set_value({ transaction_date: frappe.datetime.get_today() }); } }, - refresh: function(frm) { + refresh: function (frm) { setTimeout(() => { frm.toggle_display('generate_schedule', !(frm.is_new())); frm.toggle_display('schedule', !(frm.is_new())); - },10); + }, 10); }, - customer: function(frm) { + customer: function (frm) { erpnext.utils.get_party_details(frm) }, - customer_address: function(frm) { + customer_address: function (frm) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display'); }, - contact_person: function(frm) { + contact_person: function (frm) { erpnext.utils.get_contact_details(frm); }, - generate_schedule: function(frm) { + generate_schedule: function (frm) { if (frm.is_new()) { frappe.msgprint(__('Please save first')); } else { @@ -46,14 +45,14 @@ frappe.ui.form.on('Maintenance Schedule', { // TODO commonify this code erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ - refresh: function() { - frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} + refresh: function () { + frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' } var me = this; if (this.frm.doc.docstatus === 0) { this.frm.add_custom_button(__('Sales Order'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule", source_doctype: "Sales Order", @@ -68,7 +67,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }); }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { - this.frm.add_custom_button(__('Create Maintenance Visit'), function() { + this.frm.add_custom_button(__('Create Maintenance Visit'), function () { frappe.model.open_mapped_doc({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", source_name: me.frm.doc.name, @@ -78,26 +77,26 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } }, - start_date: function(doc, cdt, cdn) { + start_date: function (doc, cdt, cdn) { this.set_no_of_visits(doc, cdt, cdn); }, - end_date: function(doc, cdt, cdn) { + end_date: function (doc, cdt, cdn) { this.set_no_of_visits(doc, cdt, cdn); }, - periodicity: function(doc, cdt, cdn) { + periodicity: function (doc, cdt, cdn) { this.set_no_of_visits(doc, cdt, cdn); - }, - set_no_of_visits: function(doc, cdt, cdn) { + }, + no_of_visits: function(doc,cdt,cdn){ + this.set_no_of_visits(doc,cdt,cdn); + }, + + set_no_of_visits: function (doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); - if (item.start_date && item.end_date && item.periodicity) { - if(item.start_date > item.end_date) { - frappe.msgprint(__("Row {0}:Start Date must be before End Date", [item.idx])); - return; - } + if (item.start_date && item.periodicity) { var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; @@ -110,10 +109,28 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); + if (no_of_visits == 0 || !no_of_visits) { + + let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]) + frappe.model.set_value(item.doctype, item.name, "end_date", end_date) + var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; + var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); + + } + else if(item.no_of_visits > no_of_visits){ + var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) + frappe.model.set_value(item.doctype, item.name, "end_date", end_date) + + } + else if(item.no_of_visits < no_of_visits){ + var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) + frappe.model.set_value(item.doctype, item.name, "end_date", end_date) + + } } }, }); -$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({frm: cur_frm})); +$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({ frm: cur_frm })); diff --git a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json index b371dfc4f5..3dacdead62 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json @@ -1,431 +1,160 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-02-22 01:28:05", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:28:05", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "description", + "column_break_4", + "start_date", + "end_date", + "periodicity", + "schedule_details", + "no_of_visits", + "column_break_10", + "sales_person", + "reference", + "serial_no", + "sales_order" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "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": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "columns": 1, "fetch_from": "item_code.item_name", - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "options": "", - "permlevel": 0, - "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": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "item_code.description", - "fieldname": "description", - "fieldtype": "Text Editor", - "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": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Data", - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "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": "description", + "fieldtype": "Text Editor", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Data", + "print_width": "300px", + "read_only": 1, "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedule_details", - "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, - "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": "schedule_details", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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": "Start Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "start_date", - "oldfieldtype": "Date", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "oldfieldname": "start_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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": "End Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "end_date", - "oldfieldtype": "Date", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "oldfieldname": "end_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "periodicity", - "fieldtype": "Select", - "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": "Periodicity", - "length": 0, - "no_copy": 0, - "oldfieldname": "periodicity", - "oldfieldtype": "Select", - "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom", - "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 - }, + "columns": 1, + "fieldname": "periodicity", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Periodicity", + "oldfieldname": "periodicity", + "oldfieldtype": "Select", + "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "no_of_visits", - "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": "No of Visits", - "length": 0, - "no_copy": 0, - "oldfieldname": "no_of_visits", - "oldfieldtype": "Int", - "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 - }, + "columns": 1, + "fieldname": "no_of_visits", + "fieldtype": "Int", + "in_list_view": 1, + "label": "No of Visits", + "oldfieldname": "no_of_visits", + "oldfieldtype": "Int", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_person", - "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": "Sales Person", - "length": 0, - "no_copy": 0, - "oldfieldname": "incharge_name", - "oldfieldtype": "Link", - "options": "Sales Person", - "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 - }, + "fieldname": "sales_person", + "fieldtype": "Link", + "label": "Sales Person", + "oldfieldname": "incharge_name", + "oldfieldtype": "Link", + "options": "Sales Person" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference", - "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": "Reference", - "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 - }, + "fieldname": "reference", + "fieldtype": "Section Break", + "label": "Reference" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Small Text", - "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": "Serial No", - "length": 0, - "no_copy": 0, - "oldfieldname": "serial_no", - "oldfieldtype": "Small Text", - "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 - }, + "fieldname": "serial_no", + "fieldtype": "Small Text", + "label": "Serial No", + "oldfieldname": "serial_no", + "oldfieldtype": "Small Text" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_order", - "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": "Sales Order", - "length": 0, - "no_copy": 1, - "oldfieldname": "prevdoc_docname", - "oldfieldtype": "Data", - "options": "Sales Order", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "no_copy": 1, + "oldfieldname": "prevdoc_docname", + "oldfieldtype": "Data", + "options": "Sales Order", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "search_index": 1, "width": "150px" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-16 22:43:14.260729", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Schedule Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 0, - "track_seen": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-04-15 16:09:47.311994", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Schedule Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From 597bb8be184778748980ef453c7c48119317ff2f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 15 Apr 2021 20:32:45 +0530 Subject: [PATCH 0507/1264] fix: remove pickup_to, pickup_from and get_pickup_time relies on server-side validation instead js controller --- erpnext/stock/doctype/shipment/shipment.js | 37 ---------------------- 1 file changed, 37 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index 7af16af898..ce2906ecbe 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -363,43 +363,6 @@ frappe.ui.form.on('Shipment', { if (frm.doc.pickup_date < frappe.datetime.get_today()) { frappe.throw(__("Pickup Date cannot be before this day")); } - if (frm.doc.pickup_date == frappe.datetime.get_today()) { - var pickup_time = frm.events.get_pickup_time(frm); - frm.set_value("pickup_from", pickup_time); - frm.trigger('set_pickup_to_time'); - } - }, - pickup_from: function(frm) { - var pickup_time = frm.events.get_pickup_time(frm); - if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) { - let current_hour = pickup_time.split(':')[0]; - let current_min = pickup_time.split(':')[1]; - let pickup_hour = frm.doc.pickup_from.split(':')[0]; - let pickup_min = frm.doc.pickup_from.split(':')[1]; - if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) { - frm.set_value("pickup_from", pickup_time); - frappe.throw(__("Pickup Time cannot be in the past")); - } - } - frm.trigger('set_pickup_to_time'); - }, - get_pickup_time: function() { - let current_hour = new Date().getHours(); - let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'}); - if (current_min < 30) { - current_min = '30'; - } else { - current_min = '00'; - current_hour = Number(current_hour)+1; - } - let pickup_time = current_hour +':'+ current_min; - return pickup_time; - }, - set_pickup_to_time: function(frm) { - let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5; - let pickup_to_min = frm.doc.pickup_from.split(':')[1]; - let pickup_to = pickup_to_hour +':'+ pickup_to_min; - frm.set_value("pickup_to", pickup_to); }, clear_pickup_fields: function(frm) { let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"]; From 6179cc1311df15ea0459ec7c9a8e65e2b2d3086d Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 15 Apr 2021 20:36:28 +0530 Subject: [PATCH 0508/1264] fix: make pickup_to and pickup_from mandatory fields --- erpnext/stock/doctype/shipment/shipment.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index 76c331c5c2..a33cbc288c 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -275,14 +275,16 @@ "default": "09:00", "fieldname": "pickup_from", "fieldtype": "Time", - "label": "Pickup from" + "label": "Pickup from", + "reqd": 1 }, { "allow_on_submit": 1, "default": "17:00", "fieldname": "pickup_to", "fieldtype": "Time", - "label": "Pickup to" + "label": "Pickup to", + "reqd": 1 }, { "fieldname": "column_break_36", @@ -431,7 +433,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-12-25 15:02:34.891976", + "modified": "2021-04-13 17:14:18.181818", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", @@ -469,4 +471,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From c0db286dc1572d591a9a6c7c70620ffebfbd28d2 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 15 Apr 2021 23:48:25 +0530 Subject: [PATCH 0509/1264] fix: filter for employees in salary slip (#25360) --- erpnext/payroll/doctype/salary_slip/salary_slip.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index a0ddd39ca2..5258f3aff9 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -40,7 +40,9 @@ frappe.ui.form.on("Salary Slip", { frm.set_query("employee", function() { return { query: "erpnext.controllers.queries.employee_query", - filters: frm.doc.company + filters: { + company: frm.doc.company + } }; }); }, From 4b496b65b7f249a3448592b1f3fc5f1c1a69f906 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 15 Apr 2021 23:53:12 +0530 Subject: [PATCH 0510/1264] fix: filter for employees in salary slip (#25361) --- erpnext/payroll/doctype/salary_slip/salary_slip.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index a0ddd39ca2..5258f3aff9 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -40,7 +40,9 @@ frappe.ui.form.on("Salary Slip", { frm.set_query("employee", function() { return { query: "erpnext.controllers.queries.employee_query", - filters: frm.doc.company + filters: { + company: frm.doc.company + } }; }); }, From 9d9c256e70ebf00c493f5131179700c3a17e4404 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 16 Apr 2021 00:11:40 +0530 Subject: [PATCH 0511/1264] feat: added Disable Rounded Total in sales transactions --- .../doctype/sales_invoice/sales_invoice.json | 14 +++++++++++++- .../selling/doctype/sales_order/sales_order.json | 14 +++++++++++++- .../stock/doctype/delivery_note/delivery_note.json | 14 +++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index d382386a32..c6c67b4ddc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -118,6 +118,7 @@ "in_words", "total_advance", "outstanding_amount", + "disable_rounded_total", "advances_section", "allocate_advances_automatically", "get_advances", @@ -1109,6 +1110,7 @@ "reqd": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -1120,6 +1122,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -1168,6 +1171,7 @@ "reqd": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -1180,6 +1184,7 @@ }, { "bold": 1, + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -1945,6 +1950,13 @@ "fieldtype": "Link", "label": "Set Target Warehouse", "options": "Warehouse" + }, + { + "default": "0", + "depends_on": "grand_total", + "fieldname": "disable_rounded_total", + "fieldtype": "Check", + "label": "Disable Rounded Total" } ], "icon": "fa fa-file-text", @@ -1957,7 +1969,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-03-31 15:42:26.261540", + "modified": "2021-04-15 23:57:58.766651", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 0a5c6651ba..762b6f1d6c 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -98,6 +98,7 @@ "rounded_total", "in_words", "advance_paid", + "disable_rounded_total", "packing_list", "packed_items", "payment_schedule_section", @@ -901,6 +902,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -912,6 +914,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -961,6 +964,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -973,6 +977,7 @@ }, { "bold": 1, + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -1474,13 +1479,20 @@ "label": "Represents Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "depends_on": "grand_total", + "fieldname": "disable_rounded_total", + "fieldtype": "Check", + "label": "Disable Rounded Total" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-01-20 23:40:39.929296", + "modified": "2021-04-15 23:55:13.439068", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index f595aade91..280fde158f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -99,6 +99,7 @@ "rounding_adjustment", "rounded_total", "in_words", + "disable_rounded_total", "terms_section_break", "tc_name", "terms", @@ -768,6 +769,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -777,6 +779,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounded_total", "fieldtype": "Currency", "label": "Rounded Total (Company Currency)", @@ -819,6 +822,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -829,6 +833,7 @@ }, { "bold": 1, + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounded_total", "fieldtype": "Currency", "label": "Rounded Total", @@ -1271,13 +1276,20 @@ "label": "Represents Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "depends_on": "grand_total", + "fieldname": "disable_rounded_total", + "fieldtype": "Check", + "label": "Disable Rounded Total" } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2020-12-26 17:07:59.194403", + "modified": "2021-04-15 23:55:49.620641", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From f1b4ce7430e5320ad67e4430628f83c7cdc0b873 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 16 Apr 2021 15:36:43 +0530 Subject: [PATCH 0512/1264] feat: Add filtering by department Also: - Add department filter to js - Add department column to report - Fetch only those timesheets which have an Employee Linked - Update unit tests --- ...ee_hours_utilization_based_on_timesheet.js | 6 ++ ...ee_hours_utilization_based_on_timesheet.py | 61 ++++++++++++---- .../test_employee_util.py | 71 +++++++++++++------ 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js index 2a5ea3655d..b11a1fc97f 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js @@ -32,6 +32,12 @@ frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = { fieldtype: "Link", options: "Employee" }, + { + fieldname: "department", + label: __("Department"), + fieldtype: "Link", + options: "Department" + }, { fieldname: "project", label: __("Project"), diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 2c78b3125d..48eb7b4bb0 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -41,7 +41,14 @@ class EmployeeHoursReport: 'options': 'Employee', 'fieldname': 'employee', 'fieldtype': 'Link', - 'width': 200 + 'width': 230 + }, + { + 'label': _('Department'), + 'options': 'Department', + 'fieldname': 'department', + 'fieldtype': 'Link', + 'width': 170 }, { 'label': _('Total Hours'), @@ -68,7 +75,7 @@ class EmployeeHoursReport: 'width': 150 }, { - 'label': _('% Utilization'), + 'label': _('% Utilization (Billed Hours + Non-Billed Hours / Total Hours)'), 'fieldname': 'per_util', 'fieldtype': 'Percentage', 'width': 200 @@ -78,6 +85,11 @@ class EmployeeHoursReport: def generate_data(self): self.generate_filtered_time_logs() self.generate_stats_by_employee() + self.set_employee_department_and_name() + + if self.filters.department: + self.filter_stats_by_department() + self.calculate_utilizations() self.data = [] @@ -91,26 +103,36 @@ class EmployeeHoursReport: # Sort by descending order of percentage utilization self.data.sort(key=lambda x: x['per_util'], reverse=True) + def filter_stats_by_department(self): + filtered_data = frappe._dict() + for emp, data in self.stats_by_employee.items(): + if data['department'] == self.filters.department: + filtered_data[emp] = data + + # Update stats + self.stats_by_employee = filtered_data + def generate_filtered_time_logs(self): additional_filters = '' - if self.filters.employee: - additional_filters += f"AND tt.employee = '{self.filters.employee}'" - - if self.filters.project: - additional_filters += f"AND ttd.project = '{self.filters.project}'" + filter_fields = ['employee', 'project', 'company'] - if self.filters.company: - additional_filters += f"AND tt.company = '{self.filters.company}'" + for field in filter_fields: + if self.filters.get(field): + if field == 'project': + additional_filters += f"AND ttd.{field} = '{self.filters.get(field)}'" + else: + additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'" self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project FROM `tabTimesheet Detail` AS ttd JOIN `tabTimesheet` AS tt ON ttd.parent = tt.name - WHERE tt.start_date BETWEEN '{0}' AND '{1}' + WHERE tt.employee IS NOT NULL + AND tt.start_date BETWEEN '{0}' AND '{1}' AND tt.end_date BETWEEN '{0}' AND '{1}' - {2}; + {2} '''.format(self.filters.from_date, self.filters.to_date, additional_filters)) def generate_stats_by_employee(self): @@ -128,6 +150,18 @@ class EmployeeHoursReport: else: self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2) + def set_employee_department_and_name(self): + for emp in self.stats_by_employee: + emp_name = frappe.db.get_value( + 'Employee', emp, 'employee_name' + ) + emp_dept = frappe.db.get_value( + 'Employee', emp, 'department' + ) + + self.stats_by_employee[emp]['department'] = emp_dept + self.stats_by_employee[emp]['employee_name'] = emp_name + def calculate_utilizations(self): # (9.0) Will be fetched from HR settings TOTAL_HOURS = flt(9.0 * self.day_span, 2) @@ -195,10 +229,7 @@ class EmployeeHoursReport: for row in self.data: - emp_name = frappe.db.get_value( - 'Employee', row['employee'], 'employee_name' - ) - labels.append(emp_name) + labels.append(row.get('employee_name')) billed_hours.append(row.get('billed_hours')) non_billed_hours.append(row.get('non_billed_hours')) untracked_hours.append(row.get('untracked_hours')) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 8b5d5be0e1..977a10d1d9 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -77,25 +77,7 @@ class TestEmployeeUtilization(unittest.TestCase): report = execute(filters) - expected_data = [ - { - 'employee': self.test_emp2, - 'billed_hours': 0.0, - 'non_billed_hours': 10.0, - 'total_hours': 18.0, - 'untracked_hours': 8.0, - 'per_util': 55.56 - }, - { - 'employee': self.test_emp1, - 'billed_hours': 5.0, - 'non_billed_hours': 0.0, - 'total_hours': 18.0, - 'untracked_hours': 13.0, - 'per_util': 27.78 - } - ] - + expected_data = self.get_expected_data_for_test_employees() self.assertEqual(report[1], expected_data) def test_utilization_report_for_single_employee(self): @@ -108,9 +90,12 @@ class TestEmployeeUtilization(unittest.TestCase): report = execute(filters) + emp1_data = frappe.get_doc('Employee', self.test_emp1) expected_data = [ { 'employee': self.test_emp1, + 'employee_name': emp1_data.employee_name, + 'department': emp1_data.department, 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'total_hours': 18.0, @@ -130,10 +115,13 @@ class TestEmployeeUtilization(unittest.TestCase): } report = execute(filters) - + + emp2_data = frappe.get_doc('Employee', self.test_emp2) expected_data = [ { - 'employee': self.test_emp2, + 'employee': self.test_emp2, + 'employee_name': emp2_data.employee_name, + 'department': emp2_data.department, 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'total_hours': 18.0, @@ -144,6 +132,20 @@ class TestEmployeeUtilization(unittest.TestCase): self.assertEqual(report[1], expected_data) + def test_utilization_report_for_department(self): + emp1_data = frappe.get_doc('Employee', self.test_emp1) + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03", + "department": emp1_data.department + } + + report = execute(filters) + + expected_data = self.get_expected_data_for_test_employees() + self.assertEqual(report[1], expected_data) + def test_report_summary_data(self): filters = { "company": "_Test Company", @@ -161,3 +163,30 @@ class TestEmployeeUtilization(unittest.TestCase): self.assertEqual( summary[i]['value'], expected_summary_values[i] ) + + def get_expected_data_for_test_employees(self): + emp1_data = frappe.get_doc('Employee', self.test_emp1) + emp2_data = frappe.get_doc('Employee', self.test_emp2) + + return [ + { + 'employee': self.test_emp2, + 'employee_name': emp2_data.employee_name, + 'department': emp2_data.department, + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56 + }, + { + 'employee': self.test_emp1, + 'employee_name': emp1_data.employee_name, + 'department': emp1_data.department, + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78 + } + ] \ No newline at end of file From e6fd3b86bdca28e45568484e6ee0e9256a2b5f6c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 16 Apr 2021 15:48:36 +0530 Subject: [PATCH 0513/1264] fix: fixed sider issues and translation syntax. --- .../maintenance_schedule.js | 37 ++++++++----------- .../maintenance_schedule.py | 3 +- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index d954d905d9..124524684e 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -7,9 +7,6 @@ frappe.ui.form.on('Maintenance Schedule', { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); - - frm.add_fetch('item_code', 'item_name', 'item_name'); - frm.add_fetch('item_code', 'description', 'description'); }, onload: function (frm) { if (!frm.doc.status) { @@ -46,7 +43,7 @@ frappe.ui.form.on('Maintenance Schedule', { // TODO commonify this code erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ refresh: function () { - frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' } + frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' }; var me = this; @@ -89,10 +86,10 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ this.set_no_of_visits(doc, cdt, cdn); }, - no_of_visits: function(doc,cdt,cdn){ - this.set_no_of_visits(doc,cdt,cdn); + no_of_visits: function (doc, cdt, cdn) { + this.set_no_of_visits(doc, cdt, cdn); }, - + set_no_of_visits: function (doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); @@ -111,23 +108,21 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); if (no_of_visits == 0 || !no_of_visits) { - let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]) - frappe.model.set_value(item.doctype, item.name, "end_date", end_date) - var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; - var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); + let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "end_date", end_date); + date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; + no_of_visits = cint(date_diff / days_in_period[item.periodicity]); frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); + } else if (item.no_of_visits > no_of_visits) { + let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "end_date", end_date); + + } else if (item.no_of_visits < no_of_visits) { + let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "end_date", end_date); + } - else if(item.no_of_visits > no_of_visits){ - var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) - frappe.model.set_value(item.doctype, item.name, "end_date", end_date) - - } - else if(item.no_of_visits < no_of_visits){ - var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) - frappe.model.set_value(item.doctype, item.name, "end_date", end_date) - - } } }, }); diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 9acb6c29ae..60dd2983b9 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -135,8 +135,7 @@ class MaintenanceSchedule(TransactionBase): } if date_diff < days_in_period[d.periodicity]: - throw(_("Row {0}: To set {1} periodicity, difference between from and to date \ - must be greater than or equal to {2}") + throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}") .format(d.idx, d.periodicity, days_in_period[d.periodicity])) def validate_maintenance_detail(self): From b1aad63a9910367fecab17efb1193453db717a88 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 16 Apr 2021 16:08:22 +0530 Subject: [PATCH 0514/1264] fix: leave policy in leave allocation (#25334) Co-authored-by: Rucha Mahabal --- erpnext/hr/doctype/leave_allocation/leave_allocation.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 3a300c0d63..ae02c512c2 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -218,8 +218,7 @@ "fieldname": "leave_policy_assignment", "fieldtype": "Link", "label": "Leave Policy Assignment", - "options": "Leave Policy Assignment", - "read_only": 1 + "options": "Leave Policy Assignment" }, { "fetch_from": "employee.company", @@ -236,7 +235,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-04 18:46:13.184104", + "modified": "2021-04-14 15:28:26.335104", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", From 5ebc6abfadff17f7a0320317b660d67a324405b0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 16 Apr 2021 16:59:10 +0530 Subject: [PATCH 0515/1264] feat: Sales Person field is now editable after submitting. --- .../maintenance_schedule.json | 1046 ++++------------- .../maintenance_schedule_detail.json | 298 ++--- 2 files changed, 313 insertions(+), 1031 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json index 606d22f52b..1871228711 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json @@ -1,852 +1,258 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-01-10 16:34:30", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "actions": [], + "autoname": "naming_series:", + "creation": "2013-01-10 16:34:30", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "customer_details", + "naming_series", + "customer", + "column_break0", + "status", + "transaction_date", + "items_section", + "items", + "schedule", + "generate_schedule", + "schedules", + "contact_info", + "customer_name", + "contact_person", + "contact_mobile", + "contact_email", + "contact_display", + "column_break_17", + "customer_address", + "address_display", + "territory", + "customer_group", + "company", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_details", - "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, - "oldfieldtype": "Section Break", - "options": "fa fa-user", - "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 - }, + "fieldname": "customer_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-user" + }, { - "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": "Series", - "length": 0, - "no_copy": 1, - "options": "MAT-MSH-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-MSH-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "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": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "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, - "oldfieldtype": "Column Break", - "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 - }, + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "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": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nCancelled", - "permlevel": 0, - "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 - }, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "\nDraft\nSubmitted\nCancelled", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_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": "Transaction Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "transaction_date", - "oldfieldtype": "Date", - "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 - }, + "fieldname": "transaction_date", + "fieldtype": "Date", + "label": "Transaction Date", + "oldfieldname": "transaction_date", + "oldfieldtype": "Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items_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": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "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 - }, + "fieldname": "items_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "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": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_maintenance_detail", - "oldfieldtype": "Table", - "options": "Maintenance Schedule Item", - "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 - }, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "item_maintenance_detail", + "oldfieldtype": "Table", + "options": "Maintenance Schedule Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedule", - "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": "Schedule", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-time", - "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 - }, + "fieldname": "schedule", + "fieldtype": "Section Break", + "label": "Schedule", + "oldfieldtype": "Section Break", + "options": "fa fa-time" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "generate_schedule", - "fieldtype": "Button", - "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": "Generate Schedule", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Button", - "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 - }, + "fieldname": "generate_schedule", + "fieldtype": "Button", + "label": "Generate Schedule", + "oldfieldtype": "Button" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedules", - "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": "Schedules", - "length": 0, - "no_copy": 0, - "oldfieldname": "schedules", - "oldfieldtype": "Table", - "options": "Maintenance Schedule Detail", - "permlevel": 0, - "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": "schedules", + "fieldtype": "Table", + "label": "Schedules", + "oldfieldname": "schedules", + "oldfieldtype": "Table", + "options": "Maintenance Schedule Detail" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_info", - "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": "Contact Info", - "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 - }, + "fieldname": "contact_info", + "fieldtype": "Section Break", + "label": "Contact Info" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "customer_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Customer Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer_name", - "oldfieldtype": "Data", - "permlevel": 0, - "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 - }, + "bold": 1, + "depends_on": "customer", + "fieldname": "customer_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Customer Name", + "oldfieldname": "customer_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_person", - "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": "Contact Person", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "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 - }, + "depends_on": "customer", + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_mobile", - "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": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "depends_on": "customer", + "fieldname": "contact_mobile", + "fieldtype": "Data", + "hidden": 1, + "label": "Mobile No", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_email", - "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": "Contact Email", - "length": 0, - "no_copy": 0, - "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 - }, + "depends_on": "customer", + "fieldname": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "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, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "contact_display", + "fieldtype": "Small Text", + "hidden": 1, + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_17", - "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 - }, + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "customer_address", - "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": "Customer Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "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 - }, + "depends_on": "customer", + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "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": "Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "address_display", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "description": "", - "fieldname": "territory", - "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": "Territory", - "length": 0, - "no_copy": 0, - "oldfieldname": "territory", - "oldfieldtype": "Link", - "options": "Territory", - "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 - }, + "depends_on": "customer", + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "oldfieldname": "territory", + "oldfieldtype": "Link", + "options": "Territory" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "description": "", - "fieldname": "customer_group", - "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": "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, { - "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, - "oldfieldname": "company", - "oldfieldtype": "Link", - "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 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 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": "Maintenance Schedule", - "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": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Maintenance Schedule", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-calendar", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Schedule", - "owner": "Administrator", + ], + "icon": "fa fa-calendar", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-04-16 15:53:36.670816", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Schedule", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Maintenance Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "status,customer,customer_name", - "show_name_in_global_search": 0, - "sort_order": "DESC", - "timeline_field": "customer", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "status,customer,customer_name", + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "customer" } \ No newline at end of file diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 7cd3086155..73536f6549 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -1,222 +1,98 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-02-22 01:28:05", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:28:05", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "scheduled_date", + "actual_date", + "sales_person", + "serial_no" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "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": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "read_only": 1, + "search_index": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "permlevel": 0, - "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, - "unique": 0 - }, + "columns": 2, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "scheduled_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": "Scheduled Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "scheduled_date", - "oldfieldtype": "Date", - "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": 1, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "scheduled_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Scheduled Date", + "oldfieldname": "scheduled_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_date", - "fieldtype": "Date", - "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": "Actual Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "actual_date", - "oldfieldtype": "Date", - "permlevel": 0, - "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, - "unique": 0 - }, + "fieldname": "actual_date", + "fieldtype": "Date", + "label": "Actual Date", + "no_copy": 1, + "oldfieldname": "actual_date", + "oldfieldtype": "Date", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_person", - "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": "Sales Person", - "length": 0, - "no_copy": 0, - "oldfieldname": "incharge_name", - "oldfieldtype": "Link", - "options": "Sales Person", - "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, - "unique": 0 - }, + "allow_on_submit": 1, + "columns": 2, + "fieldname": "sales_person", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Person", + "oldfieldname": "incharge_name", + "oldfieldtype": "Link", + "options": "Sales Person" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "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": "Serial No", - "length": 0, - "no_copy": 0, - "oldfieldname": "serial_no", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "160px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "serial_no", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Serial No", + "oldfieldname": "serial_no", + "oldfieldtype": "Small Text", + "print_width": "160px", + "read_only": 1, "width": "160px" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-02-17 17:05:44.644663", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Schedule Detail", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-04-16 16:01:53.271287", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Schedule Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From ede339f80bf514c8f7e5372be3e91567d063b26a Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 16 Apr 2021 18:42:54 +0530 Subject: [PATCH 0516/1264] fix: Serial No not updated correctly via Inter Company Stock Transfer (#25006) * fix: Serial No not updated correctly via Inter Company Stock Transfer * chore: Added More Test Cases for inter company Serial Transfer * fix: Test for serial no duplication - fixed serial no test - made errors more meaningful on serial no validation * fix: Stock Reco Test Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../purchase_receipt/test_purchase_receipt.py | 1 + erpnext/stock/doctype/serial_no/serial_no.py | 33 ++++- .../stock/doctype/serial_no/test_serial_no.py | 129 +++++++++++++++++- .../stock_reconciliation.py | 2 +- .../test_stock_reconciliation.py | 4 +- 5 files changed, 159 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 7f0c3fa801..16eea24f84 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -582,6 +582,7 @@ class TestPurchaseReceipt(unittest.TestCase): serial_no=serial_no, basic_rate=100, do_not_submit=True) se.submit() + se.cancel() dn.cancel() pr1.cancel() diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index c8d8ca9e17..c02dd2e518 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -14,6 +14,7 @@ from frappe import _, ValidationError from erpnext.controllers.stock_controller import StockController from six import string_types from six.moves import map + class SerialNoCannotCreateDirectError(ValidationError): pass class SerialNoCannotCannotChangeError(ValidationError): pass class SerialNoNotRequiredError(ValidationError): pass @@ -322,11 +323,35 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) elif serial_nos: + # SLE is being cancelled and has serial nos for serial_no in serial_nos: - sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1) - if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: - frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") - .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) + check_serial_no_validity_on_cancel(serial_no, sle) + +def check_serial_no_validity_on_cancel(serial_no, sle): + sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1) + sr_link = frappe.utils.get_link_to_form("Serial No", serial_no) + doc_link = frappe.utils.get_link_to_form(sle.voucher_type, sle.voucher_no) + actual_qty = cint(sle.actual_qty) + is_stock_reco = sle.voucher_type == "Stock Reconciliation" + msg = None + + if sr and (actual_qty < 0 or is_stock_reco) and sr.warehouse != sle.warehouse: + # receipt(inward) is being cancelled + msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format( + sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse)) + elif sr and actual_qty > 0 and not is_stock_reco: + # delivery is being cancelled, check for warehouse. + if sr.warehouse: + # serial no is active in another warehouse/company. + msg = _("Cannot cancel {0} {1} as Serial No {2} is active in warehouse {3}").format( + sle.voucher_type, doc_link, sr_link, frappe.bold(sr.warehouse)) + elif sr.company != sle.company and sr.status == "Delivered": + # serial no is inactive (allowed) or delivered from another company (block). + msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the company {3}").format( + sle.voucher_type, doc_link, sr_link, frappe.bold(sle.company)) + + if msg: + frappe.throw(msg, title=_("Cannot cancel")) def validate_material_transfer_entry(sle_doc): sle_doc.update({ diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index ed70790b2c..cde7fe07c6 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -40,16 +40,139 @@ class TestSerialNo(unittest.TestCase): se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = get_serial_nos(se.get("items")[0].serial_no) - create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]) + dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]) + + serial_no = frappe.get_doc("Serial No", serial_nos[0]) + + # check Serial No details after delivery + self.assertEqual(serial_no.status, "Delivered") + self.assertEqual(serial_no.warehouse, None) + self.assertEqual(serial_no.company, "_Test Company") + self.assertEqual(serial_no.delivery_document_type, "Delivery Note") + self.assertEqual(serial_no.delivery_document_no, dn.name) wh = create_warehouse("_Test Warehouse", company="_Test Company 1") - make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0], + pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) - serial_no = frappe.db.get_value("Serial No", serial_nos[0], ["warehouse", "company"], as_dict=1) + serial_no.reload() + # check Serial No details after purchase in second company + self.assertEqual(serial_no.status, "Active") self.assertEqual(serial_no.warehouse, wh) self.assertEqual(serial_no.company, "_Test Company 1") + self.assertEqual(serial_no.purchase_document_type, "Purchase Receipt") + self.assertEqual(serial_no.purchase_document_no, pr.name) + + def test_inter_company_transfer_intermediate_cancellation(self): + """ + Receive into and Deliver Serial No from one company. + Then Receive into and Deliver from second company. + Try to cancel intermediate receipts/deliveries to test if it is blocked. + """ + se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + sn_doc = frappe.get_doc("Serial No", serial_nos[0]) + + # check Serial No details after purchase in first company + self.assertEqual(sn_doc.status, "Active") + self.assertEqual(sn_doc.company, "_Test Company") + self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") + self.assertEqual(sn_doc.purchase_document_no, se.name) + + dn = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0]) + sn_doc.reload() + # check Serial No details after delivery from **first** company + self.assertEqual(sn_doc.status, "Delivered") + self.assertEqual(sn_doc.company, "_Test Company") + self.assertEqual(sn_doc.warehouse, None) + self.assertEqual(sn_doc.delivery_document_no, dn.name) + + # try cancelling the first Serial No Receipt, even though it is delivered + # block cancellation is Serial No is out of the warehouse + self.assertRaises(frappe.ValidationError, se.cancel) + + # receive serial no in second company + wh = create_warehouse("_Test Warehouse", company="_Test Company 1") + pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + sn_doc.reload() + + self.assertEqual(sn_doc.warehouse, wh) + # try cancelling the delivery from the first company + # block cancellation as Serial No belongs to different company + self.assertRaises(frappe.ValidationError, dn.cancel) + + # deliver from second company + dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + sn_doc.reload() + + # check Serial No details after delivery from **second** company + self.assertEqual(sn_doc.status, "Delivered") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.warehouse, None) + self.assertEqual(sn_doc.delivery_document_no, dn_2.name) + + # cannot cancel any intermediate document before last Delivery Note + self.assertRaises(frappe.ValidationError, se.cancel) + self.assertRaises(frappe.ValidationError, dn.cancel) + self.assertRaises(frappe.ValidationError, pr.cancel) + + def test_inter_company_transfer_fallback_on_cancel(self): + """ + Test Serial No state changes on cancellation. + If Delivery cancelled, it should fall back on last Receipt in the same company. + If Receipt is cancelled, it should be Inactive in the same company. + """ + # Receipt in **first** company + se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + sn_doc = frappe.get_doc("Serial No", serial_nos[0]) + + # Delivery from first company + dn = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0]) + + # Receipt in **second** company + wh = create_warehouse("_Test Warehouse", company="_Test Company 1") + pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + + # Delivery from second company + dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + sn_doc.reload() + + self.assertEqual(sn_doc.status, "Delivered") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.delivery_document_no, dn_2.name) + + dn_2.cancel() + sn_doc.reload() + # Fallback on Purchase Receipt if Delivery is cancelled + self.assertEqual(sn_doc.status, "Active") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.warehouse, wh) + self.assertEqual(sn_doc.purchase_document_no, pr.name) + + pr.cancel() + sn_doc.reload() + # Inactive in same company if Receipt cancelled + self.assertEqual(sn_doc.status, "Inactive") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.warehouse, None) + + dn.cancel() + sn_doc.reload() + # Fallback on Purchase Receipt in FIRST company if + # Delivery from FIRST company is cancelled + self.assertEqual(sn_doc.status, "Active") + self.assertEqual(sn_doc.company, "_Test Company") + self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") + self.assertEqual(sn_doc.purchase_document_no, se.name) def tearDown(self): frappe.db.rollback() \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index b452e96c5e..1396f19d3f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -398,7 +398,7 @@ class StockReconciliation(StockController): merge_similar_entries = {} for d in sl_entries: - if not d.serial_no or d.actual_qty < 0: + if not d.serial_no or flt(d.get("actual_qty")) < 0: new_sl_entries.append(d) continue diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 6690c6a606..36380b838b 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -32,7 +32,7 @@ class TestStockReconciliation(unittest.TestCase): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') # [[qty, valuation_rate, posting_date, # posting_time, expected_stock_value, bin_qty, bin_valuation]] - + input_data = [ [50, 1000, "2012-12-26", "12:00"], [25, 900, "2012-12-26", "12:00"], @@ -86,7 +86,7 @@ class TestStockReconciliation(unittest.TestCase): se1.cancel() def test_get_items(self): - create_warehouse("_Test Warehouse Group 1", + create_warehouse("_Test Warehouse Group 1", {"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"}) create_warehouse("_Test Warehouse Ledger 1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC", "company": "_Test Company"}) From 36247faf1a77623bc0a4d739e0fa1bc75086e835 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 16 Apr 2021 18:45:19 +0530 Subject: [PATCH 0517/1264] fix(e-invoicing): add company validation for e-invoicing (#25349) * fix(e-invoicing): add company validation for e-invoicing * fix: test --- .../doctype/sales_invoice/test_sales_invoice.py | 15 +++++++++++++-- erpnext/regional/india/e_invoice/utils.py | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4a6f9d1d6a..9059d0b040 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1879,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase): def test_einvoice_submission_without_irn(self): # init - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 1 + einvoice_settings.applicable_from = nowdate() + einvoice_settings.append('credentials', { + 'company': '_Test Company', + 'gstin': '27AAECE4835E1ZR', + 'username': 'test', + 'password': 'test' + }) + einvoice_settings.save() + country = frappe.flags.country frappe.flags.country = 'India' @@ -1890,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase): si.submit() # reset - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 0 frappe.flags.country = country def test_einvoice_json(self): diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 59c098c1ca..1d3cb661dd 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -38,12 +38,13 @@ def validate_eligibility(doc): einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): return False - + + invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - if invalid_supply_type or company_transaction or no_taxes_applied: + if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied: return False return True @@ -400,7 +401,7 @@ def validate_totals(einvoice): if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) - if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1: + if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1: frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) calculated_invoice_value = \ From adf974810d03b40ad282197bd77c1513b77ef91e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Apr 2021 21:15:50 +0530 Subject: [PATCH 0518/1264] fix: equality check instead of assignment in cart --- erpnext/shopping_cart/cart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 8515db3300..56afe95efd 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -230,12 +230,12 @@ def update_cart_address(address_type, address_name): if address_type.lower() == "billing": quotation.customer_address = address_name quotation.address_display = address_display - quotation.shipping_address_name == quotation.shipping_address_name or address_name + quotation.shipping_address_name = quotation.shipping_address_name or address_name address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None) elif address_type.lower() == "shipping": quotation.shipping_address_name = address_name quotation.shipping_address = address_display - quotation.customer_address == quotation.customer_address or address_name + quotation.customer_address = quotation.customer_address or address_name address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None) apply_cart_settings(quotation=quotation) From 67e647232cf289858d1aa3dbea8fe94d5ba746d2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Apr 2021 21:44:49 +0530 Subject: [PATCH 0519/1264] ci(semgrep): Add semgrep testing (#24871) Adds semgrep testing in CI. Refer to: - https://github.com/frappe/frappe/pull/12524 - https://github.com/frappe/frappe/pull/12577 --- .github/helper/semgrep_rules/README.md | 38 +++++++++++ .../semgrep_rules/frappe_correctness.py | 28 +++++++++ .../semgrep_rules/frappe_correctness.yml | 56 +++++++++++++++++ .github/helper/semgrep_rules/security.py | 6 ++ .github/helper/semgrep_rules/security.yml | 25 ++++++++ .github/helper/semgrep_rules/translate.js | 37 +++++++++++ .github/helper/semgrep_rules/translate.py | 53 ++++++++++++++++ .github/helper/semgrep_rules/translate.yml | 63 +++++++++++++++++++ .github/helper/semgrep_rules/ux.py | 31 +++++++++ .github/helper/semgrep_rules/ux.yml | 15 +++++ .github/workflows/semgrep.yml | 24 +++++++ 11 files changed, 376 insertions(+) create mode 100644 .github/helper/semgrep_rules/README.md create mode 100644 .github/helper/semgrep_rules/frappe_correctness.py create mode 100644 .github/helper/semgrep_rules/frappe_correctness.yml create mode 100644 .github/helper/semgrep_rules/security.py create mode 100644 .github/helper/semgrep_rules/security.yml create mode 100644 .github/helper/semgrep_rules/translate.js create mode 100644 .github/helper/semgrep_rules/translate.py create mode 100644 .github/helper/semgrep_rules/translate.yml create mode 100644 .github/helper/semgrep_rules/ux.py create mode 100644 .github/helper/semgrep_rules/ux.yml create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/helper/semgrep_rules/README.md b/.github/helper/semgrep_rules/README.md new file mode 100644 index 0000000000..670d8d280f --- /dev/null +++ b/.github/helper/semgrep_rules/README.md @@ -0,0 +1,38 @@ +# Semgrep linting + +## What is semgrep? +Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc. + +Example: + +To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc. + +You can read more such examples in `.github/helper/semgrep_rules` directory. + +# Why/when to use this? +We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us. + +## Running locally + +Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`. + +To run locally use following command: + +`semgrep --config=.github/helper/semgrep_rules [file/folder names]` + +## Testing +semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/ + +When writing new rules you should write few positive and few negative cases as shown in the guide and current tests. + +To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules` + + +## Reference + +If you are new to Semgrep read following pages to get started on writing/modifying rules: + +- https://semgrep.dev/docs/getting-started/ +- https://semgrep.dev/docs/writing-rules/rule-syntax +- https://semgrep.dev/docs/writing-rules/pattern-examples/ +- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases diff --git a/.github/helper/semgrep_rules/frappe_correctness.py b/.github/helper/semgrep_rules/frappe_correctness.py new file mode 100644 index 0000000000..4798b927f8 --- /dev/null +++ b/.github/helper/semgrep_rules/frappe_correctness.py @@ -0,0 +1,28 @@ +import frappe +from frappe import _, flt + +from frappe.model.document import Document + + +def on_submit(self): + if self.value_of_goods == 0: + frappe.throw(_('Value of goods cannot be 0')) + # ruleid: frappe-modifying-after-submit + self.status = 'Submitted' + +def on_submit(self): + if flt(self.per_billed) < 100: + self.update_billing_status() + else: + # todook: frappe-modifying-after-submit + self.status = "Completed" + self.db_set("status", "Completed") + +class TestDoc(Document): + pass + + def validate(self): + #ruleid: frappe-modifying-child-tables-while-iterating + for item in self.child_table: + if item.value < 0: + self.remove(item) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml new file mode 100644 index 0000000000..394abbf74d --- /dev/null +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -0,0 +1,56 @@ +# This file specifies rules for correctness according to how frappe doctype data model works. + +rules: +- id: frappe-modifying-after-submit + patterns: + - pattern: self.$ATTR = ... + - pattern-inside: | + def on_submit(self, ...): + ... + message: | + Doctype modified after submission. Please check if modification of self.$ATTR is commited to database. + languages: [python] + severity: ERROR + +- id: frappe-print-function-in-doctypes + pattern: print(...) + message: | + Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement. + languages: [python] + severity: WARNING + paths: + exclude: + - test_*.py + include: + - "*/**/doctype/*" + +- id: frappe-modifying-child-tables-while-iterating + pattern-either: + - pattern: | + for $ROW in self.$TABLE: + ... + self.remove(...) + - pattern: | + for $ROW in self.$TABLE: + ... + self.append(...) + message: | + Child table being modified while iterating on it. + languages: [python] + severity: ERROR + paths: + include: + - "*/**/doctype/*" + +- id: frappe-same-key-assigned-twice + pattern-either: + - pattern: | + {..., $X: $A, ..., $X: $B, ...} + - pattern: | + dict(..., ($X, $A), ..., ($X, $B), ...) + - pattern: | + _dict(..., ($X, $A), ..., ($X, $B), ...) + message: | + key `$X` is uselessly assigned twice. This could be a potential bug. + languages: [python] + severity: ERROR diff --git a/.github/helper/semgrep_rules/security.py b/.github/helper/semgrep_rules/security.py new file mode 100644 index 0000000000..f477d7c176 --- /dev/null +++ b/.github/helper/semgrep_rules/security.py @@ -0,0 +1,6 @@ +def function_name(input): + # ruleid: frappe-codeinjection-eval + eval(input) + +# ok: frappe-codeinjection-eval +eval("1 + 1") diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml new file mode 100644 index 0000000000..5a5098bf50 --- /dev/null +++ b/.github/helper/semgrep_rules/security.yml @@ -0,0 +1,25 @@ +rules: +- id: frappe-codeinjection-eval + patterns: + - pattern-not: eval("...") + - pattern: eval(...) + message: | + Detected the use of eval(). eval() can be dangerous if used to evaluate + dynamic content. Avoid it or use safe_eval(). + languages: [python] + severity: ERROR + +- id: frappe-sqli-format-strings + patterns: + - pattern-inside: | + @frappe.whitelist() + def $FUNC(...): + ... + - pattern-either: + - pattern: frappe.db.sql("..." % ...) + - pattern: frappe.db.sql(f"...", ...) + - pattern: frappe.db.sql("...".format(...), ...) + message: | + Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines + languages: [python] + severity: WARNING diff --git a/.github/helper/semgrep_rules/translate.js b/.github/helper/semgrep_rules/translate.js new file mode 100644 index 0000000000..7b92fe2dff --- /dev/null +++ b/.github/helper/semgrep_rules/translate.js @@ -0,0 +1,37 @@ +// ruleid: frappe-translation-empty-string +__("") +// ruleid: frappe-translation-empty-string +__('') + +// ok: frappe-translation-js-formatting +__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]); + +// ruleid: frappe-translation-js-formatting +__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`); + +// ok: frappe-translation-js-formatting +__('This is fine'); + + +// ok: frappe-translation-trailing-spaces +__('This is fine'); + +// ruleid: frappe-translation-trailing-spaces +__(' this is not ok '); +// ruleid: frappe-translation-trailing-spaces +__('this is not ok '); +// ruleid: frappe-translation-trailing-spaces +__(' this is not ok'); + +// ok: frappe-translation-js-splitting +__('You have {0} subscribers in your mailing list.', [subscribers.length]) + +// todoruleid: frappe-translation-js-splitting +__('You have') + subscribers.length + __('subscribers in your mailing list.') + +// ruleid: frappe-translation-js-splitting +__('You have' + 'subscribers in your mailing list.') + +// ruleid: frappe-translation-js-splitting +__('You have {0} subscribers' + + 'in your mailing list', [subscribers.length]) diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py new file mode 100644 index 0000000000..bd6cd9126c --- /dev/null +++ b/.github/helper/semgrep_rules/translate.py @@ -0,0 +1,53 @@ +# Examples taken from https://frappeframework.com/docs/user/en/translations +# This file is used for testing the tests. + +from frappe import _ + +full_name = "Jon Doe" +# ok: frappe-translation-python-formatting +_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name) + +# ruleid: frappe-translation-python-formatting +_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name) +# ruleid: frappe-translation-python-formatting +_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name}) + +# ruleid: frappe-translation-python-formatting +_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name)) + + +subscribers = ["Jon", "Doe"] +# ok: frappe-translation-python-formatting +_('You have {0} subscribers in your mailing list.').format(len(subscribers)) + +# ruleid: frappe-translation-python-splitting +_('You have') + len(subscribers) + _('subscribers in your mailing list.') + +# ruleid: frappe-translation-python-splitting +_('You have {0} subscribers \ + in your mailing list').format(len(subscribers)) + +# ok: frappe-translation-python-splitting +_('You have {0} subscribers') \ + + 'in your mailing list' + +# ruleid: frappe-translation-trailing-spaces +msg = _(" You have {0} pending invoice ") +# ruleid: frappe-translation-trailing-spaces +msg = _("You have {0} pending invoice ") +# ruleid: frappe-translation-trailing-spaces +msg = _(" You have {0} pending invoice") + +# ok: frappe-translation-trailing-spaces +msg = ' ' + _("You have {0} pending invoices") + ' ' + +# ruleid: frappe-translation-python-formatting +_(f"can not format like this - {subscribers}") +# ruleid: frappe-translation-python-splitting +_(f"what" + f"this is also not cool") + + +# ruleid: frappe-translation-empty-string +_("") +# ruleid: frappe-translation-empty-string +_('') diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml new file mode 100644 index 0000000000..3737da5a7e --- /dev/null +++ b/.github/helper/semgrep_rules/translate.yml @@ -0,0 +1,63 @@ +rules: +- id: frappe-translation-empty-string + pattern-either: + - pattern: _("") + - pattern: __("") + message: | + Empty string is useless for translation. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python, javascript, json] + severity: ERROR + +- id: frappe-translation-trailing-spaces + pattern-either: + - pattern: _("=~/(^[ \t]+|[ \t]+$)/") + - pattern: __("=~/(^[ \t]+|[ \t]+$)/") + message: | + Trailing or leading whitespace not allowed in translate strings. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python, javascript, json] + severity: ERROR + +- id: frappe-translation-python-formatting + pattern-either: + - pattern: _("..." % ...) + - pattern: _("...".format(...)) + - pattern: _(f"...") + message: | + Only positional formatters are allowed and formatting should not be done before translating. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python] + severity: ERROR + +- id: frappe-translation-js-formatting + patterns: + - pattern: __(`...`) + - pattern-not: __("...") + message: | + Template strings are not allowed for text formatting. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [javascript, json] + severity: ERROR + +- id: frappe-translation-python-splitting + pattern-either: + - pattern: _(...) + ... + _(...) + - pattern: _("..." + "...") + - pattern-regex: '_\([^\)]*\\\s*' + message: | + Do not split strings inside translate function. Do not concatenate using translate functions. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python] + severity: ERROR + +- id: frappe-translation-js-splitting + pattern-either: + - pattern-regex: '__\([^\)]*[\+\\]\s*' + - pattern: __('...' + '...') + - pattern: __('...') + __('...') + message: | + Do not split strings inside translate function. Do not concatenate using translate functions. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [javascript, json] + severity: ERROR diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py new file mode 100644 index 0000000000..4a74457435 --- /dev/null +++ b/.github/helper/semgrep_rules/ux.py @@ -0,0 +1,31 @@ +import frappe +from frappe import msgprint, throw, _ + + +# ruleid: frappe-missing-translate-function +throw("Error Occured") + +# ruleid: frappe-missing-translate-function +frappe.throw("Error Occured") + +# ruleid: frappe-missing-translate-function +frappe.msgprint("Useful message") + +# ruleid: frappe-missing-translate-function +msgprint("Useful message") + + +# ok: frappe-missing-translate-function +translatedmessage = _("Hello") + +# ok: frappe-missing-translate-function +throw(translatedmessage) + +# ok: frappe-missing-translate-function +msgprint(translatedmessage) + +# ok: frappe-missing-translate-function +msgprint(_("Helpful message")) + +# ok: frappe-missing-translate-function +frappe.throw(_("Error occured")) diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml new file mode 100644 index 0000000000..ed06a6a80c --- /dev/null +++ b/.github/helper/semgrep_rules/ux.yml @@ -0,0 +1,15 @@ +rules: +- id: frappe-missing-translate-function + pattern-either: + - patterns: + - pattern: frappe.msgprint("...", ...) + - pattern-not: frappe.msgprint(_("..."), ...) + - pattern-not: frappe.msgprint(__("..."), ...) + - patterns: + - pattern: frappe.throw("...", ...) + - pattern-not: frappe.throw(_("..."), ...) + - pattern-not: frappe.throw(__("..."), ...) + message: | + All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations + languages: [python, javascript, json] + severity: ERROR diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000000..df08263236 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,24 @@ +name: Semgrep + +on: + pull_request: + branches: + - develop +jobs: + semgrep: + name: Frappe Linter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup python3 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Run semgrep + run: | + python -m pip install -q semgrep + git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q + files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) + [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files + semgrep --config="r/python.lang.correctness" --quiet --error $files + [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files From d6be154ac27dcc69655213a0770610d81e438327 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Apr 2021 22:08:44 +0530 Subject: [PATCH 0520/1264] fix: implicit string concatenation (#25371) * fix: implicit string concatenations * chore: rerun healthcare patch for company fields --- .../accounts/doctype/promotional_scheme/promotional_scheme.py | 4 ++-- erpnext/patches.txt | 2 +- .../patches/v13_0/set_company_field_in_healthcare_doctypes.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 523e9ee08a..7d9302382f 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -9,7 +9,7 @@ from frappe.utils import cstr from frappe.model.naming import make_autoname from frappe.model.document import Document -pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group' +pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group', 'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from', 'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier', 'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules'] @@ -111,4 +111,4 @@ def get_args_for_pricing_rule(doc): for d in pricing_rule_fields: args[d] = doc.get(d) - return args \ No newline at end of file + return args diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1f800889c7..112f6d8a83 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -693,7 +693,7 @@ execute:frappe.reload_doctype('Dashboard') execute:frappe.reload_doc('desk', 'doctype', 'number_card_link') execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo -erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25 +erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2021-04-16 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) diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py index be5e30f307..a5b93f6307 100644 --- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py +++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py @@ -3,7 +3,7 @@ import frappe def execute(): company = frappe.db.get_single_value('Global Defaults', 'default_company') - doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection' 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment'] + doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection', 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment'] for entry in doctypes: if frappe.db.exists('DocType', entry): frappe.reload_doc('Healthcare', 'doctype', entry) From 18c7815a1b313c10cedf4135b51ecaeaa5c76bb7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 17 Apr 2021 15:37:40 +0530 Subject: [PATCH 0521/1264] fix: presentation currency in statement of accounts (#25367) --- .../process_statement_of_accounts.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index e1ddeff61f..94ae79a0c6 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -38,22 +38,22 @@ {% endif %} - {{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }} {% else %} {{ frappe.format(row.account, {fieldtype: "Link"}) or " " }} - {{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }} + {{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }} - {{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }} + {{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }} {% endif %} - {{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }} {% endfor %} From 75e13f7bb662d903dca2a4038ed489333604e34c Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 17 Apr 2021 15:38:47 +0530 Subject: [PATCH 0522/1264] fix(e-invoicing): add company validation for e-invoicing (#25348) Co-authored-by: Nabin Hait --- .../doctype/sales_invoice/test_sales_invoice.py | 15 +++++++++++++-- erpnext/regional/india/e_invoice/utils.py | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4a6f9d1d6a..9059d0b040 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1879,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase): def test_einvoice_submission_without_irn(self): # init - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 1 + einvoice_settings.applicable_from = nowdate() + einvoice_settings.append('credentials', { + 'company': '_Test Company', + 'gstin': '27AAECE4835E1ZR', + 'username': 'test', + 'password': 'test' + }) + einvoice_settings.save() + country = frappe.flags.country frappe.flags.country = 'India' @@ -1890,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase): si.submit() # reset - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 0 frappe.flags.country = country def test_einvoice_json(self): diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 59c098c1ca..1d3cb661dd 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -38,12 +38,13 @@ def validate_eligibility(doc): einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): return False - + + invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - if invalid_supply_type or company_transaction or no_taxes_applied: + if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied: return False return True @@ -400,7 +401,7 @@ def validate_totals(einvoice): if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) - if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1: + if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1: frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) calculated_invoice_value = \ From dedf2c1b61a8f31fb8da831e2c2a96611bacaa35 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 14:57:57 +0530 Subject: [PATCH 0523/1264] fix: remove duplicate keys from dictionaries --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 - erpnext/controllers/stock_controller.py | 1 - erpnext/manufacturing/dashboard_fixtures.py | 4 +--- .../manufacturing/doctype/production_plan/production_plan.py | 1 - erpnext/regional/report/gstr_1/gstr_1.py | 2 +- erpnext/stock/get_item_details.py | 2 -- erpnext/utilities/activation.py | 1 - 7 files changed, 2 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3c91dccaa7..a731e79574 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -46,7 +46,6 @@ class SalesInvoice(SellingController): 'target_parent_dt': 'Sales Order', 'target_parent_field': 'per_billed', 'source_field': 'amount', - 'join_field': 'so_detail', 'percent_join_field': 'sales_order', 'status_field': 'billing_status', 'keyword': 'Billed', diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 20499579ca..34f7b27e00 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -117,7 +117,6 @@ class StockController(AccountsController): "account": expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, - "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"), diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py index 0e9a21c026..7ba43d6471 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -43,7 +43,6 @@ def get_charts(): return [{ "doctype": "Dashboard Chart", "based_on": "modified", - "time_interval": "Yearly", "chart_type": "Sum", "chart_name": _("Produced Quantity"), "name": "Produced Quantity", @@ -60,7 +59,6 @@ def get_charts(): }, { "doctype": "Dashboard Chart", "based_on": "creation", - "time_interval": "Yearly", "chart_type": "Sum", "chart_name": _("Completed Operation"), "name": "Completed Operation", @@ -238,4 +236,4 @@ def get_number_cards(): "label": _("Monthly Quality Inspections"), "show_percentage_stats": 1, "stats_time_interval": "Weekly" - }] \ No newline at end of file + }] diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index cef2d8be7a..e7c83ac050 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -561,7 +561,6 @@ def get_material_request_items(row, sales_order, company, 'item_name': row.item_name, 'quantity': required_qty, 'required_bom_qty': total_qty, - 'description': row.description, 'stock_uom': row.get("stock_uom"), 'warehouse': warehouse or row.get('source_warehouse') \ or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 75076231c0..b637fb47b3 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -561,7 +561,7 @@ def get_json(filters, report_name, data): fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) - gst_json = {"gstin": "", "version": "GST2.2.9", + gst_json = {"version": "GST2.2.9", "hash": "hash", "gstin": gstin, "fp": fp} res = {} diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index aaf14a535e..dedfe1d79b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -309,8 +309,6 @@ def get_basic_details(args, item, overwrite_warehouse=True): "update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0, "delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0, "is_fixed_asset": item.is_fixed_asset, - "weight_per_unit":item.weight_per_unit, - "weight_uom":item.weight_uom, "last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0, "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py index 7b17c8c464..50c4b255ce 100644 --- a/erpnext/utilities/activation.py +++ b/erpnext/utilities/activation.py @@ -18,7 +18,6 @@ def get_level(): "Delivery Note": 5, "Employee": 3, "Instructor": 5, - "Instructor": 5, "Issue": 5, "Item": 5, "Journal Entry": 3, From ad6a2657aee99b2d56afba99eca55bb09ef4f3d9 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 16:50:02 +0530 Subject: [PATCH 0524/1264] chore: minor translation fixes --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 +++--- erpnext/controllers/stock_controller.py | 2 +- .../doctype/production_plan/production_plan.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index a731e79574..4461f29fe3 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -275,7 +275,7 @@ class SalesInvoice(SellingController): pluck="pos_closing_entry" ) if pos_closing_entry: - msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format( + msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format( frappe.bold("Consolidated Sales Invoice"), get_link_to_form("POS Closing Entry", pos_closing_entry[0]) ) @@ -548,12 +548,12 @@ class SalesInvoice(SellingController): frappe.throw(_("Debit To is required"), title=_("Account Missing")) if account.report_type != "Balance Sheet": - msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To")) + msg = _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " " msg += _("You can change the parent account to a Balance Sheet account or select a different account.") frappe.throw(msg, title=_("Invalid Account")) if self.customer and account.account_type != "Receivable": - msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To")) + msg = _("Please ensure {} account is a Receivable account.").format(frappe.bold("Debit To")) + " " msg += _("Change the account type to Receivable or select a different account.") frappe.throw(msg, title=_("Invalid Account")) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 34f7b27e00..b14c274515 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -482,7 +482,7 @@ class StockController(AccountsController): ) message += "

    " rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule) - message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link) + message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link) return message def repost_future_sle_and_gle(self): diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index e7c83ac050..a3e23a6897 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -765,7 +765,7 @@ def get_items_for_material_requests(doc, warehouses=None): to_enable = frappe.bold(_("Ignore Existing Projected Quantity")) warehouse = frappe.bold(doc.get('for_warehouse')) message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "

    " - message += _(" If you still want to proceed, please enable {0}.").format(to_enable) + message += _("If you still want to proceed, please enable {0}.").format(to_enable) frappe.msgprint(message, title=_("Note")) From 9229ee1745d864656ddee0b216a33078b7a932c6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 15:41:10 +0530 Subject: [PATCH 0525/1264] fix: update shipment status in database Caught by semgrep rule: https://github.com/frappe/erpnext/blob/develop/.github/helper/semgrep_rules/frappe_correctness.yml#L4 --- erpnext/stock/doctype/shipment/shipment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index 4697a7b323..01fcee4cac 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -23,10 +23,10 @@ class Shipment(Document): frappe.throw(_('Please enter Shipment Parcel information')) if self.value_of_goods == 0: frappe.throw(_('Value of goods cannot be 0')) - self.status = 'Submitted' + self.db_set('status', 'Submitted') def on_cancel(self): - self.status = 'Cancelled' + self.db_set('status', 'Cancelled') def validate_weight(self): for parcel in self.shipment_parcel: From e972ceb79840783f3947a1856551d477154ec4be Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 15:42:03 +0530 Subject: [PATCH 0526/1264] fix: patch for updating shipment status --- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_shipment_status.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 erpnext/patches/v13_0/update_shipment_status.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 112f6d8a83..620cc5be62 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -772,3 +772,4 @@ erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 +erpnext.patches.v13_0.update_shipment_status diff --git a/erpnext/patches/v13_0/update_shipment_status.py b/erpnext/patches/v13_0/update_shipment_status.py new file mode 100644 index 0000000000..c425599e26 --- /dev/null +++ b/erpnext/patches/v13_0/update_shipment_status.py @@ -0,0 +1,14 @@ +import frappe + +def execute(): + frappe.reload_doc("stock", "doctype", "shipment") + + # update submitted status + frappe.db.sql("""UPDATE `tabShipment` + SET status = "Submitted" + WHERE status = "Draft" AND docstatus = 1""") + + # update cancelled status + frappe.db.sql("""UPDATE `tabShipment` + SET status = "Cancelled" + WHERE status = "Draft" AND docstatus = 2""") From c28fcba77964edb57609fee17b0470638596e5a3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 15:47:34 +0530 Subject: [PATCH 0527/1264] ci(semgrep): add correctness rule for on_cancel Changes done to doctype object in `on_submit` are not commited to database. Add rule to catch similar bugs. --- .../semgrep_rules/frappe_correctness.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index 394abbf74d..54df062480 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -7,11 +7,29 @@ rules: - pattern-inside: | def on_submit(self, ...): ... + - metavariable-regex: + metavariable: '$ATTR' + # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me) + regex: '^(?!status_updater)(.*)$' message: | Doctype modified after submission. Please check if modification of self.$ATTR is commited to database. languages: [python] severity: ERROR +- id: frappe-modifying-after-cancel + patterns: + - pattern: self.$ATTR = ... + - pattern-inside: | + def on_cancel(self, ...): + ... + - metavariable-regex: + metavariable: '$ATTR' + regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$' + message: | + Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database. + languages: [python] + severity: ERROR + - id: frappe-print-function-in-doctypes pattern: print(...) message: | From 80d44cada4248946ed6e0cc8e61b09d2612b1547 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 18 Apr 2021 18:33:34 +0200 Subject: [PATCH 0528/1264] fix: remove is_default from country wise tax --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 6305442ef2..5876488033 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -486,7 +486,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -513,7 +512,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -567,7 +565,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -594,7 +591,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -625,7 +621,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -652,7 +647,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -683,7 +677,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -708,7 +701,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -829,7 +821,6 @@ "item_tax_templates": [ { "title": "In State GST", - "is_default": 1, "taxes": [ { "tax_type": { @@ -893,7 +884,6 @@ "*": [ { "title": "In State GST", - "is_default": 1, "taxes": [ { "account_head": { From e78253152916ef8a16a467445d670dcd03bd6c83 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 19 Apr 2021 10:15:51 +0530 Subject: [PATCH 0529/1264] fix: Apply single transaction threshold on net_total instead of supplier credit amount (#25243) * fix: Apply single transaction threshold on net_total instead of supplier credit amount * fix: Apply single transaction threshold on net_total instead of supplier credit amount * fix: test Co-authored-by: Nabin Hait --- .../tax_withholding_category.py | 2 +- .../test_tax_withholding_category.py | 44 ------------------- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 961bdb147f..09db7fee2b 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -251,7 +251,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu threshold = tax_details.get('threshold', 0) cumulative_threshold = tax_details.get('cumulative_threshold', 0) - if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): + if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): if ldc and is_valid_certificate( ldc.valid_from, ldc.valid_upto, inv.posting_date, tax_deducted, diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index dd3b49aa04..0cea7612dd 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -87,50 +87,6 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() - def test_single_threshold_tds_with_previous_vouchers(self): - invoices = [] - frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS") - pi = create_purchase_invoice(supplier="Test TDS Supplier2") - pi.submit() - invoices.append(pi) - - pi = create_purchase_invoice(supplier="Test TDS Supplier2") - pi.submit() - invoices.append(pi) - - self.assertEqual(pi.taxes_and_charges_deducted, 2000) - self.assertEqual(pi.grand_total, 8000) - - # delete invoices to avoid clashing - for d in invoices: - d.cancel() - - def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self): - invoices = [] - doc = create_supplier(supplier_name = "Test TDS Supplier ABC", - tax_withholding_category="Single Threshold TDS") - supplier = doc.name - - pi = create_purchase_invoice(supplier=supplier) - pi.submit() - invoices.append(pi) - - # TDS not applied - pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True) - pi.submit() - invoices.append(pi) - - pi = create_purchase_invoice(supplier=supplier) - pi.submit() - invoices.append(pi) - - self.assertEqual(pi.taxes_and_charges_deducted, 2000) - self.assertEqual(pi.grand_total, 8000) - - # delete invoices to avoid clashing - for d in invoices: - d.cancel() - def test_cumulative_threshold_tcs(self): frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS") invoices = [] From 7eac4a250d1d156859f9c0d18a0f45c8bcc5215d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 19 Apr 2021 10:33:39 +0530 Subject: [PATCH 0530/1264] fix: functions using mutable defaults (#25370) --- erpnext/controllers/queries.py | 4 +++- erpnext/controllers/status_updater.py | 4 +++- .../doctype/bom_update_tool/bom_update_tool.py | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index c0c13153de..bc1ac5ea06 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -713,7 +713,9 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters): return [(d,) for d in set(taxes)] -def get_fields(doctype, fields=[]): +def get_fields(doctype, fields=None): + if fields is None: + fields = [] meta = frappe.get_meta(doctype) fields.extend(meta.get_search_fields()) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 0987d0985e..cdb6d244a6 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -371,10 +371,12 @@ class StatusUpdater(Document): ref_doc.db_set("per_billed", per_billed) ref_doc.set_status(update=True) -def get_allowance_for(item_code, item_allowance={}, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"): +def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"): """ Returns the allowance for the item, if not set, returns global allowance """ + if item_allowance is None: + item_allowance = {} if qty_or_amount == "qty": if item_allowance.get(item_code, frappe._dict()).get("qty"): return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 742d18c4cd..8fbcd4ea1d 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -53,7 +53,9 @@ class BOMUpdateTool(Document): rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""", (self.new_bom, unit_cost, unit_cost, self.current_bom)) - def get_parent_boms(self, bom, bom_list=[]): + def get_parent_boms(self, bom, bom_list=None): + if bom_list is None: + bom_list = [] data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item` WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom) @@ -106,4 +108,4 @@ def update_cost(): for bom in bom_list: frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) - frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file + frappe.db.auto_commit_on_many_writes = 0 From dcdd3bebbe3db01ee2987843d4bd4ca72cd913e5 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 19 Apr 2021 10:36:40 +0530 Subject: [PATCH 0531/1264] feat: Timer in LMS Quiz (#24246) * feat: new fields in quiz doctypes * feat: timer in lms quiz * fix: variable initialisation * fix: context, exception fix * fix:sider * fix:sider * fix: indentation * fix: timer * fix: sider * fix: return value and format * fix: show time taken only after all attempts are over * fix: sider Co-authored-by: pateljannat Co-authored-by: Marica --- .../course_enrollment/course_enrollment.py | 5 +- erpnext/education/doctype/quiz/quiz.json | 25 +- .../doctype/quiz_activity/quiz_activity.json | 423 ++---------------- erpnext/education/doctype/student/student.py | 2 +- erpnext/education/utils.py | 33 +- erpnext/public/js/education/lms/quiz.js | 72 ++- erpnext/www/lms/content.html | 55 ++- erpnext/www/lms/index.html | 12 +- erpnext/www/lms/topic.py | 2 +- 9 files changed, 218 insertions(+), 411 deletions(-) diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index f7aa6e9fc1..2b3acf1b93 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -41,7 +41,7 @@ class CourseEnrollment(Document): frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format( get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry')) - def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status): + def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken): result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()} result_data = [] for key in answers: @@ -66,7 +66,8 @@ class CourseEnrollment(Document): "activity_date": frappe.utils.datetime.datetime.now(), "result": result_data, "score": score, - "status": status + "status": status, + "time_taken": time_taken }).insert(ignore_permissions = True) def add_activity(self, content_type, content): diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json index 569c281f4c..16d7d7e4bf 100644 --- a/erpnext/education/doctype/quiz/quiz.json +++ b/erpnext/education/doctype/quiz/quiz.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:title", @@ -12,7 +13,10 @@ "quiz_configuration_section", "passing_score", "max_attempts", - "grading_basis" + "grading_basis", + "column_break_7", + "is_time_bound", + "duration" ], "fields": [ { @@ -58,9 +62,26 @@ "fieldtype": "Select", "label": "Grading Basis", "options": "Latest Highest Score\nLatest Attempt" + }, + { + "default": "0", + "fieldname": "is_time_bound", + "fieldtype": "Check", + "label": "Is Time-Bound" + }, + { + "depends_on": "is_time_bound", + "fieldname": "duration", + "fieldtype": "Duration", + "label": "Duration" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" } ], - "modified": "2019-06-12 12:23:57.020508", + "links": [], + "modified": "2020-12-24 15:41:35.043262", "modified_by": "Administrator", "module": "Education", "name": "Quiz", diff --git a/erpnext/education/doctype/quiz_activity/quiz_activity.json b/erpnext/education/doctype/quiz_activity/quiz_activity.json index e78db42f7d..742c88754a 100644 --- a/erpnext/education/doctype/quiz_activity/quiz_activity.json +++ b/erpnext/education/doctype/quiz_activity/quiz_activity.json @@ -1,490 +1,163 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, + "actions": [], "autoname": "format:EDU-QA-{YYYY}-{#####}", "beta": 1, "creation": "2018-10-15 15:48:40.482821", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "enrollment", + "student", + "column_break_3", + "course", + "section_break_5", + "quiz", + "column_break_7", + "status", + "section_break_9", + "result", + "section_break_11", + "activity_date", + "score", + "column_break_14", + "time_taken" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "enrollment", "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": "Enrollment", - "length": 0, - "no_copy": 0, "options": "Course Enrollment", - "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 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "enrollment.student", "fieldname": "student", "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": "Student", - "length": 0, - "no_copy": 0, "options": "Student", - "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 + "read_only": 1 }, { - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "enrollment.course", "fieldname": "course", "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": "Course", - "length": 0, - "no_copy": 0, "options": "Course", - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "quiz", "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": "Quiz", - "length": 0, - "no_copy": 0, "options": "Quiz", - "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 + "set_only_once": 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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "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": "\nPass\nFail", - "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 + "read_only": 1 }, { - "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": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "result", "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": "Result", - "length": 0, - "no_copy": 0, "options": "Quiz Result", - "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 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "activity_date", "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": "Activity 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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "score", "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": "Score", - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 + }, + { + "fieldname": "time_taken", + "fieldtype": "Duration", + "label": "Time Taken", + "set_only_once": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" } ], - "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": "2018-11-25 19:05:52.434437", + "links": [], + "modified": "2020-12-24 15:41:20.085380", "modified_by": "Administrator", "module": "Education", "name": "Quiz Activity", - "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, "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": "LMS User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "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": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 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, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 81626f1918..2dc0f634f0 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -114,7 +114,7 @@ class Student(Document): status = check_content_completion(content.name, content.doctype, course_enrollment_name) progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status}) elif content.doctype == 'Quiz': - status, score, result = check_quiz_completion(content, course_enrollment_name) + status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name) progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result}) return progress diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index cffc3960a0..8f51fef847 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -194,7 +194,7 @@ def add_activity(course, content_type, content, program): return enrollment.add_activity(content_type, content) @frappe.whitelist() -def evaluate_quiz(quiz_response, quiz_name, course, program): +def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken): import json student = get_current_student() @@ -209,7 +209,7 @@ def evaluate_quiz(quiz_response, quiz_name, course, program): if student: enrollment = get_or_create_course_enrollment(course, program) if quiz.allowed_attempt(enrollment, quiz_name): - enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) + enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken) return {'result': result, 'score': score, 'status': status} else: return None @@ -219,8 +219,9 @@ def get_quiz(quiz_name, course): try: quiz = frappe.get_doc("Quiz", quiz_name) questions = quiz.get_questions() + duration = quiz.duration except: - frappe.throw(_("Quiz {0} does not exist").format(quiz_name)) + frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError) return None questions = [{ @@ -232,12 +233,20 @@ def get_quiz(quiz_name, course): } for question in questions] if has_super_access(): - return {'questions': questions, 'activity': None} + return { + 'questions': questions, + 'activity': None, + 'duration':duration + } student = get_current_student() course_enrollment = get_enrollment("course", course, student.name) - status, score, result = check_quiz_completion(quiz, course_enrollment) - return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} + status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment) + return { + 'questions': questions, + 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken}, + 'duration': quiz.duration + } def get_topic_progress(topic, course_name, program): """ @@ -361,15 +370,23 @@ def check_content_completion(content_name, content_type, enrollment_name): return False def check_quiz_completion(quiz, enrollment_name): - attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"]) + attempts = frappe.get_all("Quiz Activity", + filters={ + 'enrollment': enrollment_name, + 'quiz': quiz.name + }, + fields=["name", "activity_date", "score", "status", "time_taken"] + ) status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) score = None result = None + time_taken = None if attempts: if quiz.grading_basis == 'Last Highest Score': attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True) score = attempts[0]['score'] result = attempts[0]['status'] + time_taken = attempts[0]['time_taken'] if result == 'Pass': status = True - return status, score, result \ No newline at end of file + return status, score, result, time_taken \ No newline at end of file diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index 4a9d1e34e6..32fa4ab1ec 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -20,6 +20,16 @@ class Quiz { } make(data) { + if (data.duration) { + const timer_display = document.createElement("div"); + timer_display.classList.add("lms-timer", "float-right", "font-weight-bold"); + document.getElementsByClassName("lms-title")[0].appendChild(timer_display); + if (!data.activity || (data.activity && !data.activity.is_complete)) { + this.initialiseTimer(data.duration); + this.is_time_bound = true; + this.time_taken = 0; + } + } data.questions.forEach(question_data => { let question_wrapper = document.createElement('div'); let question = new Question({ @@ -37,12 +47,51 @@ class Quiz { indicator = 'green' message = 'You have already cleared the quiz.' } - + if (data.activity.time_taken) { + this.calculate_and_display_time(data.activity.time_taken, "Time Taken - "); + } this.set_quiz_footer(message, indicator, data.activity.score) } else { this.make_actions(); } + window.addEventListener('beforeunload', (event) => { + event.preventDefault(); + event.returnValue = ''; + }); + } + + initialiseTimer(duration) { + this.time_left = duration; + var self = this; + var old_diff; + this.calculate_and_display_time(this.time_left, "Time Left - "); + this.start_time = new Date().getTime(); + this.timer = setInterval(function () { + var diff = (new Date().getTime() - self.start_time)/1000; + var variation = old_diff ? diff - old_diff : diff; + old_diff = diff; + self.time_left -= variation; + self.time_taken += variation; + self.calculate_and_display_time(self.time_left, "Time Left - "); + if (self.time_left <= 0) { + clearInterval(self.timer); + self.time_taken -= 1; + self.submit(); + } + }, 1000); + } + + calculate_and_display_time(second, text) { + var timer_display = document.getElementsByClassName("lms-timer")[0]; + var hours = this.append_zero(Math.floor(second / 3600)); + var minutes = this.append_zero(Math.floor(second % 3600 / 60)); + var seconds = this.append_zero(Math.ceil(second % 3600 % 60)); + timer_display.innerText = text + hours + ":" + minutes + ":" + seconds; + } + + append_zero(time) { + return time > 9 ? time : "0" + time; } make_actions() { @@ -57,6 +106,10 @@ class Quiz { } submit() { + if (this.is_time_bound) { + clearInterval(this.timer); + $(".lms-timer").text(""); + } this.submit_btn.innerText = 'Evaluating..' this.submit_btn.disabled = true this.disable() @@ -64,7 +117,8 @@ class Quiz { quiz_name: this.name, quiz_response: this.get_selected(), course: this.course, - program: this.program + program: this.program, + time_taken: this.is_time_bound ? this.time_taken : "" }).then(res => { this.submit_btn.remove() if (!res.message) { @@ -157,7 +211,7 @@ class Question { return input; } - let make_label = function(name, value) { + let make_label = function (name, value) { let label = document.createElement('label'); label.classList.add('form-check-label'); label.htmlFor = name; @@ -166,14 +220,14 @@ class Question { } let make_option = function (wrapper, option) { - let option_div = document.createElement('div') - option_div.classList.add('form-check', 'pb-1') + let option_div = document.createElement('div'); + option_div.classList.add('form-check', 'pb-1'); let input = make_input(option.name, option.option); let label = make_label(option.name, option.option); - option_div.appendChild(input) - option_div.appendChild(label) - wrapper.appendChild(option_div) - return {input: input, ...option} + option_div.appendChild(input); + option_div.appendChild(label); + wrapper.appendChild(option_div); + return { input: input, ...option }; } let options_wrapper = document.createElement('div') diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index dc9b6d80fb..15afb097b9 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -62,7 +62,7 @@ {{_('Back to Course')}} -
    +

    {{ content.name }} ({{ position + 1 }}/{{length}})

    {% endmacro %} @@ -169,14 +169,51 @@ const next_url = '/lms/course?name={{ course }}&program={{ program }}' {% endif %} frappe.ready(() => { - const quiz = new Quiz(document.getElementById('quiz-wrapper'), { - name: '{{ content.name }}', - course: '{{ course }}', - program: '{{ program }}', - quiz_exit_button: quiz_exit_button, - next_url: next_url - }) - window.quiz = quiz; + {% if content.is_time_bound %} + var duration = get_duration("{{content.duration}}") + var d = frappe.msgprint({ + title: __('Important Notice'), + indicator: "red", + message: __(`This is a Time-Bound Quiz.

    + A timer for ${duration} will start, once you click on Proceed.

    + If you fail to submit before the time is up, the Quiz will be submitted automatically.`), + primary_action: { + label: __("Proceed"), + action: () => { + create_quiz(); + d.hide(); + } + }, + secondary_action: { + action: () => { + d.hide(); + window.location.href = "/lms/course?name={{ course }}&program={{ program }}"; + }, + label: __("Go Back"), + } + }); + {% else %} + create_quiz(); + {% endif %} + function create_quiz() { + const quiz = new Quiz(document.getElementById('quiz-wrapper'), { + name: '{{ content.name }}', + course: '{{ course }}', + program: '{{ program }}', + quiz_exit_button: quiz_exit_button, + next_url: next_url + }) + window.quiz = quiz; + } + function get_duration(seconds){ + var hours = append_zero(Math.floor(seconds / 3600)); + var minutes = append_zero(Math.floor(seconds % 3600 / 60)); + var seconds = append_zero(Math.floor(seconds % 3600 % 60)); + return `${hours}:${minutes}:${seconds}`; + } + function append_zero(time) { + return time > 9 ? time : "0" + time; + } }) {% endif %} diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index 7b239acd56..c1e96205eb 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -42,7 +42,9 @@

    {{ education_settings.portal_title }}

    -

    {{ education_settings.description }}

    + {% if education_settings.description %} +

    {{ education_settings.description }}

    + {% endif %}

    {% if frappe.session.user == 'Guest' %} {{_('Sign Up')}} @@ -51,13 +53,15 @@

    - {% for program in featured_programs %} - {{ program_card(program.program, program.has_access) }} - {% endfor %} {% if featured_programs %} + {% for program in featured_programs %} + {{ program_card(program.program, program.has_access) }} + {% endfor %} {% for n in range( (3 - (featured_programs|length)) %3) %} {{ null_card() }} {% endfor %} + {% else %} +

    You have not enrolled in any program. Contact your Instructor.

    {% endif %}
    diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py index f75ae8e9b6..8abbc72e91 100644 --- a/erpnext/www/lms/topic.py +++ b/erpnext/www/lms/topic.py @@ -35,7 +35,7 @@ def get_contents(topic, course, program): progress.append({'content': content, 'content_type': content.doctype, 'completed': status}) elif content.doctype == 'Quiz': if student: - status, score, result = utils.check_quiz_completion(content, course_enrollment.name) + status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name) else: status = False score = None From e8bc912ffcafeccfd41c8944f6ac053a68d9ac56 Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 19 Apr 2021 11:05:21 +0530 Subject: [PATCH 0532/1264] perf: Fetching exchange rate on every line item slows down PO (#25345) * fix: Dont fetch exchange rates for each line item once fetched at parent ` * perf: Use price list conversion rate from parent - If price list conversion rate exists in args already from earlier call, use that - `get_price_list_currency_and_exchange_rate` wont be called for each child row Co-authored-by: Nabin Hait --- erpnext/public/js/controllers/transaction.js | 2 ++ erpnext/stock/get_item_details.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 6c2144d6cb..a0398e718f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1103,6 +1103,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ to_currency: to_currency, args: args }, + freeze: true, + freeze_message: __("Fetching exchange rates ..."), callback: function(r) { callback(flt(r.message)); } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index dedfe1d79b..1a61f30b9a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -609,8 +609,12 @@ def get_price_list_rate(args, item_doc, out): meta = frappe.get_meta(args.parenttype or args.doctype) if meta.get_field("currency") or args.get('currency'): - pl_details = get_price_list_currency_and_exchange_rate(args) - args.update(pl_details) + if not args.get("price_list_currency") or not args.get("plc_conversion_rate"): + # if currency and plc_conversion_rate exist then + # `get_price_list_currency_and_exchange_rate` has already been called + pl_details = get_price_list_currency_and_exchange_rate(args) + args.update(pl_details) + if meta.get_field("currency"): validate_conversion_rate(args, meta) @@ -1000,6 +1004,8 @@ def apply_price_list(args, as_doc=False): args = process_args(args) parent = get_price_list_currency_and_exchange_rate(args) + args.update(parent) + children = [] if "items" in args: @@ -1064,7 +1070,7 @@ def get_price_list_currency_and_exchange_rate(args): return frappe._dict({ "price_list_currency": price_list_currency, "price_list_uom_dependant": price_list_uom_dependant, - "plc_conversion_rate": plc_conversion_rate + "plc_conversion_rate": plc_conversion_rate or 1 }) @frappe.whitelist() From 9c9907cf8e26ea65d330f1fec07d01afa2038017 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:48:28 +0530 Subject: [PATCH 0533/1264] fix: available employee for selection (#25377) * fix: available employee for selection * fix: available employee for selection fix: available employee for selection --- .../doctype/payroll_entry/payroll_entry.js | 4 + .../doctype/payroll_entry/payroll_entry.py | 153 ++++++++++-------- 2 files changed, 88 insertions(+), 69 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 85bb651af7..f2892600d1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', { filters['company'] = frm.doc.company; filters['start_date'] = frm.doc.start_date; filters['end_date'] = frm.doc.end_date; + filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet; + filters['payroll_frequency'] = frm.doc.payroll_frequency; + filters['payroll_payable_account'] = frm.doc.payroll_payable_account; + filters['currency'] = frm.doc.currency; if (frm.doc.department) { filters['department'] = frm.doc.department; diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 4c9469e277..3953b463f1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -52,49 +52,32 @@ class PayrollEntry(Document): Returns list of active employees based on selected criteria and for which salary structure exists """ - cond = self.get_filter_condition() - cond += self.get_joining_relieving_condition() + self.check_mandatory() + filters = self.make_filters() + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(self.start_date, self.end_date) condition = '' if self.payroll_frequency: condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} - sal_struct = frappe.db.sql_list(""" - select - name from `tabSalary Structure` - where - docstatus = 1 and - is_active = 'Yes' - and company = %(company)s - and currency = %(currency)s and - ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s - {condition}""".format(condition=condition), - {"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) - + sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition) if sal_struct: cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " cond += "and %(from_date)s >= t2.from_date" - emp_list = frappe.db.sql(""" - select - distinct t1.name as employee, t1.employee_name, t1.department, t1.designation - from - `tabEmployee` t1, `tabSalary Structure Assignment` t2 - where - t1.name = t2.employee - and t2.docstatus = 1 - %s order by t2.from_date desc - """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True) - - emp_list = self.remove_payrolled_employees(emp_list) + emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date) return emp_list - def remove_payrolled_employees(self, emp_list): - for employee_details in emp_list: - if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): - emp_list.remove(employee_details) + def make_filters(self): + filters = frappe._dict() + filters['company'] = self.company + filters['branch'] = self.branch + filters['department'] = self.department + filters['designation'] = self.designation - return emp_list + return filters @frappe.whitelist() def fill_employee_details(self): @@ -122,23 +105,6 @@ class PayrollEntry(Document): if self.validate_attendance: return self.validate_employee_attendance() - def get_filter_condition(self): - self.check_mandatory() - - cond = '' - for f in ['company', 'branch', 'department', 'designation']: - if self.get(f): - cond += " and t1." + f + " = " + frappe.db.escape(self.get(f)) - - return cond - - def get_joining_relieving_condition(self): - cond = """ - and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' - and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' - """ % {"start_date": self.start_date, "end_date": self.end_date} - return cond - def check_mandatory(self): for fieldname in ['company', 'start_date', 'end_date']: if not self.get(fieldname): @@ -451,6 +417,53 @@ class PayrollEntry(Document): marked_days = attendances[0][0] return marked_days +def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition): + return frappe.db.sql_list(""" + select + name from `tabSalary Structure` + where + docstatus = 1 and + is_active = 'Yes' + and company = %(company)s + and currency = %(currency)s and + ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s + {condition}""".format(condition=condition), + {"company": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet}) + +def get_filter_condition(filters): + cond = '' + for f in ['company', 'branch', 'department', 'designation']: + if filters.get(f): + cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f)) + + return cond + +def get_joining_relieving_condition(start_date, end_date): + cond = """ + and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' + and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' + """ % {"start_date": start_date, "end_date": end_date} + return cond + +def get_emp_list(sal_struct, cond, end_date, payroll_payable_account): + return frappe.db.sql(""" + select + distinct t1.name as employee, t1.employee_name, t1.department, t1.designation + from + `tabEmployee` t1, `tabSalary Structure Assignment` t2 + where + t1.name = t2.employee + and t2.docstatus = 1 + %s order by t2.from_date desc + """ % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True) + +def remove_payrolled_employees(emp_list, start_date, end_date): + for employee_details in emp_list: + if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}): + emp_list.remove(employee_details) + + return emp_list + @frappe.whitelist() def get_start_end_dates(payroll_frequency, start_date=None, company=None): '''Returns dict of start and end dates for given payroll frequency based on start_date''' @@ -639,39 +652,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte 'start': start, 'page_len': page_len }) -def get_employee_with_existing_salary_slip(start_date, end_date, company): - return frappe.db.sql_list(""" - select employee from `tabSalary Slip` - where - (start_date between %(start_date)s and %(end_date)s - or - end_date between %(start_date)s and %(end_date)s - or - %(start_date)s between start_date and end_date) - and company = %(company)s - and docstatus = 1 - """, {'start_date': start_date, 'end_date': end_date, 'company': company}) +def get_employee_list(filters): + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(filters.start_date, filters.end_date) + condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency} + sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition) + if sal_struct: + cond += "and t2.salary_structure IN %(sal_struct)s " + cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " + cond += "and %(from_date)s >= t2.from_date" + emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date) + return emp_list @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): filters = frappe._dict(filters) conditions = [] - exclude_employees = [] + include_employees = [] emp_cond = '' if filters.start_date and filters.end_date: - employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company) + employee_list = get_employee_list(filters) emp = filters.get('employees') + include_employees = [employee.employee for employee in employee_list if employee.employee not in emp] filters.pop('start_date') filters.pop('end_date') + filters.pop('salary_slip_based_on_timesheet') + filters.pop('payroll_frequency') + filters.pop('payroll_payable_account') + filters.pop('currency') if filters.employees is not None: filters.pop('employees') - if employee_list: - exclude_employees.extend(employee_list) - if emp: - exclude_employees.extend(emp) - if exclude_employees: - emp_cond += 'and employee not in %(exclude_employees)s' + + if include_employees: + emp_cond += 'and employee in %(include_employees)s' return frappe.db.sql("""select name, employee_name from `tabEmployee` where status = 'Active' @@ -695,4 +710,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): '_txt': txt.replace("%", ""), 'start': start, 'page_len': page_len, - 'exclude_employees': exclude_employees}) + 'include_employees': include_employees}) From b6d2106184ea3420425609c2402388c7a2cacb9b Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:48:43 +0530 Subject: [PATCH 0534/1264] fix: available employee for selection (#25378) * fix: available employee for selection * fix: available employee for selection fix: available employee for selection --- .../doctype/payroll_entry/payroll_entry.js | 4 + .../doctype/payroll_entry/payroll_entry.py | 148 +++++++++--------- 2 files changed, 81 insertions(+), 71 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 85bb651af7..f2892600d1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', { filters['company'] = frm.doc.company; filters['start_date'] = frm.doc.start_date; filters['end_date'] = frm.doc.end_date; + filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet; + filters['payroll_frequency'] = frm.doc.payroll_frequency; + filters['payroll_payable_account'] = frm.doc.payroll_payable_account; + filters['currency'] = frm.doc.currency; if (frm.doc.department) { filters['department'] = frm.doc.department; diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 4c9469e277..b031129614 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -52,50 +52,24 @@ class PayrollEntry(Document): Returns list of active employees based on selected criteria and for which salary structure exists """ - cond = self.get_filter_condition() - cond += self.get_joining_relieving_condition() + self.check_mandatory() + filters = self.make_filters() + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(self.start_date, self.end_date) condition = '' if self.payroll_frequency: condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} - sal_struct = frappe.db.sql_list(""" - select - name from `tabSalary Structure` - where - docstatus = 1 and - is_active = 'Yes' - and company = %(company)s - and currency = %(currency)s and - ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s - {condition}""".format(condition=condition), - {"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) - + sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition) if sal_struct: cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " cond += "and %(from_date)s >= t2.from_date" - emp_list = frappe.db.sql(""" - select - distinct t1.name as employee, t1.employee_name, t1.department, t1.designation - from - `tabEmployee` t1, `tabSalary Structure Assignment` t2 - where - t1.name = t2.employee - and t2.docstatus = 1 - %s order by t2.from_date desc - """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True) - - emp_list = self.remove_payrolled_employees(emp_list) + emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date) return emp_list - def remove_payrolled_employees(self, emp_list): - for employee_details in emp_list: - if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): - emp_list.remove(employee_details) - - return emp_list - @frappe.whitelist() def fill_employee_details(self): self.set('employees', []) @@ -122,23 +96,6 @@ class PayrollEntry(Document): if self.validate_attendance: return self.validate_employee_attendance() - def get_filter_condition(self): - self.check_mandatory() - - cond = '' - for f in ['company', 'branch', 'department', 'designation']: - if self.get(f): - cond += " and t1." + f + " = " + frappe.db.escape(self.get(f)) - - return cond - - def get_joining_relieving_condition(self): - cond = """ - and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' - and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' - """ % {"start_date": self.start_date, "end_date": self.end_date} - return cond - def check_mandatory(self): for fieldname in ['company', 'start_date', 'end_date']: if not self.get(fieldname): @@ -451,6 +408,53 @@ class PayrollEntry(Document): marked_days = attendances[0][0] return marked_days +def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition): + return frappe.db.sql_list(""" + select + name from `tabSalary Structure` + where + docstatus = 1 and + is_active = 'Yes' + and company = %(company)s + and currency = %(currency)s and + ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s + {condition}""".format(condition=condition), + {"company": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet}) + +def get_filter_condition(filters): + cond = '' + for f in ['company', 'branch', 'department', 'designation']: + if filters.get(f): + cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f)) + + return cond + +def get_joining_relieving_condition(start_date, end_date): + cond = """ + and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' + and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' + """ % {"start_date": start_date, "end_date": end_date} + return cond + +def get_emp_list(sal_struct, cond, end_date, payroll_payable_account): + return frappe.db.sql(""" + select + distinct t1.name as employee, t1.employee_name, t1.department, t1.designation + from + `tabEmployee` t1, `tabSalary Structure Assignment` t2 + where + t1.name = t2.employee + and t2.docstatus = 1 + %s order by t2.from_date desc + """ % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True) + +def remove_payrolled_employees(emp_list, start_date, end_date): + for employee_details in emp_list: + if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}): + emp_list.remove(employee_details) + + return emp_list + @frappe.whitelist() def get_start_end_dates(payroll_frequency, start_date=None, company=None): '''Returns dict of start and end dates for given payroll frequency based on start_date''' @@ -639,39 +643,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte 'start': start, 'page_len': page_len }) -def get_employee_with_existing_salary_slip(start_date, end_date, company): - return frappe.db.sql_list(""" - select employee from `tabSalary Slip` - where - (start_date between %(start_date)s and %(end_date)s - or - end_date between %(start_date)s and %(end_date)s - or - %(start_date)s between start_date and end_date) - and company = %(company)s - and docstatus = 1 - """, {'start_date': start_date, 'end_date': end_date, 'company': company}) +def get_employee_list(filters): + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(filters.start_date, filters.end_date) + condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency} + sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition) + if sal_struct: + cond += "and t2.salary_structure IN %(sal_struct)s " + cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " + cond += "and %(from_date)s >= t2.from_date" + emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date) + return emp_list @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): filters = frappe._dict(filters) conditions = [] - exclude_employees = [] + include_employees = [] emp_cond = '' if filters.start_date and filters.end_date: - employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company) + employee_list = get_employee_list(filters) emp = filters.get('employees') + include_employees = [employee.employee for employee in employee_list if employee.employee not in emp] filters.pop('start_date') filters.pop('end_date') + filters.pop('salary_slip_based_on_timesheet') + filters.pop('payroll_frequency') + filters.pop('payroll_payable_account') + filters.pop('currency') if filters.employees is not None: filters.pop('employees') - if employee_list: - exclude_employees.extend(employee_list) - if emp: - exclude_employees.extend(emp) - if exclude_employees: - emp_cond += 'and employee not in %(exclude_employees)s' + + if include_employees: + emp_cond += 'and employee in %(include_employees)s' return frappe.db.sql("""select name, employee_name from `tabEmployee` where status = 'Active' @@ -695,4 +701,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): '_txt': txt.replace("%", ""), 'start': start, 'page_len': page_len, - 'exclude_employees': exclude_employees}) + 'include_employees': include_employees}) From 6c88ab07c779de864a88d9c073a1092b696f51c8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 19 Apr 2021 11:51:46 +0530 Subject: [PATCH 0535/1264] fix: commit leave_allocation change to db (#25382) --- .../compensatory_leave_request/compensatory_leave_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index aa5a67f40c..a6fe429be1 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document): else: leave_allocation = self.create_leave_allocation(leave_period, date_difference) - self.leave_allocation=leave_allocation.name + self.db_set("leave_allocation", leave_allocation.name) else: frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date))) @@ -124,4 +124,4 @@ class CompensatoryLeaveRequest(Document): )) allocation.insert(ignore_permissions=True) allocation.submit() - return allocation \ No newline at end of file + return allocation From bb746fcbe4b8caaf7178e0443e2ed67e556e2839 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 12:19:31 +0530 Subject: [PATCH 0536/1264] fix: Updated filters for process statement of accounts --- .../process_statement_of_accounts.html | 10 +++++----- .../process_statement_of_accounts.py | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 94ae79a0c6..b623898829 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -19,7 +19,7 @@ - {% for row in data %} + {% for row in data %} {% if(row.posting_date) %} {{ frappe.format(row.posting_date, 'Date') }} @@ -78,10 +78,10 @@ - {{ aging.range1 }} - {{ aging.range2 }} - {{ aging.range3 }} - {{ aging.range4 }} + {{ frappe.utils.fmt_money(aging.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 43fbb0600a..3cbf59448a 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -7,8 +7,9 @@ import frappe from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing -from frappe.core.doctype.communication.email import make +from erpnext import get_company_currency +from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -64,16 +65,16 @@ def get_report_pdf(doc, consolidated=True): 'to_date': doc.to_date, 'company': doc.company, 'finance_book': doc.finance_book if doc.finance_book else None, - "account": doc.account if doc.account else None, + 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], + 'presentation_currency': doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], 'project': [p.project_name for p in doc.project], 'show_opening_entries': 0, 'include_default_book_entries': 0, - 'show_cancelled_entries': 1, 'tax_id': tax_id if tax_id else None }) col, res = get_soa(filters) From ac8a467b0a5694a86a992d3c8e1b14b362e19a57 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Mon, 19 Apr 2021 12:38:25 +0530 Subject: [PATCH 0537/1264] fix: exclude spurious Stock Entry Types from 'consumed' calculation (#25352) * fix: exclude spurious 'Stock Entry Type's from 'consumed' calculation * fix: filter using purpose, make requested changes Co-authored-by: Ankush Menat --- .../itemwise_recommended_reorder_level.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index 5df3fa8067..2f70523264 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -55,19 +55,31 @@ def get_item_info(filters): def get_consumed_items(condition): + purpose_to_exclude = [ + "Material Transfer for Manufacture", + "Material Transfer", + "Send to Subcontractor" + ] + + condition += """ + and ( + purpose is NULL + or purpose not in ({}) + ) + """.format(', '.join([f"'{p}'" for p in purpose_to_exclude])) + condition = condition.replace("posting_date", "sle.posting_date") + consumed_items = frappe.db.sql(""" select item_code, abs(sum(actual_qty)) as consumed_qty - from `tabStock Ledger Entry` - where actual_qty < 0 + from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se + on sle.voucher_no = se.name + where + actual_qty < 0 and voucher_type not in ('Delivery Note', 'Sales Invoice') %s - group by item_code - """ % condition, as_dict=1) - - consumed_items_map = {} - for item in consumed_items: - consumed_items_map.setdefault(item.item_code, item.consumed_qty) + group by item_code""" % condition, as_dict=1) + consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items} return consumed_items_map def get_delivered_items(condition): From 119b27b97f8cd2092c8e915e5283dbd26876d1ce Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:46:14 +0530 Subject: [PATCH 0538/1264] feat: Delayed Tasks Summary (#25024) * feat: delayed deliverables summary * fix: sider * fix: renamed to delayed tasks * fix: renamed test * fix: test * fix: sider * fix: dates, validations and chart * fix: space and column width * feat: Sort tasks by descending order of delay Co-authored-by: Rucha Mahabal --- erpnext/projects/doctype/task/task.json | 15 +- erpnext/projects/doctype/task/task.py | 5 + .../report/delayed_tasks_summary/__init__.py | 0 .../delayed_tasks_summary.js | 41 ++++++ .../delayed_tasks_summary.json | 29 ++++ .../delayed_tasks_summary.py | 133 ++++++++++++++++++ .../test_delayed_tasks_summary.py | 54 +++++++ .../projects/workspace/projects/projects.json | 13 +- 8 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 erpnext/projects/report/delayed_tasks_summary/__init__.py create mode 100644 erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js create mode 100644 erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json create mode 100644 erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py create mode 100644 erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 160cc5812f..ef4740d9ee 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -11,15 +11,16 @@ "project", "issue", "type", + "color", "is_group", "is_template", "column_break0", "status", "priority", "task_weight", - "completed_by", - "color", "parent_task", + "completed_by", + "completed_on", "sb_timeline", "exp_start_date", "expected_time", @@ -358,6 +359,7 @@ "read_only": 1 }, { + "depends_on": "eval: doc.status == \"Completed\"", "fieldname": "completed_by", "fieldtype": "Link", "label": "Completed By", @@ -381,6 +383,13 @@ "fieldname": "duration", "fieldtype": "Int", "label": "Duration (Days)" + }, + { + "depends_on": "eval: doc.status == \"Completed\"", + "fieldname": "completed_on", + "fieldtype": "Date", + "label": "Completed On", + "mandatory_depends_on": "eval: doc.status == \"Completed\"" } ], "icon": "fa fa-check", @@ -388,7 +397,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2020-12-28 11:32:58.714991", + "modified": "2021-04-16 12:46:51.556741", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 855ff5f83e..d1583f1473 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -36,6 +36,7 @@ class Task(NestedSet): self.validate_status() self.update_depends_on() self.validate_dependencies_for_template_task() + self.validate_completed_on() def validate_dates(self): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): @@ -100,6 +101,10 @@ class Task(NestedSet): dependent_task_format = """{0}""".format(task.task) frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) + def validate_completed_on(self): + if self.completed_on and getdate(self.completed_on) > getdate(): + frappe.throw(_("Completed On cannot be greater than Today")) + def update_depends_on(self): depends_on_tasks = self.depends_on_tasks or "" for d in self.depends_on: diff --git a/erpnext/projects/report/delayed_tasks_summary/__init__.py b/erpnext/projects/report/delayed_tasks_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js new file mode 100644 index 0000000000..5aa44c0a8c --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js @@ -0,0 +1,41 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Delayed Tasks Summary"] = { + "filters": [ + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date" + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date" + }, + { + "fieldname": "priority", + "label": __("Priority"), + "fieldtype": "Select", + "options": ["", "Low", "Medium", "High", "Urgent"] + }, + { + "fieldname": "status", + "label": __("Status"), + "fieldtype": "Select", + "options": ["", "Open", "Working","Pending Review","Overdue","Completed"] + }, + ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (column.id == "delay") { + if (data["delay"] > 0) { + value = `

    ${value}

    `; + } else { + value = `

    ${value}

    `; + } + } + return value + } +}; diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json new file mode 100644 index 0000000000..100c422433 --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-25 15:03:19.857418", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-15 15:49:35.432486", + "modified_by": "Administrator", + "module": "Projects", + "name": "Delayed Tasks Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Task", + "report_name": "Delayed Tasks Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Projects User" + }, + { + "role": "Projects Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py new file mode 100644 index 0000000000..cdabe6487e --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py @@ -0,0 +1,133 @@ +# 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.utils import date_diff, nowdate + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns() + charts = get_chart_data(data) + return columns, data, None, charts + +def get_data(filters): + conditions = get_conditions(filters) + tasks = frappe.get_all("Task", + filters = conditions, + fields = ["name", "subject", "exp_start_date", "exp_end_date", + "status", "priority", "completed_on", "progress"], + order_by="creation" + ) + for task in tasks: + if task.exp_end_date: + if task.completed_on: + task.delay = date_diff(task.completed_on, task.exp_end_date) + elif task.status == "Completed": + # task is completed but completed on is not set (for older tasks) + task.delay = 0 + else: + # task not completed + task.delay = date_diff(nowdate(), task.exp_end_date) + else: + # task has no end date, hence no delay + task.delay = 0 + + # Sort by descending order of delay + tasks.sort(key=lambda x: x["delay"], reverse=True) + return tasks + +def get_conditions(filters): + conditions = frappe._dict() + keys = ["priority", "status"] + for key in keys: + if filters.get(key): + conditions[key] = filters.get(key) + if filters.get("from_date"): + conditions.exp_end_date = [">=", filters.get("from_date")] + if filters.get("to_date"): + conditions.exp_start_date = ["<=", filters.get("to_date")] + return conditions + +def get_chart_data(data): + delay, on_track = 0, 0 + for entry in data: + if entry.get("delay") > 0: + delay = delay + 1 + else: + on_track = on_track + 1 + charts = { + "data": { + "labels": ["On Track", "Delayed"], + "datasets": [ + { + "name": "Delayed", + "values": [on_track, delay] + } + ] + }, + "type": "percentage", + "colors": ["#84D5BA", "#CB4B5F"] + } + return charts + +def get_columns(): + columns = [ + { + "fieldname": "name", + "fieldtype": "Link", + "label": "Task", + "options": "Task", + "width": 150 + }, + { + "fieldname": "subject", + "fieldtype": "Data", + "label": "Subject", + "width": 200 + }, + { + "fieldname": "status", + "fieldtype": "Data", + "label": "Status", + "width": 100 + }, + { + "fieldname": "priority", + "fieldtype": "Data", + "label": "Priority", + "width": 80 + }, + { + "fieldname": "progress", + "fieldtype": "Data", + "label": "Progress (%)", + "width": 120 + }, + { + "fieldname": "exp_start_date", + "fieldtype": "Date", + "label": "Expected Start Date", + "width": 150 + }, + { + "fieldname": "exp_end_date", + "fieldtype": "Date", + "label": "Expected End Date", + "width": 150 + }, + { + "fieldname": "completed_on", + "fieldtype": "Date", + "label": "Actual End Date", + "width": 130 + }, + { + "fieldname": "delay", + "fieldtype": "Data", + "label": "Delay (In Days)", + "width": 120 + } + ] + return columns diff --git a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py new file mode 100644 index 0000000000..dbeedb4be9 --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py @@ -0,0 +1,54 @@ +from __future__ import unicode_literals +import unittest +import frappe +from frappe.utils import nowdate, add_days, add_months +from erpnext.projects.doctype.task.test_task import create_task +from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute + +class TestDelayedTasksSummary(unittest.TestCase): + @classmethod + def setUp(self): + task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate()) + create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1)) + + task1.status = "Completed" + task1.completed_on = add_days(nowdate(), -1) + task1.save() + + def test_delayed_tasks_summary(self): + filters = frappe._dict({ + "from_date": add_months(nowdate(), -1), + "to_date": nowdate(), + "priority": "Low", + "status": "Open" + }) + expected_data = [ + { + "subject": "_Test Task 99", + "status": "Open", + "priority": "Low", + "delay": 1 + }, + { + "subject": "_Test Task 98", + "status": "Completed", + "priority": "Low", + "delay": -1 + } + ] + report = execute(filters) + data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0] + + for key in ["subject", "status", "priority", "delay"]: + self.assertEqual(expected_data[0].get(key), data.get(key)) + + filters.status = "Completed" + report = execute(filters) + data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0] + + for key in ["subject", "status", "priority", "delay"]: + self.assertEqual(expected_data[1].get(key), data.get(key)) + + def tearDown(self): + for task in ["_Test Task 98", "_Test Task 99"]: + frappe.get_doc("Task", {"subject": task}).delete() \ No newline at end of file diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index dbbd7e1458..0ec17029a2 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "project", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Projects", "links": [ @@ -148,9 +149,19 @@ "link_type": "Report", "onboard": 0, "type": "Link" + }, + { + "dependencies": "Task", + "hidden": 0, + "is_query_report": 1, + "label": "Delayed Tasks Summary", + "link_to": "Delayed Tasks Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2020-12-01 13:38:37.856224", + "modified": "2021-03-26 16:32:00.628561", "modified_by": "Administrator", "module": "Projects", "name": "Projects", From edf3dfa5a4a147e16c6cde83e911be201d3e8ba8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 19 Apr 2021 12:47:41 +0530 Subject: [PATCH 0539/1264] fix: commit leave_allocation change to db (#25382) (#25383) --- .../compensatory_leave_request/compensatory_leave_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index aa5a67f40c..a6fe429be1 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document): else: leave_allocation = self.create_leave_allocation(leave_period, date_difference) - self.leave_allocation=leave_allocation.name + self.db_set("leave_allocation", leave_allocation.name) else: frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date))) @@ -124,4 +124,4 @@ class CompensatoryLeaveRequest(Document): )) allocation.insert(ignore_permissions=True) allocation.submit() - return allocation \ No newline at end of file + return allocation From 632166a933d77be944e09453f9f4dd522804a6c6 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:58:27 +0530 Subject: [PATCH 0540/1264] fix: make filters for payroll entry (#25386) --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index b031129614..3953b463f1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -70,6 +70,15 @@ class PayrollEntry(Document): emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date) return emp_list + def make_filters(self): + filters = frappe._dict() + filters['company'] = self.company + filters['branch'] = self.branch + filters['department'] = self.department + filters['designation'] = self.designation + + return filters + @frappe.whitelist() def fill_employee_details(self): self.set('employees', []) From ceba5774be6910d6c48f0f69e3f4adc1345b1b4b Mon Sep 17 00:00:00 2001 From: Rakshith N <36509967+rakshithrddy@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:21:49 +0530 Subject: [PATCH 0541/1264] fix(pos): special character scanning in point of sale (#25353) Co-authored-by: rakshith.n Co-authored-by: Saqib --- .../page/point_of_sale/pos_item_selector.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index e0d5b73166..9fb3943b53 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -159,6 +159,31 @@ erpnext.PointOfSale.ItemSelector = class { bind_events() { const me = this; window.onScan = onScan; + + onScan.decodeKeyEvent = function (oEvent) { + var iCode = this._getNormalizedKeyNum(oEvent); + switch (true) { + case iCode >= 48 && iCode <= 90: // numbers and letters + case iCode >= 106 && iCode <= 111: // operations on numeric keypad (+, -, etc.) + case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ * + case iCode >= 186 && iCode <= 194: // (; = , - . / `) + case iCode >= 219 && iCode <= 222: // ([ \ ] ') + if (oEvent.key !== undefined && oEvent.key !== '') { + return oEvent.key; + } + + var sDecoded = String.fromCharCode(iCode); + switch (oEvent.shiftKey) { + case false: sDecoded = sDecoded.toLowerCase(); break; + case true: sDecoded = sDecoded.toUpperCase(); break; + } + return sDecoded; + case iCode >= 96 && iCode <= 105: // numbers on numeric keypad + return 0 + (iCode - 96); + } + return ''; + }; + onScan.attachTo(document, { onScan: (sScancode) => { if (this.search_field && this.$component.is(':visible')) { From cb718fce88e3dd7866980333237cec25b9a72acf Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:25:15 +0530 Subject: [PATCH 0542/1264] feat: Role to allow over billing, delivery, receipt (#24854) * feat: Role to allow over billing, delivery, receipt * fix: Typo --- .../doctype/accounts_settings/accounts_settings.json | 10 +++++++++- erpnext/controllers/accounts_controller.py | 4 +++- erpnext/controllers/status_updater.py | 10 +++++++--- .../stock/doctype/stock_settings/stock_settings.json | 10 +++++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index a3c29b6d64..e1276e7da3 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -12,6 +12,7 @@ "frozen_accounts_modifier", "determine_address_tax_category_from", "over_billing_allowance", + "role_allowed_to_over_bill", "column_break_4", "credit_controller", "check_supplier_invoice_uniqueness", @@ -226,6 +227,13 @@ "fieldname": "delete_linked_ledger_entries", "fieldtype": "Check", "label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction" + }, + { + "description": "Users with this role are allowed to over bill above the allowance percentage", + "fieldname": "role_allowed_to_over_bill", + "fieldtype": "Link", + "label": "Role Allowed to Over Bill ", + "options": "Role" } ], "icon": "icon-cog", @@ -233,7 +241,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-05 13:04:00.118892", + "modified": "2021-03-11 18:52:05.601996", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 33fbf1c0b9..d36e7b03f4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -717,7 +717,9 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - if total_billed_amt - max_allowed_amt > 0.01: + role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + + if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles(): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index cdb6d244a6..5276da9720 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -201,10 +201,14 @@ class StatusUpdater(Document): get_allowance_for(item['item_code'], self.item_allowance, self.global_qty_allowance, self.global_amount_allowance, qty_or_amount) - overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / - item[args['target_ref_field']]) * 100 + role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive') + role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill - if overflow_percent - allowance > 0.01: + overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / + item[args['target_ref_field']]) * 100 + + if overflow_percent - allowance > 0.01 and role not in frappe.get_roles(): item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100) item['reduce_by'] = item[args['target_field']] - item['max_allowed'] diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 84af57b48d..f18eabc84b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -13,6 +13,7 @@ "column_break_4", "valuation_method", "over_delivery_receipt_allowance", + "role_allowed_to_over_deliver_receive", "action_if_quality_inspection_is_not_submitted", "show_barcode_field", "clean_description_html", @@ -234,6 +235,13 @@ "fieldname": "disable_serial_no_and_batch_selector", "fieldtype": "Check", "label": "Disable Serial No And Batch Selector" + }, + { + "description": "Users with this role are allowed to over deliver/receive against orders above the allowance percentage", + "fieldname": "role_allowed_to_over_deliver_receive", + "fieldtype": "Link", + "label": "Role Allowed to Over Deliver/Receive", + "options": "Role" } ], "icon": "icon-cog", @@ -241,7 +249,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-18 13:15:38.352796", + "modified": "2021-03-11 18:48:14.513055", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 1f8326bb1f8884b8efabb40c38d744ec03b35374 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Mon, 19 Apr 2021 16:19:08 +0530 Subject: [PATCH 0543/1264] chore: Add Report to Dashboard --- erpnext/projects/workspace/projects/projects.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index dbbd7e1458..bc2e71097f 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "project", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Projects", "links": [ @@ -129,6 +130,16 @@ "onboard": 1, "type": "Link" }, + { + "dependencies": "Timesheet", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Hours Utilization", + "link_to": "Employee Hours Utilization Based On Timesheet", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Project", "hidden": 0, @@ -150,7 +161,7 @@ "type": "Link" } ], - "modified": "2020-12-01 13:38:37.856224", + "modified": "2021-04-19 16:17:30.142545", "modified_by": "Administrator", "module": "Projects", "name": "Projects", From 9a0a561ec65f5a1d9cf5ced858aa99c0ef49301c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 19 Apr 2021 16:42:50 +0530 Subject: [PATCH 0544/1264] feat: Created Dialog Box on trying to create a maintenance visit. --- .../maintenance_schedule.js | 92 +++++++++++++++++- .../maintenance_schedule.py | 93 ++++++++++--------- .../maintenance_schedule_detail.json | 12 ++- .../maintenance_visit_purpose.json | 10 +- 4 files changed, 153 insertions(+), 54 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 124524684e..d07710878d 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -65,11 +65,93 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { this.frm.add_custom_button(__('Create Maintenance Visit'), function () { - frappe.model.open_mapped_doc({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", - source_name: me.frm.doc.name, - frm: me.frm - }); + let items = me.frm.doc.items; + let s = me.frm.doc.schedules; + let options = ""; + let dates = ""; + for (let i in items) { + for(let d in s){ + if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { + options = options + '\n' + items[i].item_name + break + } + } + } + function formatDate(date) { + var d = new Date(date), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); + + if (month.length < 2) + month = '0' + month; + if (day.length < 2) + day = '0' + day; + + return [day, month, year].join('-'); + } + var schedule_id = "" + var d = new frappe.ui.Dialog({ + title: __("Enter Visit Details"), + fields: [{ + fieldtype: "Select", + fieldname: "item_name", + label: __("Item Name"), + options: options, + reqd: 1, + onchange: function () { + let field = d.get_field("scheduled_date"); + dates = "" + for (let i in s) { + if (s[i].item_name == this.value) { + dates = dates + '\n' + formatDate(s[i].scheduled_date); + } + + } + field.df.options = dates; + field.refresh(); + } + }, + { + label: __('Scheduled Date'), + fieldname: 'scheduled_date', + fieldtype: 'Select', + options: dates, + reqd: 1, + onchange: function(){ + let field = d.get_field('item_name'); + for(let i in s ){ + if(s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value){ + schedule_id = s[i].name; + } + } + } + }, + ], + primary_action_label: 'Create Visit', + primary_action(values) { + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", + args: { + item_name: values.item_name, + s_id: schedule_id, + source_name: me.frm.doc.name, + + }, + callback: function (r) { + if (!r.exc) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + + + }); + d.hide(); + } + }) + d.show() + }, __('Create')); } }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 60dd2983b9..fd06a4ef9b 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -32,8 +32,7 @@ class MaintenanceSchedule(TransactionBase): child.idx = count count = count + 1 child.sales_person = d.sales_person - - + child.completion_status = "Pending" def on_submit(self): if not self.get('schedules'): @@ -58,9 +57,9 @@ class MaintenanceSchedule(TransactionBase): if no_email_sp: frappe.msgprint( - frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( - self.owner, "
    " + "
    ".join(no_email_sp) - )) + frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( + self.owner, "
    " + "
    ".join(no_email_sp) + )) scheduled_date = frappe.db.sql("""select scheduled_date from `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and @@ -69,12 +68,12 @@ class MaintenanceSchedule(TransactionBase): for key in scheduled_date: description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer) event = frappe.get_doc({ - "doctype": "Event", - "owner": email_map.get(d.sales_person, self.owner), - "subject": description, - "description": description, - "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", - "event_type": "Private", + "doctype": "Event", + "owner": email_map.get(d.sales_person, self.owner), + "subject": description, + "description": description, + "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", + "event_type": "Private", }) event.add_participant(self.doctype, self.name) event.insert(ignore_permissions=1) @@ -92,7 +91,7 @@ class MaintenanceSchedule(TransactionBase): start_date_copy = add_days(start_date_copy, add_by) if len(schedule_list) < no_of_visit: schedule_date = self.validate_schedule_date_for_holiday_list(getdate(start_date_copy), - sales_person) + sales_person) if schedule_date > getdate(end_date): schedule_date = getdate(end_date) schedule_list.append(schedule_date) @@ -127,16 +126,16 @@ class MaintenanceSchedule(TransactionBase): if d.start_date and d.end_date and d.periodicity and d.periodicity!="Random": date_diff = (getdate(d.end_date) - getdate(d.start_date)).days + 1 days_in_period = { - "Weekly": 7, - "Monthly": 30, - "Quarterly": 90, - "Half Yearly": 180, - "Yearly": 365 + "Weekly": 7, + "Monthly": 30, + "Quarterly": 90, + "Half Yearly": 180, + "Yearly": 365 } if date_diff < days_in_period[d.periodicity]: throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}") - .format(d.idx, d.periodicity, days_in_period[d.periodicity])) + .format(d.idx, d.periodicity, days_in_period[d.periodicity])) def validate_maintenance_detail(self): if not self.get('items'): @@ -172,8 +171,8 @@ class MaintenanceSchedule(TransactionBase): def on_update(self): frappe.db.set(self, 'status', 'Draft') - - + + def update_amc_date(self, serial_nos, amc_expiry_date=None): for serial_no in serial_nos: @@ -184,27 +183,27 @@ class MaintenanceSchedule(TransactionBase): 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", "item_code"], 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") + .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): + and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under warranty upto {1}") - .format(serial_no, sr_details.warranty_expiry_date)) + .format(serial_no, sr_details.warranty_expiry_date)) if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under maintenance contract upto {1}") - .format(serial_no, sr_details.amc_expiry_date)) + .format(serial_no, sr_details.amc_expiry_date)) if not sr_details.warehouse and sr_details.delivery_date and \ - getdate(sr_details.delivery_date) >= getdate(amc_start_date): - throw(_("Maintenance start date can not be before delivery date for Serial No {0}") + getdate(sr_details.delivery_date) >= getdate(amc_start_date): + throw(_("Maintenance start date can not be before delivery date for Serial No {0}") .format(serial_no)) def validate_schedule(self): @@ -248,31 +247,37 @@ class MaintenanceSchedule(TransactionBase): delete_events(self.doctype, self.name) @frappe.whitelist() -def make_maintenance_visit(source_name, target_doc=None): +def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None): from frappe.model.mapper import get_mapped_doc def update_status(source, target, parent): target.maintenance_type = "Scheduled" + def update_sid(source, target, parent): + target.prevdoc_detail_docname = s_id + doclist = get_mapped_doc("Maintenance Schedule", source_name, { - "Maintenance Schedule": { - "doctype": "Maintenance Visit", - "field_map": { - "name": "maintenance_schedule" + "Maintenance Schedule": { + "doctype": "Maintenance Visit", + "field_map": { + "name": "maintenance_schedule" + }, + "validation": { + "docstatus": ["=", 1] + }, + "postprocess": update_status }, - "validation": { - "docstatus": ["=", 1] - }, - "postprocess": update_status - }, - "Maintenance Schedule Item": { - "doctype": "Maintenance Visit Purpose", - "field_map": { - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", - "sales_person": "service_person" + "Maintenance Schedule Item": { + "doctype": "Maintenance Visit Purpose", + "field_map": { + "parent": "prevdoc_docname", + "parenttype": "prevdoc_doctype", + }, + "condition": lambda doc: doc.item_name == item_name, + + "postprocess": update_sid + } - } }, target_doc) return doclist diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 73536f6549..7fda687ca4 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -12,7 +12,8 @@ "scheduled_date", "actual_date", "sales_person", - "serial_no" + "serial_no", + "completion_status" ], "fields": [ { @@ -52,6 +53,7 @@ { "fieldname": "actual_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Actual Date", "no_copy": 1, "oldfieldname": "actual_date", @@ -81,12 +83,18 @@ "print_width": "160px", "read_only": 1, "width": "160px" + }, + { + "fieldname": "completion_status", + "fieldtype": "Select", + "label": "Completion Status", + "options": "Pending\nPartially Completed\nFully Completed" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-16 16:01:53.271287", + "modified": "2021-04-19 16:18:36.723319", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 467441d841..60e5afe806 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-02-22 01:28:06", "doctype": "DocType", @@ -62,6 +63,8 @@ "fieldtype": "Section Break" }, { + "fetch_from": "prevdoc_detail_docname.sales_person", + "fetch_if_empty": 1, "fieldname": "service_person", "fieldtype": "Link", "in_list_view": 1, @@ -110,12 +113,12 @@ }, { "fieldname": "prevdoc_detail_docname", - "fieldtype": "Data", - "hidden": 1, + "fieldtype": "Link", "label": "Against Document Detail No", "no_copy": 1, "oldfieldname": "prevdoc_detail_docname", "oldfieldtype": "Data", + "options": "Maintenance Schedule Detail", "print_hide": 1, "print_width": "160px", "read_only": 1, @@ -125,7 +128,8 @@ ], "idx": 1, "istable": 1, - "modified": "2020-09-18 17:26:09.703215", + "links": [], + "modified": "2021-04-19 16:08:10.671163", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", From 3b54b1e97588127d0a673f0a1cc769b78867c597 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 19 Apr 2021 16:49:28 +0530 Subject: [PATCH 0545/1264] fix: report name and columns --- .../hr/doctype/hr_settings/hr_settings.json | 8 +- .../report/profitability/profitability.py | 162 ---------------- .../profitability/test_profitability.py | 51 ----- .../__init__.py | 0 .../project_profitability.js} | 8 +- .../project_profitability.json} | 25 ++- .../project_profitability.py | 174 ++++++++++++++++++ .../test_project_profitability.py | 51 +++++ .../projects/workspace/projects/projects.json | 6 +- erpnext/regional/india/utils.py | 2 +- 10 files changed, 255 insertions(+), 232 deletions(-) delete mode 100644 erpnext/projects/report/profitability/profitability.py delete mode 100644 erpnext/projects/report/profitability/test_profitability.py rename erpnext/projects/report/{profitability => project_profitability}/__init__.py (100%) rename erpnext/projects/report/{profitability/profitability.js => project_profitability/project_profitability.js} (84%) rename erpnext/projects/report/{profitability/profitability.json => project_profitability/project_profitability.json} (69%) create mode 100644 erpnext/projects/report/project_profitability/project_profitability.py create mode 100644 erpnext/projects/report/project_profitability/test_project_profitability.py diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 4fa50c4852..35532291a5 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -10,7 +10,7 @@ "retirement_age", "emp_created_by", "column_break_4", - "default_working_hours", + "standard_working_hours", "stop_birthday_reminders", "expense_approver_mandatory_in_expense_claim", "leave_settings", @@ -147,16 +147,16 @@ }, { "default": "8", - "fieldname": "default_working_hours", + "fieldname": "standard_working_hours", "fieldtype": "Int", - "label": "Default Working Hours" + "label": "Standard Working Hours" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-03-25 13:18:21.648077", + "modified": "2021-04-16 15:45:18.467699", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py deleted file mode 100644 index 8ce2eb09ee..0000000000 --- a/erpnext/projects/report/profitability/profitability.py +++ /dev/null @@ -1,162 +0,0 @@ -# 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): - columns, data = [], [] - data = get_data(filters) - columns = get_columns() - charts = get_chart_data(data) - return columns, data, None, charts - -def get_data(filters): - conditions = get_conditions(filters) - default_working_hours = frappe.db.get_single_value("HR Settings", "default_working_hours") - sql = """ - select - *, - t.gross_pay * t.utilization as fractional_cost, - t.grand_total - t.gross_pay * t.utilization as profit - from - (select - si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, - ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, - tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization - from - `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet - join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name - join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(default_working_hours) - if conditions: - sql += """ - where - {0}) as t""".format(conditions) - data = frappe.db.sql(sql,filters, as_dict=True) - return data - -def get_conditions(filters): - conditions = [] - if filters.get("company"): - conditions.append('tabTimesheet.company="{0}"'.format(filters.get("company"))) - if filters.get("customer_name"): - conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) - if filters.get("start_date"): - conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) - if filters.get("end_date"): - conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) - if filters.get("employee"): - conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) - - conditions = " and ".join(conditions) - return conditions - -def get_chart_data(data): - if not data: - return None - - labels = [] - utilization = [] - - for entry in data: - labels.append(entry.get("title") + " - " + str(entry.get("end_date"))) - utilization.append(entry.get("utilization")) - charts = { - 'data': { - 'labels': labels, - 'datasets': [ - { - 'name': 'Utilization', - 'values': utilization - } - ] - }, - 'type': 'bar', - 'colors': ['#84BDD5'] - } - return charts - -def get_columns(): - return [ - { - "fieldname": "customer_name", - "label": _("Customer"), - "fieldtype": "Link", - "options": "Customer", - "width": 150 - }, - { - "fieldname": "title", - "label": _("Name"), - "fieldtype": "Data", - "width": 120 - }, - { - "fieldname": "employee", - "label": _("Employee"), - "fieldtype": "Link", - "options": "Employee", - "width": 150 - }, - { - "fieldname": "voucher_no", - "label": _("Sales Invoice"), - "fieldtype": "Link", - "options": "Sales Invoice", - "width": 200 - }, - { - "fieldname": "timesheet", - "label": _("Timesheet"), - "fieldtype": "Link", - "options": "Timesheet", - "width": 150 - }, - { - "fieldname": "grand_total", - "label": _("Bill Amount"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "gross_pay", - "label": _("Cost"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "profit", - "label": _("Profit"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "end_date", - "label": _("End Date"), - "fieldtype": "Date", - "width": 120 - }, - { - "fieldname": "total_billed_hours", - "label": _("Total Billed Hours"), - "fieldtype": "Int", - "width": 100 - }, - { - "fieldname": "utilization", - "label": _("Utilization"), - "fieldtype": "Percentage", - "width": 120 - }, - { - "fieldname": "fractional_cost", - "label": _("Fractional Cost"), - "fieldtype": "Int", - "width": 100 - } - ] \ No newline at end of file diff --git a/erpnext/projects/report/profitability/test_profitability.py b/erpnext/projects/report/profitability/test_profitability.py deleted file mode 100644 index 64ab6787eb..0000000000 --- a/erpnext/projects/report/profitability/test_profitability.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import unicode_literals -import unittest -import frappe -from frappe.utils import getdate, nowdate -from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet -from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice -from erpnext.projects.report.profitability.profitability import execute - -class TestProfitability(unittest.TestCase): - @classmethod - def setUp(self): - emp = make_employee("test_employee_9@salary.com", company="_Test Company") - if not frappe.db.exists("Salary Component", "Timesheet Component"): - frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert() - make_salary_structure_for_timesheet(emp, company="_Test Company") - self.timesheet = make_timesheet(emp, simulate = True, billable=1) - self.salary_slip = make_salary_slip(self.timesheet.name) - self.salary_slip.submit() - self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') - self.sales_invoice.due_date = nowdate() - self.sales_invoice.submit() - - def test_profitability(self): - filters = { - 'company': '_Test Company', - 'start_date': getdate(), - 'end_date': getdate() - } - - report = execute(filters) - expected_data = [ - { - "customer_name": "_Test Customer", - "title": "test_employee_9@salary.com", - "grand_total": 100.0, - "gross_pay": 78100.0, - "profit": -19425.0, - "total_billed_hours": 2.0, - "utilization": 0.25, - "fractional_cost": 19525.0, - "total_working_days": 1.0 - } - ] - for key in ["customer_name","title","grand_total","gross_pay","profit","total_billed_hours","utilization","fractional_cost","total_working_days"]: - self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) - - def tearDown(self): - frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() - frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() - frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file diff --git a/erpnext/projects/report/profitability/__init__.py b/erpnext/projects/report/project_profitability/__init__.py similarity index 100% rename from erpnext/projects/report/profitability/__init__.py rename to erpnext/projects/report/project_profitability/__init__.py diff --git a/erpnext/projects/report/profitability/profitability.js b/erpnext/projects/report/project_profitability/project_profitability.js similarity index 84% rename from erpnext/projects/report/profitability/profitability.js rename to erpnext/projects/report/project_profitability/project_profitability.js index 6cb6e39d34..cdf7bfdc9f 100644 --- a/erpnext/projects/report/profitability/profitability.js +++ b/erpnext/projects/report/project_profitability/project_profitability.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Profitability"] = { +frappe.query_reports["Project Profitability"] = { "filters": [ { "fieldname": "company", @@ -26,6 +26,12 @@ frappe.query_reports["Profitability"] = { "reqd": 1, "default": frappe.datetime.now_date() }, + { + "fieldname": "project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" + }, { "fieldname": "customer_name", "label": __("Customer"), diff --git a/erpnext/projects/report/profitability/profitability.json b/erpnext/projects/report/project_profitability/project_profitability.json similarity index 69% rename from erpnext/projects/report/profitability/profitability.json rename to erpnext/projects/report/project_profitability/project_profitability.json index 4f91accf58..0b092cd2c0 100644 --- a/erpnext/projects/report/profitability/profitability.json +++ b/erpnext/projects/report/project_profitability/project_profitability.json @@ -1,7 +1,7 @@ { "add_total_row": 0, "columns": [], - "creation": "2021-03-18 10:19:40.124932", + "creation": "2021-04-16 15:50:28.914872", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, @@ -9,31 +9,36 @@ "filters": [], "idx": 0, "is_standard": "Yes", - "json": "{}", - "modified": "2021-03-18 10:20:15.559305", + "modified": "2021-04-16 15:50:48.490866", "modified_by": "Administrator", "module": "Projects", - "name": "Profitability", + "name": "Project Profitability", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "Timesheet", - "report_name": "Profitability", + "report_name": "Project Profitability", "report_type": "Script Report", "roles": [ - { - "role": "Projects User" - }, { "role": "HR User" }, { - "role": "Manufacturing User" + "role": "Accounts User" }, { "role": "Employee" }, { - "role": "Accounts User" + "role": "Projects User" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Employee Self Service" + }, + { + "role": "HR Manager" } ] } \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py new file mode 100644 index 0000000000..7a76213994 --- /dev/null +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -0,0 +1,174 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns() + charts = get_chart_data(data) + return columns, data, None, charts + +def get_data(filters): + conditions = get_conditions(filters) + standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours') + sql = ''' + SELECT + * + FROM + (SELECT + si.customer_name,tabTimesheet.title, + tabTimesheet.employee,si.base_grand_total + si.name as voucher_no,ss.base_gross_pay,ss.total_working_days, + tabTimesheet.end_date,tabTimesheet.total_billed_hours, + tabTimesheet.name as timesheet, + tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization + FROM + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name + join `tabSales Invoice` as si on si.name = sit.parent and si.status != 'Cancelled' + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != 'Cancelled' '''.format(standard_working_hours) + if conditions: + sql += ''' + where + {0}) as t'''.format(conditions) + data = frappe.db.sql(sql,filters, as_dict=True) + data = perform_calculations(data) + return data + +def perform_calculations(data): + data.fractional_cost = data.base_gross_pay * data.utilization + data.profit = data.base_grand_total - data.base_gross_pay + return data + +def get_conditions(filters): + conditions = [] + if filters.get('company'): + conditions.append('tabTimesheet.company="{0}"'.format(filters.get('company'))) + if filters.get('customer_name'): + conditions.append('si.customer_name="{0}"'.format(filters.get('customer_name'))) + if filters.get('start_date'): + conditions.append('tabTimesheet.start_date>="{0}"'.format(filters.get('start_date'))) + if filters.get('end_date'): + conditions.append('tabTimesheet.end_date<="{0}"'.format(filters.get('end_date'))) + if filters.get('employee'): + conditions.append('tabTimesheet.employee="{0}"'.format(filters.get('employee'))) + + conditions = ' and '.join(conditions) + return conditions + +def get_chart_data(data): + if not data: + return None + + labels = [] + utilization = [] + + for entry in data: + labels.append(entry.get('title') + ' - ' + str(entry.get('end_date'))) + utilization.append(entry.get('utilization')) + charts = { + 'data': { + 'labels': labels, + 'datasets': [ + { + 'name': 'Utilization', + 'values': utilization + } + ] + }, + 'type': 'bar', + 'colors': ['#84BDD5'] + } + return charts + +def get_columns(): + return [ + { + 'fieldname': 'customer_name', + 'label': _('Customer'), + 'fieldtype': 'Link', + 'options': 'Customer', + 'width': 150 + }, + { + 'fieldname': 'employee', + 'label': _('Employee'), + 'fieldtype': 'Link', + 'options': 'Employee', + 'width': 150 + }, + { + 'fieldname': 'employee_name', + 'label': _('Employee Name'), + 'fieldtype': 'Data', + 'width': 120 + }, + { + 'fieldname': 'voucher_no', + 'label': _('Sales Invoice'), + 'fieldtype': 'Link', + 'options': 'Sales Invoice', + 'width': 200 + }, + { + 'fieldname': 'timesheet', + 'label': _('Timesheet'), + 'fieldtype': 'Link', + 'options': 'Timesheet', + 'width': 150 + }, + { + 'fieldname': 'grand_total', + 'label': _('Bill Amount'), + 'fieldtype': 'Currency', + 'options': 'currency', + 'width': 120 + }, + { + 'fieldname': 'gross_pay', + 'label': _('Cost'), + 'fieldtype': 'Currency', + 'options': 'currency', + 'width': 120 + }, + { + 'fieldname': 'profit', + 'label': _('Profit'), + 'fieldtype': 'Currency', + 'options': 'currency', + 'width': 120 + }, + { + 'fieldname': 'utilization', + 'label': _('Utilization'), + 'fieldtype': 'Percentage', + 'width': 120 + }, + { + 'fieldname': 'fractional_cost', + 'label': _('Fractional Cost'), + 'fieldtype': 'Int', + 'width': 100 + }, + { + 'fieldname': 'total_billed_hours', + 'label': _('Total Billed Hours'), + 'fieldtype': 'Int', + 'width': 100 + }, + { + 'fieldname': 'start_date', + 'label': _('Start Date'), + 'fieldtype': 'Date', + 'width': 120 + }, + { + 'fieldname': 'end_date', + 'label': _('End Date'), + 'fieldtype': 'Date', + 'width': 120 + } + ] diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py new file mode 100644 index 0000000000..7036547e40 --- /dev/null +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -0,0 +1,51 @@ +from __future__ import unicode_literals +import unittest +import frappe +from frappe.utils import getdate, nowdate +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet +from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice +from erpnext.projects.report.project_profitability.project_profitability import execute + +class TestProjectProfitability(unittest.TestCase): + @classmethod + def setUp(self): + emp = make_employee('test_employee_9@salary.com', company='_Test Company') + if not frappe.db.exists('Salary Component', 'Timesheet Component'): + frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert() + make_salary_structure_for_timesheet(emp, company='_Test Company') + self.timesheet = make_timesheet(emp, simulate = True, billable=1) + self.salary_slip = make_salary_slip(self.timesheet.name) + self.salary_slip.submit() + self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') + self.sales_invoice.due_date = nowdate() + self.sales_invoice.submit() + + def test_project_profitability(self): + filters = { + 'company': '_Test Company', + 'start_date': getdate(), + 'end_date': getdate() + } + + report = execute(filters) + expected_data = [ + { + 'customer_name': '_Test Customer', + 'title': 'test_employee_9@salary.com', + 'grand_total': 100.0, + 'gross_pay': 78100.0, + 'profit': -19425.0, + 'total_billed_hours': 2.0, + 'utilization': 0.25, + 'fractional_cost': 19525.0, + 'total_working_days': 1.0 + } + ] + for key in ['customer_name','title','grand_total','gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: + self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) + + def tearDown(self): + frappe.get_doc('Sales Invoice', self.sales_invoice.name).cancel() + frappe.get_doc('Salary Slip', self.salary_slip.name).cancel() + frappe.get_doc('Timesheet', self.timesheet.name).cancel() \ No newline at end of file diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 8703ffb756..621c4bb52f 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -134,8 +134,8 @@ "dependencies": "Timesheet, Sales Invoice, Salary Slip", "hidden": 0, "is_query_report": 1, - "label": "Profitability", - "link_to": "Profitability", + "label": "Project Profitability", + "link_to": "Project Profitability", "link_type": "Report", "onboard": 0, "type": "Link" @@ -161,7 +161,7 @@ "type": "Link" } ], - "modified": "2021-03-25 13:25:17.609608", + "modified": "2021-04-16 16:27:16.548780", "modified_by": "Administrator", "module": "Projects", "name": "Projects", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 3637de438c..ff4ac07041 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -159,7 +159,7 @@ def validate_document_name(doc, method=None): # Date was chosen as start of next FY to avoid irritating current users. if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"): return - + print(doc.name) if len(doc.name) > 16: frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series.")) From 135b852cf01e891e64c56289349cfcd54d124b26 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 17:27:26 +0530 Subject: [PATCH 0546/1264] fix: Use party account currency --- .../process_statement_of_accounts.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 3cbf59448a..33c32ebce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -8,6 +8,7 @@ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency +from erpnext.accounts.party import get_party_account_currency from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf @@ -68,7 +69,8 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': doc.currency or get_company_currency(doc.company), + 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], From 047044f9759cf4e9de933612d451a6b52eaeb980 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 18:27:37 +0530 Subject: [PATCH 0547/1264] fix: Add amend perm for loan and system manager for loan doctypes --- erpnext/loan_management/doctype/loan/loan.json | 3 ++- .../doctype/loan_application/loan_application.json | 5 ++++- .../doctype/loan_disbursement/loan_disbursement.json | 4 +++- .../doctype/loan_interest_accrual/loan_interest_accrual.json | 4 +++- .../doctype/loan_repayment/loan_repayment.json | 4 +++- .../doctype/loan_security_pledge/loan_security_pledge.json | 4 +++- .../loan_security_unpledge/loan_security_unpledge.json | 4 +++- erpnext/loan_management/doctype/loan_type/loan_type.json | 3 ++- .../doctype/loan_write_off/loan_write_off.json | 4 +++- 9 files changed, 26 insertions(+), 9 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 4f8ceb0de8..c9f23ca4df 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -360,13 +360,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-10 09:28:21.946972", + "modified": "2021-04-19 18:10:32.360818", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.json b/erpnext/loan_management/doctype/loan_application/loan_application.json index a353a7740d..f91fa07235 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.json +++ b/erpnext/loan_management/doctype/loan_application/loan_application.json @@ -212,15 +212,17 @@ "read_only": 1 } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-03-01 10:21:44.413353", + "modified": "2021-04-19 18:24:40.119647", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Application", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -235,6 +237,7 @@ "write": 1 }, { + "amend": 1, "create": 1, "delete": 1, "email": 1, diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json index 662c626b8d..7811d56a75 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json @@ -154,13 +154,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-10 10:03:41.502210", + "modified": "2021-04-19 18:09:32.175355", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Disbursement", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -175,6 +176,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 185bf7a666..30e2328442 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -185,13 +185,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-10 00:15:21.544140", + "modified": "2021-04-19 18:26:38.871889", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -206,6 +207,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 8fbf233be5..6479853246 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -248,13 +248,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-10 10:00:31.859076", + "modified": "2021-04-19 18:10:00.935364", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -269,6 +270,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json index 7dd5725e2e..18bd4aea78 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json @@ -160,13 +160,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-04 22:38:19.894488", + "modified": "2021-04-19 18:23:16.953305", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Pledge", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -181,6 +182,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json index 2e2b2518d2..92923bbf25 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json @@ -126,13 +126,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-04 22:39:57.756146", + "modified": "2021-04-19 18:12:01.401744", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Unpledge", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -147,6 +148,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index 3ef53044c2..c0a5d2cda1 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -154,13 +154,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-17 06:51:26.082879", + "modified": "2021-04-19 18:10:57.368490", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json index 4617a62f5b..4ca9ef174c 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json @@ -116,13 +116,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-26 07:13:43.663924", + "modified": "2021-04-19 18:11:27.759862", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Write Off", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -137,6 +138,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, From 5d7d338d2ae407ad2b48511405465311355e4a09 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 19 Apr 2021 18:39:34 +0530 Subject: [PATCH 0548/1264] feat: Schedule status is now updated on submitting a visit. --- .../maintenance_schedule.js | 11 +++++++- .../maintenance_schedule.py | 9 +++--- .../maintenance_schedule_detail.json | 23 ++++++++++----- .../maintenance_visit/maintenance_visit.py | 28 +++++++++++++++++++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index d07710878d..45e632cd35 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -64,6 +64,14 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }); }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { + var s = me.frm.doc.schedules; + let flag = 0 + for(let i in s){ + if (s[i].completion_status == pending){ + flag = 1 + } + } + if(count){ this.frm.add_custom_button(__('Create Maintenance Visit'), function () { let items = me.frm.doc.items; let s = me.frm.doc.schedules; @@ -103,7 +111,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let field = d.get_field("scheduled_date"); dates = "" for (let i in s) { - if (s[i].item_name == this.value) { + if (s[i].item_name == this.value && s[i].completion_status == "Pending") { dates = dates + '\n' + formatDate(s[i].scheduled_date); } @@ -154,6 +162,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }, __('Create')); } + } }, start_date: function (doc, cdt, cdn) { diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index fd06a4ef9b..aa582ee219 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -33,6 +33,7 @@ class MaintenanceSchedule(TransactionBase): count = count + 1 child.sales_person = d.sales_person child.completion_status = "Pending" + child.item_ref = d.name def on_submit(self): if not self.get('schedules'): @@ -171,9 +172,7 @@ class MaintenanceSchedule(TransactionBase): def on_update(self): frappe.db.set(self, 'status', 'Draft') - - - + def update_amc_date(self, serial_nos, amc_expiry_date=None): for serial_no in serial_nos: serial_no_doc = frappe.get_doc("Serial No", serial_no) @@ -255,6 +254,8 @@ def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None def update_sid(source, target, parent): target.prevdoc_detail_docname = s_id + sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') + target.service_person = sales_person doclist = get_mapped_doc("Maintenance Schedule", source_name, { "Maintenance Schedule": { @@ -275,7 +276,7 @@ def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None }, "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sid + "postprocess": update_sid } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 7fda687ca4..f1e2e21a12 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -10,10 +10,11 @@ "item_code", "item_name", "scheduled_date", - "actual_date", "sales_person", + "actual_date", + "completion_status", "serial_no", - "completion_status" + "item_ref" ], "fields": [ { @@ -29,11 +30,9 @@ "search_index": 1 }, { - "columns": 2, "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, - "in_list_view": 1, "label": "Item Name", "oldfieldname": "item_name", "oldfieldtype": "Data", @@ -71,7 +70,8 @@ "label": "Sales Person", "oldfieldname": "incharge_name", "oldfieldtype": "Link", - "options": "Sales Person" + "options": "Sales Person", + "read_only_depends_on": "eval:doc.completion_status != \"Pending\"" }, { "fieldname": "serial_no", @@ -85,16 +85,25 @@ "width": "160px" }, { + "columns": 2, "fieldname": "completion_status", "fieldtype": "Select", + "in_list_view": 1, "label": "Completion Status", - "options": "Pending\nPartially Completed\nFully Completed" + "options": "Pending\nPartially Completed\nFully Completed", + "read_only": 1 + }, + { + "fieldname": "item_ref", + "fieldtype": "Data", + "label": "Item Reference", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-19 16:18:36.723319", + "modified": "2021-04-19 17:42:31.685710", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 2f2ad00e02..9505a36545 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import get_datetime from erpnext.utilities.transaction_base import TransactionBase @@ -16,8 +17,33 @@ class MaintenanceVisit(TransactionBase): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): frappe.throw(_("Serial No {0} does not exist").format(d.serial_no)) + def validate_mntc_date(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + item_ref = frappe.db.get_value('Maintenance Schedule Detail', detail_ref , 'item_ref') + start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) + if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): + frappe.throw(_("Date must be between {0} and {1}").format(start_date,end_date)) + def validate(self): self.validate_serial_no() + self.validate_mntc_date() + + def update_completion_status(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) + + def update_actual_date(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) def update_customer_issue(self, flag): for d in self.get('purposes'): @@ -77,6 +103,8 @@ class MaintenanceVisit(TransactionBase): def on_submit(self): self.update_customer_issue(1) frappe.db.set(self, 'status', 'Submitted') + self.update_completion_status() + self.update_actual_date() def on_cancel(self): self.check_if_last_visit() From ebbcc90d89539fd9dd50478d42f25809241aabfb Mon Sep 17 00:00:00 2001 From: Meike Nedwidek Date: Mon, 19 Apr 2021 15:28:37 +0200 Subject: [PATCH 0549/1264] :sparkles: add check field for subscription invoices if they should be submitted automatically --- .../doctype/subscription/subscription.json | 145 +++++------------- .../doctype/subscription/subscription.py | 4 +- 2 files changed, 45 insertions(+), 104 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index e80df2ab88..c4e4be7f78 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -36,6 +36,7 @@ "additional_discount_percentage", "additional_discount_amount", "sb_3", + "submit_invoice", "invoices", "accounting_dimensions_section", "cost_center", @@ -45,9 +46,7 @@ { "allow_on_submit": 1, "fieldname": "cb_1", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "status", @@ -55,97 +54,73 @@ "label": "Status", "no_copy": 1, "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "subscription_period", "fieldtype": "Section Break", - "label": "Subscription Period", - "show_days": 1, - "show_seconds": 1 + "label": "Subscription Period" }, { "fieldname": "cancelation_date", "fieldtype": "Date", "label": "Cancelation Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "allow_on_submit": 1, "fieldname": "trial_period_start", "fieldtype": "Date", "label": "Trial Period Start Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "depends_on": "eval:doc.trial_period_start", "fieldname": "trial_period_end", "fieldtype": "Date", "label": "Trial Period End Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "current_invoice_start", "fieldtype": "Date", "label": "Current Invoice Start Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "current_invoice_end", "fieldtype": "Date", "label": "Current Invoice End Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "description": "Number of days that the subscriber has to pay invoices generated by this subscription", "fieldname": "days_until_due", "fieldtype": "Int", - "label": "Days Until Due", - "show_days": 1, - "show_seconds": 1 + "label": "Days Until Due" }, { "default": "0", "fieldname": "cancel_at_period_end", "fieldtype": "Check", - "label": "Cancel At End Of Period", - "show_days": 1, - "show_seconds": 1 + "label": "Cancel At End Of Period" }, { "default": "0", "fieldname": "generate_invoice_at_period_start", "fieldtype": "Check", - "label": "Generate Invoice At Beginning Of Period", - "show_days": 1, - "show_seconds": 1 + "label": "Generate Invoice At Beginning Of Period" }, { "allow_on_submit": 1, "fieldname": "sb_4", "fieldtype": "Section Break", - "label": "Plans", - "show_days": 1, - "show_seconds": 1 + "label": "Plans" }, { "allow_on_submit": 1, @@ -153,84 +128,62 @@ "fieldtype": "Table", "label": "Plans", "options": "Subscription Plan Detail", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)", "fieldname": "sb_1", "fieldtype": "Section Break", - "label": "Taxes", - "show_days": 1, - "show_seconds": 1 + "label": "Taxes" }, { "fieldname": "sb_2", "fieldtype": "Section Break", - "label": "Discounts", - "show_days": 1, - "show_seconds": 1 + "label": "Discounts" }, { "fieldname": "apply_additional_discount", "fieldtype": "Select", "label": "Apply Additional Discount On", - "options": "\nGrand Total\nNet Total", - "show_days": 1, - "show_seconds": 1 + "options": "\nGrand Total\nNet Total" }, { "fieldname": "cb_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "additional_discount_percentage", "fieldtype": "Percent", - "label": "Additional DIscount Percentage", - "show_days": 1, - "show_seconds": 1 + "label": "Additional DIscount Percentage" }, { "collapsible": 1, "fieldname": "additional_discount_amount", "fieldtype": "Currency", - "label": "Additional DIscount Amount", - "show_days": 1, - "show_seconds": 1 + "label": "Additional DIscount Amount" }, { "depends_on": "eval:doc.invoices", "fieldname": "sb_3", "fieldtype": "Section Break", - "label": "Invoices", - "show_days": 1, - "show_seconds": 1 + "label": "Invoices" }, { "collapsible": 1, "fieldname": "invoices", "fieldtype": "Table", "label": "Invoices", - "options": "Subscription Invoice", - "show_days": 1, - "show_seconds": 1 + "options": "Subscription Invoice" }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions" }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "party_type", @@ -238,9 +191,7 @@ "label": "Party Type", "options": "DocType", "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "party", @@ -249,27 +200,21 @@ "label": "Party", "options": "party_type", "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 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", - "show_days": 1, - "show_seconds": 1 + "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", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Taxes and Charges Template" }, { "default": "0", @@ -277,55 +222,49 @@ "fieldname": "follow_calendar_months", "fieldtype": "Check", "label": "Follow Calendar Months", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "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", - "show_days": 1, - "show_seconds": 1 + "label": "Generate New Invoices Past Due Date" }, { "fieldname": "end_date", "fieldtype": "Date", "label": "Subscription End Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "start_date", "fieldtype": "Date", "label": "Subscription Start Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" + }, + { + "default": "1", + "fieldname": "submit_invoice", + "fieldtype": "Check", + "label": "Submit Invoice Automatically" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-02-09 15:44:20.024789", + "modified": "2021-04-19 15:24:27.550797", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 826044a407..f19bae7465 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -383,7 +383,9 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True invoice.save() - invoice.submit() + + if self.submit_invoice == 1: + invoice.submit() return invoice From 923fb008c7a0f7125dda3e6e0a66c7500cfcc2c5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 19 Apr 2021 21:09:47 +0530 Subject: [PATCH 0550/1264] fix: remove non-standard module cards from Home Workspace (#25391) --- erpnext/setup/workspace/home/home.json | 170 +------------------------ 1 file changed, 1 insertion(+), 169 deletions(-) diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index 305456b266..1576d5a399 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -248,177 +248,9 @@ "link_type": "DocType", "onboard": 1, "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient", - "link_to": "Patient", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Diagnosis", - "link_to": "Diagnosis", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Education", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Student", - "link_to": "Student", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Instructor", - "link_to": "Instructor", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Course", - "link_to": "Course", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Room", - "link_to": "Room", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Non Profit", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Donor", - "link_to": "Donor", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Member", - "link_to": "Member", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Volunteer", - "link_to": "Volunteer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Chapter", - "link_to": "Chapter", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Agriculture", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Location", - "link_to": "Location", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Crop", - "link_to": "Crop", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Crop Cycle", - "link_to": "Crop Cycle", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Fertilizer", - "link_to": "Fertilizer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" } ], - "modified": "2021-03-16 15:59:58.416154", + "modified": "2021-04-19 15:48:44.089927", "modified_by": "Administrator", "module": "Setup", "name": "Home", From c22168237353aeb7da4153acdfb64e0470c337fb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 19 Apr 2021 21:11:27 +0530 Subject: [PATCH 0551/1264] fix: Bulk Salary Structure Assignment (#25389) --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 2 -- erpnext/payroll/doctype/salary_structure/salary_structure.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index b539b1b8a9..e00bd87084 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -133,8 +133,6 @@ frappe.ui.form.on('Salary Structure', { title: __("Assign to Employees"), fields: [ {fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")}, - {fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1}, - {fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1}, {fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")}, {fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')}, {fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')}, diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py index 352c1804f0..58c445f8a9 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py @@ -88,7 +88,7 @@ class SalaryStructure(Document): return employees @frappe.whitelist() - def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None, + def assign_salary_structure(self, grade=None, department=None, designation=None, employee=None, payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None): employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee) From 846200d06905b3b6bf507b4bd2354607b592eb9d Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 19 Apr 2021 22:20:01 +0530 Subject: [PATCH 0552/1264] fix: Ignore Customer Group Perm on AlL Products page (#25397) --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b91a7a5bd2..d23b952bdc 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): if parenttype in ["Customer Group", "Item Group", "Territory"]: parent_field = "parent_{0}".format(frappe.scrub(parenttype)) root_name = frappe.db.get_list(parenttype, - {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1) + {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True) if root_name and root_name[0][0]: parent_groups.append(root_name[0][0]) From b91fcbd70edf085b853336e314c99a4354f7d79d Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 19 Apr 2021 22:21:07 +0530 Subject: [PATCH 0553/1264] fix: Ignore Customer Group Perm on AlL Products page (#25396) --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b91a7a5bd2..d23b952bdc 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): if parenttype in ["Customer Group", "Item Group", "Territory"]: parent_field = "parent_{0}".format(frappe.scrub(parenttype)) root_name = frappe.db.get_list(parenttype, - {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1) + {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True) if root_name and root_name[0][0]: parent_groups.append(root_name[0][0]) From 125c3d64a9b2392144426b506055b38b781237c1 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 20 Apr 2021 12:53:22 +0530 Subject: [PATCH 0554/1264] fix: Cashier query in POS Opening/Closing Entry (#25398) --- .../accounts/doctype/pos_closing_entry/pos_closing_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index a05e5984f5..949211d35a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -89,8 +89,8 @@ class POSClosingEntry(StatusUpdater): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs 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] + cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1) + return [c for c in cashiers_list] @frappe.whitelist() def get_pos_invoices(start, end, pos_profile, user): From 0278646fd8cb95357745f8e1379d7a810e548df3 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 20 Apr 2021 12:53:33 +0530 Subject: [PATCH 0555/1264] fix: Cashier query in POS Opening/Closing Entry (#25399) --- .../accounts/doctype/pos_closing_entry/pos_closing_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index a05e5984f5..949211d35a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -89,8 +89,8 @@ class POSClosingEntry(StatusUpdater): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs 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] + cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1) + return [c for c in cashiers_list] @frappe.whitelist() def get_pos_invoices(start, end, pos_profile, user): From 57f487a16b03e337b51f7889e51c39bed8e54091 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Tue, 20 Apr 2021 13:12:14 +0530 Subject: [PATCH 0556/1264] fix: Updated serial_no filters in maintenance visit. --- .../maintenance_schedule.js | 178 +++++++++--------- .../maintenance_schedule.py | 15 +- .../maintenance_visit/maintenance_visit.js | 67 ++++--- .../maintenance_visit/maintenance_visit.py | 21 ++- 4 files changed, 158 insertions(+), 123 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 45e632cd35..3c05526496 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -65,104 +65,104 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { var s = me.frm.doc.schedules; - let flag = 0 - for(let i in s){ - if (s[i].completion_status == pending){ - flag = 1 + let flag = 0; + for (let i in s) { + if (s[i].completion_status == "Pending") { + flag = 1; } } - if(count){ - this.frm.add_custom_button(__('Create Maintenance Visit'), function () { - let items = me.frm.doc.items; - let s = me.frm.doc.schedules; - let options = ""; - let dates = ""; - for (let i in items) { - for(let d in s){ - if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { - options = options + '\n' + items[i].item_name - break + if (flag) { + this.frm.add_custom_button(__('Create Maintenance Visit'), function () { + let items = me.frm.doc.items; + let s = me.frm.doc.schedules; + let options = ""; + let dates = ""; + for (let i in items) { + for (let d in s) { + if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { + options = options + '\n' + items[i].item_name; + break; + } } } - } - function formatDate(date) { - var d = new Date(date), - month = '' + (d.getMonth() + 1), - day = '' + d.getDate(), - year = d.getFullYear(); + function formatDate(date) { + var d = new Date(date), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); - if (month.length < 2) - month = '0' + month; - if (day.length < 2) - day = '0' + day; + if (month.length < 2) + month = '0' + month; + if (day.length < 2) + day = '0' + day; - return [day, month, year].join('-'); - } - var schedule_id = "" - var d = new frappe.ui.Dialog({ - title: __("Enter Visit Details"), - fields: [{ - fieldtype: "Select", - fieldname: "item_name", - label: __("Item Name"), - options: options, - reqd: 1, - onchange: function () { - let field = d.get_field("scheduled_date"); - dates = "" - for (let i in s) { - if (s[i].item_name == this.value && s[i].completion_status == "Pending") { - dates = dates + '\n' + formatDate(s[i].scheduled_date); - } - - } - field.df.options = dates; - field.refresh(); - } - }, - { - label: __('Scheduled Date'), - fieldname: 'scheduled_date', - fieldtype: 'Select', - options: dates, - reqd: 1, - onchange: function(){ - let field = d.get_field('item_name'); - for(let i in s ){ - if(s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value){ - schedule_id = s[i].name; - } - } - } - }, - ], - primary_action_label: 'Create Visit', - primary_action(values) { - frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", - args: { - item_name: values.item_name, - s_id: schedule_id, - source_name: me.frm.doc.name, - - }, - callback: function (r) { - if (!r.exc) { - frappe.model.sync(r.message); - frappe.set_route("Form", r.message.doctype, r.message.name); - } - } - - - }); - d.hide(); + return [day, month, year].join('-'); } - }) - d.show() + var schedule_id = ""; + var d = new frappe.ui.Dialog({ + title: __("Enter Visit Details"), + fields: [{ + fieldtype: "Select", + fieldname: "item_name", + label: __("Item Name"), + options: options, + reqd: 1, + onchange: function () { + let field = d.get_field("scheduled_date"); + dates = ""; + for (let i in s) { + if (s[i].item_name == this.value && s[i].completion_status == "Pending") { + dates = dates + '\n' + formatDate(s[i].scheduled_date); + } - }, __('Create')); + } + field.df.options = dates; + field.refresh(); + } + }, + { + label: __('Scheduled Date'), + fieldname: 'scheduled_date', + fieldtype: 'Select', + options: dates, + reqd: 1, + onchange: function () { + let field = d.get_field('item_name'); + for (let i in s) { + if (s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value) { + schedule_id = s[i].name; + } + } + } + }, + ], + primary_action_label: 'Create Visit', + primary_action(values) { + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", + args: { + item_name: values.item_name, + s_id: schedule_id, + source_name: me.frm.doc.name, + + }, + callback: function (r) { + if (!r.exc) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + + + }); + d.hide(); + } + }); + d.show(); + + }, __('Create')); + } } - } }, start_date: function (doc, cdt, cdn) { diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index aa582ee219..b89d540ebb 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -10,6 +10,7 @@ from frappe import throw, _ from erpnext.utilities.transaction_base import TransactionBase, delete_events from erpnext.stock.utils import get_valid_serial_nos from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class MaintenanceSchedule(TransactionBase): @frappe.whitelist() @@ -245,18 +246,28 @@ class MaintenanceSchedule(TransactionBase): def on_trash(self): delete_events(self.doctype, self.name) +@frappe.whitelist() +def update_serial_nos(s_id): + serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') + if serial_nos: + serial_nos = get_serial_nos(serial_nos) + return serial_nos + else: + return False + @frappe.whitelist() def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None): from frappe.model.mapper import get_mapped_doc def update_status(source, target, parent): target.maintenance_type = "Scheduled" - + def update_sid(source, target, parent): target.prevdoc_detail_docname = s_id sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') target.service_person = sales_person - + target.serial_no = '' + doclist = get_mapped_doc("Maintenance Schedule", source_name, { "Maintenance Schedule": { "doctype": "Maintenance Visit", diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 4cbb02a5b3..d5e8e51dfd 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -2,39 +2,62 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.maintenance"); - +var serial_nos = []; frappe.ui.form.on('Maintenance Visit', { - refresh: function(frm) { + refresh: function (frm) { //filters for serial_no based on item_code - frm.set_query('serial_no', 'purposes', function(frm, cdt, cdn) { + frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) { let item = locals[cdt][cdn]; - return { - filters: { - 'item_code': item.item_code - } - }; + if (serial_nos) { + return { + filters: { + 'item_code': item.item_code, + 'name': ["in", serial_nos] + } + }; + + } else { + + return { + filters: { + 'item_code': item.item_code + } + }; + } + }); }, - setup: function(frm) { + setup: function (frm) { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); }, - onload: function(frm) { + onload: function (frm, cdt, cdn) { + let item = locals[cdt][cdn]; + let s_id = item.purposes[0].prevdoc_detail_docname; + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", + args: { + s_id: s_id + }, + callback: function (r) { + serial_nos = r.message; + } + }); if (!frm.doc.status) { - frm.set_value({status:'Draft'}); + frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { - frm.set_value({mntc_date: frappe.datetime.get_today()}); + frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, - customer: function(frm) { + customer: function (frm) { erpnext.utils.get_party_details(frm); }, - customer_address: function(frm) { + customer_address: function (frm) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display'); }, - contact_person: function(frm) { + contact_person: function (frm) { erpnext.utils.get_contact_details(frm); } @@ -42,14 +65,14 @@ frappe.ui.form.on('Maintenance Visit', { // TODO commonify this code erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ - refresh: function() { - frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} + refresh: function () { + frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' }; var me = this; - if (this.frm.doc.docstatus===0) { + if (this.frm.doc.docstatus === 0) { this.frm.add_custom_button(__('Maintenance Schedule'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", source_doctype: "Maintenance Schedule", @@ -64,7 +87,7 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ }) }, __("Get Items From")); this.frm.add_custom_button(__('Warranty Claim'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit", source_doctype: "Warranty Claim", @@ -80,7 +103,7 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ }) }, __("Get Items From")); this.frm.add_custom_button(__('Sales Order'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit", source_doctype: "Sales Order", @@ -99,4 +122,4 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ }, }); -$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({frm: cur_frm})); \ No newline at end of file +$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({ frm: cur_frm })); \ No newline at end of file diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 9505a36545..8a3094cb36 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -30,20 +30,21 @@ class MaintenanceVisit(TransactionBase): def validate(self): self.validate_serial_no() self.validate_mntc_date() + + def get_schedule_datail_ref(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + return detail_ref def update_completion_status(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) + detail_ref = self.get_schedule_datail_ref() + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) def update_actual_date(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) + detail_ref = self.get_schedule_datail_ref() + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) def update_customer_issue(self, flag): for d in self.get('purposes'): From 524b81abd95692332f3d5d067845ea7329fddba2 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Fri, 2 Apr 2021 15:36:50 +0530 Subject: [PATCH 0557/1264] feat: manage Python 3 compatiblity with dependencies --- .github/workflows/ci-tests.yml | 7 +++---- requirements.txt | 26 ++++++++++++-------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 78c2f5a187..824b74e013 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -85,10 +85,9 @@ jobs: run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} - pip install coveralls==2.2.0 - pip install coverage==4.5.4 - coveralls + pip install coveralls==3.0.1 + pip install coverage==5.5 + coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - diff --git a/requirements.txt b/requirements.txt index 5a352364b6..377fd7df6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,13 @@ -braintree==3.57.1 frappe -gocardless-pro==1.11.0 -googlemaps==3.1.1 -pandas>=1.0.5 -plaid-python>=7.0.0 -pycountry==19.8.18 -PyGithub==1.44.1 -python-stdnum==1.12 -python-youtube==0.6.0 -taxjar==1.9.0 -tweepy==3.8.0 -Unidecode==1.1.1 -WooCommerce==2.1.1 -pycryptodome==3.9.8 +gocardless-pro~=1.22.0 +googlemaps # used in ERPNext, but dependency is defined in Frappe +pandas~=1.1.5 +plaid-python~=7.2.1 +pycountry~=20.7.3 +PyGithub~=1.54.1 +python-stdnum~=1.16 +python-youtube~=0.8.0 +taxjar~=1.9.2 +tweepy~=3.10.0 +Unidecode~=1.2.0 +WooCommerce~=3.0.0 From 704ec132b7b5a8353906f77332496a1f92ba27c6 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Tue, 20 Apr 2021 16:39:46 +0530 Subject: [PATCH 0558/1264] feat:Inpatient Occupancy Table Editable for Healthcare Admin (#24989) Make Admitted datetime and Discharge Datetime editable along with Inaptient Occupancy Service Unit, Check_In and Check_out time. Signed-off-by: Syed Mujeer Hashmi Co-authored-by: Rucha Mahabal --- .../inpatient_occupancy.json | 252 ++++-------------- .../inpatient_record/inpatient_record.json | 39 ++- 2 files changed, 90 insertions(+), 201 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json index 818f125e77..3fa98b6678 100644 --- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json +++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json @@ -1,206 +1,64 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-07-12 12:07:36.932333", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-07-12 12:07:36.932333", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "service_unit", + "check_in", + "left", + "check_out", + "invoiced" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "service_unit", - "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": "Healthcare Service Unit", - "length": 0, - "no_copy": 0, - "options": "Healthcare Service Unit", - "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 - }, + "fieldname": "service_unit", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "check_in", - "fieldtype": "Datetime", - "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": "Check In", - "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 - }, + "fieldname": "check_in", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Check In" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "left", - "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": "Left", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "left", + "fieldtype": "Check", + "label": "Left", + "read_only": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "check_out", - "fieldtype": "Datetime", - "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": "Check Out", - "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 - }, + "fieldname": "check_out", + "fieldtype": "Datetime", + "label": "Check Out" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "invoiced", - "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": "Invoiced", - "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 + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "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-11-04 03:33:26.958713", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Occupancy", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "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": 1, - "track_seen": 0, - "track_views": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-03-18 15:08:54.634132", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Occupancy", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index aaf0e855d4..0e1c2ba766 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -185,7 +185,7 @@ "fieldtype": "Datetime", "in_list_view": 1, "label": "Admitted Datetime", - "read_only": 1 + "permlevel": 2 }, { "depends_on": "eval:(doc.expected_length_of_stay > 0)", @@ -312,7 +312,7 @@ "fieldname": "inpatient_occupancies", "fieldtype": "Table", "options": "Inpatient Occupancy", - "read_only": 1 + "permlevel": 2 }, { "fieldname": "btn_transfer", @@ -407,12 +407,12 @@ "fieldname": "discharge_datetime", "fieldtype": "Datetime", "label": "Discharge Date", - "read_only": 1 + "permlevel": 2 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-18 14:44:11.689956", + "modified": "2021-03-18 15:59:17.318988", "modified_by": "Administrator", "module": "Healthcare", "name": "Inpatient Record", @@ -465,6 +465,37 @@ "read": 1, "report": 1, "role": "Nursing User" + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1 } ], "restrict_to_domain": "Healthcare", From cbdcfb4873f5b63e6215df9aef86a788f097b70b Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 20 Apr 2021 17:53:41 +0530 Subject: [PATCH 0559/1264] fix: multicurrency --- erpnext/hooks.py | 1 - .../project_profitability.js | 12 +- .../project_profitability.py | 281 +++++++++++------- .../test_project_profitability.py | 14 +- 4 files changed, 186 insertions(+), 122 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bb6cd8bdc2..af357cd331 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,7 +262,6 @@ doc_events = { ], "on_trash": "erpnext.regional.check_deletion_permission", "validate": [ - "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" ] }, diff --git a/erpnext/projects/report/project_profitability/project_profitability.js b/erpnext/projects/report/project_profitability/project_profitability.js index cdf7bfdc9f..13ae19bb29 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.js +++ b/erpnext/projects/report/project_profitability/project_profitability.js @@ -26,12 +26,6 @@ frappe.query_reports["Project Profitability"] = { "reqd": 1, "default": frappe.datetime.now_date() }, - { - "fieldname": "project", - "label": __("Project"), - "fieldtype": "Link", - "options": "Project" - }, { "fieldname": "customer_name", "label": __("Customer"), @@ -43,6 +37,12 @@ frappe.query_reports["Project Profitability"] = { "label": __("Employee"), "fieldtype": "Link", "options": "Employee" + }, + { + "fieldname": "project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" } ] }; diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 7a76213994..405c6fd344 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -2,7 +2,12 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ +from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency +from erpnext.accounts.utils import get_currency_precision +from frappe.utils import flt +from erpnext.setup.utils import get_exchange_rate def execute(filters=None): columns, data = [], [] @@ -12,51 +17,96 @@ def execute(filters=None): return columns, data, None, charts def get_data(filters): - conditions = get_conditions(filters) - standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours') - sql = ''' - SELECT - * - FROM - (SELECT - si.customer_name,tabTimesheet.title, - tabTimesheet.employee,si.base_grand_total - si.name as voucher_no,ss.base_gross_pay,ss.total_working_days, - tabTimesheet.end_date,tabTimesheet.total_billed_hours, - tabTimesheet.name as timesheet, - tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization - FROM - `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet - join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name - join `tabSales Invoice` as si on si.name = sit.parent and si.status != 'Cancelled' - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != 'Cancelled' '''.format(standard_working_hours) - if conditions: - sql += ''' - where - {0}) as t'''.format(conditions) - data = frappe.db.sql(sql,filters, as_dict=True) - data = perform_calculations(data) + data = get_rows(filters) + data = handle_multi_currency(data, filters) + data = calculate_cost_and_profit(data) return data -def perform_calculations(data): - data.fractional_cost = data.base_gross_pay * data.utilization - data.profit = data.base_grand_total - data.base_gross_pay +def get_rows(filters): + conditions = get_conditions(filters) + standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") + sql = """ + SELECT + * + FROM + (SELECT + si.customer_name,si.base_grand_total, + si.name as voucher_no,tabTimesheet.employee, + tabTimesheet.title as employee_name,tabTimesheet.parent_project as project, + tabTimesheet.start_date,tabTimesheet.end_date, + tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, + ss.base_gross_pay,ss.total_working_days, + tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization + FROM + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name + join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(standard_working_hours) + if conditions: + sql += """ + WHERE + {0}) as t""".format(conditions) + return frappe.db.sql(sql,filters, as_dict=True) + +def handle_multi_currency(data, filters): + currency_precision = get_currency_precision() or 2 + company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") + + for row in data: + row.currency = company_currency + + if filters.get("employee"): + party_currency = get_employee_currency(row.employee) + + if filters.get("customer_name"): + party_currency = frappe.db.get_value("Customer", row.customer_name, ["default_currency"]) + + if party_currency and party_currency != company_currency: + exchange_rate = get_exchange_rate(company_currency, party_currency) + row.currency = party_currency + + row.base_grand_total = flt(flt(row.base_grand_total) * + flt(exchange_rate), currency_precision) + + row.base_gross_pay = flt(flt(row.base_gross_pay) * + flt(exchange_rate), currency_precision) + + row.profit = flt(flt(row.profit) * + flt(exchange_rate), currency_precision) + + row.fractional_cost = flt(flt(row.fractional_cost) * + flt(exchange_rate), currency_precision) + + return data + +def calculate_cost_and_profit(data): + for row in data: + row.fractional_cost = row.base_gross_pay * row.utilization + row.profit = row.base_grand_total - row.base_gross_pay * row.utilization return data def get_conditions(filters): conditions = [] - if filters.get('company'): - conditions.append('tabTimesheet.company="{0}"'.format(filters.get('company'))) - if filters.get('customer_name'): - conditions.append('si.customer_name="{0}"'.format(filters.get('customer_name'))) - if filters.get('start_date'): - conditions.append('tabTimesheet.start_date>="{0}"'.format(filters.get('start_date'))) - if filters.get('end_date'): - conditions.append('tabTimesheet.end_date<="{0}"'.format(filters.get('end_date'))) - if filters.get('employee'): - conditions.append('tabTimesheet.employee="{0}"'.format(filters.get('employee'))) + + if filters.get("company"): + conditions.append("tabTimesheet.company='{0}'".format(filters.get("company"))) + + if filters.get("start_date"): + conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) + + if filters.get("end_date"): + conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) + + if filters.get("customer_name"): + conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) + + if filters.get("employee"): + conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) + + if filters.get("project"): + conditions.append("tabTimesheet.parent_project='{0}'".format(filters.get("project"))) - conditions = ' and '.join(conditions) + conditions = " and ".join(conditions) return conditions def get_chart_data(data): @@ -67,108 +117,123 @@ def get_chart_data(data): utilization = [] for entry in data: - labels.append(entry.get('title') + ' - ' + str(entry.get('end_date'))) - utilization.append(entry.get('utilization')) + labels.append(entry.get("employee_name") + " - " + str(entry.get("end_date"))) + utilization.append(entry.get("utilization")) + charts = { - 'data': { - 'labels': labels, - 'datasets': [ + "data": { + "labels": labels, + "datasets": [ { - 'name': 'Utilization', - 'values': utilization + "name": "Utilization", + "values": utilization } ] }, - 'type': 'bar', - 'colors': ['#84BDD5'] + "type": "bar", + "colors": ["#84BDD5"] } return charts def get_columns(): return [ { - 'fieldname': 'customer_name', - 'label': _('Customer'), - 'fieldtype': 'Link', - 'options': 'Customer', - 'width': 150 + "fieldname": "customer_name", + "label": _("Customer"), + "fieldtype": "Link", + "options": "Customer", + "width": 150 }, { - 'fieldname': 'employee', - 'label': _('Employee'), - 'fieldtype': 'Link', - 'options': 'Employee', - 'width': 150 + "fieldname": "employee", + "label": _("Employee"), + "fieldtype": "Link", + "options": "Employee", + "width": 130 }, { - 'fieldname': 'employee_name', - 'label': _('Employee Name'), - 'fieldtype': 'Data', - 'width': 120 + "fieldname": "employee_name", + "label": _("Employee Name"), + "fieldtype": "Data", + "width": 120 }, { - 'fieldname': 'voucher_no', - 'label': _('Sales Invoice'), - 'fieldtype': 'Link', - 'options': 'Sales Invoice', - 'width': 200 + "fieldname": "voucher_no", + "label": _("Sales Invoice"), + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 180 }, { - 'fieldname': 'timesheet', - 'label': _('Timesheet'), - 'fieldtype': 'Link', - 'options': 'Timesheet', - 'width': 150 + "fieldname": "timesheet", + "label": _("Timesheet"), + "fieldtype": "Link", + "options": "Timesheet", + "width": 130 }, { - 'fieldname': 'grand_total', - 'label': _('Bill Amount'), - 'fieldtype': 'Currency', - 'options': 'currency', - 'width': 120 + "fieldname": "project", + "label": _("Project"), + "fieldtype": "Link", + "options": "Project", + "width": 100 }, { - 'fieldname': 'gross_pay', - 'label': _('Cost'), - 'fieldtype': 'Currency', - 'options': 'currency', - 'width': 120 + "fieldname": "base_grand_total", + "label": _("Bill Amount"), + "fieldtype": "Currency", + "options": "currency", + "width": 100 }, { - 'fieldname': 'profit', - 'label': _('Profit'), - 'fieldtype': 'Currency', - 'options': 'currency', - 'width': 120 + "fieldname": "base_gross_pay", + "label": _("Cost"), + "fieldtype": "Currency", + "options": "currency", + "width": 100 }, { - 'fieldname': 'utilization', - 'label': _('Utilization'), - 'fieldtype': 'Percentage', - 'width': 120 + "fieldname": "profit", + "label": _("Profit"), + "fieldtype": "Currency", + "options": "currency", + "width": 100 }, { - 'fieldname': 'fractional_cost', - 'label': _('Fractional Cost'), - 'fieldtype': 'Int', - 'width': 100 + "fieldname": "utilization", + "label": _("Utilization"), + "fieldtype": "Percentage", + "width": 120 }, { - 'fieldname': 'total_billed_hours', - 'label': _('Total Billed Hours'), - 'fieldtype': 'Int', - 'width': 100 + "fieldname": "fractional_cost", + "label": _("Fractional Cost"), + "fieldtype": "Int", + "width": 100 }, { - 'fieldname': 'start_date', - 'label': _('Start Date'), - 'fieldtype': 'Date', - 'width': 120 + "fieldname": "total_billed_hours", + "label": _("Total Billed Hours"), + "fieldtype": "Int", + "width": 100 }, { - 'fieldname': 'end_date', - 'label': _('End Date'), - 'fieldtype': 'Date', - 'width': 120 + "fieldname": "start_date", + "label": _("Start Date"), + "fieldtype": "Date", + "width": 120 + }, + { + "fieldname": "end_date", + "label": _("End Date"), + "fieldtype": "Date", + "width": 120 + }, + { + "label": _("Currency"), + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "width": 100 } - ] + ] \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 7036547e40..96659bc606 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -32,9 +32,9 @@ class TestProjectProfitability(unittest.TestCase): expected_data = [ { 'customer_name': '_Test Customer', - 'title': 'test_employee_9@salary.com', - 'grand_total': 100.0, - 'gross_pay': 78100.0, + 'employee_name': 'test_employee_9@salary.com', + 'base_grand_total': 100.0, + 'base_gross_pay': 78100.0, 'profit': -19425.0, 'total_billed_hours': 2.0, 'utilization': 0.25, @@ -42,10 +42,10 @@ class TestProjectProfitability(unittest.TestCase): 'total_working_days': 1.0 } ] - for key in ['customer_name','title','grand_total','gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: + for key in ['customer_name','employee_name','base_grand_total','base_gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) def tearDown(self): - frappe.get_doc('Sales Invoice', self.sales_invoice.name).cancel() - frappe.get_doc('Salary Slip', self.salary_slip.name).cancel() - frappe.get_doc('Timesheet', self.timesheet.name).cancel() \ No newline at end of file + frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() + frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() + frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file From e379f083bbc5b40faa12c8adf41aedccb8b56f48 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Apr 2021 23:04:39 +0530 Subject: [PATCH 0560/1264] feat(India): Separate Input and Output GST tax accounts --- .../setup_wizard/data/country_wise_tax.json | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 5876488033..9db2122be2 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -881,35 +881,70 @@ ] } ], - "*": [ + "sales_tax_templates": [ { - "title": "In State GST", + "title": "Output GST In-state", "taxes": [ { "account_head": { - "account_name": "SGST", + "account_name": "Output Tax SGST", "tax_rate": 9.00 } }, { "account_head": { - "account_name": "CGST", + "account_name": "Output Tax CGST", "tax_rate": 9.00 } } ] }, { - "title": "Out of State GST", + "title": "Output GST Out-state", "taxes": [ { "account_head": { - "account_name": "IGST", + "account_name": "Output Tax IGST", "tax_rate": 18.00 } } ] + } + ], + "purchase_tax_templates": [ + { + "title": "Input GST In-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax SGST", + "tax_rate": 9.00, + "root_type": "Asset" + } + }, + { + "account_head": { + "account_name": "Input Tax CGST", + "tax_rate": 9.00, + "root_type": "Asset" + } + } + ] }, + { + "title": "Input GST Out-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax IGST", + "tax_rate": 18.00, + "root_type": "Asset" + } + } + ] + } + ], + "*": [ { "title": "VAT 5%", "taxes": [ @@ -1001,7 +1036,7 @@ "Italy VAT 4%":{ "account_name": "IVA 4%", "tax_rate": 4.00 - } + } }, "Ivory Coast": { From 3130ed52ff0625c150b1c0b8492adc4474ddbf60 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Apr 2021 23:23:07 +0530 Subject: [PATCH 0561/1264] fix: Item Tax templates for GST --- .../setup_wizard/data/country_wise_tax.json | 148 +++++++++++++++++- 1 file changed, 142 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 9db2122be2..3119eeec49 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -820,29 +820,165 @@ "*": { "item_tax_templates": [ { - "title": "In State GST", + "title": "GST 9%", "taxes": [ { "tax_type": { - "account_name": "SGST", + "account_name": "Output Tax SGST", "tax_rate": 9.00 } }, { "tax_type": { - "account_name": "CGST", + "account_name": "Output Tax CGST", "tax_rate": 9.00 } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 18.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 18.00 + } } ] }, { - "title": "Out of State GST", + "title": "GST 5%", "taxes": [ { "tax_type": { - "account_name": "IGST", - "tax_rate": 18.00 + "account_name": "Output Tax SGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Output Tax CGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 5.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 5.0 + } + } + ] + }, + { + "title": "GST 12%", + "taxes": [ + { + "tax_type": { + "account_name": "Output Tax SGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax CGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 12.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 12.0 + } + } + ] + }, + { + "title": "GST 28%", + "taxes": [ + { + "tax_type": { + "account_name": "Output Tax SGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax CGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 28.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 28.0 } } ] From 66250351d2e8cd6aa6d02b81bc3e1540243fdd60 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 20 Apr 2021 14:35:14 -0600 Subject: [PATCH 0562/1264] fix: Add transtlation function to strings Add transtlation function to strings --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index e00bd87084..44368a69fb 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -16,11 +16,11 @@ frappe.ui.form.on('Salary Structure', { onload: function(frm) { let help_button = $(` - Condition and Formula Help + ${__(Condition and Formula Help)} `).click(()=>{ let d = new frappe.ui.Dialog({ - title: 'Condition and Formula Help', + title: __('Condition and Formula Help'), fields: [ { fieldname: 'msg_wrapper', From cb92ead0616efa1ea7dd8d12605f5c8ddf2f5723 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 21 Apr 2021 11:22:35 +0530 Subject: [PATCH 0563/1264] fix: Added Change Log --- erpnext/change_log/v13/v13_1_0.md | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 erpnext/change_log/v13/v13_1_0.md diff --git a/erpnext/change_log/v13/v13_1_0.md b/erpnext/change_log/v13/v13_1_0.md new file mode 100644 index 0000000000..d991034e36 --- /dev/null +++ b/erpnext/change_log/v13/v13_1_0.md @@ -0,0 +1,129 @@ +# Version 13.1.0 Release Notes + +### Features + +- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922)) +- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586)) +- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969)) +- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408)) +- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190)) +- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685)) +- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897)) +- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256)) +- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877)) +- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025)) + + +### Fixes and Enhancements +- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917)) +- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222)) +- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317)) +- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109)) +- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921)) +- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086)) +- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188)) +- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896)) +- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972)) +- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867)) +- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925)) +- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040)) +- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881)) +- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883)) +- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963)) +- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112)) +- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330)) +- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622)) +- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836)) +- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105)) +- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113)) +- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916)) +- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893)) +- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022)) +- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386)) +- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310)) +- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271)) +- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842)) +- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159)) +- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318)) +- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060)) +- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356)) +- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912)) +- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885)) +- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128)) +- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053)) +- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172)) +- Pending shortfall update on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971)) +- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041)) +- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004)) +- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980)) +- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230)) +- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001)) +- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000)) +- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193)) +- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082)) +- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399)) +- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583)) +- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937)) +- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397)) +- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212)) +- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138)) +- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261)) +- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117)) +- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235)) +- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063)) +- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305)) +- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855)) +- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240)) +- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939)) +- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127)) +- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179)) +- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901)) +- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924)) +- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255)) +- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879)) +- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349)) +- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118)) +- Update Bin via Update Item on Purchase/Sales Order ([#23509](https://github.com/frappe/erpnext/pull/23509)) +- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287)) +- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096)) +- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107)) +- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290)) +- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279)) +- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888)) +- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148)) +- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080)) +- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831)) +- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196)) +- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378)) +- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258)) +- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217)) +- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914)) +- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876)) +- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033)) +- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242)) +- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361)) +- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862)) +- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988)) +- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119)) +- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095)) +- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276)) +- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206)) +- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301)) +- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900)) +- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251)) +- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934)) +- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121)) +- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993)) +- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084)) +- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153)) +- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982)) +- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136)) +- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824)) +- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029)) +- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003)) +- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873)) +- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540)) +- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863)) +- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092)) +- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940)) +- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949)) From fa9629c1e1cf110b7c55230da3ea65b0ecd7d0d6 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 21 Apr 2021 11:29:40 +0530 Subject: [PATCH 0564/1264] fix: Updated forms and fixed an error. --- .../maintenance_schedule.json | 9 ++++-- .../maintenance_schedule_detail.json | 28 +++++++++++++++++-- .../maintenance_visit/maintenance_visit.js | 25 ++++++++++------- .../maintenance_visit_purpose.json | 16 +++++++++-- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json index 1871228711..4df0c6c0f7 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json @@ -229,8 +229,13 @@ "icon": "fa fa-calendar", "idx": 1, "is_submittable": 1, - "links": [], - "modified": "2021-04-16 15:53:36.670816", + "links": [ + { + "link_doctype": "Maintenance Visit Purpose", + "link_fieldname": "prevdoc_docname" + } + ], + "modified": "2021-04-21 11:27:05.744109", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule", diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index f1e2e21a12..76acefbf61 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -9,10 +9,14 @@ "field_order": [ "item_code", "item_name", + "column_break_3", "scheduled_date", - "sales_person", "actual_date", + "section_break_6", + "sales_person", + "column_break_8", "completion_status", + "section_break_10", "serial_no", "item_ref" ], @@ -95,15 +99,33 @@ }, { "fieldname": "item_ref", - "fieldtype": "Data", + "fieldtype": "Link", + "hidden": 1, "label": "Item Reference", + "options": "Maintenance Schedule Item", "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-19 17:42:31.685710", + "modified": "2021-04-21 11:07:29.524071", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index d5e8e51dfd..403d1ab4cc 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -34,16 +34,21 @@ frappe.ui.form.on('Maintenance Visit', { }, onload: function (frm, cdt, cdn) { let item = locals[cdt][cdn]; - let s_id = item.purposes[0].prevdoc_detail_docname; - frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", - args: { - s_id: s_id - }, - callback: function (r) { - serial_nos = r.message; - } - }); + if (frm.maintenance_type == 'Scheduled') { + + let s_id = item.purposes[0].prevdoc_detail_docname; + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", + args: { + s_id: s_id + }, + callback: function (r) { + serial_nos = r.message; + } + }); + + } + if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); } diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 60e5afe806..0d19d708d9 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -9,10 +9,12 @@ "field_order": [ "item_code", "item_name", + "column_break_3", + "service_person", "serial_no", + "section_break_6", "description", "work_details", - "service_person", "work_done", "prevdoc_doctype", "prevdoc_docname", @@ -86,6 +88,7 @@ { "fieldname": "prevdoc_doctype", "fieldtype": "Link", + "hidden": 1, "label": "Document Type", "no_copy": 1, "oldfieldname": "prevdoc_doctype", @@ -114,6 +117,7 @@ { "fieldname": "prevdoc_detail_docname", "fieldtype": "Link", + "hidden": 1, "label": "Against Document Detail No", "no_copy": 1, "oldfieldname": "prevdoc_detail_docname", @@ -124,12 +128,20 @@ "read_only": 1, "report_hide": 1, "width": "160px" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-19 16:08:10.671163", + "modified": "2021-04-21 11:16:52.025914", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", From 2acc66db2cc55208c73a045a4f5cd64693d06606 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Apr 2021 11:49:02 +0530 Subject: [PATCH 0565/1264] fix: Add tax categories on company setup --- .../setup_wizard/data/country_wise_tax.json | 22 +++++++++++++++++++ .../setup_wizard/operations/taxes_setup.py | 10 +++++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 3119eeec49..e3fde60ef3 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -818,6 +818,28 @@ "India": { "chart_of_accounts": { "*": { + "tax_categories": [ + { + "title": "In-Sate", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Out-Sate", + "is_inter_state": 1, + "gst_state": "" + }, + { + "title": "Reverse Charge", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Registered Composition", + "is_inter_state": 0, + "gst_state": "" + } + ], "item_tax_templates": [ { "title": "GST 9%", diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 429a558c58..578a270dd9 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -78,6 +78,7 @@ def from_detailed_data(company_name, data): sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*') item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') + tax_categories = tax_templates.get('tax_categories') if sales_tax_templates: for template in sales_tax_templates: @@ -91,6 +92,10 @@ def from_detailed_data(company_name, data): for template in item_tax_templates: make_item_tax_template(company_name, template) + if tax_categories: + for tax_category in tax_categories: + make_tax_category(tax_category) + def make_taxes_and_charges_template(company_name, doctype, template): template['company'] = company_name @@ -146,6 +151,11 @@ def make_item_tax_template(company_name, template): return frappe.get_doc(template).insert(ignore_permissions=True) +def make_tax_category(tax_category): + """ Make tax category based on title if not already created """ + doctype = 'Tax Category' + if not frappe.db.exists(doctype, tax_category) + frappe.get_doc(tax_category).insert(ignore_permissions=True) def get_or_create_account(company_name, account): """ From 039a5a829ddc7a7ef034665c7050f06277c7da8b Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 21 Apr 2021 12:32:25 +0530 Subject: [PATCH 0566/1264] fix: dynamic values for tests --- .../test_project_profitability.py | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 96659bc606..251b71da59 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -29,21 +29,26 @@ class TestProjectProfitability(unittest.TestCase): } report = execute(filters) - expected_data = [ - { - 'customer_name': '_Test Customer', - 'employee_name': 'test_employee_9@salary.com', - 'base_grand_total': 100.0, - 'base_gross_pay': 78100.0, - 'profit': -19425.0, - 'total_billed_hours': 2.0, - 'utilization': 0.25, - 'fractional_cost': 19525.0, - 'total_working_days': 1.0 - } - ] - for key in ['customer_name','employee_name','base_grand_total','base_gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: - self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) + + row = report[1][0] + timesheet = frappe.get_doc("Timesheet", self.timesheet.name) + + self.assertEqual(self.sales_invoice.customer, row.customer_name) + self.assertEqual(timesheet.title, row.employee_name) + self.assertEqual(self.sales_invoice.base_grand_total, row.base_grand_total) + self.assertEqual(self.salary_slip.base_gross_pay, row.base_gross_pay) + self.assertEqual(timesheet.total_billed_hours, row.total_billed_hours) + self.assertEqual(self.salary_slip.total_working_days, row.total_working_days) + + standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") + utilization = timesheet.total_billed_hours/(self.salary_slip.total_working_days * standard_working_hours) + self.assertEqual(utilization, row.utilization) + + profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization + self.assertEqual(profit, row.profit) + + fractional_cost = self.salary_slip.base_gross_pay * utilization + self.assertEqual(fractional_cost, row.fractional_cost) def tearDown(self): frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() From c3b7c1b998d3012f08ff986774c2e2801f74aadf Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 21 Apr 2021 12:54:49 +0550 Subject: [PATCH 0567/1264] bumped to version 13.1.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 199a183e47..4da0605370 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-dev' +__version__ = '13.1.0' def get_default_company(user=None): '''Get default company for user''' From 6d3305c4461053a6d978beef379ca0f090fc10fa Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 21 Apr 2021 15:34:58 +0530 Subject: [PATCH 0568/1264] fix: added total column --- .../dimension_wise_accounts_balance_report.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py index 7f38770a32..de7ed4926e 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -120,6 +120,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency, dimen for d in accounts: has_value = False + total = 0 row = { "account": d.name, "parent_account": d.parent_account, @@ -137,8 +138,10 @@ def prepare_data(accounts, filters, parent_children_map, company_currency, dimen if abs(row[frappe.scrub(item)]) >= 0.005: # ignore zero values has_value = True + total += flt(d.get(frappe.scrub(item), 0.0), 3) row["has_value"] = has_value + row["total"] = total data.append(row) return data @@ -199,5 +202,12 @@ def get_columns(dimension_items_list, accumulated_values=1, company=None): "options": "currency", "width": 150 }) + columns.append({ + "fieldname": "total", + "label": "Total", + "fieldtype": "Currency", + "options": "currency", + "width": 150 + }) return columns From 485464d1f49172debd439fd50917fba2744b2526 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 21 Apr 2021 18:04:58 +0530 Subject: [PATCH 0569/1264] fix: added back a hook --- erpnext/hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index af357cd331..bb6cd8bdc2 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,6 +262,7 @@ doc_events = { ], "on_trash": "erpnext.regional.check_deletion_permission", "validate": [ + "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" ] }, From 090177494dbc4bd6477c7783ee5eaa6113726658 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Wed, 21 Apr 2021 09:04:06 -0600 Subject: [PATCH 0570/1264] fix: Make strings translatable Make strings translatable --- .../projects/report/project_summary/project_summary.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index ea7f1ab2e7..2c7bb49cfb 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -131,25 +131,25 @@ def get_report_summary(data): { "value": avg_completion, "indicator": "Green" if avg_completion > 50 else "Red", - "label": "Average Completion", + "label": _("Average Completion"), "datatype": "Percent", }, { "value": total, "indicator": "Blue", - "label": "Total Tasks", + "label": _("Total Tasks"), "datatype": "Int", }, { "value": completed, "indicator": "Green", - "label": "Completed Tasks", + "label": _("Completed Tasks"), "datatype": "Int", }, { "value": total_overdue, "indicator": "Green" if total_overdue == 0 else "Red", - "label": "Overdue Tasks", + "label": _("Overdue Tasks"), "datatype": "Int", } ] From 50997709d5f714ed59517177954f0c624a8f7473 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Apr 2021 20:41:57 +0530 Subject: [PATCH 0571/1264] fix: Update country-wise-tax JSON and tax setup --- .../setup_wizard/data/country_wise_tax.json | 34 ++++++++++++------- .../setup_wizard/operations/taxes_setup.py | 22 ++++++------ 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index e3fde60ef3..d21ef03e19 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -820,12 +820,12 @@ "*": { "tax_categories": [ { - "title": "In-Sate", + "title": "In-State", "is_inter_state": 0, "gst_state": "" }, { - "title": "Out-Sate", + "title": "Out-State", "is_inter_state": 1, "gst_state": "" }, @@ -1046,16 +1046,19 @@ { "account_head": { "account_name": "Output Tax SGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "account_type": "Tax" } }, { "account_head": { "account_name": "Output Tax CGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "account_type": "Tax" } } - ] + ], + "tax_category": "In-State" }, { "title": "Output GST Out-state", @@ -1063,10 +1066,12 @@ { "account_head": { "account_name": "Output Tax IGST", - "tax_rate": 18.00 + "tax_rate": 18.00, + "account_type": "Tax" } } - ] + ], + "tax_category": "Out-State" } ], "purchase_tax_templates": [ @@ -1077,17 +1082,20 @@ "account_head": { "account_name": "Input Tax SGST", "tax_rate": 9.00, - "root_type": "Asset" + "root_type": "Asset", + "account_type": "Tax" } }, { "account_head": { "account_name": "Input Tax CGST", "tax_rate": 9.00, - "root_type": "Asset" + "root_type": "Asset", + "account_type": "Tax" } } - ] + ], + "tax_category": "In-State" }, { "title": "Input GST Out-state", @@ -1096,10 +1104,12 @@ "account_head": { "account_name": "Input Tax IGST", "tax_rate": 18.00, - "root_type": "Asset" + "root_type": "Asset", + "account_type": "Tax" } } - ] + ], + "tax_category": "Out-State" } ], "*": [ diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 578a270dd9..bbe301bb37 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -80,6 +80,10 @@ def from_detailed_data(company_name, data): item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') tax_categories = tax_templates.get('tax_categories') + if tax_categories: + for tax_category in tax_categories: + make_tax_category(tax_category) + if sales_tax_templates: for template in sales_tax_templates: make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template) @@ -92,10 +96,6 @@ def from_detailed_data(company_name, data): for template in item_tax_templates: make_item_tax_template(company_name, template) - if tax_categories: - for tax_category in tax_categories: - make_tax_category(tax_category) - def make_taxes_and_charges_template(company_name, doctype, template): template['company'] = company_name @@ -154,8 +154,9 @@ def make_item_tax_template(company_name, template): def make_tax_category(tax_category): """ Make tax category based on title if not already created """ doctype = 'Tax Category' - if not frappe.db.exists(doctype, tax_category) - frappe.get_doc(tax_category).insert(ignore_permissions=True) + if not frappe.db.exists(doctype, tax_category): + tax_category['doctype'] = doctype + frappe.get_doc(tax_category).insert(ignore_permissions=True) def get_or_create_account(company_name, account): """ @@ -167,12 +168,13 @@ def get_or_create_account(company_name, account): existing_accounts = frappe.get_list('Account', filters={ - 'company': company_name, - 'root_type': root_type + 'account_name': account.get('account_name'), + 'account_number': account.get('account_number', '') }, or_filters={ - 'account_name': account.get('account_name'), - 'account_number': account.get('account_number') + 'company': company_name, + 'root_type': root_type, + 'is_group': 0 } ) From 66a71bdd1a78c4b9253080ed229a8dc25995c53c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Apr 2021 20:42:20 +0530 Subject: [PATCH 0572/1264] fix: Issues on new company setup --- erpnext/regional/india/setup.py | 10 +++++----- .../report/e_invoice_summary/e_invoice_summary.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 9ded8dab5b..f70ba90506 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -427,13 +427,13 @@ def make_custom_fields(update=True): dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', print_hide=1, hidden=1), - + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', no_copy=1, print_hide=1), - + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', no_copy=1, print_hide=1), dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', @@ -696,12 +696,12 @@ def set_tax_withholding_category(company): docs = get_tds_details(accounts, fiscal_year) for d in docs: - try: + if not frappe.db.exists("Tax Withholding Category", d.get("name")): doc = frappe.get_doc(d) doc.flags.ignore_permissions = True doc.flags.ignore_mandatory = True doc.insert() - except frappe.DuplicateEntryError: + else: doc = frappe.get_doc("Tax Withholding Category", d.get("name")) if accounts: diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json index 4deb073a53..d0000ad50d 100644 --- a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json @@ -11,7 +11,7 @@ "is_standard": "Yes", "json": "{}", "letter_head": "Logo", - "modified": "2021-03-12 12:36:48.689413", + "modified": "2021-03-13 12:36:48.689413", "modified_by": "Administrator", "module": "Regional", "name": "E-Invoice Summary", From 157b3882098a0d1b72edf48a46ad364ae481befd Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Wed, 21 Apr 2021 21:00:22 +0530 Subject: [PATCH 0573/1264] fix: change subcontracted item display (#25425) --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index fd98f17ac1..19c9073090 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -712,7 +712,7 @@ erpnext.utils.map_current_doc = function(opts) { } frappe.form.link_formatters['Item'] = function(value, doc) { - if (doc && value && doc.item_name && doc.item_name !== value) { + if (doc && value && doc.item_name && doc.item_name !== value && doc.item_code === value) { return value + ': ' + doc.item_name; } else if (!value && doc.doctype && doc.item_name) { // format blank value in child table From 7f932cfb30f32224cbf500efa86ea7d87462b543 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 22 Apr 2021 04:53:09 +0530 Subject: [PATCH 0574/1264] fix: display reconcile tool when closing balance 0 (#25417) Co-authored-by: Saqib --- .../bank_reconciliation_tool/bank_reconciliation_tool.js | 3 +-- .../bank_reconciliation_tool.json | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index 10f660a140..f7d471b725 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -78,8 +78,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { if ( frm.doc.bank_account && frm.doc.bank_statement_from_date && - frm.doc.bank_statement_to_date && - frm.doc.bank_statement_closing_balance + frm.doc.bank_statement_to_date ) { frm.trigger("render_chart"); frm.trigger("render"); diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json index 4837db3b86..b643e6e091 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json @@ -39,13 +39,13 @@ "depends_on": "eval: doc.bank_account", "fieldname": "bank_statement_from_date", "fieldtype": "Date", - "label": "Bank Statement From Date" + "label": "From Date" }, { "depends_on": "eval: doc.bank_statement_from_date", "fieldname": "bank_statement_to_date", "fieldtype": "Date", - "label": "Bank Statement To Date" + "label": "To Date" }, { "fieldname": "column_break_2", @@ -63,11 +63,10 @@ "depends_on": "eval: doc.bank_statement_to_date", "fieldname": "bank_statement_closing_balance", "fieldtype": "Currency", - "label": "Bank Statement Closing Balance", + "label": "Closing Balance", "options": "Currency" }, { - "depends_on": "eval: doc.bank_statement_closing_balance", "fieldname": "section_break_1", "fieldtype": "Section Break", "label": "Reconcile" @@ -90,7 +89,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-02-02 01:35:53.043578", + "modified": "2021-04-21 11:13:49.831769", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Reconciliation Tool", From ba6ad7f5be20b9d23346a439d1e0092857553d66 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 22 Apr 2021 11:19:44 +0530 Subject: [PATCH 0575/1264] fix: escape for format and msg for working hours if not set --- .../project_profitability/project_profitability.py | 13 +++++++++---- erpnext/regional/india/utils.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 405c6fd344..7703b81cf7 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -25,6 +25,11 @@ def get_data(filters): def get_rows(filters): conditions = get_conditions(filters) standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") + if not standard_working_hours: + hr_settings = "HR Settings" + frappe.msgprint(_("The metrics for this report are calculated based on the Standard Working Hours. Please set Standard Working Hours in {0}.").format(hr_settings)) + return [] + sql = """ SELECT * @@ -89,7 +94,7 @@ def get_conditions(filters): conditions = [] if filters.get("company"): - conditions.append("tabTimesheet.company='{0}'".format(filters.get("company"))) + conditions.append("tabTimesheet.company={0}".format(frappe.db.escape(filters.get("company")))) if filters.get("start_date"): conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) @@ -98,13 +103,13 @@ def get_conditions(filters): conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) if filters.get("customer_name"): - conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) + conditions.append("si.customer_name={0}".format(frappe.db.escape(filters.get("customer_name")))) if filters.get("employee"): - conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) + conditions.append("tabTimesheet.employee={0}".format(frappe.db.escape(filters.get("employee")))) if filters.get("project"): - conditions.append("tabTimesheet.parent_project='{0}'".format(filters.get("project"))) + conditions.append("tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project")))) conditions = " and ".join(conditions) return conditions diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0c757e962d..6338056698 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -161,7 +161,7 @@ def validate_document_name(doc, method=None): # Date was chosen as start of next FY to avoid irritating current users. if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"): return - print(doc.name) + if len(doc.name) > 16: frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series.")) From b0e160ff7838442270d6918ef1aa4a6668e3b7f4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 22 Apr 2021 13:23:50 +0530 Subject: [PATCH 0576/1264] fix: ignore fraction difference while making round off gl entry --- erpnext/accounts/general_ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index dac0c216c8..f18de020fc 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -170,11 +170,11 @@ def round_off_debit_credit(gl_map): else: allowance = .5 - if abs(debit_credit_diff) >= allowance: + if abs(debit_credit_diff) > allowance: frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.") .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff)) - elif abs(debit_credit_diff) >= (1.0 / (10**precision)): + elif abs(debit_credit_diff) > (1.0 / (10**precision)): make_round_off_gle(gl_map, debit_credit_diff, precision) def make_round_off_gle(gl_map, debit_credit_diff, precision): From 19b73968d75f8976bd0dd47310919be320c42433 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 22 Apr 2021 13:38:20 +0530 Subject: [PATCH 0577/1264] fix(pos): validations & minor ui issues (#25351) * fix: pos print format * fix: stock validation on pos invoice creation --- .../pos_closing_entry/pos_closing_entry.py | 20 ------ .../doctype/pos_invoice/pos_invoice.py | 68 +++++++++++-------- .../doctype/pos_invoice/test_pos_invoice.py | 30 ++++++++ .../page/point_of_sale/pos_past_order_list.js | 2 +- .../gst_pos_invoice/gst_pos_invoice.json | 5 +- .../print_format/pos_invoice/pos_invoice.json | 5 +- 6 files changed, 76 insertions(+), 54 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 949211d35a..1065168a50 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -16,28 +16,8 @@ class POSClosingEntry(StatusUpdater): 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")) - self.validate_pos_closing() self.validate_pos_invoices() - def validate_pos_closing(self): - user = frappe.db.sql(""" - SELECT name FROM `tabPOS Closing Entry` - WHERE - user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND - (period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s) - """, { - 'user': self.user, - 'profile': self.pos_profile, - 'start': self.period_start_date, - 'end': self.period_end_date - }) - - if user: - bold_already_exists = frappe.bold(_("already exists")) - bold_user = frappe.bold(self.user) - frappe.throw(_("POS Closing Entry {} against {} between selected period") - .format(bold_already_exists, bold_user), title=_("Invalid Period")) - def validate_pos_invoices(self): invalid_rows = [] for d in self.pos_transactions: diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index e614459252..1e6a3d1b3b 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -96,30 +96,45 @@ class POSInvoice(SalesInvoice): if paid_amt and pay.amount != paid_amt: return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment)) + def validate_pos_reserved_serial_nos(self, item): + serial_nos = get_serial_nos(item.serial_no) + filters = {"item_code": item.item_code, "warehouse": item.warehouse} + if item.batch_no: + filters["batch_no"] = item.batch_no + + reserved_serial_nos = get_pos_reserved_serial_nos(filters) + invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos] + + bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos)) + if len(invalid_serial_nos) == 1: + frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.") + .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable")) + elif invalid_serial_nos: + frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.") + .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable")) + + def validate_delivered_serial_nos(self, item): + serial_nos = get_serial_nos(item.serial_no) + delivered_serial_nos = frappe.db.get_list('Serial No', { + 'item_code': item.item_code, + 'name': ['in', serial_nos], + 'sales_invoice': ['is', 'set'] + }, pluck='name') + + if delivered_serial_nos: + bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos)) + frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.") + .format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable")) + def validate_stock_availablility(self): if self.is_return: return - allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') - error_msg = [] + allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') for d in self.get('items'): - msg = "" if d.serial_no: - filters = { "item_code": d.item_code, "warehouse": d.warehouse } - if d.batch_no: - filters["batch_no"] = d.batch_no - reserved_serial_nos = get_pos_reserved_serial_nos(filters) - serial_nos = get_serial_nos(d.serial_no) - invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos] - - bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos)) - if len(invalid_serial_nos) == 1: - msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.") - .format(d.idx, bold_invalid_serial_nos)) - elif invalid_serial_nos: - msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.") - .format(d.idx, bold_invalid_serial_nos)) - + self.validate_pos_reserved_serial_nos(d) + self.validate_delivered_serial_nos(d) else: if allow_negative_stock: return @@ -127,15 +142,11 @@ class POSInvoice(SalesInvoice): available_stock = get_stock_availability(d.item_code, d.warehouse) item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty) if flt(available_stock) <= 0: - msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse)) + frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.') + .format(d.idx, item_code, warehouse), title=_("Item Unavailable")) elif flt(available_stock) < flt(d.qty): - msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.') - .format(d.idx, item_code, warehouse, qty)) - if msg: - error_msg.append(msg) - - if error_msg: - frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True) + frappe.throw(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.') + .format(d.idx, item_code, warehouse, available_stock), title=_("Item Unavailable")) def validate_serialised_or_batched_item(self): error_msg = [] @@ -202,9 +213,8 @@ class POSInvoice(SalesInvoice): for d in self.get("items"): is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item") if not is_stock_item: - frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format( - d.idx, frappe.bold(d.item_code) - ), title=_("Invalid Item")) + frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ") + .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) def validate_mode_of_payment(self): if len(self.payments) == 0: diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 6d388c4aaa..6172796129 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -10,10 +10,12 @@ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.item.test_item import make_item +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice class TestPOSInvoice(unittest.TestCase): @classmethod def setUpClass(cls): + make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100) frappe.db.sql("delete from `tabTax Rule`") def tearDown(self): @@ -320,6 +322,34 @@ class TestPOSInvoice(unittest.TestCase): self.assertRaises(frappe.ValidationError, pos2.insert) + def test_delivered_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(company='_Test Company', + target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') + + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + si = create_sales_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', + item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + + si.get("items")[0].serial_no = serial_nos[0] + si.insert() + si.submit() + + pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', + 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 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 index ec392313f5..70c7dc2adf 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -105,7 +105,7 @@ erpnext.PointOfSale.PastOrderList = class { - ${invoice.customer} + ${frappe.ellipsis(invoice.customer, 20)}
    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 index 9094a07bcc..9d1b196cf0 100644 --- a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json +++ b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json @@ -1,4 +1,5 @@ { + "absolute_value": 0, "align_labels_right": 0, "creation": "2017-08-08 12:33:04.773099", "custom_format": 1, @@ -7,10 +8,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
    {{ _(\"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.\") }}

    ", + "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

    \n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
    \n\t{{ _(\"Cashier\") }}: {{ doc.owner }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
    \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{%- for row in doc.payments -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\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 {{ row.mode_of_payment }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\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": "2021-04-15 15:26:04.396169", "modified_by": "Administrator", "module": "Selling", "name": "GST POS Invoice", diff --git a/erpnext/selling/print_format/pos_invoice/pos_invoice.json b/erpnext/selling/print_format/pos_invoice/pos_invoice.json index 99094ed9b0..6c01e26587 100644 --- a/erpnext/selling/print_format/pos_invoice/pos_invoice.json +++ b/erpnext/selling/print_format/pos_invoice/pos_invoice.json @@ -1,4 +1,5 @@ { + "absolute_value": 0, "align_labels_right": 0, "creation": "2011-12-21 11:08:55", "custom_format": 1, @@ -6,10 +7,10 @@ "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.\") }}

    ", + "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{{ _(\"Cashier\") }}: {{ doc.owner }}
    \n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}
    \n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
    \n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
    \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{%- for row in doc.payments -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\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 {{ row.mode_of_payment }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\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": "2021-04-15 15:23:28.867135", "modified_by": "Administrator", "module": "Selling", "name": "POS Invoice", From 2004fec8eb3aa03d933fe5fdc6f217f5ec7c70e5 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 22 Apr 2021 14:52:36 +0530 Subject: [PATCH 0578/1264] fix: removed multicurrency --- .../project_profitability.py | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 7703b81cf7..0051c47458 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -18,7 +18,6 @@ def execute(filters=None): def get_data(filters): data = get_rows(filters) - data = handle_multi_currency(data, filters) data = calculate_cost_and_profit(data) return data @@ -53,37 +52,6 @@ def get_rows(filters): {0}) as t""".format(conditions) return frappe.db.sql(sql,filters, as_dict=True) -def handle_multi_currency(data, filters): - currency_precision = get_currency_precision() or 2 - company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - - for row in data: - row.currency = company_currency - - if filters.get("employee"): - party_currency = get_employee_currency(row.employee) - - if filters.get("customer_name"): - party_currency = frappe.db.get_value("Customer", row.customer_name, ["default_currency"]) - - if party_currency and party_currency != company_currency: - exchange_rate = get_exchange_rate(company_currency, party_currency) - row.currency = party_currency - - row.base_grand_total = flt(flt(row.base_grand_total) * - flt(exchange_rate), currency_precision) - - row.base_gross_pay = flt(flt(row.base_gross_pay) * - flt(exchange_rate), currency_precision) - - row.profit = flt(flt(row.profit) * - flt(exchange_rate), currency_precision) - - row.fractional_cost = flt(flt(row.fractional_cost) * - flt(exchange_rate), currency_precision) - - return data - def calculate_cost_and_profit(data): for row in data: row.fractional_cost = row.base_gross_pay * row.utilization From 3847452b60d6c0856e97cce0d84f3d05b1cc9ee6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 22 Apr 2021 16:11:01 +0530 Subject: [PATCH 0579/1264] fix: Laboratory Module patch (#25436) --- .../healthcare_lab_module_rename_doctypes.py | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index a78f802574..028c61976c 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -38,16 +38,37 @@ def execute(): """.format(doctype), {'parentfield': parentfield}) # copy renamed child table fields (fields were already renamed in old doctype json, hence sql) - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""") + rename_fields = { + 'lab_test_name': 'test_name', + 'lab_test_event': 'test_event', + 'lab_test_uom': 'test_uom', + 'lab_test_comment': 'test_comment' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Normal Test Result', old): + frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) + + if frappe.db.has_column('Normal Test Template', 'test_event'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") + + if frappe.db.has_column('Normal Test Template', 'test_uom'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") + + if frappe.db.has_column('Descriptive Test Result', 'test_particulars'): + frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") + + rename_fields = { + 'lab_test_template': 'test_template', + 'lab_test_description': 'test_description', + 'lab_test_rate': 'test_rate' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Lab Test Group Template', old): + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From e3b8057eb4fc79f5b7fcc1b85a1c4a141cbc7859 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 22 Apr 2021 16:11:17 +0530 Subject: [PATCH 0580/1264] fix: Laboratory Module patch (#25431) --- .../healthcare_lab_module_rename_doctypes.py | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index a78f802574..028c61976c 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -38,16 +38,37 @@ def execute(): """.format(doctype), {'parentfield': parentfield}) # copy renamed child table fields (fields were already renamed in old doctype json, hence sql) - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""") + rename_fields = { + 'lab_test_name': 'test_name', + 'lab_test_event': 'test_event', + 'lab_test_uom': 'test_uom', + 'lab_test_comment': 'test_comment' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Normal Test Result', old): + frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) + + if frappe.db.has_column('Normal Test Template', 'test_event'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") + + if frappe.db.has_column('Normal Test Template', 'test_uom'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") + + if frappe.db.has_column('Descriptive Test Result', 'test_particulars'): + frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") + + rename_fields = { + 'lab_test_template': 'test_template', + 'lab_test_description': 'test_description', + 'lab_test_rate': 'test_rate' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Lab Test Group Template', old): + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From 3d4acf9e5cc1e388ec7a2e07ed4c78dca56be422 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 22 Apr 2021 16:16:03 +0530 Subject: [PATCH 0581/1264] fix: permission error after submitting exchange rate revaluation (#25432) --- .../exchange_rate_revaluation.js | 24 ++++++++----------- .../exchange_rate_revaluation.py | 17 +++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index 1092f4c8f1..b7b6020caa 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -21,21 +21,17 @@ frappe.ui.form.on('Exchange Rate Revaluation', { refresh: function(frm) { if(frm.doc.docstatus==1) { - frappe.db.get_value("Journal Entry Account", { - 'reference_type': 'Exchange Rate Revaluation', - 'reference_name': frm.doc.name, - 'docstatus': 1 - }, "sum(debit) as sum", (r) =>{ - let total_amt = 0; - frm.doc.accounts.forEach(d=> { - total_amt = total_amt + d['new_balance_in_base_currency']; - }); - if(total_amt !== r.sum) { - frm.add_custom_button(__('Journal Entry'), function() { - return frm.events.make_jv(frm); - }, __('Create')); + frappe.call({ + method: 'check_journal_entry_condition', + doc: frm.doc, + callback: function(r) { + if (r.message) { + frm.add_custom_button(__('Journal Entry'), function() { + return frm.events.make_jv(frm); + }, __('Create')); + } } - }, 'Journal Entry'); + }); } }, diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index c1b8ba70ba..56193216a2 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -27,6 +27,23 @@ class ExchangeRateRevaluation(Document): if not (self.company and self.posting_date): frappe.throw(_("Please select Company and Posting Date to getting entries")) + @frappe.whitelist() + def check_journal_entry_condition(self): + total_debit = frappe.db.get_value("Journal Entry Account", { + 'reference_type': 'Exchange Rate Revaluation', + 'reference_name': self.name, + 'docstatus': 1 + }, "sum(debit) as sum") + + total_amt = 0 + for d in self.accounts: + total_amt = total_amt + d.new_balance_in_base_currency + + if total_amt != total_debit: + return True + + return False + @frappe.whitelist() def get_accounts_data(self, account=None): accounts = [] From 84ef7419da0e117466cac6170bade3ec29c23caf Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 22 Apr 2021 16:31:16 +0530 Subject: [PATCH 0582/1264] fix: sider --- .../report/project_profitability/project_profitability.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 0051c47458..593ce1b22b 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -4,10 +4,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency -from erpnext.accounts.utils import get_currency_precision -from frappe.utils import flt -from erpnext.setup.utils import get_exchange_rate def execute(filters=None): columns, data = [], [] From eed60bf035ddce257e7dd1a0402100cff2f3cfa5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Apr 2021 17:11:22 +0530 Subject: [PATCH 0583/1264] fix: Backward compatibility for GSTR-1 report --- erpnext/regional/report/gstr_1/gstr_1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index b637fb47b3..808fd3a2cc 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -199,7 +199,7 @@ class Gstr1Report(object): self.item_tax_rate = frappe._dict() items = frappe.db.sql(""" - select item_code, parent, taxable_value, item_tax_rate + select item_code, parent, taxable_value, base_net_amount, item_tax_rate from `tab%s Item` where parent in (%s) """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) @@ -207,7 +207,7 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum(i.get('taxable_value', 0) for i in items + sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items if i.item_code == d.item_code and i.parent == d.parent)) item_tax_rate = {} From c21ce42559edba387f9959f2b502dc493afb1fc0 Mon Sep 17 00:00:00 2001 From: Anoop <3326959+akurungadam@users.noreply.github.com> Date: Thu, 22 Apr 2021 22:44:44 +0530 Subject: [PATCH 0584/1264] fix(patch): lab module patch fix (#25447) * fix(patch): lab module patch fix * fix: param Co-authored-by: Rucha Mahabal --- .../v13_0/healthcare_lab_module_rename_doctypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index 028c61976c..9af0a8dbef 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -47,8 +47,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Normal Test Result', old): - frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}""" + .format(new, old)) if frappe.db.has_column('Normal Test Template', 'test_event'): frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") @@ -67,8 +67,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Lab Test Group Template', old): - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}""" + .format(new, old)) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From 373e248e9fac5500462fb97d6c80c721f0e3ba6a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 22 Apr 2021 22:47:39 +0530 Subject: [PATCH 0585/1264] fix(patch): lab module patch fix (#25447) (#25449) * fix(patch): lab module patch fix * fix: param Co-authored-by: Rucha Mahabal Co-authored-by: Anoop <3326959+akurungadam@users.noreply.github.com> --- .../v13_0/healthcare_lab_module_rename_doctypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index 028c61976c..9af0a8dbef 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -47,8 +47,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Normal Test Result', old): - frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}""" + .format(new, old)) if frappe.db.has_column('Normal Test Template', 'test_event'): frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") @@ -67,8 +67,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Lab Test Group Template', old): - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}""" + .format(new, old)) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From 8787ebf83d7a3ca217e130d8c8eec62d36410a3e Mon Sep 17 00:00:00 2001 From: meike289 <63092915+meike289@users.noreply.github.com> Date: Fri, 23 Apr 2021 08:06:29 +0200 Subject: [PATCH 0586/1264] Apply suggestions from code review Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/subscription/subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index f19bae7465..175710c9f8 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -384,7 +384,7 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True invoice.save() - if self.submit_invoice == 1: + if self.submit_invoice: invoice.submit() return invoice From 68b514b1f5c1d8221415b39a55b299082dd10d9c Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Fri, 23 Apr 2021 13:06:13 +0530 Subject: [PATCH 0587/1264] docs: add docker repo link in README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index bb592ae75c..708668a1a5 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a --- +### Containerized Installation + +Use docker to deploy ERPNext in production or for development of frappe framework apps. See https://github.com/frappe/frappe_docker for more details. + ### Full Install The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details. From 52ea6b126bb68e72780ffef4d79c2e8dafd3f96e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 23 Apr 2021 14:14:47 +0530 Subject: [PATCH 0588/1264] Revert "fix: email digest user not found" This reverts commit 188657d05a9f0199de898094bbe033e51d52b930. --- erpnext/setup/doctype/email_digest/email_digest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index ac55fdfdb8..8c97322a71 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -50,8 +50,12 @@ class EmailDigest(Document): recipients = list(filter(lambda r: r in valid_users, self.recipient_list.split("\n"))) + original_user = frappe.session.user + if recipients: for user_id in recipients: + frappe.set_user(user_id) + frappe.set_user_lang(user_id) msg_for_this_recipient = self.get_msg_html() if msg_for_this_recipient: frappe.sendmail( @@ -62,6 +66,9 @@ class EmailDigest(Document): reference_name = self.name, unsubscribe_message = _("Unsubscribe from this Email Digest")) + frappe.set_user(original_user) + frappe.set_user_lang(original_user) + def get_msg_html(self): """Build email digest content""" frappe.flags.ignore_account_permission = True From d552fe6778f71e940f4acdb594afcf79fe5757c2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 14:46:52 +0530 Subject: [PATCH 0589/1264] feat: base payment amount in payment schedule --- .../payment_schedule/payment_schedule.json | 36 +++++++++++-- .../purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 53 ++++++++++++------- erpnext/public/js/controllers/transaction.js | 5 +- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 39eda331db..e9f8ba29dc 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -20,10 +20,13 @@ "discount", "section_break_9", "payment_amount", + "outstanding", + "paid_amount", "discounted_amount", "column_break_3", - "outstanding", - "paid_amount" + "base_payment_amount", + "base_outstanding", + "base_paid_amount" ], "fields": [ { @@ -65,6 +68,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", + "options": "currency", "reqd": 1 }, { @@ -77,7 +81,8 @@ "depends_on": "paid_amount", "fieldname": "paid_amount", "fieldtype": "Currency", - "label": "Paid Amount" + "label": "Paid Amount", + "options": "currency" }, { "fieldname": "column_break_3", @@ -96,6 +101,7 @@ "fieldname": "outstanding", "fieldtype": "Currency", "label": "Outstanding", + "options": "currency", "read_only": 1 }, { @@ -144,12 +150,34 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break" + }, + { + "fieldname": "base_payment_amount", + "fieldtype": "Currency", + "label": "Payment Amount (Company Currency)", + "options": "Company:company:default_currency" + }, + { + "default": "0", + "fieldname": "base_outstanding", + "fieldtype": "Currency", + "label": "Outstanding (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-12 15:39:47.363609", + "modified": "2021-04-23 13:55:59.548043", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 50492f50b5..66be11ff23 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase): pi.update({ "payment_schedule": get_payment_terms("_Test Payment Term Template", - pi.posting_date, pi.grand_total) + pi.posting_date, pi.grand_total, pi.base_grand_total) }) pi.save() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d36e7b03f4..c51d8283e5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -904,20 +904,24 @@ class AccountsController(TransactionBase): date = self.get("due_date") due_date = date or posting_date - if party_account_currency == self.company_currency: - grand_total = self.get("base_rounded_total") or self.base_grand_total - else: - grand_total = self.get("rounded_total") or self.grand_total + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) if self.get("total_advance"): - grand_total -= self.get("total_advance") + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): if self.get("payment_terms_template"): - data = get_payment_terms(self.payment_terms_template, posting_date, grand_total) + data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) else: @@ -927,7 +931,9 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount + d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -963,22 +969,28 @@ class AccountsController(TransactionBase): if self.get("payment_schedule"): total = 0 + base_total = 0 for d in self.get("payment_schedule"): total += flt(d.payment_amount) + base_total += flt(d.base_payment_amount) - if party_account_currency == self.company_currency: - total = flt(total, self.precision("base_grand_total")) - grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) - else: - total = flt(total, self.precision("grand_total")) - grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) - - if self.get("total_advance"): - grand_total -= self.get("total_advance") + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) - if total != flt(grand_total, self.precision("grand_total")): + + if self.get("total_advance"): + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) + + if total != flt(grand_total, self.precision("grand_total")) or \ + base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): @@ -1218,7 +1230,7 @@ def update_invoice_status(): @frappe.whitelist() -def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None): +def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): if not terms_template: return @@ -1226,14 +1238,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_ schedule = [] for d in terms_doc.get("terms"): - term_details = get_payment_term_details(d, posting_date, grand_total, bill_date) + term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date) schedule.append(term_details) return schedule @frappe.whitelist() -def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None): +def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): term_details = frappe._dict() if isinstance(term, text_type): term = frappe.get_doc("Payment Term", term) @@ -1242,10 +1254,11 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat term_details.description = term.description term_details.invoice_portion = term.invoice_portion term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 + term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100 term_details.discount_type = term.discount_type term_details.discount = term.discount - # term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount term_details.outstanding = term_details.payment_amount + term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a0398e718f..258262aed3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1995,6 +1995,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ terms_template: doc.payment_terms_template, posting_date: posting_date, grand_total: doc.rounded_total || doc.grand_total, + base_grand_total: doc.base_rounded_total || doc.base_grand_total, bill_date: doc.bill_date }, callback: function(r) { @@ -2009,13 +2010,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { + debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { term: row.payment_term, bill_date: this.frm.doc.bill_date, posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date, - grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total + grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total, + base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total }, callback: function(r) { if(r.message && !r.exc) { From c5c9f9a9416250d8ed4fe72d9b738ba586aa54b4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 15:34:58 +0530 Subject: [PATCH 0590/1264] feat: set dynamic labels for payment schedule fields --- erpnext/public/js/controllers/transaction.js | 54 +++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 258262aed3..dc731ca213 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -640,6 +640,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ let key = item.name; me.apply_rule_on_other_items({key: item}); } + }, + () => { + var company_currency = me.get_company_currency(); + me.update_item_grid_labels(company_currency); } ]); } @@ -1321,11 +1325,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ change_grid_labels: function(company_currency) { var me = this; - this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"], - company_currency, "items"); + this.update_item_grid_labels(company_currency); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"], - this.frm.doc.currency, "items"); + this.toggle_item_grid_columns(company_currency); if(this.frm.fields_dict["operations"]) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); @@ -1360,6 +1362,42 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.doc.party_account_currency, "advances"); } + this.update_payment_schedule_grid_labels(company_currency); + + // set labels + var $wrapper = $(this.frm.wrapper); + }, + + update_item_grid_labels: function(company_currency) { + this.frm.set_currency_labels([ + "base_rate", "base_net_rate", "base_price_list_rate", + "base_amount", "base_net_amount", "base_rate_with_margin" + ], company_currency, "items"); + + this.frm.set_currency_labels([ + "rate", "net_rate", "price_list_rate", "amount", + "net_amount", "stock_uom_rate", "rate_with_margin" + ], this.frm.doc.currency, "items"); + }, + + update_payment_schedule_grid_labels: function(company_currency) { + const me = this; + if(this.frm.fields_dict["payment_schedule"]) { + this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], + company_currency, "payment_schedule"); + this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], + this.frm.doc.currency, "payment_schedule"); + + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; + $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { + if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); + }); + } + }, + + toggle_item_grid_columns: function(company_currency) { + const me = this; // toggle columns var item_grid = this.frm.fields_dict["items"].grid; $.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) { @@ -1379,9 +1417,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency))); }); - - // set labels - var $wrapper = $(this.frm.wrapper); }, recalculate: function() { @@ -2001,6 +2036,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if(r.message && !r.exc) { me.frm.set_value("payment_schedule", r.message); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } }) @@ -2010,7 +2047,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { - debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { @@ -2024,6 +2060,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } } From 62d2df58156b53ed3520444ca305eee838dbc659 Mon Sep 17 00:00:00 2001 From: gavin Date: Fri, 23 Apr 2021 16:31:13 +0530 Subject: [PATCH 0591/1264] docs: Add Frappe reference in Containerized Install section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 708668a1a5..0a556f57b4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a ### Containerized Installation -Use docker to deploy ERPNext in production or for development of frappe framework apps. See https://github.com/frappe/frappe_docker for more details. +Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details. ### Full Install From dc599e01643cd53fcacd4bee805061b01bd526ac Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 23 Apr 2021 19:24:53 +0530 Subject: [PATCH 0592/1264] fix: to update Allocated amount after Paid Amount is changed --- .../doctype/payment_entry/payment_entry.js | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index c2e804e441..936c538e20 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -582,7 +582,7 @@ frappe.ui.form.on('Payment Entry', { } if(frm.doc.payment_type == "Receive") - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount); + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1); else frm.events.set_unallocated_amount(frm); }, @@ -606,9 +606,9 @@ frappe.ui.form.on('Payment Entry', { {fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, {fieldtype:"Section Break"}, {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center", - "get_query": function() { - return { - "filters": {"company": frm.doc.company} + "get_query": function() { + return { + "filters": {"company": frm.doc.company} } } }, @@ -743,7 +743,7 @@ frappe.ui.form.on('Payment Entry', { }); }, - allocate_party_amount_against_ref_docs: function(frm, paid_amount) { + allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) { var total_positive_outstanding_including_order = 0; var total_negative_outstanding = 0; var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [], @@ -800,22 +800,18 @@ frappe.ui.form.on('Payment Entry', { //If allocate payment amount checkbox is unchecked, set zero to allocate amount row.allocated_amount = 0; - } else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) { + } else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) { if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { - if (row.outstanding_amount >= allocated_positive_outstanding) { - row.allocated_amount = allocated_positive_outstanding; - } else { - row.allocated_amount = row.outstanding_amount; - } - + row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ? + allocated_positive_outstanding : row.outstanding_amount; allocated_positive_outstanding -= flt(row.allocated_amount); - } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { - if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) { - row.allocated_amount = -1*allocated_negative_outstanding; - } else { - row.allocated_amount = row.outstanding_amount; - }; + } else if (row.outstanding_amount > 0 && allocated_positive_outstanding == 0) { + row.allocated_amount = null; + + } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { + row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ? + -1*allocated_negative_outstanding : row.outstanding_amount; allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount)); } } From 184317d5bd7ebc9a6ce4ffaf353e6f7ebfb674c5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Apr 2021 20:03:50 +0530 Subject: [PATCH 0593/1264] fix(HR): Permission error while adding weekly holidays (#25450) --- erpnext/hr/doctype/holiday_list/holiday_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 6df7bc88c0..67630a0abe 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -16,6 +16,7 @@ class HolidayList(Document): self.validate_days() self.total_holidays = len(self.holidays) + @frappe.whitelist() def get_weekly_off_dates(self): self.validate_values() date_list = self.get_weekly_off_date_list(self.from_date, self.to_date) From e63f3b2f36fe3cbfec6278b59902974f00f6b906 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Apr 2021 20:33:42 +0530 Subject: [PATCH 0594/1264] fix: standard working hours message and link for HR Settings --- .../project_profitability/project_profitability.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 593ce1b22b..23e8f79b45 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -21,11 +21,13 @@ def get_rows(filters): conditions = get_conditions(filters) standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") if not standard_working_hours: - hr_settings = "HR Settings" - frappe.msgprint(_("The metrics for this report are calculated based on the Standard Working Hours. Please set Standard Working Hours in {0}.").format(hr_settings)) + msg = _("The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.").format( + frappe.bold("Standard Working Hours"), frappe.utils.get_link_to_form("HR Settings", "HR Settings")) + + frappe.msgprint(msg) return [] - sql = """ + sql = """ SELECT * FROM @@ -74,7 +76,7 @@ def get_conditions(filters): if filters.get("project"): conditions.append("tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project")))) - + conditions = " and ".join(conditions) return conditions From 4a805b5622cde87a9436113e1d7f9d1dcf71172b Mon Sep 17 00:00:00 2001 From: Walstan Baptista <38958184+walstanb@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:23:08 +0530 Subject: [PATCH 0595/1264] chore: frappe.whitelist for doc methods (#25465) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 1 + .../doctype/amazon_mws_settings/amazon_mws_settings.py | 4 +++- erpnext/healthcare/doctype/therapy_type/therapy_type.py | 1 + erpnext/hr/doctype/holiday_list/holiday_list.py | 1 + .../doctype/leave_control_panel/leave_control_panel.py | 1 + .../import_supplier_invoice/import_supplier_invoice.py | 3 ++- erpnext/selling/doctype/sms_center/sms_center.py | 2 ++ erpnext/setup/doctype/naming_series/naming_series.py | 1 + .../stock/doctype/purchase_receipt/purchase_receipt.py | 1 + erpnext/stock/doctype/stock_entry/stock_entry.py | 9 +++++++-- 10 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ff2c8c29b4..fefab82efc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -592,6 +592,7 @@ class JournalEntry(AccountsController): self.validate_total_debit_and_credit() + @frappe.whitelist() def get_outstanding_invoices(self): self.set('accounts', []) total = 0 diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py index 899b7ffe13..9c59840149 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py @@ -17,10 +17,12 @@ class AmazonMWSSettings(Document): else: self.enable_sync = 0 + @frappe.whitelist() def get_products_details(self): if self.enable_amazon == 1: frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details') + @frappe.whitelist() def get_order_details(self): if self.enable_amazon == 1: after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d") @@ -40,4 +42,4 @@ def setup_custom_fields(): fieldtype='Data', insert_after='title', read_only=1, print_hide=1)] } - create_custom_fields(custom_fields) \ No newline at end of file + create_custom_fields(custom_fields) diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.py b/erpnext/healthcare/doctype/therapy_type/therapy_type.py index 6c825b8a58..3f6a36a968 100644 --- a/erpnext/healthcare/doctype/therapy_type/therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.py @@ -50,6 +50,7 @@ class TherapyType(Document): self.db_set('change_in_item', 0) + @frappe.whitelist() def add_exercises(self): exercises = self.get_exercises_for_body_parts() last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,]) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 67630a0abe..8af8cea605 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -62,6 +62,7 @@ class HolidayList(Document): return date_list + @frappe.whitelist() def clear_table(self): self.set('holidays', []) diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py index 57e61b5e08..74014020fc 100644 --- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py +++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py @@ -29,6 +29,7 @@ class LeaveControlPanel(Document): frappe.throw(_("{0} is required").format(self.meta.get_label(f))) self.validate_from_to_dates('from_date', 'to_date') + @frappe.whitelist() def allocate_leave(self): self.validate_values() leave_allocated_for = [] diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 31a7545a0d..cc6b907bc1 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -124,6 +124,7 @@ class ImportSupplierInvoice(Document): if disc_line.find("Percentuale"): invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty)) + @frappe.whitelist() def process_file_data(self): self.status = "Processing File Data" self.save() @@ -400,4 +401,4 @@ def get_full_path(file_name): elif not self.file_url: frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) - return file_path \ No newline at end of file + return file_path diff --git a/erpnext/selling/doctype/sms_center/sms_center.py b/erpnext/selling/doctype/sms_center/sms_center.py index bb6ba1ffce..d142d16248 100644 --- a/erpnext/selling/doctype/sms_center/sms_center.py +++ b/erpnext/selling/doctype/sms_center/sms_center.py @@ -12,6 +12,7 @@ from frappe.model.document import Document from frappe.core.doctype.sms_settings.sms_settings import send_sms class SMSCenter(Document): + @frappe.whitelist() def create_receiver_list(self): rec, where_clause = '', '' if self.send_to == 'All Customer Contact': @@ -73,6 +74,7 @@ class SMSCenter(Document): return receiver_nos + @frappe.whitelist() def send_sms(self): receiver_list = [] if not self.message: diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index c4f1de14e4..373b0a58c9 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -159,6 +159,7 @@ class NamingSeries(Document): if frappe.db.get_value('Series', series, 'name', order_by="name") == None: frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series)) + @frappe.whitelist() def update_series_start(self): if self.prefix: prefix = self.parse_naming_series() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 5d7597b2db..d8d8310733 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -221,6 +221,7 @@ class PurchaseReceipt(BuyingController): self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') self.delete_auto_created_batches() + @frappe.whitelist() def get_current_stock(self): for d in self.get('supplied_items'): if self.supplier_warehouse: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f8ac400a8e..48cfa51041 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -398,8 +398,12 @@ class StockEntry(StockController): and item_code = %s and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0] if fg_qty_already_entered and fg_qty_already_entered >= qty: - frappe.throw(_("Stock Entries already created for Work Order ") - + self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError) + frappe.throw( + _("Stock Entries already created for Work Order {0}: {1}").format( + self.work_order, ", ".join(other_ste) + ), + DuplicateEntryForWorkOrderError, + ) def set_actual_qty(self): allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")) @@ -435,6 +439,7 @@ class StockEntry(StockController): if transferred_serial_no: d.serial_no = transferred_serial_no + @frappe.whitelist() def get_stock_and_rate(self): """ Updates rate and availability of all the items. From 2a25c7505efbba95393a4a0b6ed6916510885227 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 25 Apr 2021 20:29:14 +0530 Subject: [PATCH 0596/1264] fix: report column widths --- .../project_profitability.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 23e8f79b45..5ad2d85232 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -133,14 +133,14 @@ def get_columns(): "label": _("Sales Invoice"), "fieldtype": "Link", "options": "Sales Invoice", - "width": 180 + "width": 120 }, { "fieldname": "timesheet", "label": _("Timesheet"), "fieldtype": "Link", "options": "Timesheet", - "width": 130 + "width": 120 }, { "fieldname": "project", @@ -174,37 +174,37 @@ def get_columns(): "fieldname": "utilization", "label": _("Utilization"), "fieldtype": "Percentage", - "width": 120 + "width": 100 }, { "fieldname": "fractional_cost", "label": _("Fractional Cost"), "fieldtype": "Int", - "width": 100 + "width": 120 }, { "fieldname": "total_billed_hours", "label": _("Total Billed Hours"), "fieldtype": "Int", - "width": 100 + "width": 150 }, { "fieldname": "start_date", "label": _("Start Date"), "fieldtype": "Date", - "width": 120 + "width": 100 }, { "fieldname": "end_date", "label": _("End Date"), "fieldtype": "Date", - "width": 120 + "width": 100 }, { "label": _("Currency"), "fieldname": "currency", "fieldtype": "Link", "options": "Currency", - "width": 100 + "width": 80 } ] \ No newline at end of file From 0decc2b66a060586e4a88a5356bf89754e196c1f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 11:00:54 +0530 Subject: [PATCH 0597/1264] fix: validate Standard Working Hours - use standard working hours in metrics calculation --- .../hr/doctype/hr_settings/hr_settings.json | 3 +- ...ee_hours_utilization_based_on_timesheet.py | 34 ++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 35532291a5..3db6c239ef 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -146,7 +146,6 @@ "label": "Send Leave Notification" }, { - "default": "8", "fieldname": "standard_working_hours", "fieldtype": "Int", "label": "Standard Working Hours" @@ -156,7 +155,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2021-04-16 15:45:18.467699", + "modified": "2021-04-26 10:52:56.192773", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 48eb7b4bb0..7337a03eb4 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -19,13 +19,22 @@ class EmployeeHoursReport: self.to_date = getdate(self.filters.to_date) self.validate_dates() - + self.validate_standard_working_hours() + def validate_dates(self): self.day_span = (self.to_date - self.from_date).days if self.day_span <= 0: frappe.throw(_('From Date must come before To Date')) + def validate_standard_working_hours(self): + self.standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours') + if not self.standard_working_hours: + msg = _('The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.').format( + frappe.bold('Standard Working Hours'), frappe.utils.get_link_to_form('HR Settings', 'HR Settings')) + + frappe.throw(msg) + def run(self): self.generate_columns() self.generate_data() @@ -47,7 +56,7 @@ class EmployeeHoursReport: 'label': _('Department'), 'options': 'Department', 'fieldname': 'department', - 'fieldtype': 'Link', + 'fieldtype': 'Link', 'width': 170 }, { @@ -81,7 +90,7 @@ class EmployeeHoursReport: 'width': 200 } ] - + def generate_data(self): self.generate_filtered_time_logs() self.generate_stats_by_employee() @@ -108,7 +117,7 @@ class EmployeeHoursReport: for emp, data in self.stats_by_employee.items(): if data['department'] == self.filters.department: filtered_data[emp] = data - + # Update stats self.stats_by_employee = filtered_data @@ -126,8 +135,8 @@ class EmployeeHoursReport: self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project - FROM `tabTimesheet Detail` AS ttd - JOIN `tabTimesheet` AS tt + FROM `tabTimesheet Detail` AS ttd + JOIN `tabTimesheet` AS tt ON ttd.parent = tt.name WHERE tt.employee IS NOT NULL AND tt.start_date BETWEEN '{0}' AND '{1}' @@ -161,10 +170,9 @@ class EmployeeHoursReport: self.stats_by_employee[emp]['department'] = emp_dept self.stats_by_employee[emp]['employee_name'] = emp_name - + def calculate_utilizations(self): - # (9.0) Will be fetched from HR settings - TOTAL_HOURS = flt(9.0 * self.day_span, 2) + TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) for emp, data in iteritems(self.stats_by_employee): data['total_hours'] = TOTAL_HOURS data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) @@ -172,14 +180,14 @@ class EmployeeHoursReport: # To handle overtime edge-case if data['untracked_hours'] < 0: data['untracked_hours'] = 0.0 - + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) - + def generate_report_summary(self): self.report_summary = [] if not self.data: - return + return avg_utilization = 0.0 total_billed, total_non_billed = 0.0, 0.0 @@ -233,7 +241,7 @@ class EmployeeHoursReport: billed_hours.append(row.get('billed_hours')) non_billed_hours.append(row.get('non_billed_hours')) untracked_hours.append(row.get('untracked_hours')) - + self.chart = { 'data': { 'labels': labels[:30], From b9d4719285baabdb031bdce649947fd5b49a73fc Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Mon, 26 Apr 2021 11:14:07 +0530 Subject: [PATCH 0598/1264] fix: circular loop in project task (#25454) * fix: circular loop in project task --- erpnext/projects/doctype/task/task.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 002ddb2f40..6a9d2d1424 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -32,7 +32,8 @@ frappe.ui.form.on("Task", { frm.set_query("parent_task", function () { let filters = { - "is_group": 1 + "is_group": 1, + "name": ["!=", frm.doc.name] }; if (frm.doc.project) filters["project"] = frm.doc.project; return { From 2a1a4723712b6cae1c15c88c3a1e3bff4f6ea7ad Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 11:18:32 +0530 Subject: [PATCH 0599/1264] chore: clean-up - set default date filters for last month instead of year - added abbreviations for columns to use them in the metrics labels --- ...ee_hours_utilization_based_on_timesheet.js | 4 +-- ...ee_hours_utilization_based_on_timesheet.py | 34 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js index b11a1fc97f..9a30b99f9b 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js @@ -16,14 +16,14 @@ frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = { fieldname: "from_date", label: __("From Date"), fieldtype: "Date", - default: frappe.defaults.get_global_default("year_start_date"), + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), reqd: 1 }, { fieldname:"to_date", label: __("To Date"), fieldtype: "Date", - default: frappe.defaults.get_global_default("year_end_date"), + default: frappe.datetime.now_date(), reqd: 1 }, { diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 7337a03eb4..b38da1416f 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -57,34 +57,34 @@ class EmployeeHoursReport: 'options': 'Department', 'fieldname': 'department', 'fieldtype': 'Link', + 'width': 120 + }, + { + 'label': _('Total Hours (T)'), + 'fieldname': 'total_hours', + 'fieldtype': 'Float', + 'width': 120 + }, + { + 'label': _('Billed Hours (B)'), + 'fieldname': 'billed_hours', + 'fieldtype': 'Float', 'width': 170 }, { - 'label': _('Total Hours'), - 'fieldname': 'total_hours', - 'fieldtype': 'Float', - 'width': 150 - }, - { - 'label': _('Billed Hours'), - 'fieldname': 'billed_hours', - 'fieldtype': 'Float', - 'width': 150 - }, - { - 'label': _('Non-Billed Hours'), + 'label': _('Non-Billed Hours (NB)'), 'fieldname': 'non_billed_hours', 'fieldtype': 'Float', - 'width': 150 + 'width': 170 }, { - 'label': _('Untracked Hours'), + 'label': _('Untracked Hours (U)'), 'fieldname': 'untracked_hours', 'fieldtype': 'Float', - 'width': 150 + 'width': 170 }, { - 'label': _('% Utilization (Billed Hours + Non-Billed Hours / Total Hours)'), + 'label': _('% Utilization (B + NB) / T'), 'fieldname': 'per_util', 'fieldtype': 'Percentage', 'width': 200 From f74d8d2e167decd1155cae6b469024f280407316 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Mon, 26 Apr 2021 11:31:51 +0530 Subject: [PATCH 0600/1264] fix: suggested changes --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 936c538e20..830a7f25c0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -801,14 +801,11 @@ frappe.ui.form.on('Payment Entry', { row.allocated_amount = 0; } else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) { - if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { + if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) { row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ? allocated_positive_outstanding : row.outstanding_amount; allocated_positive_outstanding -= flt(row.allocated_amount); - } else if (row.outstanding_amount > 0 && allocated_positive_outstanding == 0) { - row.allocated_amount = null; - } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ? -1*allocated_negative_outstanding : row.outstanding_amount; From 4716ebea5a5e7588f4d0d0440dbefaf7a342cab9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 11:40:26 +0530 Subject: [PATCH 0601/1264] feat: Add % Utilization (Billed Only) - Added billed utilization metric in report summary --- ...ee_hours_utilization_based_on_timesheet.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index b38da1416f..842fd4d314 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -88,6 +88,12 @@ class EmployeeHoursReport: 'fieldname': 'per_util', 'fieldtype': 'Percentage', 'width': 200 + }, + { + 'label': _('% Utilization (B / T)'), + 'fieldname': 'per_util_billed_only', + 'fieldtype': 'Percentage', + 'width': 200 } ] @@ -182,6 +188,7 @@ class EmployeeHoursReport: data['untracked_hours'] = 0.0 data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) + data['per_util_billed_only'] = flt((data['billed_hours'] / TOTAL_HOURS) * 100, 2) def generate_report_summary(self): self.report_summary = [] @@ -190,11 +197,13 @@ class EmployeeHoursReport: return avg_utilization = 0.0 + avg_utilization_billed_only = 0.0 total_billed, total_non_billed = 0.0, 0.0 total_untracked = 0.0 for row in self.data: avg_utilization += row['per_util'] + avg_utilization_billed_only += row['per_util_billed_only'] total_billed += row['billed_hours'] total_non_billed += row['non_billed_hours'] total_untracked += row['untracked_hours'] @@ -202,12 +211,21 @@ class EmployeeHoursReport: avg_utilization /= len(self.data) avg_utilization = flt(avg_utilization, 2) + avg_utilization_billed_only /= len(self.data) + avg_utilization_billed_only = flt(avg_utilization_billed_only, 2) + THRESHOLD_PERCENTAGE = 70.0 self.report_summary = [ { 'value': f'{avg_utilization}%', 'indicator': 'Red' if avg_utilization < THRESHOLD_PERCENTAGE else 'Green', - 'label': _('Average Utilization'), + 'label': _('Avg Utilization'), + 'datatype': 'Percentage' + }, + { + 'value': f'{avg_utilization_billed_only}%', + 'indicator': 'Red' if avg_utilization_billed_only < THRESHOLD_PERCENTAGE else 'Green', + 'label': _('Avg Utilization (Billed Only)'), 'datatype': 'Percentage' }, { @@ -219,11 +237,6 @@ class EmployeeHoursReport: 'value': total_non_billed, 'label': _('Total Non-Billed Hours'), 'datatype': 'Float' - }, - { - 'value': total_untracked, - 'label': _('Total Untracked Hours'), - 'datatype': 'Float' } ] From 968ec110f304e8c8f1710ddccef972c35d1cef73 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Apr 2021 14:51:00 +0530 Subject: [PATCH 0602/1264] fix: Linting and translation issues --- .../process_statement_of_accounts.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 33c32ebce2..b6149e89f5 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -4,13 +4,13 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency from erpnext.accounts.party import get_party_account_currency -from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -31,7 +31,7 @@ class ProcessStatementOfAccounts(Document): validate_template(self.body) if not self.customers: - frappe.throw(frappe._('Customers not selected.')) + frappe.throw(_('Customers not selected.')) if self.enable_auto_email: self.to_date = self.start_date @@ -60,6 +60,8 @@ def get_report_pdf(doc, consolidated=True): aging[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id + presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company) filters= frappe._dict({ 'from_date': doc.from_date, @@ -69,8 +71,7 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ - or doc.currency or get_company_currency(doc.company), + 'presentation_currency': presentation_currency, 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], @@ -170,7 +171,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): if customer_collection == 'Sales Person': customers = get_customers_based_on_sales_person(collection_name) if not bool(customers): - frappe.throw('No Customers found with selected options.') + frappe.throw(_('No Customers found with selected options.')) else: if customer_collection == 'Sales Partner': customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ @@ -202,14 +203,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: - frappe.throw('No billing email found for customer: '+ customer_name) + frappe.throw(_("No billing email found for customer: {0}").format(customer_name)) else: return '' if billing_and_primary: primary_email = frappe.get_value('Customer', customer_name, 'email_id') if primary_email is None and int(primary_mandatory): - frappe.throw('No primary email found for customer: '+ customer_name) + frappe.throw(_("No primary email found for customer: {0}").format(customer_name)) return [primary_email or '', billing_email[0][0]] else: return billing_email[0][0] or '' From 4c8d15b487df963875509956dbcb5df332db4aea Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 26 Apr 2021 15:24:34 +0530 Subject: [PATCH 0603/1264] fix: Incorrect GL Entry validation (#25474) * fix: Incorrect GL Entry validation * fix: Add validation for party accounts * fix: Scrub labels * fix: Translation --- erpnext/accounts/general_ledger.py | 3 ++- erpnext/controllers/accounts_controller.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index dac0c216c8..85bff10fb4 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -18,7 +18,8 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd gl_map = process_gl_map(gl_map, merge_entries) if gl_map and len(gl_map) > 1: save_entries(gl_map, adv_adj, update_outstanding, from_repost) - else: + # Post GL Map proccess there may no be any GL Entries + elif gl_map: frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.")) else: make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d36e7b03f4..97bcb00210 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -90,6 +90,8 @@ class AccountsController(TransactionBase): self.ensure_supplier_is_not_blocked() self.validate_date_with_fiscal_year() + self.validate_party_accounts() + self.validate_inter_company_reference() self.set_incoming_rate() @@ -233,6 +235,23 @@ class AccountsController(TransactionBase): validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company, self.meta.get_label(date_field), self) + def validate_party_accounts(self): + if self.doctype not in ('Sales Invoice', 'Purchase Invoice'): + return + + if self.doctype == 'Sales Invoice': + party_account_field = 'debit_to' + item_field = 'income_account' + else: + party_account_field = 'credit_to' + item_field = 'expense_account' + + for item in self.get('items'): + if item.get(item_field) == self.get(party_account_field): + frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx, + frappe.bold(frappe.unscrub(item_field)), item.get(item_field), + frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field))) + def validate_inter_company_reference(self): if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'): return @@ -240,7 +259,7 @@ class AccountsController(TransactionBase): if self.is_internal_transfer(): if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference') or self.get('inter_company_order_reference')): - msg = _("Internal Sale or Delivery Reference missing. ") + msg = _("Internal Sale or Delivery Reference missing.") msg += _("Please create purchase from internal sale or delivery document itself") frappe.throw(msg, title=_("Internal Sales Reference Missing")) From c2de4f6b3513043f13d7c28cb3d6711f8c021be4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Apr 2021 15:28:17 +0530 Subject: [PATCH 0604/1264] chore: update snyk (#25477) --- package.json | 2 +- yarn.lock | 3606 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 2476 insertions(+), 1132 deletions(-) diff --git a/package.json b/package.json index d12661b5cc..c9ee7a622c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/frappe/erpnext/issues" }, "devDependencies": { - "snyk": "^1.290.1" + "snyk": "^1.518.0" }, "dependencies": { "onscan.js": "^1.5.2" diff --git a/yarn.lock b/yarn.lock index e5a2da1e40..0a2ac1affc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,241 +2,622 @@ # yarn lockfile v1 -"@snyk/cli-interface@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-1.5.0.tgz#b9dbe6ebfb86e67ffabf29d4e0d28a52670ac456" - integrity sha512-+Qo+IO3YOXWgazlo+CKxOuWFLQQdaNCJ9cSfhFQd687/FuesaIxWdInaAdfpsLScq0c6M1ieZslXgiZELSzxbg== +"@arcanis/slice-ansi@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz#35331e41a1062e3c53c01ad2ec1555c5c1959d8f" + integrity sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw== dependencies: - tslib "^1.9.3" + grapheme-splitter "^1.0.4" -"@snyk/cli-interface@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.2.0.tgz#5536bc913917c623d16d727f9f3759521a916026" - integrity sha512-sA7V2JhgqJB9z5uYotgQc5iNDv//y+Mdm39rANxmFjtZMSYJZHkP80arzPjw1mB5ni/sWec7ieYUUFeySZBfVg== +"@deepcode/dcignore@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@deepcode/dcignore/-/dcignore-1.0.2.tgz#39e4a3df7dde8811925330506e4bb3fbf3c288d8" + integrity sha512-DPgxtHuJwBORpqRkPXzzOT+uoPRVJmaN7LR+pmeL6DQM90kj6G6GFUH1i/YpRH8NbML8ZGEDwB9f9u4UwD2pzg== + +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== dependencies: - tslib "^1.9.3" + "@nodelib/fs.stat" "2.0.4" + run-parallel "^1.1.9" -"@snyk/cli-interface@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.0.tgz#9d38f815a5cf2be266006954c2a058463d531e08" - integrity sha512-ecbylK5Ol2ySb/WbfPj0s0GuLQR+KWKFzUgVaoNHaSoN6371qRWwf2uVr+hPUP4gXqCai21Ug/RDArfOhlPwrQ== +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== dependencies: - tslib "^1.9.3" + "@nodelib/fs.scandir" "2.1.4" + fastq "^1.6.0" -"@snyk/cli-interface@2.3.1", "@snyk/cli-interface@^2.0.3": +"@octetstream/promisify@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@octetstream/promisify/-/promisify-2.0.2.tgz#29ac3bd7aefba646db670227f895d812c1a19615" + integrity sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg== + +"@open-policy-agent/opa-wasm@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@open-policy-agent/opa-wasm/-/opa-wasm-1.2.0.tgz#481b766093f70b00efefbee1e4192f375fd34ca2" + integrity sha512-CtUBTnzvDrT0NASa8IuGQTxFGgt2vxbLnMYuTA+uDFxOcA4uK4mGFgrhHJtxUZnWHiwemOvKKSY3BMCo7qiAsQ== + dependencies: + sprintf-js "^1.1.2" + utf8 "^3.0.0" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@sindresorhus/is@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" + integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== + +"@sindresorhus/is@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" + integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== + +"@snyk/cli-interface@2.11.0", "@snyk/cli-interface@^2.11.0", "@snyk/cli-interface@^2.9.1", "@snyk/cli-interface@^2.9.2": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.11.0.tgz#9df68c8cd54de5dff69f0ab797a188541d9c8965" + integrity sha512-T3xfDqrEFKclHGdJx4/5+D5F7e76/99f33guE4RTlVITBhy7VVnjz4t/NDr3UYqcC0MgAmiC4bSVYHnlshuwJw== + dependencies: + "@types/graphlib" "^2" + +"@snyk/cli-interface@^2.0.3": version "2.3.1" resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.1.tgz#73f2f4bd717b9f03f096ede3ff5830eb8d2f3716" integrity sha512-JZvsmhDXSyjv1dkc12lPI3tNTNYlIaOiIQMYFg2RgqF3QmWjTyBUgRZcF7LoKyufHtS4dIudM6k1aHBpSaDrhw== dependencies: tslib "^1.9.3" -"@snyk/cocoapods-lockfile-parser@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.0.0.tgz#514b744cedd9d3d3efb2a5d06fce1662fec2ff1a" - integrity sha512-AebCc+v9vtOL9tFkU4/tommgVsXxqdx6t45kCkBW+FC4PaYvfYEg9Eg/9GqlY9+nFrLFo/uTr+E/aR0AF/KqYA== +"@snyk/cloud-config-parser@^1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@snyk/cloud-config-parser/-/cloud-config-parser-1.9.2.tgz#e6c8e575db8527b33cf1ba766f86e1b3414cf6e1" + integrity sha512-m8Y2+3l4fxj96QMrTfiCEaXgCpDkCkJIX/5wv0V0RHuxpUiyh+KxC2yJ8Su4wybBj6v6hB9hB7h5/L+Gy4V4PA== dependencies: - "@snyk/dep-graph" "^1.11.0" - "@snyk/ruby-semver" "^2.0.4" - "@types/js-yaml" "^3.12.1" - core-js "^3.2.0" - js-yaml "^3.13.1" - source-map-support "^0.5.7" - tslib "^1.9.3" - -"@snyk/composer-lockfile-parser@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.2.0.tgz#62c6d88c6a39c55dda591854f5380923a993182f" - integrity sha512-kZT+HTqgNcQMeoE5NM9M3jj463M8zI7ZxqZXLw9WoyVs5JTt9g0qFWxIG1cNwZdGVI+y7tzZbNWw9BlMD1vCCQ== - dependencies: - lodash "^4.17.13" - -"@snyk/configstore@3.2.0-rc1", "@snyk/configstore@^3.2.0-rc1": - version "3.2.0-rc1" - resolved "https://registry.yarnpkg.com/@snyk/configstore/-/configstore-3.2.0-rc1.tgz#385c050d11926a26d0335a4b3be9e55f90f6e0ac" - integrity sha512-CV3QggFY8BY3u8PdSSlUGLibqbqCG1zJRmGM2DhnhcxQDRRPTGTP//l7vJphOVsUP1Oe23+UQsj7KRWpRUZiqg== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - -"@snyk/dep-graph@1.13.1": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.13.1.tgz#45721f7e21136b62d1cdd99b3319e717d9071dfb" - integrity sha512-Ww2xvm5UQgrq9eV0SdTBCh+w/4oI2rCx5vn1IOSeypaR0CO4p+do1vm3IDZ2ugg4jLSfHP8+LiD6ORESZMkQ2w== - dependencies: - graphlib "^2.1.5" - lodash "^4.7.14" - object-hash "^1.3.1" - semver "^6.0.0" - source-map-support "^0.5.11" - tslib "^1.9.3" - -"@snyk/dep-graph@^1.11.0", "@snyk/dep-graph@^1.13.1": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.15.0.tgz#67bf790bc9f0eee36ad7dad053465cdd928ce223" - integrity sha512-GdF/dvqfKRVHqQio/tSkR4GRpAqIglLPEDZ+XlV7jT5btq9+Fxq2h25Lmm/a7sw+ODTOOqNhTF9y8ASc9VIhww== - dependencies: - graphlib "^2.1.5" - lodash "^4.7.14" - object-hash "^1.3.1" - semver "^6.0.0" - source-map-support "^0.5.11" + esprima "^4.0.1" tslib "^1.10.0" + yaml-js "^0.3.0" + +"@snyk/cocoapods-lockfile-parser@3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.6.2.tgz#803ae9466f408c48ba7c5a8ec51b9dbac6f633b3" + integrity sha512-ca2JKOnSRzYHJkhOB9gYmdRZHmd02b/uBd/S0D5W+L9nIMS7sUBV5jfhKwVgrYPIpVNIc0XCI9rxK4TfkQRpiA== + dependencies: + "@snyk/dep-graph" "^1.23.1" + "@types/js-yaml" "^3.12.1" + js-yaml "^3.13.1" + tslib "^1.10.0" + +"@snyk/code-client@3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@snyk/code-client/-/code-client-3.4.1.tgz#b9d025897cd586e0aef903162ac0407d0bffc3cd" + integrity sha512-XJ7tUdX1iQyzN/BmHac7p+Oyw1SyTcqSkCNExwBJxyQdlnUAKK6QKIWLXS81tTpZ79FgCdT+0fdS0AjsyS99eA== + dependencies: + "@deepcode/dcignore" "^1.0.2" + "@snyk/fast-glob" "^3.2.6-patch" + "@types/flat-cache" "^2.0.0" + "@types/lodash.chunk" "^4.2.6" + "@types/lodash.omit" "^4.5.6" + "@types/lodash.union" "^4.6.6" + "@types/micromatch" "^4.0.1" + "@types/sarif" "^2.1.3" + "@types/uuid" "^8.3.0" + axios "^0.21.1" + ignore "^5.1.8" + lodash.chunk "^4.2.0" + lodash.omit "^4.5.0" + lodash.union "^4.6.0" + micromatch "^4.0.2" + queue "^6.0.1" + uuid "^8.3.2" + +"@snyk/composer-lockfile-parser@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz#2f7c93ad367520322b16d9490a208fec08445e0e" + integrity sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ== + dependencies: + lodash.findkey "^4.6.0" + lodash.get "^4.4.2" + lodash.invert "^4.3.0" + lodash.isempty "^4.4.0" + +"@snyk/dep-graph@^1.19.3", "@snyk/dep-graph@^1.21.0", "@snyk/dep-graph@^1.23.0", "@snyk/dep-graph@^1.23.1", "@snyk/dep-graph@^1.27.1", "@snyk/dep-graph@^1.28.0": + version "1.28.0" + resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.28.0.tgz#d68c0576cb3562c6e819ca8a8c7ac29ee11d9776" + integrity sha512-Oup9nAvb558jdNvbZah/vaBtOtCcizkdeS+OBQeBIqIffyer4mc4juSn4b1SFjCpu7AG7piio8Lj8k1B9ps6Tg== + dependencies: + event-loop-spinner "^2.1.0" + lodash.clone "^4.5.0" + lodash.constant "^3.0.0" + lodash.filter "^4.6.0" + lodash.foreach "^4.5.0" + lodash.isempty "^4.4.0" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isundefined "^3.0.1" + lodash.keys "^4.2.0" + lodash.map "^4.6.0" + lodash.reduce "^4.6.0" + lodash.size "^4.2.0" + lodash.transform "^4.6.0" + lodash.union "^4.6.0" + lodash.values "^4.3.0" + object-hash "^2.0.3" + semver "^7.0.0" + tslib "^1.13.0" + +"@snyk/docker-registry-v2-client@1.13.9": + version "1.13.9" + resolved "https://registry.yarnpkg.com/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.9.tgz#54c2e3071de58fc6fc12c5fef5eaeae174ecda12" + integrity sha512-DIFLEhr8m1GrAwsLGInJmpcQMacjuhf3jcbpQTR+LeMvZA9IuKq+B7kqw2O2FzMiHMZmUb5z+tV+BR7+IUHkFQ== + dependencies: + needle "^2.5.0" + parse-link-header "^1.0.1" + tslib "^1.10.0" + +"@snyk/fast-glob@^3.2.6-patch": + version "3.2.6-patch" + resolved "https://registry.yarnpkg.com/@snyk/fast-glob/-/fast-glob-3.2.6-patch.tgz#a0866bedb17f95255e4050dad08daeaff0f4caa8" + integrity sha512-E/Pfdze/WFfxwyuTFcfhQN1SwyUsc43yuCoW63RVBCaxTD6OzhVD2Pvc/Sy7BjiWUfmelzyKkIBpoow8zZX7Zg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + "@snyk/glob-parent" "^5.1.2-patch.1" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +"@snyk/fix@1.554.0": + version "1.554.0" + resolved "https://registry.yarnpkg.com/@snyk/fix/-/fix-1.554.0.tgz#7ae786882e0ffea5e7f10d0b41e3d593b65555c4" + integrity sha512-q2eRVStgspPeI2wZ2EQGLpiWZMRg7o+4tsCk6m/kHZgQGDN4Bb7L3xslFW3OgF0+ZksYSaHl2cW2HmGiLRaYcA== + dependencies: + "@snyk/dep-graph" "^1.21.0" + chalk "4.1.0" + debug "^4.3.1" + ora "5.3.0" + p-map "^4.0.0" + strip-ansi "6.0.0" "@snyk/gemfile@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457" integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA== -"@snyk/ruby-semver@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@snyk/ruby-semver/-/ruby-semver-2.0.4.tgz#457686ea7a4d60b10efddde99587efb3a53ba884" - integrity sha512-ceMD4CBS3qtAg+O0BUvkKdsheUNCqi+/+Rju243Ul8PsUgZnXmGiqfk/2z7DCprRQnxUTra4+IyeDQT7wAheCQ== +"@snyk/glob-parent@^5.1.2-patch.1": + version "5.1.2-patch.1" + resolved "https://registry.yarnpkg.com/@snyk/glob-parent/-/glob-parent-5.1.2-patch.1.tgz#87733b4ab282043fa7915200bc94cb391df6d44f" + integrity sha512-OkUPdHgxIWKAAzceG1nraNA0kgI+eS0I9wph8tll9UL0slD2mIWSj4mAqroGovaEXm8nHedoUfuDRGEb6wnzCQ== dependencies: - lodash "^4.17.14" + is-glob "^4.0.1" -"@snyk/snyk-cocoapods-plugin@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.0.1.tgz#be8660c854d551a56baa9d072bb4ae7f188cc1cd" - integrity sha512-XVkvaMvMzQ3miJi/YZmsRJSAUfDloYhfg6pXPgzAeAugB4p+cNi01Z68pT62ypB8U/Ugh1Xx2pb9aoOFqBbSjA== +"@snyk/graphlib@2.1.9-patch.3", "@snyk/graphlib@^2.1.9-patch.3": + version "2.1.9-patch.3" + resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz#b8edb2335af1978db7f3cb1f28f5d562960acf23" + integrity sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q== dependencies: - "@snyk/cli-interface" "1.5.0" - "@snyk/cocoapods-lockfile-parser" "3.0.0" - "@snyk/dep-graph" "^1.13.1" + lodash.clone "^4.5.0" + lodash.constant "^3.0.0" + lodash.filter "^4.6.0" + lodash.foreach "^4.5.0" + lodash.has "^4.5.2" + lodash.isempty "^4.4.0" + lodash.isfunction "^3.0.9" + lodash.isundefined "^3.0.1" + lodash.keys "^4.2.0" + lodash.map "^4.6.0" + lodash.reduce "^4.6.0" + lodash.size "^4.2.0" + lodash.transform "^4.6.0" + lodash.union "^4.6.0" + lodash.values "^4.3.0" + +"@snyk/inquirer@^7.3.3-patch": + version "7.3.3-patch" + resolved "https://registry.yarnpkg.com/@snyk/inquirer/-/inquirer-7.3.3-patch.tgz#ef84d531724c53b755e8dd454e1a3c2ccdcfc0bf" + integrity sha512-aWiQSOacH2lOpJ1ard9ErABcH4tdJogdr+mg1U67iZJOPO9n2gFgAwz1TQJDyPkv4/A5mh4hT2rg03Uq+KBn2Q== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash.assign "^4.2.0" + lodash.assignin "^4.2.0" + lodash.clone "^4.5.0" + lodash.defaults "^4.2.0" + lodash.filter "^4.6.0" + lodash.find "^4.6.0" + lodash.findindex "^4.6.0" + lodash.flatten "^4.4.0" + lodash.isboolean "^3.0.3" + lodash.isfunction "^3.0.9" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.last "^3.0.0" + lodash.map "^4.6.0" + lodash.omit "^4.5.0" + lodash.set "^4.3.2" + lodash.sum "^4.0.2" + lodash.uniq "^4.5.0" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +"@snyk/java-call-graph-builder@1.19.1": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.19.1.tgz#1d579d782df3bb5f9d5171cc35180596cd90aa8b" + integrity sha512-bxjHef5Qm3pNc+BrFlxMudmSSbOjA395ZqBddc+dvsFHoHeyNbiY56Y1JSGUlTgjRM+PKNPBiCuELTSMaROeZg== + dependencies: + "@snyk/graphlib" "2.1.9-patch.3" + ci-info "^2.0.0" + debug "^4.1.1" + glob "^7.1.6" + jszip "^3.2.2" + needle "^2.3.3" + progress "^2.0.3" + snyk-config "^4.0.0-rc.2" source-map-support "^0.5.7" + temp-dir "^2.0.0" + tmp "^0.2.1" tslib "^1.9.3" + xml-js "^1.6.11" -"@snyk/update-notifier@^2.5.1-rc2": - version "2.5.1-rc2" - resolved "https://registry.yarnpkg.com/@snyk/update-notifier/-/update-notifier-2.5.1-rc2.tgz#14bf816114b5698a255289d7170157f254202fad" - integrity sha512-dlled3mfpnAt3cQb5hxkFiqfPCj4Yk0xV8Yl5P8PeVv1pUmO7vI4Ka4Mjs4r6CYM5f9kZhviFPQQcWOIDlMRcw== +"@snyk/java-call-graph-builder@1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.20.0.tgz#ffca734cf7ce276a69277963149358190eaac3e5" + integrity sha512-NX8bpIu7oG5cuSSm6WvtxqcCuJs2gRjtKhtuSeF1p5TYXyESs3FXQ0nHjfY90LiyTTc+PW/UBq6SKbBA6bCBww== dependencies: - "@snyk/configstore" "3.2.0-rc1" - boxen "^1.3.0" - chalk "^2.3.2" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.1.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" + "@snyk/graphlib" "2.1.9-patch.3" + ci-info "^2.0.0" + debug "^4.1.1" + glob "^7.1.6" + jszip "^3.2.2" + needle "^2.3.3" + progress "^2.0.3" + snyk-config "^4.0.0-rc.2" + source-map-support "^0.5.7" + temp-dir "^2.0.0" + tmp "^0.2.1" + tslib "^1.9.3" + xml-js "^1.6.11" -"@types/agent-base@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@types/agent-base/-/agent-base-4.2.0.tgz#00644e8b395b40e1bf50aaf1d22cabc1200d5051" - integrity sha512-8mrhPstU+ZX0Ugya8tl5DsDZ1I5ZwQzbL/8PA0z8Gj0k9nql7nkaMzmPVLj+l/nixWaliXi+EBiLA8bptw3z7Q== - dependencies: - "@types/events" "*" - "@types/node" "*" - -"@types/bunyan@*": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.6.tgz#6527641cca30bedec5feb9ab527b7803b8000582" - integrity sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ== +"@snyk/mix-parser@^1.1.1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@snyk/mix-parser/-/mix-parser-1.3.2.tgz#930de1d9c3a91e20660751f78c3e6f6a88ac5b2b" + integrity sha512-0Aq9vcgmjH0d9Gk5q0k6l4ZOvSHPf6/BCQGDVOpKp0hwOkXWnpDOLLPxL+uBCktuH9zTYQFB0aTk91kQImZqmA== dependencies: + "@snyk/dep-graph" "^1.28.0" + tslib "^2.0.0" + +"@snyk/rpm-parser@^2.0.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@snyk/rpm-parser/-/rpm-parser-2.2.1.tgz#b61ccf5478684b203576bd2be68de434ccbb0069" + integrity sha512-OAON0bPf3c5fgM/GK9DX0aZErB6SnuRyYlPH0rqI1TXGsKrYnVELhaE6ctNbEfPTQuY9r6q0vM+UYDaFM/YliA== + dependencies: + event-loop-spinner "^2.0.0" + +"@snyk/snyk-cocoapods-plugin@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.2.tgz#cd724fcd637cb3af76187bf7254819b6079489f6" + integrity sha512-WHhnwyoGOhjFOjBXqUfszD84SErrtjHjium/4xFbqKpEE+yuwxs8OwV/S29BtxhYiGtjpD1azv5QtH30VUMl0A== + dependencies: + "@snyk/cli-interface" "^2.11.0" + "@snyk/cocoapods-lockfile-parser" "3.6.2" + "@snyk/dep-graph" "^1.23.1" + source-map-support "^0.5.7" + tslib "^2.0.0" + +"@snyk/snyk-docker-pull@3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.3.tgz#9743ea624098c7abd0f95c438c76067530494f4b" + integrity sha512-hiFiSmWGLc2tOI7FfgIhVdFzO2f69im8O6p3OV4xEZ/Ss1l58vwtqudItoswsk7wj/azRlgfBW8wGu2MjoudQg== + dependencies: + "@snyk/docker-registry-v2-client" "1.13.9" + child-process "^1.0.2" + tar-stream "^2.1.2" + tmp "^0.1.0" + +"@snyk/snyk-hex-plugin@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@snyk/snyk-hex-plugin/-/snyk-hex-plugin-1.1.4.tgz#4a5b1684cecc1a557ec1a9f5f8646683ae89f0da" + integrity sha512-kLfFGckSmyKe667UGPyWzR/H7/Trkt4fD8O/ktElOx1zWgmivpLm0Symb4RCfEmz9irWv+N6zIKRrfSNdytcPQ== + dependencies: + "@snyk/dep-graph" "^1.28.0" + "@snyk/mix-parser" "^1.1.1" + debug "^4.3.1" + tmp "^0.0.33" + tslib "^2.0.0" + upath "2.0.1" + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@szmarczak/http-timer@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + +"@types/braces@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb" + integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw== + +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" "@types/node" "*" + "@types/responselike" "*" "@types/debug@^4.1.4": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/emscripten@^1.38.0": + version "1.39.4" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.4.tgz#d61990c0cee72c4e475de737a140b51fe925a2c8" + integrity sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ== + +"@types/flat-cache@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/flat-cache/-/flat-cache-2.0.0.tgz#64e5d3b426c392b603a208a55bdcc7d920ce6e57" + integrity sha512-fHeEsm9hvmZ+QHpw6Fkvf19KIhuqnYLU6vtWLjd5BsMd/qVi7iTkMioDZl0mQmfNRA1A6NwvhrSRNr9hGYZGww== + +"@types/graphlib@^2": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.7.tgz#e6a47a4f43511f5bad30058a669ce5ce93bfd823" + integrity sha512-K7T1n6U2HbTYu+SFHlBjz/RH74OA2D/zF1qlzn8uXbvB4uRg7knOM85ugS2bbXI1TXMh7rLqk4OVRwIwEBaixg== + +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== "@types/js-yaml@^3.12.1": version "3.12.2" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.2.tgz#a35a1809c33a68200fb6403d1ad708363c56470a" integrity sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug== +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + +"@types/lodash.chunk@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz#9d35f05360b0298715d7f3d9efb34dd4f77e5d2a" + integrity sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash.omit@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.omit/-/lodash.omit-4.5.6.tgz#f2a9518259e481a48ff7ec423420fa8fd58933e2" + integrity sha512-KXPpOSNX2h0DAG2w7ajpk7TXvWF28ZHs5nJhOJyP0BQHkehgr948RVsToItMme6oi0XJkp19CbuNXkIX8FiBlQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash.union@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.union/-/lodash.union-4.6.6.tgz#2f77f2088326ed147819e9e384182b99aae8d4b0" + integrity sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + +"@types/micromatch@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7" + integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw== + dependencies: + "@types/braces" "*" + "@types/node@*": version "13.5.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.3.tgz#37f1f539b7535b9fb4ef77d59db1847a837b7f17" integrity sha512-ZPnWX9PW992w6DUsz3JIXHaSb5v7qmKCVzC3km6SxcDGxk7zmLfYaCJTbktIa5NeywJkkZDhGldKqDIvC5DRrA== -"@types/node@^6.14.4": - version "6.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.14.9.tgz#733583e21ef0eab85a9737dfafbaa66345a92ef0" - integrity sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w== +"@types/node@^13.7.0": + version "13.13.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.50.tgz#bc8ebf70c392a98ffdba7aab9b46989ea96c1c62" + integrity sha512-y7kkh+hX/0jZNxMyBR/6asG0QMSaPSzgeVK63dhWHl4QAXCQB8lExXmzLL6SzmOgKHydtawpMnNhlDbv7DXPEA== -"@types/restify@^4.3.6": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@types/restify/-/restify-4.3.6.tgz#5da5889b65c34c33937a67686bab591325dde806" - integrity sha512-4l4f0EXnleXQttlhRCXtTuJ8UelsKiAKIK2AAEd2epBHu41aEbM0U2z6E5tUrNwlbxz7qaNBISduGMeg+G3PaA== +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== dependencies: - "@types/bunyan" "*" "@types/node" "*" -"@types/semver@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" - integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== +"@types/sarif@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/sarif/-/sarif-2.1.3.tgz#1f9c16033f1461536ac014284920350109614c02" + integrity sha512-zf+EoIplTkQW2TV2mwtJtlI0g540Z3Rs9tX9JqRAtyjnDCqkP+eMTgWCj3PGNbQpi+WXAjvC3Ou/dvvX2sLK4w== -"@types/xml2js@0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.3.tgz#2f41bfc74d5a4022511721f872ed395a210ad3b7" - integrity sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g== +"@types/semver@^7.1.0": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb" + integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ== + +"@types/treeify@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/treeify/-/treeify-1.0.0.tgz#f04743cb91fc38254e8585d692bd92503782011c" + integrity sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg== + +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + +"@yarnpkg/core@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/core/-/core-2.4.0.tgz#b5d8cc7ee2ddb022816c7afa3f83c3ee3d317c80" + integrity sha512-FYjcPNTfDfMKLFafQPt49EY28jnYC82Z2S7oMwLPUh144BL8v8YXzb4aCnFyi5nFC5h2kcrJfZh7+Pm/qvCqGw== dependencies: - "@types/events" "*" - "@types/node" "*" + "@arcanis/slice-ansi" "^1.0.2" + "@types/semver" "^7.1.0" + "@types/treeify" "^1.0.0" + "@yarnpkg/fslib" "^2.4.0" + "@yarnpkg/json-proxy" "^2.1.0" + "@yarnpkg/libzip" "^2.2.1" + "@yarnpkg/parsers" "^2.3.0" + "@yarnpkg/pnp" "^2.3.2" + "@yarnpkg/shell" "^2.4.1" + binjumper "^0.1.4" + camelcase "^5.3.1" + chalk "^3.0.0" + ci-info "^2.0.0" + clipanion "^2.6.2" + cross-spawn "7.0.3" + diff "^4.0.1" + globby "^11.0.1" + got "^11.7.0" + json-file-plus "^3.3.1" + lodash "^4.17.15" + micromatch "^4.0.2" + mkdirp "^0.5.1" + p-limit "^2.2.0" + pluralize "^7.0.0" + pretty-bytes "^5.1.0" + semver "^7.1.2" + stream-to-promise "^2.2.0" + tar-stream "^2.0.1" + treeify "^1.1.0" + tslib "^1.13.0" + tunnel "^0.0.6" -"@yarnpkg/lockfile@^1.0.2": +"@yarnpkg/fslib@^2.1.0", "@yarnpkg/fslib@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-2.4.0.tgz#a265b737cd089ef293ad964e06c143f5efd411a9" + integrity sha512-CwffYY9owtl3uImNOn1K4jl5iIb/L16a9UZ9Q3lkBARk6tlUsPrNFX00eoUlFcLn49TTfd3zdN6higloGCyncw== + dependencies: + "@yarnpkg/libzip" "^2.2.1" + tslib "^1.13.0" + +"@yarnpkg/json-proxy@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/json-proxy/-/json-proxy-2.1.0.tgz#362a161678cd7dda74b47b4fc848a2f1730d16cd" + integrity sha512-rOgCg2DkyviLgr80mUMTt9vzdf5RGOujQB26yPiXjlz4WNePLBshKlTNG9rKSoKQSOYEQcw6cUmosfOKDatrCw== + dependencies: + "@yarnpkg/fslib" "^2.1.0" + tslib "^1.13.0" + +"@yarnpkg/libzip@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@yarnpkg/libzip/-/libzip-2.2.1.tgz#61c9b8b2499ee6bd9c4fcbf8248f68e07bd89948" + integrity sha512-AYDJXrkzayoDd3ZlVgFJ+LyDX+Zj/cki3vxIpcYxejtgkl3aquVWOxlC0DD9WboBWsJFIP1MjrUbchLyh++/7A== + dependencies: + "@types/emscripten" "^1.38.0" + tslib "^1.13.0" + +"@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +"@yarnpkg/parsers@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-2.3.0.tgz#7b9564c6df02f4921d5cfe8287c4b648e93ea84b" + integrity sha512-qgz0QUgOvnhtF92kaluIhIIKBUHlYlHUBQxqh5v9+sxEQvUeF6G6PKiFlzo3E6O99XwvNEGpVu1xZPoSGyGscQ== + dependencies: + js-yaml "^3.10.0" + tslib "^1.13.0" + +"@yarnpkg/pnp@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@yarnpkg/pnp/-/pnp-2.3.2.tgz#9a052a06bf09c9f0b7c31e0867a7e725cb6401ed" + integrity sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA== + dependencies: + "@types/node" "^13.7.0" + "@yarnpkg/fslib" "^2.4.0" + tslib "^1.13.0" + +"@yarnpkg/shell@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@yarnpkg/shell/-/shell-2.4.1.tgz#abc557f8924987c9c382703e897433a82780265d" + integrity sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g== + dependencies: + "@yarnpkg/fslib" "^2.4.0" + "@yarnpkg/parsers" "^2.3.0" + clipanion "^2.6.2" + cross-spawn "7.0.3" + fast-glob "^3.2.2" + micromatch "^4.0.2" + stream-buffers "^3.0.2" + tslib "^1.13.0" + abbrev@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== dependencies: - es6-promisify "^5.0.0" + clean-stack "^2.0.0" + indent-string "^4.0.0" -agent-base@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== +ansi-align@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" + integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== dependencies: - es6-promisify "^5.0.0" + string-width "^3.0.0" -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= - dependencies: - string-width "^2.0.0" - -ansi-escapes@3.2.0, ansi-escapes@^3.2.0: +ansi-escapes@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -244,11 +625,23 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + ansicolors@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= +any-promise@^1.1.0, any-promise@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" @@ -261,26 +654,57 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -ast-types@0.x.x: - version "0.13.2" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48" - integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA== +asn1@~0.2.0: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" -async@^1.4.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= +async@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bcrypt-pbkdf@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +binjumper@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/binjumper/-/binjumper-0.1.4.tgz#4acc0566832714bd6508af6d666bd9e5e21fc7f8" + integrity sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w== + bl@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f" @@ -288,18 +712,33 @@ bl@^3.0.0: dependencies: readable-stream "^3.0.1" -boxen@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" - term-size "^1.2.0" - widest-line "^2.0.0" + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +boolean@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.3.tgz#0fee0c9813b66bef25a8a6a904bb46736d05f024" + integrity sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA== + +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" brace-expansion@^1.1.7: version "1.1.11" @@ -309,41 +748,86 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserify-zlib@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" + integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0= + dependencies: + pako "~0.2.0" + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -camelcase@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - -capture-stack-trace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" - integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== - -chalk@^2.0.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" + base64-js "^1.3.1" + ieee754 "^1.1.13" -chalk@^2.3.2, chalk@^2.4.2: +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -352,56 +836,90 @@ chalk@^2.3.2, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -ci-info@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" - integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== +child-process@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/child-process/-/child-process-1.0.2.tgz#98974dc7ed1ee4c6229f8e305fa7313a6885a7f2" + integrity sha1-mJdNx+0e5MYin44wX6cxOmiFp/I= -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-boxes@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: - restore-cursor "^2.0.0" + restore-cursor "^3.1.0" cli-spinner@0.2.10: version "0.2.10" resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47" integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q== -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +cli-spinners@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" + integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== -cliui@^3.0.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +clipanion@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.6.2.tgz#820e7440812052442455b248f927b187ed732f71" + integrity sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw== + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" + mimic-response "^1.0.0" -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= color-convert@^1.9.0: version "1.9.3" @@ -410,41 +928,58 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -core-js@^3.2.0: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +core-js@^3.6.5: + version "3.11.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.0.tgz#05dac6aa70c0a4ad842261f8957b961d36eb8926" + integrity sha512-bd79DPpx+1Ilh9+30aT5O1sgpQd4Ttg8oqkqi51ZzhedMM1omD2e6IOF48Z/DzDCZ2svp49tN/3vneTK6ZBkXw== core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -create-error-class@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= +cross-spawn@7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: - capture-stack-trace "^1.0.0" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" cross-spawn@^6.0.0: version "6.0.5" @@ -457,84 +992,108 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -crypto-random-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" - integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -data-uri-to-buffer@1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835" - integrity sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ== - -debug@2: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== dependencies: ms "^2.1.1" -decamelize@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +debug@^4.2.0, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -degenerator@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" - integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= dependencies: - ast-types "0.x.x" - escodegen "1.x.x" - esprima "3.x.x" + clone "^1.0.2" -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +detect-node@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" + integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw== diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -dockerfile-ast@0.0.18: - version "0.0.18" - resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.0.18.tgz#94a0ba84eb9b3e9fb7bd6beae0ea7eb6dcbca75a" - integrity sha512-SEp95qCox1KAzf8BBtjHoBDD0a7/eNlZJ6fgDf9RxqeSEDwLuEN9YjdZ/tRlkrYLxXR4i+kqZzS4eDRSqs8VKQ== +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: - vscode-languageserver-types "^3.5.0" + path-type "^4.0.0" + +docker-modem@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.3.tgz#15432225f63db02eb5de4bb9a621b7293e5f264d" + integrity sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^0.8.7" + +dockerfile-ast@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.2.0.tgz#13cc4a6fe3aea30a4104622b30f49a0fe3a5c038" + integrity sha512-iQyp12k1A4tF3sEfLAq2wfFPKdpoiGTJeuiu2Y1bdEqIZu0DfSSL2zm0fk7a/UHeQkngnYaRRGuON+C+2LO1Fw== + dependencies: + vscode-languageserver-types "^3.16.0" dot-prop@^5.2.0: version "5.2.0" @@ -543,22 +1102,40 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotnet-deps-parser@4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-4.9.0.tgz#d14f9f92ae9a64062cd215c8863d1e77e80236f0" - integrity sha512-V0O+7pI7Ei+iL5Kgy6nYq1UTwzrpqci5K/zf8cXyP5RWBSQBUl/JOE9I67zLUkKiwOdfPhbMQgcRj/yGA+NL1A== +dotnet-deps-parser@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-5.0.0.tgz#5115c442cbefea59e4fb9f9ed8fa4863a0f3186d" + integrity sha512-1l9K4UnQQHSfKgeHeLrxnB53AidCZqPyf9dkRL4/fZl8//NPiiDD43zHtgylw8DHlO7gvM8+O5a0UPHesNYZKw== dependencies: - "@types/xml2js" "0.4.3" - lodash "^4.17.11" + lodash.isempty "^4.4.0" + lodash.set "^4.3.2" + lodash.uniq "^4.5.0" source-map-support "^0.5.7" tslib "^1.10.0" - xml2js "0.4.19" + xml2js "0.4.23" duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= +duplexify@^3.5.0, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +elfy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/elfy/-/elfy-1.0.0.tgz#7a1c86af7d41e0a568cbb4a3fa5b685648d9efcd" + integrity sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ== + dependencies: + endian-reader "^0.3.0" + email-validator@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" @@ -569,81 +1146,61 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= +end-of-stream@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" + integrity sha1-6TUyWLqpEIll78QcsO+K3i88+wc= dependencies: - es6-promise "^4.0.3" + once "~1.3.0" + +endian-reader@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0" + integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA= + +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.x.x: - version "1.13.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29" - integrity sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -estraverse@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -event-loop-spinner@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-1.1.0.tgz#96de9c70e6e2b0b3e257b0901e25e792e3c9c8d0" - integrity sha512-YVFs6dPpZIgH665kKckDktEVvSBccSYJmoZUfhNUdv5d3Xv+Q+SKF4Xis1jolq9aBzuW1ZZhQh/m/zU/TPdDhw== +event-loop-spinner@^2.0.0, event-loop-spinner@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-2.1.0.tgz#75f501d585105c6d57f174073b39af1b6b3a1567" + integrity sha512-RJ10wL8/F9AlfBgRCvYctJIXSb9XkVmSCK3GGUvPD3dJrvTjDeDT0tmhcbEC6I2NEjNM9xD38HQJ4F/f/gb4VQ== dependencies: - tslib "^1.10.0" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" + tslib "^2.1.0" execa@^1.0.0: version "1.0.0" @@ -658,11 +1215,6 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -672,81 +1224,97 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-glob@^3.1.1, fast-glob@^3.2.2: + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= +fastq@^1.6.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" + integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + dependencies: + reusify "^1.0.4" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" -file-uri-to-path@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +follow-redirects@^1.10.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe" + integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg== fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -ftp@~0.3.10: - version "0.3.10" - resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" - integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== dependencies: - readable-stream "1.1.x" - xregexp "2.0.0" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" -get-uri@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.4.tgz#d4937ab819e218d4cb5ae18e4f5962bef169cc6a" - integrity sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: - data-uri-to-buffer "1" - debug "2" - extend "~3.0.2" - file-uri-to-path "1" - ftp "~0.3.10" - readable-stream "2" + pump "^3.0.0" -git-up@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.1.tgz#cb2ef086653640e721d2042fe3104857d89007c0" - integrity sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw== +glob-parent@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: - is-ssh "^1.3.0" - parse-url "^5.0.0" + is-glob "^4.0.1" -git-url-parse@11.1.2: - version "11.1.2" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.1.2.tgz#aff1a897c36cc93699270587bea3dbcbbb95de67" - integrity sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ== - dependencies: - git-up "^4.0.0" - -glob@^7.1.3: +glob@^7.1.3, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -758,91 +1326,182 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= +global-agent@^2.1.12: + version "2.2.0" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc" + integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg== dependencies: - ini "^1.3.4" + boolean "^3.0.1" + core-js "^3.6.5" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" -got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= +global-dirs@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" + integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== dependencies: - create-error-class "^3.0.0" + ini "1.3.7" + +globalthis@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" + integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== + dependencies: + define-properties "^1.1.3" + +globby@^11.0.1: + version "11.0.3" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" + integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +got@11.4.0: + version "11.4.0" + resolved "https://registry.yarnpkg.com/got/-/got-11.4.0.tgz#1f0910310572af4efcc6890e1dacd7affb710b39" + integrity sha512-XysJZuZNVpaQ37Oo2LV90MIkPeYITehyy1A0QzO1JwOXm8EWuEf9eeGk2XuHePvLEGnm9AVOI37bHwD6KYyBtg== + dependencies: + "@sindresorhus/is" "^2.1.1" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.4.5" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +got@^11.7.0: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - -graceful-fs@^4.1.11: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" graceful-fs@^4.1.2: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== -graphlib@^2.1.1, graphlib@^2.1.5: - version "2.1.8" - resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" - integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +gunzip-maybe@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac" + integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw== dependencies: - lodash "^4.17.15" + browserify-zlib "^0.1.4" + is-deflate "^1.0.0" + is-gzip "^1.0.0" + peek-stream "^1.1.0" + pumpify "^1.3.3" + through2 "^2.0.3" has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -hosted-git-info@^2.7.1: - version "2.8.5" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" - integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -http-errors@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -http-proxy-agent@^2.1.0: +has-yarn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== -https-proxy-agent@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81" - integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: - agent-base "^4.3.0" - debug "^3.1.0" + function-bind "^1.1.1" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +hosted-git-info@^3.0.4, hosted-git-info@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== + dependencies: + lru-cache "^6.0.0" + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http2-wrapper@^1.0.0-beta.4.5, http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.1.4, ignore@^5.1.8: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -858,6 +1517,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -871,125 +1535,134 @@ inherits@2, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1: +inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.0, ini@^1.3.4, ini@~1.3.0: +ini@1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + +ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@^6.2.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" +is-callable@^1.1.5: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== -invert-kv@^1.0.0: +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-deflate@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14" + integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ= -ip@1.1.5, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-ci@^1.0.10: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" - integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== - dependencies: - ci-info "^1.5.0" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= -is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-npm@^1.0.0: +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-gzip@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= + resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" + integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM= + +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= - dependencies: - path-is-inside "^1.0.1" +is-path-inside@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= - -is-retry-allowed@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - -is-ssh@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3" - integrity sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg== - dependencies: - protocols "^1.1.0" - -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +is@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" + integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== isarray@~1.0.0: version "1.0.0" @@ -1001,6 +1674,14 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +js-yaml@^3.10.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -1009,37 +1690,72 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jszip@^3.1.5: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d" - integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-file-plus@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/json-file-plus/-/json-file-plus-3.3.1.tgz#f4363806b82819ff8803d83d539d6a9edd2a5258" + integrity sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA== + dependencies: + is "^3.2.1" + node.extend "^2.0.0" + object.assign "^4.1.0" + promiseback "^2.0.2" + safer-buffer "^2.0.2" + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jszip@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" + integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== dependencies: lie "~3.3.0" pako "~1.0.2" readable-stream "~2.3.6" set-immediate-shim "~1.0.1" -latest-version@^3.1.0: +jszip@^3.2.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9" + integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + +keyv@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== dependencies: - package-json "^4.0.0" + json-buffer "3.0.0" -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= +keyv@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== dependencies: - invert-kv "^1.0.0" + json-buffer "3.0.1" -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" + package-json "^6.3.0" lie@~3.3.0: version "3.3.0" @@ -1058,6 +1774,16 @@ lodash.assignin@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + +lodash.chunk@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" + integrity sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw= + lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" @@ -1068,32 +1794,235 @@ lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.constant@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash.constant/-/lodash.constant-3.0.0.tgz#bfe05cce7e515b3128925d6362138420bd624910" + integrity sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA= + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.endswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" + integrity sha1-/tWawXOO0+I27dcGTsRWRIs3vAk= + +lodash.filter@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= + +lodash.find@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" + integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= + +lodash.findindex@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" + integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY= + +lodash.findkey@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718" + integrity sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg= + +lodash.flatmap@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" + integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4= + lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.foreach@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= + +lodash.has@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= + +lodash.invert@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.invert/-/lodash.invert-4.3.0.tgz#8ffe20d4b616f56bea8f1aa0c6ebd80dcf742aee" + integrity sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= + +lodash.keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= + +lodash.last@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash.last/-/lodash.last-3.0.0.tgz#242f663112dd4c6e63728c60a3c909d1bdadbd4c" + integrity sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw= + +lodash.map@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.omit@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" + integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= + +lodash.orderby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" + integrity sha1-5pfwTOXXhSL1TZM4syuBozk+TrM= + +lodash.reduce@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= + lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.7.14: +lodash.size@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" + integrity sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.sum@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lodash.sum/-/lodash.sum-4.0.2.tgz#ad90e397965d803d4f1ff7aa5b2d0197f3b4637b" + integrity sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s= + +lodash.topairs@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" + integrity sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ= + +lodash.transform@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0" + integrity sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A= + +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash.upperfirst@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" + integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= + +lodash.values@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= + +lodash@^4.17.15: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -lowercase-keys@^1.0.0: +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== -lru-cache@^4.0.0, lru-cache@^4.0.1: +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^4.0.0: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -1108,22 +2037,67 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + macos-release@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: - pify "^3.0.0" + semver "^6.0.0" -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +micromatch@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== minimatch@^3.0.4: version "3.0.4" @@ -1132,59 +2106,79 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +minipass@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nconf@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.10.0.tgz#da1285ee95d0a922ca6cee75adcf861f48205ad2" - integrity sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q== - dependencies: - async "^1.4.0" - ini "^1.3.0" - secure-keys "^1.0.0" - yargs "^3.19.0" - -needle@^2.2.4, needle@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== +needle@2.6.0, needle@^2.3.3, needle@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" + integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" sax "^1.2.4" -netmask@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" - integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -normalize-url@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +node.extend@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" + integrity sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ== + dependencies: + has "^1.0.3" + is "^3.2.1" + +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== npm-run-path@^2.0.0: version "2.0.2" @@ -1193,15 +2187,25 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +object-hash@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" + integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== -object-hash@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" - integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -1210,43 +2214,46 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= +once@~1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" + integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= dependencies: - mimic-fn "^1.0.0" + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" onscan.js@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341" integrity sha512-9oGYy2gXYRjvXO9GYqqVca0VuCTAmWhbmX3egBSBP13rXiMNb+dKPJzKFEeECGqPBpf0m40Zoo+GUQ7eCackdw== -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== +open@^7.0.3: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: - is-wsl "^1.1.0" + is-docker "^2.0.0" + is-wsl "^2.1.1" -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== +ora@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" + integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" + bl "^4.0.3" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + log-symbols "^4.0.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" os-name@^3.0.0: version "3.1.0" @@ -1261,134 +2268,164 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-cancelable@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.0.tgz#4d51c3b91f483d02a0d300765321fca393d758dd" + integrity sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-map@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -pac-proxy-agent@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz#115b1e58f92576cac2eba718593ca7b0e37de2ad" - integrity sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: - agent-base "^4.2.0" - debug "^4.1.1" - get-uri "^2.0.0" - http-proxy-agent "^2.1.0" - https-proxy-agent "^3.0.0" - pac-resolver "^3.0.0" - raw-body "^2.2.0" - socks-proxy-agent "^4.0.1" + aggregate-error "^3.0.0" -pac-resolver@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" - integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA== - dependencies: - co "^4.6.0" - degenerator "^1.0.4" - ip "^1.1.5" - netmask "^1.0.6" - thunkify "^2.1.2" +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +pako@~0.2.0: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= pako@~1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -parse-path@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.1.tgz#0ec769704949778cb3b8eda5e994c32073a1adff" - integrity sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA== +parse-link-header@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7" + integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc= dependencies: - is-ssh "^1.3.0" - protocols "^1.4.0" - -parse-url@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f" - integrity sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg== - dependencies: - is-ssh "^1.3.0" - normalize-url "^3.3.0" - parse-path "^4.0.0" - protocols "^1.4.0" + xtend "~4.0.1" path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +peek-stream@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67" + integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA== + dependencies: + buffer-from "^1.0.0" + duplexify "^3.5.0" + through2 "^2.0.3" + +picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" + integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + +pretty-bytes@^5.1.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== -"promise@>=3.2 <8": +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-deferred@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/promise-deferred/-/promise-deferred-2.0.3.tgz#b99c9588820798501862a593d49cece51d06fd7f" + integrity sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ== + dependencies: + promise "^7.3.1" + +promise-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/promise-fs/-/promise-fs-2.1.1.tgz#0b725a592c165ff16157d1f13640ba390637e557" + integrity sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw== + dependencies: + "@octetstream/promisify" "2.0.2" + +promise-queue@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.5.tgz#2f6f5f7c0f6d08109e967659c79b88a9ed5e93b4" + integrity sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q= + +"promise@>=3.2 <8", promise@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" -protocols@^1.1.0, protocols@^1.4.0: - version "1.4.7" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.7.tgz#95f788a4f0e979b291ffefcf5636ad113d037d32" - integrity sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg== - -proxy-agent@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-3.1.1.tgz#7e04e06bf36afa624a1540be247b47c970bd3014" - integrity sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw== +promiseback@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/promiseback/-/promiseback-2.0.3.tgz#bd468d86930e8cd44bfc3292de9a6fbafb6378e6" + integrity sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w== dependencies: - agent-base "^4.2.0" - debug "4" - http-proxy-agent "^2.1.0" - https-proxy-agent "^3.0.0" - lru-cache "^5.1.1" - pac-proxy-agent "^3.0.1" - proxy-from-env "^1.0.0" - socks-proxy-agent "^4.0.1" + is-callable "^1.1.5" + promise-deferred "^2.0.3" proxy-from-env@^1.0.0: version "1.0.0" @@ -1400,6 +2437,14 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -1408,17 +2453,40 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -raw-body@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" - integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== dependencies: - bytes "3.1.0" - http-errors "1.7.3" - iconv-lite "0.4.24" - unpipe "1.0.0" + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" -rc@^1.0.1, rc@^1.1.6: +pupa@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -1428,17 +2496,7 @@ rc@^1.0.1, rc@^1.1.6: minimist "^1.2.0" strip-json-comments "~2.0.1" -readable-stream@1.1.x: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@2, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -1451,7 +2509,7 @@ readable-stream@2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.1, readable-stream@^3.1.1: +readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -1460,29 +2518,52 @@ readable-stream@^3.0.1, readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -registry-auth-token@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" - integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" + rc "^1.2.8" -registry-url@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== dependencies: - rc "^1.0.1" + rc "^1.2.8" -restore-cursor@^2.0.0: +resolve-alpn@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28" + integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA== + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +responselike@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== dependencies: - onetime "^2.0.0" + lowercase-keys "^2.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" signal-exit "^3.0.2" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -1490,21 +2571,45 @@ rimraf@^2.6.3: dependencies: glob "^7.1.3" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: - is-promise "^2.1.0" + glob "^7.1.3" -rxjs@^6.4.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== +roarr@^2.15.3: + version "2.15.4" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" + integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== + dependencies: + boolean "^3.0.1" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -1514,7 +2619,7 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -1524,43 +2629,52 @@ sax@>=0.6.0, sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -secure-keys@^1.0.0: +semver-compare@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca" - integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o= + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -semver-diff@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== dependencies: - semver "^5.0.3" - -semver@^5.0.3, semver@^5.1.0, semver@^5.5.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver "^6.3.0" semver@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^6.0.0, semver@^6.1.0, semver@^6.1.2: +semver@^5.5.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.0.0, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + set-immediate-shim@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -1568,167 +2682,244 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= -smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -snyk-config@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-2.2.3.tgz#8e09bb98602ad044954d30a9fc1695ab5b6042fa" - integrity sha512-9NjxHVMd1U1LFw66Lya4LXgrsFUiuRiL4opxfTFo0LmMNzUoU5Bk/p0zDdg3FE5Wg61r4fP2D8w+QTl6M8CGiw== - dependencies: - debug "^3.1.0" - lodash "^4.17.15" - nconf "^0.10.0" - -snyk-docker-plugin@1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-1.38.0.tgz#afe0ac316e461b200bcd0063295a3f8bd3655e93" - integrity sha512-43HbJj6QatuL2BNG+Uq2Taa73wdfSQSID8FJWW4q5/LYgd9D+RtdiE4lAMwxqYYbvThU9uuza4epuF/B1CAlYw== +snyk-config@4.0.0, snyk-config@^4.0.0-rc.2: + version "4.0.0" + resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0.tgz#21d459f19087991246cc07a7ffb4501dce6f4159" + integrity sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ== dependencies: + async "^3.2.0" debug "^4.1.1" - dockerfile-ast "0.0.18" - event-loop-spinner "^1.1.0" - semver "^6.1.0" - tar-stream "^2.1.0" - tslib "^1" + lodash.merge "^4.6.2" + minimist "^1.2.5" -snyk-go-parser@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.3.1.tgz#427387507578baf008a3e73828e0e53ed8c796f3" - integrity sha512-jrFRfIk6yGHFeipGD66WV9ei/A/w/lIiGqI80w1ndMbg6D6M5pVNbK7ngDTmo4GdHrZDYqx/VBGBsUm2bol3Rg== - dependencies: - toml "^3.0.0" - tslib "^1.9.3" - -snyk-go-plugin@1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.11.1.tgz#cd7c73c42bd3cf5faa2a90a54cd7c6db926fea5d" - integrity sha512-IsNi7TmpHoRHzONOWJTT8+VYozQJnaJpKgnYNQjzNm2JlV8bDGbdGQ1a8LcEoChxnJ8v8aMZy7GTiQyGGABtEQ== +snyk-cpp-plugin@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz#55891511a43a6448e5a7c836a94f66f70fa705eb" + integrity sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ== dependencies: + "@snyk/dep-graph" "^1.19.3" + chalk "^4.1.0" debug "^4.1.1" - graphlib "^2.1.1" - snyk-go-parser "1.3.1" - tmp "0.0.33" - tslib "^1.10.0" + hosted-git-info "^3.0.7" + tslib "^2.0.0" -snyk-gradle-plugin@3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.2.4.tgz#c1ff1dfbbe3c1a254d0da54a91c3f59c1b5582ca" - integrity sha512-XmS1gl7uZNHP9HP5RaPuRXW3VjkbdWe+EgSOlvmspztkubIOIainqc87k7rIJ6u3tLBhqsZK8b5ru0/E9Q69hQ== +snyk-docker-plugin@4.19.3: + version "4.19.3" + resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-4.19.3.tgz#14569f25c52a3fc71a20f80f5beac4ccdc326c11" + integrity sha512-5WkXyT7uY5NrTOvEqxeMqb6dDcskT3c/gbHUTOyPuvE6tMut+OOYK8RRXbwZFeLzpS8asq4e1R7U7syYG3VXwg== dependencies: - "@snyk/cli-interface" "2.3.0" - "@types/debug" "^4.1.4" + "@snyk/dep-graph" "^1.21.0" + "@snyk/rpm-parser" "^2.0.0" + "@snyk/snyk-docker-pull" "3.2.3" chalk "^2.4.2" debug "^4.1.1" - tmp "0.0.33" - tslib "^1.9.3" + docker-modem "2.1.3" + dockerfile-ast "0.2.0" + elfy "^1.0.0" + event-loop-spinner "^2.0.0" + gunzip-maybe "^1.4.2" + mkdirp "^1.0.4" + semver "^7.3.4" + snyk-nodejs-lockfile-parser "1.30.2" + tar-stream "^2.1.0" + tmp "^0.2.1" + tslib "^1" + uuid "^8.2.0" -snyk-module@1.9.1, snyk-module@^1.6.0, snyk-module@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-1.9.1.tgz#b2a78f736600b0ab680f1703466ed7309c980804" - integrity sha512-A+CCyBSa4IKok5uEhqT+hV/35RO6APFNLqk9DRRHg7xW2/j//nPX8wTSZUPF8QeRNEk/sX+6df7M1y6PBHGSHA== +snyk-go-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz#df16a5fbd7a517ee757268ef081abc33506c8857" + integrity sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w== dependencies: - debug "^3.1.0" - hosted-git-info "^2.7.1" + toml "^3.0.0" + tslib "^1.10.0" -snyk-mvn-plugin@2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.8.0.tgz#20c4201debd99928ade099fd426d13bd17b2cc85" - integrity sha512-Jt6lsVOFOYj7rp0H2IWz/BZS9xxaO0jEFTAoafLCocJIWWuGhPpVocCqmh/hrYAdKY9gS4gVOViMJ3EvcC1r1Q== - dependencies: - "@snyk/cli-interface" "2.3.1" - debug "^4.1.1" - lodash "^4.17.15" - needle "^2.4.0" - tmp "^0.1.0" - tslib "1.9.3" - -snyk-nodejs-lockfile-parser@1.17.0: +snyk-go-plugin@1.17.0: version "1.17.0" - resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.17.0.tgz#709e1d8c83faccae3bfdac5c10620dcedbf8c4ac" - integrity sha512-i4GAYFj9TJLOQ8F+FbIJuJWdGymi8w/XcrEX0FzXk7DpYUCY3mWibyKhw8RasfYBx5vLwUzEvRMaQuc2EwlyfA== + resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz#56d0c92d7def29ba4c3c2030c5830093e3b0dd26" + integrity sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q== dependencies: - "@yarnpkg/lockfile" "^1.0.2" - graphlib "^2.1.5" - lodash "^4.17.14" - p-map "2.1.0" - source-map-support "^0.5.7" - tslib "^1.9.3" - uuid "^3.3.2" + "@snyk/dep-graph" "^1.23.1" + "@snyk/graphlib" "2.1.9-patch.3" + debug "^4.1.1" + snyk-go-parser "1.4.1" + tmp "0.2.1" + tslib "^1.10.0" -snyk-nuget-plugin@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.16.0.tgz#241c6c8a417429c124c3ebf6db314a14eb8eed89" - integrity sha512-OEusK3JKKpR4Yto5KwuqjQGgb9wAhmDqBWSQomWdtKQVFrzn5B6BMzOFikUzmeMTnUGGON7gurQBLXeZZLhRqg== +snyk-gradle-plugin@3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.14.2.tgz#898b051f679e681b6d859f0ca84a500ac028af7d" + integrity sha512-l/nivKDZz7e2wymrwP6g2WQD8qgaYeE22SnbZrfIpwGolif81U28A9FsRedwkxKyB/shrM0vGEoD3c3zI8NLBw== dependencies: - debug "^3.1.0" - dotnet-deps-parser "4.9.0" - jszip "^3.1.5" - lodash "^4.17.14" - snyk-paket-parser "1.5.0" + "@snyk/cli-interface" "2.11.0" + "@snyk/dep-graph" "^1.28.0" + "@snyk/java-call-graph-builder" "1.20.0" + "@types/debug" "^4.1.4" + chalk "^3.0.0" + debug "^4.1.1" + tmp "0.2.1" + tslib "^2.0.0" + +snyk-module@3.1.0, snyk-module@^3.0.0, snyk-module@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-3.1.0.tgz#3e088ff473ddf0d4e253a46ea6749d76d8e6e7ba" + integrity sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw== + dependencies: + debug "^4.1.1" + hosted-git-info "^3.0.4" + +snyk-mvn-plugin@2.25.3: + version "2.25.3" + resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.25.3.tgz#fb7f6fa1d565b9f07c032e8b34e6308c310b2a27" + integrity sha512-JAxOThX51JDbgMMjp3gQDVi07G9VgTYSF06QC7f5LNA0zoXNr743e2rm78RGw5bqE3JRjZxEghiLHPPuvS5DDg== + dependencies: + "@snyk/cli-interface" "2.11.0" + "@snyk/dep-graph" "^1.23.1" + "@snyk/java-call-graph-builder" "1.19.1" + debug "^4.1.1" + glob "^7.1.6" + needle "^2.5.0" + tmp "^0.1.0" + tslib "1.11.1" + +snyk-nodejs-lockfile-parser@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.30.2.tgz#8dbb64c42382aeaf4488c36e48c1e48eb75a1584" + integrity sha512-wI3VXVYO/ok0uaQm5i+Koo4rKBNilYC/QRIQFlyGbZXf+WBdRcTBKVDfTy8uNfUhMRSGzd84lNclMnetU9Y+vw== + dependencies: + "@snyk/graphlib" "2.1.9-patch.3" + "@yarnpkg/lockfile" "^1.1.0" + event-loop-spinner "^2.0.0" + got "11.4.0" + lodash.clonedeep "^4.5.0" + lodash.flatmap "^4.5.0" + lodash.isempty "^4.4.0" + lodash.set "^4.3.2" + lodash.topairs "^4.3.0" + p-map "2.1.0" + snyk-config "^4.0.0-rc.2" tslib "^1.9.3" + uuid "^8.3.0" + yaml "^1.9.2" + +snyk-nodejs-lockfile-parser@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.32.0.tgz#2e25ea8622ef03ae7457a93ae70e156d6c46c2ef" + integrity sha512-FdYa/7NibnJPqBfobyw5jgI1/rd0LpMZf2W4WYYLRc2Hz7LZjKAByPjIX6qoA+lB9SC7yk5HYwWj2n4Fbg/DDw== + dependencies: + "@snyk/graphlib" "2.1.9-patch.3" + "@yarnpkg/core" "^2.4.0" + "@yarnpkg/lockfile" "^1.1.0" + event-loop-spinner "^2.0.0" + got "11.4.0" + lodash.clonedeep "^4.5.0" + lodash.flatmap "^4.5.0" + lodash.isempty "^4.4.0" + lodash.set "^4.3.2" + lodash.topairs "^4.3.0" + p-map "2.1.0" + snyk-config "^4.0.0-rc.2" + tslib "^1.9.3" + uuid "^8.3.0" + yaml "^1.9.2" + +snyk-nuget-plugin@1.21.1: + version "1.21.1" + resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.21.1.tgz#a79bbc65456823a1148119873226afb0e4907ec8" + integrity sha512-nRtedIvrow5ODqOKkQWolKrxn8ZoNL3iNJGuW0jNhwv+/9K0XE1UORM5F1ENAsd+nzCSO/kiYAXCc5CNK8HWEw== + dependencies: + debug "^4.1.1" + dotnet-deps-parser "5.0.0" + jszip "3.4.0" + snyk-paket-parser "1.6.0" + tslib "^1.11.2" xml2js "^0.4.17" -snyk-paket-parser@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.5.0.tgz#a0e96888d9d304b1ae6203a0369971575f099548" - integrity sha512-1CYMPChJ9D9LBy3NLqHyv8TY7pR/LMISSr08LhfFw/FpfRZ+gTH8W6bbxCmybAYrOFNCqZkRprqOYDqZQFHipA== +snyk-paket-parser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.6.0.tgz#f70c423b33d31484c8c4cae74bb7f5deb9bbc382" + integrity sha512-6htFynjBe/nakclEHUZ1A3j5Eu32/0pNve5Qm4MFn3YQmJgj7UcAO8hdyK3QfzEY29/kAv/rkJQg+SKshn+N9Q== dependencies: tslib "^1.9.3" -snyk-php-plugin@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.7.0.tgz#cf1906ed8a10db134c803be3d6e4be0cbdc5fe33" - integrity sha512-mDe90xkqSEVrpx1ZC7ItqCOc6fZCySbE+pHVI+dAPUmf1C1LSWZrZVmAVeo/Dw9sJzJfzmcdAFQl+jZP8/uV0A== +snyk-php-plugin@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz#282ef733060aab49da23e1fb2d2dd1af8f71f7cd" + integrity sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA== dependencies: - "@snyk/cli-interface" "2.2.0" - "@snyk/composer-lockfile-parser" "1.2.0" - tslib "1.9.3" + "@snyk/cli-interface" "^2.9.1" + "@snyk/composer-lockfile-parser" "^1.4.1" + tslib "1.11.1" -snyk-policy@1.13.5: - version "1.13.5" - resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.13.5.tgz#c5cf262f759879a65ab0810dd58d59c8ec7e9e47" - integrity sha512-KI6GHt+Oj4fYKiCp7duhseUj5YhyL/zJOrrJg0u6r59Ux9w8gmkUYT92FHW27ihwuT6IPzdGNEuy06Yv2C9WaQ== +snyk-poetry-lockfile-parser@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.6.tgz#bab5a279c103cbcca8eb86ab87717b115592881e" + integrity sha512-MoekbWOZPj9umfukjk2bd2o3eRj0OyO+58sxq9crMtHmTlze4h0/Uj4+fb0JFPBOtBO3c2zwbA+dvFQmpKoOTA== dependencies: - debug "^3.1.0" + "@snyk/cli-interface" "^2.9.2" + "@snyk/dep-graph" "^1.23.0" + debug "^4.2.0" + toml "^3.0.0" + tslib "^2.0.0" + +snyk-policy@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.19.0.tgz#0cbc442d9503970fb3afea938f57d57993a914ad" + integrity sha512-XYjhOTRPFA7NfDUsH6uH1fbML2OgSFsqdUPbud7x01urNP9CHXgUgAD4NhKMi3dVQK+7IdYadWt0wrFWw4y+qg== + dependencies: + debug "^4.1.1" email-validator "^2.0.4" js-yaml "^3.13.1" lodash.clonedeep "^4.5.0" + promise-fs "^2.1.1" semver "^6.0.0" - snyk-module "^1.9.1" - snyk-resolve "^1.0.1" - snyk-try-require "^1.3.1" - then-fs "^2.0.0" + snyk-module "^3.0.0" + snyk-resolve "^1.1.0" + snyk-try-require "^2.0.0" -snyk-python-plugin@1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.17.0.tgz#9bc38ba3c799c3cbef7676a1081f52608690d254" - integrity sha512-EKdVOUlvhiVpXA5TeW8vyxYVqbITAfT+2AbL2ZRiiUNLP5ae+WiNYaPy7aB5HAS9IKBKih+IH8Ag65Xu1IYSYA== +snyk-python-plugin@1.19.8: + version "1.19.8" + resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.19.8.tgz#9e4dfa8ed7e16ef2752f934b786d2e033de62ce0" + integrity sha512-LMKVnv0J4X/qHMoKB17hMND0abWtm9wdgI4xVzrOcf2Vtzs3J87trRhwLxQA2lMoBW3gcjtTeBUvNKaxikSVeQ== dependencies: "@snyk/cli-interface" "^2.0.3" + snyk-poetry-lockfile-parser "^1.1.6" tmp "0.0.33" -snyk-resolve-deps@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.4.0.tgz#ef20fb578a4c920cc262fb73dd292ff21215f52d" - integrity sha512-aFPtN8WLqIk4E1ulMyzvV5reY1Iksz+3oPnUVib1jKdyTHymmOIYF7z8QZ4UUr52UsgmrD9EA/dq7jpytwFoOQ== +snyk-resolve-deps@4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.7.2.tgz#11e7051110dadd8756819594bb30e6b88777a8b4" + integrity sha512-Bmtr7QdRL2b3Js+mPDmvXbkprOpzO8aUFXqR0nJKAOlUVQqZ84yiuT0n/mssEiJJ0vP+k0kZvTeiTwgio4KZRg== dependencies: - "@types/node" "^6.14.4" - "@types/semver" "^5.5.0" ansicolors "^0.3.2" - debug "^3.2.5" + debug "^4.1.1" lodash.assign "^4.2.0" lodash.assignin "^4.2.0" lodash.clone "^4.5.0" @@ -1737,13 +2928,21 @@ snyk-resolve-deps@4.4.0: lodash.set "^4.3.2" lru-cache "^4.0.0" semver "^5.5.1" - snyk-module "^1.6.0" + snyk-module "^3.1.0" snyk-resolve "^1.0.0" snyk-tree "^1.0.0" snyk-try-require "^1.1.1" then-fs "^2.0.0" -snyk-resolve@1.0.1, snyk-resolve@^1.0.0, snyk-resolve@^1.0.1: +snyk-resolve@1.1.0, snyk-resolve@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.1.0.tgz#52740cb01ba477851086855f9857b3a44296ee0e" + integrity sha512-OZMF8I8TOu0S58Z/OS9mr8jkEzGAPByCsAkrWlcmZgPaE0RsxVKVIFPhbMNy/JlYswgGDYYIEsNw+e0j1FnTrw== + dependencies: + debug "^4.1.1" + promise-fs "^2.1.1" + +snyk-resolve@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.0.1.tgz#eaa4a275cf7e2b579f18da5b188fe601b8eed9ab" integrity sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw== @@ -1769,7 +2968,7 @@ snyk-tree@^1.0.0: dependencies: archy "^1.0.0" -snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1: +snyk-try-require@1.3.1, snyk-try-require@^1.1.1: version "1.3.1" resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-1.3.1.tgz#6e026f92e64af7fcccea1ee53d524841e418a212" integrity sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI= @@ -1779,74 +2978,90 @@ snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1: lru-cache "^4.0.0" then-fs "^2.0.0" -snyk@^1.290.1: - version "1.290.2" - resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.290.2.tgz#a5e36e735a8083464263abdb266b6c9b3d46de7f" - integrity sha512-siieHkSY/b3Yw1Gf84L07j65m2Bht1PamAbX3cmZ1zAzsUxfXpqZq5W9PlAp5z1d0Tp1vxsQmXw6UGW0K1Tq1Q== +snyk-try-require@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-2.0.1.tgz#076ae9bc505d64d28389452ce19fcac28f26655a" + integrity sha512-VCOfFIvqLMXgCXEdooQgu3A40XYIFBnj0X8Y01RJ5iAbu08b4WKGN/uAKaRVF30dABS4EcjsalmCO+YlKUPEIA== dependencies: - "@snyk/cli-interface" "2.3.0" - "@snyk/configstore" "^3.2.0-rc1" - "@snyk/dep-graph" "1.13.1" + debug "^4.1.1" + lodash.clonedeep "^4.3.0" + lru-cache "^5.1.1" + +snyk@^1.518.0: + version "1.564.0" + resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.564.0.tgz#c8c511128351f8b8fe239b010b6799f40bb659c5" + integrity sha512-Fh4YusvJ9XdQfyz8JH9J8mBbipfgGLiF60MW9DYhQgP6h8z5uckAfd+S/uFMwPOVOIoe00fFo7aCLxFUuPcVJQ== + dependencies: + "@open-policy-agent/opa-wasm" "^1.2.0" + "@snyk/cli-interface" "2.11.0" + "@snyk/cloud-config-parser" "^1.9.2" + "@snyk/code-client" "3.4.1" + "@snyk/dep-graph" "^1.27.1" + "@snyk/fix" "1.554.0" "@snyk/gemfile" "1.2.0" - "@snyk/snyk-cocoapods-plugin" "2.0.1" - "@snyk/update-notifier" "^2.5.1-rc2" - "@types/agent-base" "^4.2.0" - "@types/restify" "^4.3.6" + "@snyk/graphlib" "^2.1.9-patch.3" + "@snyk/inquirer" "^7.3.3-patch" + "@snyk/snyk-cocoapods-plugin" "2.5.2" + "@snyk/snyk-hex-plugin" "1.1.4" abbrev "^1.1.1" ansi-escapes "3.2.0" chalk "^2.4.2" cli-spinner "0.2.10" - debug "^3.1.0" + configstore "^5.0.1" + debug "^4.1.1" diff "^4.0.1" - git-url-parse "11.1.2" - glob "^7.1.3" - inquirer "^6.2.2" - lodash "^4.17.14" - needle "^2.2.4" - opn "^5.5.0" + global-agent "^2.1.12" + lodash.assign "^4.2.0" + lodash.camelcase "^4.3.0" + lodash.clonedeep "^4.5.0" + lodash.endswith "^4.2.1" + lodash.flatten "^4.4.0" + lodash.flattendeep "^4.4.0" + lodash.get "^4.4.2" + lodash.groupby "^4.6.0" + lodash.isempty "^4.4.0" + lodash.isobject "^3.0.2" + lodash.map "^4.6.0" + lodash.omit "^4.5.0" + lodash.orderby "^4.6.0" + lodash.sortby "^4.7.0" + lodash.uniq "^4.5.0" + lodash.upperfirst "^4.3.1" + lodash.values "^4.3.0" + micromatch "4.0.2" + needle "2.6.0" + open "^7.0.3" + ora "5.3.0" os-name "^3.0.0" - proxy-agent "^3.1.1" + promise-queue "^2.2.5" proxy-from-env "^1.0.0" + rimraf "^2.6.3" semver "^6.0.0" - snyk-config "^2.2.1" - snyk-docker-plugin "1.38.0" - snyk-go-plugin "1.11.1" - snyk-gradle-plugin "3.2.4" - snyk-module "1.9.1" - snyk-mvn-plugin "2.8.0" - snyk-nodejs-lockfile-parser "1.17.0" - snyk-nuget-plugin "1.16.0" - snyk-php-plugin "1.7.0" - snyk-policy "1.13.5" - snyk-python-plugin "1.17.0" - snyk-resolve "1.0.1" - snyk-resolve-deps "4.4.0" + snyk-config "4.0.0" + snyk-cpp-plugin "2.2.1" + snyk-docker-plugin "4.19.3" + snyk-go-plugin "1.17.0" + snyk-gradle-plugin "3.14.2" + snyk-module "3.1.0" + snyk-mvn-plugin "2.25.3" + snyk-nodejs-lockfile-parser "1.32.0" + snyk-nuget-plugin "1.21.1" + snyk-php-plugin "1.9.2" + snyk-policy "1.19.0" + snyk-python-plugin "1.19.8" + snyk-resolve "1.1.0" + snyk-resolve-deps "4.7.2" snyk-sbt-plugin "2.11.0" snyk-tree "^1.0.0" snyk-try-require "1.3.1" source-map-support "^0.5.11" strip-ansi "^5.2.0" + tar "^6.1.0" tempfile "^2.0.0" - then-fs "^2.0.0" + update-notifier "^4.1.0" uuid "^3.3.2" wrap-ansi "^5.1.0" -socks-proxy-agent@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" - integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== - dependencies: - agent-base "~4.2.1" - socks "~2.3.2" - -socks@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3" - integrity sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA== - dependencies: - ip "1.1.5" - smart-buffer "^4.1.0" - source-map-support@^0.5.11, source-map-support@^0.5.7: version "0.5.16" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" @@ -1855,37 +3070,72 @@ source-map-support@^0.5.11, source-map-support@^0.5.7: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@~0.6.1: +source-map@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= + +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -"statuses@>= 1.5.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= +ssh2-streams@~0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" + integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ== dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" + asn1 "~0.2.0" + bcrypt-pbkdf "^1.0.2" + streamsearch "~0.1.2" -string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== +ssh2@^0.8.7: + version "0.8.9" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3" + integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw== dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" + ssh2-streams "~0.4.10" + +stream-buffers@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" + integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +stream-to-array@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" + integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M= + dependencies: + any-promise "^1.1.0" + +stream-to-promise@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f" + integrity sha1-se2y4cjLESidG1A8CNPyrvUeZQ8= + dependencies: + any-promise "~1.3.0" + end-of-stream "~1.1.0" + stream-to-array "~2.3.0" + +streamsearch@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= string-width@^3.0.0: version "3.1.0" @@ -1896,6 +3146,15 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.0.0, string-width@^4.1.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1903,11 +3162,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -1915,19 +3169,12 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= +strip-ansi@6.0.0, strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" + ansi-regex "^5.0.0" strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" @@ -1953,6 +3200,24 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tar-stream@^2.0.1, tar-stream@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar-stream@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" @@ -1964,11 +3229,28 @@ tar-stream@^2.1.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + tempfile@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" @@ -1977,12 +3259,10 @@ tempfile@^2.0.0: temp-dir "^1.0.0" uuid "^3.0.1" -term-size@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" - integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= - dependencies: - execa "^0.7.0" +term-size@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== then-fs@^2.0.0: version "2.0.0" @@ -1991,21 +3271,19 @@ then-fs@^2.0.0: dependencies: promise ">=3.2 <8" +through2@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunkify@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" - integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= - -timed-out@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -2013,6 +3291,13 @@ tmp@0.0.33, tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@0.2.1, tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmp@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" @@ -2020,10 +3305,17 @@ tmp@^0.1.0: dependencies: rimraf "^2.6.3" -toidentifier@1.0.0: +to-readable-stream@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" toml@^3.0.0: version "3.0.0" @@ -2035,46 +3327,105 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -tslib@1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + +tslib@1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" + integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== tslib@^1, tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= +tslib@^1.11.2, tslib@^1.13.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + +tweetnacl@^0.14.3: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== dependencies: - prelude-ls "~1.1.2" + is-typedarray "^1.0.0" -unique-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" - integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== dependencies: - crypto-random-string "^1.0.0" + crypto-random-string "^2.0.0" -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -unzip-response@^2.0.1: +upath@2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= + resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= +update-notifier@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" + integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== dependencies: - prepend-http "^1.0.1" + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" @@ -2086,10 +3437,22 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -vscode-languageserver-types@^3.5.0: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" - integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== +uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vscode-languageserver-types@^3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" + integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" which@^1.2.9: version "1.3.1" @@ -2098,17 +3461,19 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -widest-line@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" - integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: - string-width "^2.1.1" + isexe "^2.0.0" -window-size@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" windows-release@^3.1.0: version "3.2.0" @@ -2117,19 +3482,6 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" -word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -2144,29 +3496,29 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^2.0.0: - version "2.4.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" - integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== dependencies: - graceful-fs "^4.1.11" imurmurhash "^0.1.4" + is-typedarray "^1.0.0" signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== -xml2js@0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" - integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== dependencies: - sax ">=0.6.0" - xmlbuilder "~9.0.1" + sax "^1.2.4" -xml2js@^0.4.17: +xml2js@0.4.23, xml2js@^0.4.17: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== @@ -2179,20 +3531,10 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xmlbuilder@~9.0.1: - version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= - -xregexp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" - integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= - -y18n@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== yallist@^2.1.2: version "2.1.2" @@ -2204,15 +3546,17 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs@^3.19.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" - integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= - dependencies: - camelcase "^2.0.1" - cliui "^3.0.3" - decamelize "^1.1.1" - os-locale "^1.4.0" - string-width "^1.0.1" - window-size "^0.1.4" - y18n "^3.2.0" +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml-js@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/yaml-js/-/yaml-js-0.3.0.tgz#ad0893d9de881a93fd6bf936e8d89cdde309e848" + integrity sha512-JbTUdsPiCkOyz+JOSqAVc19omTnUBnBQglhuclYov5HpWbEOz8y+ftqWjiMa9Pe/eF/dmCUeNgVs/VWg53GlgQ== + +yaml@^1.9.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== From 71342320ce33fb09fa7f0f26f8f1ce070c2bb7c3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 26 Apr 2021 15:32:28 +0530 Subject: [PATCH 0605/1264] fix(Italy): multiple fixes in import supplier invoice (#25466) * fix(Italy): remove incorrect method, use defined variable * fix: show attach section after save; use db_set --- .../import_supplier_invoice.json | 5 ++- .../import_supplier_invoice.py | 33 +++++-------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json index c1680c4b49..afdd54b418 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-10-15 12:33:21.845329", "doctype": "DocType", "editable_grid": 1, @@ -86,12 +87,14 @@ "reqd": 1 }, { + "depends_on": "eval:!doc.__islocal", "fieldname": "upload_xml_invoices_section", "fieldtype": "Section Break", "label": "Upload XML Invoices" } ], - "modified": "2020-05-25 21:32:49.064579", + "links": [], + "modified": "2021-04-24 10:33:12.250687", "modified_by": "Administrator", "module": "Regional", "name": "Import Supplier Invoice", diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index cc6b907bc1..00300539e9 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -28,14 +28,19 @@ class ImportSupplierInvoice(Document): self.name = "Import Invoice on " + format_datetime(self.creation) def import_xml_data(self): - import_file = frappe.get_doc("File", {"file_url": self.zip_file}) + zip_file = frappe.get_doc("File", { + "file_url": self.zip_file, + "attached_to_doctype": self.doctype, + "attached_to_name": self.name + }) + self.publish("File Import", _("Processing XML Files"), 1, 3) self.file_count = 0 self.purchase_invoices_count = 0 self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") - with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: + with zipfile.ZipFile(zip_file.get_full_path()) as zf: for file_name in zf.namelist(): content = get_file_content(file_name, zf) file_content = bs(content, "xml") @@ -126,8 +131,7 @@ class ImportSupplierInvoice(Document): @frappe.whitelist() def process_file_data(self): - self.status = "Processing File Data" - self.save() + self.db_set("status", "Processing File Data", notify=True, commit=True) frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600) def publish(self, title, message, count, total): @@ -381,24 +385,3 @@ def create_uom(uom): new_uom.uom_name = uom new_uom.save() return new_uom.uom_name - -def get_full_path(file_name): - """Returns file path from given file name""" - file_path = file_name - - if "/" not in file_path: - file_path = "/files/" + file_path - - if file_path.startswith("/private/files/"): - file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) - - elif file_path.startswith("/files/"): - file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) - - elif file_path.startswith("http"): - pass - - elif not self.file_url: - frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) - - return file_path From 616f1d36242e15fab5429dc57592a7c801e6ba09 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 26 Apr 2021 15:35:49 +0530 Subject: [PATCH 0606/1264] fix: issue in project custom status (#25452) --- erpnext/projects/doctype/project/project.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index f9e1359b45..55c5149a9c 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -179,9 +179,6 @@ class Project(Document): if self.percent_complete == 100: self.status = "Completed" - else: - self.status = "Open" - def update_costing(self): from_time_sheet = frappe.db.sql("""select sum(costing_amount) as costing_amount, From 9178d95cca43c7b8124190572e7ae0131d9d7d45 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Apr 2021 15:37:17 +0530 Subject: [PATCH 0607/1264] fix: can not create item variants (#25433) * fix: item_code assigned to function returning None * fix: ignore has_variants and attributes in variant * fix: patch to remove has_variants and attributes * chore: fix whitespace in translation --- erpnext/controllers/item_variant.py | 3 ++- erpnext/patches.txt | 1 + .../remove_attribute_field_from_item_variant_setting.py | 8 ++++++++ erpnext/stock/doctype/item/item.py | 6 +++--- .../item_variant_settings/item_variant_settings.py | 5 +++-- 5 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 1f95e00424..051481ff60 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -262,7 +262,8 @@ def copy_attributes_to_variant(item, variant): # copy non no-copy fields exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", - "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"] + "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate", + "has_variants", "attributes"] if item.variant_based_on=='Manufacturer': # don't copy manufacturer values if based on part no diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 620cc5be62..de9f6e31f8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -773,3 +773,4 @@ erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 erpnext.patches.v13_0.update_shipment_status +erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting diff --git a/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py new file mode 100644 index 0000000000..53da7006b9 --- /dev/null +++ b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + """Remove has_variants and attribute fields from item variant settings.""" + frappe.reload_doc("stock", "doctype", "Item Variant Settings") + + frappe.db.sql("""delete from `tabVariant Field` + where field_name in ('attributes', 'has_variants')""") diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 7cb84a69f0..dbac79465e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -63,7 +63,7 @@ class Item(WebsiteGenerator): if self.variant_of: if not self.item_code: template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name") - self.item_code = make_variant_item_code(self.variant_of, template_item_name, self) + make_variant_item_code(self.variant_of, template_item_name, self) else: from frappe.model.naming import set_name_by_naming_series set_name_by_naming_series(self) @@ -674,10 +674,10 @@ class Item(WebsiteGenerator): 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( + msg = _("The items {0} and {1} are present in the following {2} :").format( frappe.bold(old_name), frappe.bold(new_name), document) - msg += '
    ' + msg += '
    ' 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( diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py index 04224424a5..78f1131b76 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py @@ -13,10 +13,11 @@ class ItemVariantSettings(Document): def set_default_fields(self): self.fields = [] fields = frappe.get_meta('Item').fields - exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", + exclude_fields = {"naming_series", "item_code", "item_name", "show_in_website", "show_variant_in_website", "standard_rate", "opening_stock", "image", "description", "variant_of", "valuation_rate", "description", "barcodes", - "website_image", "thumbnail", "website_specifiations", "web_long_description"] + "website_image", "thumbnail", "website_specifiations", "web_long_description", + "has_variants", "attributes"} for d in fields: if not d.no_copy and d.fieldname not in exclude_fields and \ From 1e912db3bb43afcff98947e46149c1c848287c0e Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 26 Apr 2021 15:46:18 +0530 Subject: [PATCH 0608/1264] feat: Added check box to combine items --- .../production_plan/production_plan.js | 18 +- .../production_plan/production_plan.json | 22 +- .../production_plan/production_plan.py | 43 +- .../production_plan_item.json | 950 ++++-------------- .../__init__.py | 0 .../production_plan_item_reference.json | 52 + .../production_plan_item_reference.py | 10 + .../doctype/work_order/work_order.py | 27 +- 8 files changed, 355 insertions(+), 767 deletions(-) create mode 100644 erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py create mode 100644 erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json create mode 100644 erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 288c1d0cd6..39b8c94bc1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -211,16 +211,30 @@ frappe.ui.form.on('Production Plan', { }); }, - get_items: function(frm) { + get_items: function (frm) { + frm.clear_table('prod_plan_ref'); + frappe.call({ method: "get_items", freeze: true, doc: frm.doc, - callback: function() { + callback: function () { refresh_field('po_items'); } }); }, + combine_items: function (frm) { + frm.clear_table('po_items'); + frm.clear_table('prod_plan_ref'); + + frappe.call({ + method: "get_items", + freeze: true, + doc: frm.doc, + }); + + + }, get_items_for_mr: function(frm) { if (!frm.doc.for_warehouse) { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index f11470086a..5c73992d1b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -28,7 +28,10 @@ "material_requests", "select_items_to_manufacture_section", "get_items", + "combine_items", "po_items", + "section_break_25", + "prod_plan_ref", "material_request_planning", "include_non_stock_items", "include_subcontracted_items", @@ -316,13 +319,30 @@ "fieldname": "include_safety_stock", "fieldtype": "Check", "label": "Include Safety Stock in Required Qty Calculation" + }, + { + "fieldname": "prod_plan_ref", + "fieldtype": "Table", + "hidden": 1, + "label": "Production Plan Item Reference", + "options": "Production Plan Item Reference" + }, + { + "default": "0", + "fieldname": "combine_items", + "fieldtype": "Check", + "label": "Consolidate Items" + }, + { + "fieldname": "section_break_25", + "fieldtype": "Section Break" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-03-08 11:17:25.470147", + "modified": "2021-04-26 14:11:43.564957", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a3e23a6897..b2bc21fb6d 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -96,8 +96,10 @@ class ProductionPlan(Document): @frappe.whitelist() def get_items(self): + self.set('po_items', []) if self.get_items_from == "Sales Order": - self.get_so_items() + self.get_so_items() + elif self.get_items_from == "Material Request": self.get_mr_items() @@ -165,10 +167,24 @@ class ProductionPlan(Document): self.calculate_total_planned_qty() def add_items(self, items): - self.set('po_items', []) + refs = {} for data in items: item_details = get_item_details(data.item_code) + if self.combine_items: + if item_details.bom_no in refs.keys(): + refs[item_details.bom_no]['qty'] = refs[item_details.bom_no]['qty'] + data.pending_qty + refs[item_details.bom_no]['so'].append(data.parent) + refs[item_details.bom_no]['so_items'].append(data.name) + refs[item_details.bom_no]['planned_qty'].append(data.pending_qty) + continue + else: + refs[item_details.bom_no] = {'qty': data.pending_qty, 'ref': data.name} + refs[item_details.bom_no]['so'] = [data.parent] + refs[item_details.bom_no]['so_items'] = [data.name] + refs[item_details.bom_no]['planned_qty'] = [data.pending_qty] + pi = self.append('po_items', { + 'name': data.name, 'include_exploded_items': 1, 'warehouse': data.warehouse, 'item_code': data.item_code, @@ -185,11 +201,32 @@ class ProductionPlan(Document): pi.sales_order = data.parent pi.sales_order_item = data.name pi.description = data.description - + + elif self.get_items_from == "Material Request": pi.material_request = data.parent pi.material_request_item = data.name pi.description = data.description + + if refs: + for d in self.po_items: + d.planned_qty = refs[d.bom_no]['qty'] + d.pending_qty = refs[d.bom_no]['qty'] + d.sales_order = '' + self.add_pp_ref(refs) + + def add_pp_ref(self, refs): + for r in refs: + idx = 0 + for so in refs[r]['so']: + self.append('prod_plan_ref', { + 'item_ref': refs[r]['ref'], + 'sales_order': so, + 'sales_order_item':refs[r]['so_items'][idx], + 'qty':refs[r]['planned_qty'][idx] + }) + idx+=1 + def calculate_total_planned_qty(self): self.total_planned_qty = 0 diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index d0dce53437..9ff1717e70 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -1,792 +1,222 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-02-22 01:27:49", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:27:49", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "include_exploded_items", + "item_code", + "bom_no", + "planned_qty", + "column_break_6", + "make_work_order_for_sub_assembly_items", + "warehouse", + "planned_start_date", + "section_break_9", + "pending_qty", + "ordered_qty", + "produced_qty", + "column_break_17", + "description", + "stock_uom", + "reference_section", + "sales_order", + "sales_order_item", + "column_break_19", + "material_request", + "material_request_item", + "product_bundle_item" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fetch_if_empty": 0, - "fieldname": "include_exploded_items", - "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": "Include Exploded Items", - "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 - }, + "columns": 2, + "default": "0", + "fieldname": "include_exploded_items", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Include Exploded Items" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fetch_if_empty": 0, - "fieldname": "item_code", - "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": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "print_width": "150px", + "reqd": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fetch_if_empty": 0, - "fieldname": "bom_no", - "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": "BOM No", - "length": 0, - "no_copy": 0, - "oldfieldname": "bom_no", - "oldfieldtype": "Link", - "options": "BOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "columns": 2, + "fieldname": "bom_no", + "fieldtype": "Link", + "in_list_view": 1, + "label": "BOM No", + "oldfieldname": "bom_no", + "oldfieldtype": "Link", + "options": "BOM", + "print_width": "100px", + "reqd": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "planned_qty", - "fieldtype": "Float", - "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": "Planned Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "planned_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "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": "planned_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Planned Qty", + "oldfieldname": "planned_qty", + "oldfieldtype": "Currency", + "print_width": "100px", + "reqd": 1, "width": "100px" - }, + }, { - "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_6", - "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_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", - "fetch_if_empty": 0, - "fieldname": "make_work_order_for_sub_assembly_items", - "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": "Make Work Order for Sub Assembly Items", - "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", + "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", + "fieldname": "make_work_order_for_sub_assembly_items", + "fieldtype": "Check", + "label": "Make Work Order for Sub Assembly Items" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "warehouse", - "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": "For Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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 - }, + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "For Warehouse", + "options": "Warehouse" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fetch_if_empty": 0, - "fieldname": "planned_start_date", - "fieldtype": "Datetime", - "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": "Planned Start 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "planned_start_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Planned Start Date", + "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": "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, - "label": "Quantity and Description", - "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": "section_break_9", + "fieldtype": "Section Break", + "label": "Quantity and Description" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "pending_qty", - "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": "Pending Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "prevdoc_reqd_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "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": "pending_qty", + "fieldtype": "Float", + "label": "Pending Qty", + "oldfieldname": "prevdoc_reqd_qty", + "oldfieldtype": "Currency", + "print_width": "100px", + "read_only": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "ordered_qty", - "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": "Ordered Qty", - "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": "ordered_qty", + "fieldtype": "Float", + "label": "Ordered Qty", + "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, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "produced_qty", - "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": "Produced Qty", - "length": 0, - "no_copy": 1, - "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 - }, + "default": "0", + "fieldname": "produced_qty", + "fieldtype": "Float", + "label": "Produced Qty", + "no_copy": 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": "column_break_17", - "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_17", + "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": "description", - "fieldtype": "Text Editor", - "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": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "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": "description", + "fieldtype": "Text Editor", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "200px", + "read_only": 1, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_uom", - "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": "UOM", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "80px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "UOM", + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "print_width": "80px", + "read_only": 1, + "reqd": 1, "width": "80px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "reference_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": "Reference", - "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": "reference_section", + "fieldtype": "Section Break", + "label": "Reference" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sales_order", - "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": "Sales Order", - "length": 0, - "no_copy": 0, - "oldfieldname": "source_docname", - "oldfieldtype": "Data", - "options": "Sales Order", - "permlevel": 0, - "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": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "oldfieldname": "source_docname", + "oldfieldtype": "Data", + "options": "Sales Order", + "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": "sales_order_item", - "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": "Sales Order Item", - "length": 0, - "no_copy": 1, - "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 - }, + "fieldname": "sales_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Sales Order Item", + "no_copy": 1, + "print_hide": 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": "column_break_19", - "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_19", + "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": "material_request", - "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": "Material Request", - "length": 0, - "no_copy": 0, - "options": "Material Request", - "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": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "options": "Material Request", + "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": "material_request_item", - "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": "material_request_item", - "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": "material_request_item", + "fieldtype": "Data", + "hidden": 1, + "label": "material_request_item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "product_bundle_item", - "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": "Product Bundle Item", - "length": 0, - "no_copy": 1, - "options": "Item", - "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": "product_bundle_item", + "fieldtype": "Link", + "label": "Product Bundle Item", + "no_copy": 1, + "options": "Item", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 1, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-04-08 23:09:57.199423", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Production Plan Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-04-22 12:10:01.102440", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py b/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json new file mode 100644 index 0000000000..19e813c5b3 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "creation": "2021-04-22 10:32:58.896330", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_ref", + "sales_order", + "sales_order_item", + "qty" + ], + "fields": [ + { + "fieldname": "item_ref", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Reference" + }, + { + "fieldname": "sales_order", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Order Reference", + "options": "Sales Order" + }, + { + "fieldname": "sales_order_item", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Sales Order Item" + }, + { + "fieldname": "qty", + "fieldtype": "Data", + "in_list_view": 1, + "label": "qty" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-23 16:55:22.161418", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Item Reference", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py new file mode 100644 index 0000000000..51fbc3633b --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 ProductionPlanItemReference(Document): + pass diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 8507f5eb34..bd286507ec 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -241,7 +241,13 @@ class WorkOrder(Document): if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) - self.update_work_order_qty_in_so() + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_ref = prod_plan.prod_plan_ref + if pp_ref: + self.update_work_order_qty_in_combined_so() + else: + self.update_work_order_qty_in_so() + self.update_reserved_qty_for_production() self.update_completed_qty_in_material_request() self.update_planned_qty() @@ -357,6 +363,25 @@ class WorkOrder(Document): work_order_qty = qty[0][0] if qty and qty[0][0] else 0 frappe.db.set_value('Sales Order Item', self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + + def update_work_order_qty_in_combined_so(self): + total_bundle_qty = 1 + if self.product_bundle_item: + total_bundle_qty = frappe.db.sql(""" select sum(qty) from + `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0] + + if not total_bundle_qty: + # product bundle is 0 (product bundle allows 0 qty for items) + total_bundle_qty = 1 + + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_ref = prod_plan.prod_plan_ref + for p in pp_ref: + if p.item_ref == self.production_plan_item: + work_order_qty = int(p.qty) + frappe.db.set_value('Sales Order Item', + p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + def update_completed_qty_in_material_request(self): if self.material_request: From 7a84d76489c9b621c1ddf755112990088595707f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 16:03:10 +0530 Subject: [PATCH 0609/1264] fix: tests --- ...ee_hours_utilization_based_on_timesheet.py | 4 - .../test_employee_util.py | 78 ++++++++++--------- .../test_project_profitability.py | 6 +- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 842fd4d314..87bac02f9a 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -167,15 +167,11 @@ class EmployeeHoursReport: def set_employee_department_and_name(self): for emp in self.stats_by_employee: - emp_name = frappe.db.get_value( - 'Employee', emp, 'employee_name' - ) emp_dept = frappe.db.get_value( 'Employee', emp, 'department' ) self.stats_by_employee[emp]['department'] = emp_dept - self.stats_by_employee[emp]['employee_name'] = emp_name def calculate_utilizations(self): TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 977a10d1d9..6d955450ec 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -20,6 +20,8 @@ class TestEmployeeUtilization(unittest.TestCase): # Create test timesheets cls.create_test_timesheets() + frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 9) + @classmethod def create_test_timesheets(cls): timesheet1 = frappe.new_doc("Timesheet") @@ -51,7 +53,7 @@ class TestEmployeeUtilization(unittest.TestCase): }) timesheet2.save() - timesheet2.submit() + timesheet2.submit() @classmethod def tearDownClass(cls): @@ -59,8 +61,8 @@ class TestEmployeeUtilization(unittest.TestCase): frappe.db.sql(""" DELETE FROM `tabTimesheet Detail` WHERE parent IN ( - SELECT name - FROM `tabTimesheet` + SELECT name + FROM `tabTimesheet` WHERE company = '_Test Company' ) """) @@ -72,14 +74,14 @@ class TestEmployeeUtilization(unittest.TestCase): filters = { "company": "_Test Company", "from_date": "2021-04-01", - "to_date": "2021-04-03" + "to_date": "2021-04-03" } report = execute(filters) expected_data = self.get_expected_data_for_test_employees() self.assertEqual(report[1], expected_data) - + def test_utilization_report_for_single_employee(self): filters = { "company": "_Test Company", @@ -93,14 +95,14 @@ class TestEmployeeUtilization(unittest.TestCase): emp1_data = frappe.get_doc('Employee', self.test_emp1) expected_data = [ { - 'employee': self.test_emp1, - 'employee_name': emp1_data.employee_name, + 'employee': self.test_emp1, + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, 'department': emp1_data.department, - 'billed_hours': 5.0, - 'non_billed_hours': 0.0, - 'total_hours': 18.0, - 'untracked_hours': 13.0, - 'per_util': 27.78 + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78, + 'per_util_billed_only': 27.78 } ] @@ -115,18 +117,18 @@ class TestEmployeeUtilization(unittest.TestCase): } report = execute(filters) - + emp2_data = frappe.get_doc('Employee', self.test_emp2) expected_data = [ { 'employee': self.test_emp2, - 'employee_name': emp2_data.employee_name, + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, 'department': emp2_data.department, - 'billed_hours': 0.0, - 'non_billed_hours': 10.0, - 'total_hours': 18.0, - 'untracked_hours': 8.0, - 'per_util': 55.56 + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56, + 'per_util_billed_only': 0.0 } ] @@ -155,7 +157,7 @@ class TestEmployeeUtilization(unittest.TestCase): report = execute(filters) summary = report[4] - expected_summary_values = ['41.67%', 5.0, 10.0, 21.0] + expected_summary_values = ['41.67%', '13.89%', 5.0, 10.0] self.assertEqual(len(summary), 4) @@ -167,26 +169,26 @@ class TestEmployeeUtilization(unittest.TestCase): def get_expected_data_for_test_employees(self): emp1_data = frappe.get_doc('Employee', self.test_emp1) emp2_data = frappe.get_doc('Employee', self.test_emp2) - + return [ { - 'employee': self.test_emp2, - 'employee_name': emp2_data.employee_name, - 'department': emp2_data.department, - 'billed_hours': 0.0, - 'non_billed_hours': 10.0, - 'total_hours': 18.0, - 'untracked_hours': 8.0, - 'per_util': 55.56 - }, + 'employee': self.test_emp2, + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'department': emp2_data.department, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56, + 'per_util_billed_only': 0.0 + }, { - 'employee': self.test_emp1, - 'employee_name': emp1_data.employee_name, - 'department': emp1_data.department, - 'billed_hours': 5.0, - 'non_billed_hours': 0.0, - 'total_hours': 18.0, - 'untracked_hours': 13.0, - 'per_util': 27.78 + 'employee': self.test_emp1, + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'department': emp1_data.department, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78, + 'per_util_billed_only': 27.78 } ] \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 251b71da59..7fe28b1074 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -3,7 +3,7 @@ import unittest import frappe from frappe.utils import getdate, nowdate from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet +from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice from erpnext.projects.report.project_profitability.project_profitability import execute @@ -21,6 +21,8 @@ class TestProjectProfitability(unittest.TestCase): self.sales_invoice.due_date = nowdate() self.sales_invoice.submit() + frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 8) + def test_project_profitability(self): filters = { 'company': '_Test Company', @@ -43,7 +45,7 @@ class TestProjectProfitability(unittest.TestCase): standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") utilization = timesheet.total_billed_hours/(self.salary_slip.total_working_days * standard_working_hours) self.assertEqual(utilization, row.utilization) - + profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization self.assertEqual(profit, row.profit) From d5ca9474ce792f26e327745cd798fe7006e65262 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 16:10:06 +0530 Subject: [PATCH 0610/1264] fix: add employee name in report data --- .../employee_hours_utilization_based_on_timesheet.py | 4 ++++ .../test_employee_util.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 87bac02f9a..842fd4d314 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -167,11 +167,15 @@ class EmployeeHoursReport: def set_employee_department_and_name(self): for emp in self.stats_by_employee: + emp_name = frappe.db.get_value( + 'Employee', emp, 'employee_name' + ) emp_dept = frappe.db.get_value( 'Employee', emp, 'department' ) self.stats_by_employee[emp]['department'] = emp_dept + self.stats_by_employee[emp]['employee_name'] = emp_name def calculate_utilizations(self): TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 6d955450ec..fa8782733f 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -96,6 +96,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { 'employee': self.test_emp1, + 'employee_name': 'test1@employeeutil.com', 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'department': emp1_data.department, @@ -122,6 +123,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { 'employee': self.test_emp2, + 'employee_name': 'test2@employeeutil.com', 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'department': emp2_data.department, @@ -173,6 +175,7 @@ class TestEmployeeUtilization(unittest.TestCase): return [ { 'employee': self.test_emp2, + 'employee_name': 'test2@employeeutil.com', 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'department': emp2_data.department, @@ -183,6 +186,7 @@ class TestEmployeeUtilization(unittest.TestCase): }, { 'employee': self.test_emp1, + 'employee_name': 'test1@employeeutil.com', 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'department': emp1_data.department, From 34ce4edf2953a781e3aacbf072d8016a796dfd2e Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 26 Apr 2021 16:38:57 +0530 Subject: [PATCH 0611/1264] fix: cannot scan spacebar character in pos (#25479) --- erpnext/selling/page/point_of_sale/pos_item_selector.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 9fb3943b53..709fe57747 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -168,6 +168,7 @@ erpnext.PointOfSale.ItemSelector = class { case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ * case iCode >= 186 && iCode <= 194: // (; = , - . / `) case iCode >= 219 && iCode <= 222: // ([ \ ] ') + case iCode == 32: // spacebar if (oEvent.key !== undefined && oEvent.key !== '') { return oEvent.key; } From b643776c53742b7c190dfa38512f192bb4151b1e Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 26 Apr 2021 17:12:20 +0530 Subject: [PATCH 0612/1264] feat(e-invoicing): auto calculate distance for e-way bill generations (#25480) --- erpnext/regional/india/e_invoice/einvoice.js | 18 ++++++++++-------- erpnext/regional/india/e_invoice/utils.py | 10 ++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 8d682beec3..23d4fe9030 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -115,17 +115,19 @@ erpnext.setup_einvoice_actions = (doctype) => { message += '

    '; message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); - frappe.msgprint({ + const dialog = frappe.msgprint({ title: __('Update E-Way Bill Cancelled Status?'), message: message, indicator: 'orange', - primary_action: function() { - frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', - args: { doctype, docname: name }, - freeze: true, - callback: () => frm.reload_doc() - }); + primary_action: { + action: function() { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { doctype, docname: name }, + freeze: true, + callback: () => frm.reload_doc() || dialog.hide() + }); + } }, primary_action_label: __('Yes') }); diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 1d3cb661dd..699441be7e 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -339,9 +339,7 @@ def get_eway_bill_details(invoice): if invoice.is_return: frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'), title=_('Invalid Fields')) - - if not invoice.distance: - frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field')) + mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } @@ -450,7 +448,7 @@ def make_einvoice(invoice): if invoice.is_return: prev_doc_details = get_return_doc_reference(invoice) - if invoice.transporter and flt(invoice.distance) and not invoice.is_return: + if invoice.transporter and not invoice.is_return: eway_bill_details = get_eway_bill_details(invoice) # not yet implemented @@ -1027,12 +1025,12 @@ def generate_eway_bill(doctype, docname, **kwargs): gsp_connector.generate_eway_bill(**kwargs) @frappe.whitelist() -def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): +def cancel_eway_bill(doctype, docname): # TODO: uncomment when eway_bill api from Adequare is enabled # gsp_connector = GSPConnector(doctype, docname) # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) - # update cancelled status only, to be able to cancel irn next + frappe.db.set_value(doctype, docname, 'ewaybill', '') frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) @frappe.whitelist() From 7f1b2de74d989cbaa49d05eb1e1548271bd24092 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 26 Apr 2021 22:23:48 +0530 Subject: [PATCH 0613/1264] feat: Employee Referral (#24997) * feat: Employee referal design and status sync * feat: create job Applicant and linked Document * feat: Added list view indicator * chore: formatted file * feat: Addedd Employee Referral form Dashboard * feat: pay compensation via additional salary * test: Employee Rreferral * Update erpnext/hr/doctype/employee_advance/employee_advance.js Co-authored-by: Rucha Mahabal * fix: changes requested * fix: changes requested * fix: sider * fix: translation * fix: test * feat: added to Dashboard * fix: changes * chore: clean-up Employee Referral form - fix labels - fix full name field - fix failing test - set title for Employee Referral form - set fields in List View and Standard Filter * feat: option to add a resume link - copy resume and resume link from Employee Referral to Job Applicant * fix: multiple fixes - confirm before rejecting employee referral - set primary actions for custom buttons - Validations for additional salary against employee referrals with status as accepted only - fix list view indicators - code formatting style * feat: Add field to track Referral Bonus Payment Status - fix visibility of additional salary button Co-authored-by: Rucha Mahabal --- .../employee_advance/employee_advance.js | 53 ++-- .../employee_advance_dashboard.py | 8 +- .../hr/doctype/employee_referral/__init__.py | 0 .../employee_referral/employee_referral.js | 68 ++++ .../employee_referral/employee_referral.json | 294 ++++++++++++++++++ .../employee_referral/employee_referral.py | 71 +++++ .../employee_referral_dashboard.py | 15 + .../employee_referral_list.js | 14 + .../test_employee_referral.py | 60 ++++ .../doctype/job_applicant/job_applicant.json | 10 +- .../hr/doctype/job_applicant/job_applicant.py | 11 + erpnext/hr/workspace/hr/hr.json | 11 +- .../additional_salary/additional_salary.py | 33 +- 13 files changed, 613 insertions(+), 35 deletions(-) create mode 100644 erpnext/hr/doctype/employee_referral/__init__.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.js create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.json create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral_list.js create mode 100644 erpnext/hr/doctype/employee_referral/test_employee_referral.py diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index 5037ceb489..fa4b06aad3 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', { }; }); - frm.set_query('salary_component', function(doc) { + frm.set_query('salary_component', function() { return { filters: { "type": "Deduction" @@ -44,48 +44,49 @@ frappe.ui.form.on('Employee Advance', { }, refresh: function(frm) { - if (frm.doc.docstatus===1 - && (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) - && frappe.model.can_create("Payment Entry")) { + if (frm.doc.docstatus === 1 && + (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) && + frappe.model.can_create("Payment Entry")) { frm.add_custom_button(__('Payment'), - function() { frm.events.make_payment_entry(frm); }, __('Create')); - } - else if ( - frm.doc.docstatus === 1 - && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) - && frappe.model.can_create("Expense Claim") + function () { + frm.events.make_payment_entry(frm); + }, __('Create')); + } else if ( + frm.doc.docstatus === 1 && + flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) && + frappe.model.can_create("Expense Claim") ) { frm.add_custom_button( __("Expense Claim"), - function() { + function () { frm.events.make_expense_claim(frm); }, __('Create') ); } - if (frm.doc.docstatus === 1 - && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) { + if (frm.doc.docstatus === 1 && + (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) { - if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){ - frm.add_custom_button(__("Return"), function() { + if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) { + frm.add_custom_button(__("Return"), function() { frm.trigger('make_return_entry'); }, __('Create')); - }else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){ - frm.add_custom_button(__("Deduction from salary"), function() { + } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) { + frm.add_custom_button(__("Deduction from salary"), function() { frm.events.make_deduction_via_additional_salary(frm); }, __('Create')); } } }, - make_deduction_via_additional_salary: function(frm){ + make_deduction_via_additional_salary: function(frm) { frappe.call({ method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary", args: { doc: frm.doc }, - callback: function (r){ + callback: function(r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); } @@ -94,7 +95,7 @@ frappe.ui.form.on('Employee Advance', { make_payment_entry: function(frm) { var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; - if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { + if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry"; } return frappe.call({ @@ -148,11 +149,11 @@ frappe.ui.form.on('Employee Advance', { }); }, - employee: function (frm) { + employee: function(frm) { if (frm.doc.employee) { frappe.run_serially([ - () => frm.trigger('get_employee_currency'), - () => frm.trigger('get_pending_amount') + () => frm.trigger('get_employee_currency'), + () => frm.trigger('get_pending_amount') ]); } }, @@ -199,7 +200,7 @@ frappe.ui.form.on('Employee Advance', { } else { frm.set_value("exchange_rate", 1.0); frm.set_df_property('exchange_rate', 'hidden', 1); - frm.set_df_property("exchange_rate", "description", "" ); + frm.set_df_property("exchange_rate", "description", ""); } frm.refresh_fields(); } @@ -215,8 +216,8 @@ frappe.ui.form.on('Employee Advance', { callback: function(r) { frm.set_value("exchange_rate", flt(r.message)); frm.set_df_property('exchange_rate', 'hidden', 0); - frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency - + " = [?] " + company_currency); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + + " = [?] " + company_currency); } }); } diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py index c3b4a3a889..2f493e2d4e 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py @@ -4,10 +4,10 @@ from frappe import _ def get_data(): return { 'fieldname': 'employee_advance', - 'non_standard_fieldnames': { - 'Payment Entry': 'reference_name', - 'Journal Entry': 'reference_name' - }, + 'non_standard_fieldnames': { + 'Payment Entry': 'reference_name', + 'Journal Entry': 'reference_name' + }, 'transactions': [ { 'items': ['Expense Claim'] diff --git a/erpnext/hr/doctype/employee_referral/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js new file mode 100644 index 0000000000..9c99bbbefa --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.js @@ -0,0 +1,68 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Employee Referral", { + refresh: function(frm) { + if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") { + frm.add_custom_button(__("Reject Employee Referral"), function() { + frappe.confirm( + __("Are you sure you want to reject the Employee Referral?"), + function() { + frm.doc.status = "Rejected"; + frm.dirty(); + frm.save_or_update(); + }, + function() { + window.close(); + } + ); + }); + + frm.add_custom_button(__("Create Job Applicant"), function() { + frm.events.create_job_applicant(frm); + }).addClass("btn-primary"); + } + + // To check whether Payment is done or not + if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") { + frappe.db.get_list("Additional Salary", { + filters: { + ref_docname: cur_frm.doc.name, + docstatus: 1 + }, + fields: ["count(name) as additional_salary_count"] + }).then((data) => { + + let additional_salary_count = data[0].additional_salary_count; + + if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) { + frm.add_custom_button(__("Create Additional Salary"), function() { + frm.events.create_additional_salary(frm); + }).addClass("btn-primary"); + } + }); + } + + + + }, + create_job_applicant: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant", + frm: frm + }); + }, + + create_additional_salary: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary", + args: { + doc: frm.doc + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }, +}); diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.json b/erpnext/hr/doctype/employee_referral/employee_referral.json new file mode 100644 index 0000000000..bfd404b435 --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.json @@ -0,0 +1,294 @@ +{ + "actions": [], + "autoname": "format:HR-REF-{####}", + "creation": "2021-03-23 14:54:45.047051", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "first_name", + "last_name", + "full_name", + "email", + "contact_no", + "resume", + "resume_link", + "column_break_6", + "date", + "status", + "for_designation", + "current_employer", + "current_job_title", + "referrer_details_section", + "referrer", + "referrer_name", + "column_break_14", + "is_applicable_for_referral_bonus", + "referral_payment_status", + "department", + "additional_information_section", + "qualification_reason", + "work_references", + "amended_from" + ], + "fields": [ + { + "fieldname": "first_name", + "fieldtype": "Data", + "label": "First Name ", + "reqd": 1 + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name", + "reqd": 1 + }, + { + "fieldname": "full_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Full Name", + "read_only": 1 + }, + { + "fieldname": "contact_no", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Contact No.", + "options": "Phone" + }, + { + "fieldname": "current_employer", + "fieldtype": "Data", + "label": "Current Employer " + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_standard_filter": 1, + "label": "Date", + "reqd": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Pending\nIn Process\nAccepted\nRejected", + "permlevel": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "current_job_title", + "fieldtype": "Data", + "label": "Current Job Title" + }, + { + "fieldname": "resume", + "fieldtype": "Attach", + "label": "Resume" + }, + { + "fieldname": "referrer_details_section", + "fieldtype": "Section Break", + "label": "Referrer Details" + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "additional_information_section", + "fieldtype": "Section Break", + "label": "Additional Information " + }, + { + "fieldname": "work_references", + "fieldtype": "Text Editor", + "label": "Work References" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Referral", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "for_designation", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "For Designation ", + "options": "Designation", + "reqd": 1 + }, + { + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Email", + "options": "Email", + "reqd": 1, + "unique": 1 + }, + { + "default": "1", + "fieldname": "is_applicable_for_referral_bonus", + "fieldtype": "Check", + "label": "Is Applicable for Referral Bonus" + }, + { + "fieldname": "qualification_reason", + "fieldtype": "Text Editor", + "label": "Why is this Candidate Qualified for this Position?" + }, + { + "fieldname": "referrer", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Referrer", + "options": "Employee", + "reqd": 1 + }, + { + "fetch_from": "referrer.employee_name", + "fieldname": "referrer_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Referrer Name", + "read_only": 1 + }, + { + "fieldname": "resume_link", + "fieldtype": "Data", + "label": "Resume Link" + }, + { + "fieldname": "referral_payment_status", + "fieldtype": "Select", + "label": "Referral Bonus Payment Status", + "options": "\nUnpaid\nPaid", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-04-26 21:21:38.094086", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Referral", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "full_name" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py new file mode 100644 index 0000000000..45d68729ce --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 get_link_to_form +from frappe.model.document import Document + +class EmployeeReferral(Document): + def validate(self): + self.set_full_name() + self.set_referral_bonus_payment_status() + + def set_full_name(self): + self.full_name = " ".join(filter(None, [self.first_name, self.last_name])) + + def set_referral_bonus_payment_status(self): + if not self.is_applicable_for_referral_bonus: + self.referral_payment_status = "" + else: + if not self.referral_payment_status: + self.referral_payment_status = "Unpaid" + + +@frappe.whitelist() +def create_job_applicant(source_name, target_doc=None): + emp_ref = frappe.get_doc("Employee Referral", source_name) + #just for Api call if some set status apart from default Status + status = emp_ref.status + if emp_ref.status in ["Pending", "In process"]: + status = "Open" + + job_applicant = frappe.new_doc("Job Applicant") + job_applicant.employee_referral = emp_ref.name + job_applicant.status = status + job_applicant.applicant_name = emp_ref.full_name + job_applicant.email_id = emp_ref.email + job_applicant.phone_number = emp_ref.contact_no + job_applicant.resume_attachment = emp_ref.resume + job_applicant.resume_link = emp_ref.resume_link + job_applicant.save() + + frappe.msgprint(_("Job Applicant {0} created successfully.").format( + get_link_to_form("Job Applicant", job_applicant.name)), + title=_("Success"), indicator="green") + + emp_ref.db_set("status", "In Process") + + return job_applicant + + +@frappe.whitelist() +def create_additional_salary(doc): + import json + from six import string_types + + if isinstance(doc, string_types): + doc = frappe._dict(json.loads(doc)) + + if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}): + additional_salary = frappe.new_doc("Additional Salary") + additional_salary.employee = doc.referrer + additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company") + additional_salary.overwrite_salary_structure_amount = 0 + additional_salary.ref_doctype = doc.doctype + additional_salary.ref_docname = doc.name + + return additional_salary + diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py new file mode 100644 index 0000000000..afa2a1ff1f --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +def get_data(): + return { + 'fieldname': 'employee_referral', + 'non_standard_fieldnames': { + 'Additional Salary': 'ref_docname' + }, + 'transactions': [ + { + 'items': ['Job Applicant', 'Additional Salary'] + }, + + ] + } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_list.js b/erpnext/hr/doctype/employee_referral/employee_referral_list.js new file mode 100644 index 0000000000..7533ab635f --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js @@ -0,0 +1,14 @@ +frappe.listview_settings['Employee Referral'] = { + add_fields: ["status"], + get_indicator: function (doc) { + if (doc.status == "Pending") { + return [__(doc.status), "grey", "status,=," + doc.status]; + } else if (doc.status == "In Process") { + return [__(doc.status), "orange", "status,=," + doc.status]; + } else if (doc.status == "Accepted") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (doc.status == "Rejected") { + return [__(doc.status), "red", "status,=," + doc.status]; + } + }, +}; \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py new file mode 100644 index 0000000000..a674f39026 --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +from frappe.utils import today +from erpnext.hr.doctype.designation.test_designation import create_designation +from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary +from erpnext.hr.doctype.employee.test_employee import make_employee +import unittest + +class TestEmployeeReferral(unittest.TestCase): + def test_workflow_and_status_sync(self): + emp_ref = create_employee_referral() + + #Check Initial status + self.assertTrue(emp_ref.status, "Pending") + + job_applicant = create_job_applicant(emp_ref.name) + + + #Check status sync + emp_ref.reload() + self.assertTrue(emp_ref.status, "In Process") + + job_applicant.reload() + job_applicant.status = "Rejected" + job_applicant.save() + + emp_ref.reload() + self.assertTrue(emp_ref.status, "Rejected") + + job_applicant.reload() + job_applicant.status = "Accepted" + job_applicant.save() + + emp_ref.reload() + self.assertTrue(emp_ref.status, "Accepted") + + + # Check for Referral reference in additional salary + + add_sal = create_additional_salary(emp_ref) + self.assertTrue(add_sal.ref_docname, emp_ref.name) + + +def create_employee_referral(): + emp_ref = frappe.new_doc("Employee Referral") + emp_ref.first_name = "Mahesh" + emp_ref.last_name = "Singh" + emp_ref.email = "a@b.c" + emp_ref.date = today() + emp_ref.for_designation = create_designation().name + emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company") + emp_ref.is_applicable_for_employee_referral_compensation = 1 + emp_ref.save() + emp_ref.submit() + + return emp_ref \ No newline at end of file diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json index 1360fd1890..bcea5f50d9 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.json +++ b/erpnext/hr/doctype/job_applicant/job_applicant.json @@ -18,6 +18,7 @@ "job_title", "source", "source_name", + "employee_referral", "applicant_rating", "section_break_6", "notes", @@ -152,13 +153,20 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "employee_referral", + "fieldtype": "Link", + "label": "Employee Referral", + "options": "Employee Referral", + "read_only": 1 } ], "icon": "fa fa-user", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-18 12:39:02.557563", + "modified": "2021-03-24 15:51:11.117517", "modified_by": "Administrator", "module": "HR", "name": "Job Applicant", diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index a6aef04919..0594ba395b 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -28,10 +28,21 @@ class JobApplicant(Document): if self.email_id: validate_email_address(self.email_id, True) + if self.employee_referral: + self.set_status_for_employee_referral() + if not self.applicant_name and self.email_id: guess = self.email_id.split('@')[0] self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')]) + def set_status_for_employee_referral(self): + emp_ref = frappe.get_doc("Employee Referral", self.employee_referral) + if self.status in ["Open", "Replied", "Hold"]: + emp_ref.db_set("status", "In Process") + elif self.status in ["Accepted", "Rejected"]: + emp_ref.db_set("status", self.status) + + def check_email_id_is_unique(self): if self.email_id: names = frappe.db.sql_list("""select name from `tabJob Applicant` diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index f4b56a0e17..c5201c22c9 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -520,6 +520,15 @@ "onboard": 1, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Employee Referral", + "link_to": "Employee Referral", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -814,7 +823,7 @@ "type": "Link" } ], - "modified": "2021-03-24 17:35:21.483297", + "modified": "2021-04-26 13:36:15.413819", "modified_by": "Administrator", "module": "HR", "name": "HR", diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 13b6c05e22..ebeddf97f9 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -13,12 +13,19 @@ class AdditionalSalary(Document): if self.ref_doctype == "Employee Advance" and self.ref_docname: frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount) + self.update_employee_referral() + + def on_cancel(self): + self.update_employee_referral(cancel=True) + def validate(self): self.validate_dates() self.validate_salary_structure() self.validate_recurring_additional_salary_overlap() + self.validate_employee_referral() + if self.amount < 0: - frappe.throw(_("Amount should not be less than zero.")) + frappe.throw(_("Amount should not be less than zero")) def validate_salary_structure(self): if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}): @@ -70,6 +77,27 @@ class AdditionalSalary(Document): 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 validate_employee_referral(self): + if self.ref_doctype == "Employee Referral": + referral_details = frappe.db.get_value("Employee Referral", self.ref_docname, + ["is_applicable_for_referral_bonus", "status"], as_dict=1) + + if not referral_details.is_applicable_for_referral_bonus: + frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format( + self.ref_docname)) + + if self.type == "Deduction": + frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus.")) + + if referral_details.status != "Accepted": + frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format( + frappe.bold("Accepted"))) + + def update_employee_referral(self, cancel=False): + if self.ref_doctype == "Employee Referral": + status = "Unpaid" if cancel else "Paid" + frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status) + def get_amount(self, sal_start_date, sal_end_date): start_date = getdate(sal_start_date) end_date = getdate(sal_end_date) @@ -110,8 +138,7 @@ def get_additional_salaries(employee, start_date, end_date, component_type): for d in additional_salary_list: if d.overwrite: if d.component in components_to_overwrite: - frappe.throw(_("Multiple Additional Salaries with overwrite " - "property exist for Salary Component {0} between {1} and {2}.").format( + frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format( frappe.bold(d.component), start_date, end_date), title=_("Error")) components_to_overwrite.append(d.component) From df06e49e0c821e2ea2989588cafbbeeb0a21ec83 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 26 Apr 2021 22:48:13 +0530 Subject: [PATCH 0614/1264] feat: enable custom field search on POS (#25421) * feat: multi lingual item search in POS * fix: limit the options for selection and broken down funtion for geting condition --- .../doctype/pos_search_fields/__init__.py | 0 .../pos_search_fields/pos_search_fields.json | 37 +++++++++++++++++ .../pos_search_fields/pos_search_fields.py | 10 +++++ .../doctype/pos_settings/pos_settings.js | 40 +++++++++++++++++++ .../doctype/pos_settings/pos_settings.json | 11 ++++- .../page/point_of_sale/point_of_sale.py | 26 +++++++++--- 6 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 erpnext/accounts/doctype/pos_search_fields/__init__.py create mode 100644 erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json create mode 100644 erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py diff --git a/erpnext/accounts/doctype/pos_search_fields/__init__.py b/erpnext/accounts/doctype/pos_search_fields/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json new file mode 100644 index 0000000000..a627f5b5be --- /dev/null +++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json @@ -0,0 +1,37 @@ +{ + "actions": [], + "creation": "2021-04-19 14:56:06.652327", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "field", + "fieldname" + ], + "fields": [ + { + "fieldname": "fieldname", + "fieldtype": "Data", + "hidden": 1, + "label": "Fieldname" + }, + { + "fieldname": "field", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Field" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-21 11:12:54.632093", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Search Fields", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py new file mode 100644 index 0000000000..720ea77745 --- /dev/null +++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 POSSearchFields(Document): + pass diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index 3625393a80..9003af56a5 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -1,9 +1,17 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt +let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor']; +let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series", + "default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series", + "serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account", + "deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail", + "web_long_description", "hub_sync_id"] + frappe.ui.form.on('POS Settings', { onload: function(frm) { frm.trigger("get_invoice_fields"); + frm.trigger("add_search_options"); }, get_invoice_fields: function(frm) { @@ -21,6 +29,38 @@ frappe.ui.form.on('POS Settings', { ); }); + }, + + add_search_options: function(frm) { + frappe.model.with_doctype("Item", () => { + var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) { + if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) { + return [d.label]; + } else { + return null; + } + }); + + fields.unshift(''); + frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields); + }); + + } +}); + +frappe.ui.form.on("POS Search Fields", { + field: function(frm, doctype, name) { + var doc = frappe.get_doc(doctype, name); + var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) { + if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) { + return d; + } else { + return null; + } + })[0]; + + doc.fieldname = df.fieldname; + frm.refresh_field("fields"); } }); diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json index 35395889a6..962eb94a29 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.json +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json @@ -5,7 +5,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "invoice_fields" + "invoice_fields", + "pos_search_fields" ], "fields": [ { @@ -13,11 +14,17 @@ "fieldtype": "Table", "label": "POS Field", "options": "POS Field" + }, + { + "fieldname": "pos_search_fields", + "fieldtype": "Table", + "label": "POS Search Fields", + "options": "POS Search Fields" } ], "issingle": 1, "links": [], - "modified": "2020-06-01 15:46:41.478928", + "modified": "2021-04-19 14:56:24.465218", "modified_by": "Administrator", "module": "Accounts", "name": "POS Settings", 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 062cba19e6..750a1a6071 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -23,7 +23,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va if search_value: data = search_serial_or_batch_or_barcode_number(search_value) - + item_code = data.get("item_code") if data.get("item_code") else search_value serial_no = data.get("serial_no") if data.get("serial_no") else "" batch_no = data.get("batch_no") if data.get("batch_no") else "" @@ -31,7 +31,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va if data: item_info = frappe.db.get_value( - "Item", data.get("item_code"), + "Item", data.get("item_code"), ["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"] , as_dict=1) item_info.setdefault('serial_no', serial_no) @@ -139,8 +139,24 @@ def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: return "item.name = {0}".format(frappe.db.escape(item_code)) - return """(item.name like {item_code} - or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%')) + return make_condition(item_code) + +def make_condition(item_code): + condition = "(" + condition += """item.name like {item_code} + or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%')) + condition += add_search_fields_condition(item_code) + condition += ")" + + return condition + +def add_search_fields_condition(item_code): + condition = '' + search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname']) + if search_fields: + for field in search_fields: + condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%')) + return condition def get_item_group_condition(pos_profile): cond = "and 1=1" @@ -257,4 +273,4 @@ def set_customer_info(fieldname, customer, 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 + contact_doc.save() From 5b9d3f15a243511a4fedcd12eb25fbd248399aac Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 27 Apr 2021 12:34:10 +0200 Subject: [PATCH 0615/1264] docs: replace whitespace indent in docstring with tabs --- erpnext/regional/report/datev/datev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 1a215031c1..a5ca7eee5d 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -3,9 +3,9 @@ Provide a report and downloadable CSV according to the German DATEV format. - Query report showing only the columns that contain data, formatted nicely for - dispay to the user. + dispay to the user. - CSV download functionality `download_datev_csv` that provides a CSV file with - all required columns. Used to import the data into the DATEV Software. + all required columns. Used to import the data into the DATEV Software. """ from __future__ import unicode_literals From 8a1e5e189ca0705ed85db27f9665ac34e066cea3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Apr 2021 16:57:34 +0530 Subject: [PATCH 0616/1264] fix: Ageing errors in PSOA --- .../process_statement_of_accounts.html | 12 ++++++------ .../process_statement_of_accounts.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index b623898829..f61aacbce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -60,8 +60,8 @@

    -{% if aging %} -

    {{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}

    +{% if ageing %} +

    {{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

    {{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
    @@ -78,10 +78,10 @@ - {{ frappe.utils.fmt_money(aging.range1, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range2, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range3, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range4, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index b6149e89f5..62090c0255 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -40,7 +40,7 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): statement_dict = {} - aging = '' + ageing = '' base_template_path = "frappe/www/printview.html" template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" @@ -56,8 +56,10 @@ def get_report_pdf(doc, consolidated=True): 'range4': 120, 'customer': entry.customer }) - col1, aging = get_ageing(ageing_filters) - aging[0]['ageing_based_on'] = doc.ageing_based_on + col1, ageing = get_ageing(ageing_filters) + + if ageing: + ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ @@ -87,11 +89,14 @@ def get_report_pdf(doc, consolidated=True): if len(res) == 3: continue + html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None}) + {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None}) + html = frappe.render_template(base_template_path, {"body": html, \ "css": get_print_style(), "title": "Statement For " + entry.customer}) statement_dict[entry.customer] = html + if not bool(statement_dict): return False elif consolidated: From ad4365eb0eb4583333243c5c7a18995e736ec0d0 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 27 Apr 2021 20:00:21 +0530 Subject: [PATCH 0617/1264] fix: unexpected keyword argument 'merge_logs' (#25489) * fix: unexpected keyword arguement 'merge_logs' * fix: reference error * fix: test --- .../pos_invoice_merge_log.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 index 6d2cffcf68..4d5472df4b 100644 --- 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 @@ -235,11 +235,11 @@ def get_invoice_customer_map(pos_invoices): return pos_invoice_customer_map -def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): - invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices() +def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): + invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() invoice_by_customer = get_invoice_customer_map(invoices) - if len(invoices) >= 5 and closing_entry: + if len(invoices) >= 1 and closing_entry: closing_entry.set_status(update=True, status='Queued') enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: @@ -252,18 +252,18 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 5: + if len(merge_logs) >= 1: closing_entry.set_status(update=True, status='Queued') enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) -def create_merge_logs(invoice_by_customer, closing_entry={}): +def create_merge_logs(invoice_by_customer, closing_entry=None): for customer, invoices in iteritems(invoice_by_customer): merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(closing_entry.get('posting_date')) + merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() merge_log.customer = customer - merge_log.pos_closing_entry = closing_entry.get('name', None) + merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None merge_log.set('pos_invoices', invoices) merge_log.save(ignore_permissions=True) @@ -273,7 +273,7 @@ def create_merge_logs(invoice_by_customer, closing_entry={}): closing_entry.set_status(update=True, status='Submitted') closing_entry.update_opening_entry() -def cancel_merge_logs(merge_logs, closing_entry={}): +def cancel_merge_logs(merge_logs, closing_entry=None): for log in merge_logs: merge_log = frappe.get_doc('POS Invoice Merge Log', log) merge_log.flags.ignore_permissions = True @@ -283,20 +283,20 @@ def cancel_merge_logs(merge_logs, closing_entry={}): closing_entry.set_status(update=True, status='Cancelled') closing_entry.update_opening_entry(for_cancel=True) -def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None): +def enqueue_job(job, **kwargs): check_scheduler_status() + closing_entry = kwargs.get('closing_entry') or {} + job_name = closing_entry.get("name") if not job_already_enqueued(job_name): enqueue( job, + **kwargs, queue="long", timeout=10000, event="processing_merge_logs", job_name=job_name, - closing_entry=closing_entry, - invoice_by_customer=invoice_by_customer, - merge_logs=merge_logs, now=frappe.conf.developer_mode or frappe.flags.in_test ) From 7b3dd3585b5f2371254a02fe2ae713a23bdc9871 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 27 Apr 2021 20:00:21 +0530 Subject: [PATCH 0618/1264] fix: unexpected keyword argument 'merge_logs' (#25489) * fix: unexpected keyword arguement 'merge_logs' * fix: reference error * fix: test --- .../pos_invoice_merge_log.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 index 6d2cffcf68..4d5472df4b 100644 --- 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 @@ -235,11 +235,11 @@ def get_invoice_customer_map(pos_invoices): return pos_invoice_customer_map -def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): - invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices() +def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): + invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() invoice_by_customer = get_invoice_customer_map(invoices) - if len(invoices) >= 5 and closing_entry: + if len(invoices) >= 1 and closing_entry: closing_entry.set_status(update=True, status='Queued') enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: @@ -252,18 +252,18 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 5: + if len(merge_logs) >= 1: closing_entry.set_status(update=True, status='Queued') enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) -def create_merge_logs(invoice_by_customer, closing_entry={}): +def create_merge_logs(invoice_by_customer, closing_entry=None): for customer, invoices in iteritems(invoice_by_customer): merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(closing_entry.get('posting_date')) + merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() merge_log.customer = customer - merge_log.pos_closing_entry = closing_entry.get('name', None) + merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None merge_log.set('pos_invoices', invoices) merge_log.save(ignore_permissions=True) @@ -273,7 +273,7 @@ def create_merge_logs(invoice_by_customer, closing_entry={}): closing_entry.set_status(update=True, status='Submitted') closing_entry.update_opening_entry() -def cancel_merge_logs(merge_logs, closing_entry={}): +def cancel_merge_logs(merge_logs, closing_entry=None): for log in merge_logs: merge_log = frappe.get_doc('POS Invoice Merge Log', log) merge_log.flags.ignore_permissions = True @@ -283,20 +283,20 @@ def cancel_merge_logs(merge_logs, closing_entry={}): closing_entry.set_status(update=True, status='Cancelled') closing_entry.update_opening_entry(for_cancel=True) -def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None): +def enqueue_job(job, **kwargs): check_scheduler_status() + closing_entry = kwargs.get('closing_entry') or {} + job_name = closing_entry.get("name") if not job_already_enqueued(job_name): enqueue( job, + **kwargs, queue="long", timeout=10000, event="processing_merge_logs", job_name=job_name, - closing_entry=closing_entry, - invoice_by_customer=invoice_by_customer, - merge_logs=merge_logs, now=frappe.conf.developer_mode or frappe.flags.in_test ) From a86a51f0207cdd9480d84cbebea3abd93a60f435 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Apr 2021 20:03:50 +0530 Subject: [PATCH 0619/1264] fix(HR): Permission error while adding weekly holidays (#25450) --- erpnext/hr/doctype/holiday_list/holiday_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 6df7bc88c0..67630a0abe 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -16,6 +16,7 @@ class HolidayList(Document): self.validate_days() self.total_holidays = len(self.holidays) + @frappe.whitelist() def get_weekly_off_dates(self): self.validate_values() date_list = self.get_weekly_off_date_list(self.from_date, self.to_date) From 3e5c6424ed4f15c16be0f909f0cf110879fa50d5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 27 Apr 2021 20:28:01 +0550 Subject: [PATCH 0620/1264] bumped to version 13.1.1 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4da0605370..ea83d08bf2 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.1.0' +__version__ = '13.1.1' def get_default_company(user=None): '''Get default company for user''' From 82905166d988f162b6fa84a49ee63a0704bda661 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 28 Apr 2021 10:12:17 +0530 Subject: [PATCH 0621/1264] fix: Fixed updating sales order work qty after cancelling work order --- .../doctype/production_plan/production_plan.js | 1 - .../manufacturing/doctype/work_order/work_order.py | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 39b8c94bc1..29c3d5b18e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -224,7 +224,6 @@ frappe.ui.form.on('Production Plan', { }); }, combine_items: function (frm) { - frm.clear_table('po_items'); frm.clear_table('prod_plan_ref'); frappe.call({ diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index bd286507ec..87d57ad42c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -258,7 +258,13 @@ class WorkOrder(Document): self.validate_cancel() frappe.db.set(self,'status', 'Cancelled') - self.update_work_order_qty_in_so() + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_ref = prod_plan.prod_plan_ref + if pp_ref: + self.update_work_order_qty_in_combined_so(cancel = True) + else: + self.update_work_order_qty_in_so() + self.delete_job_card() self.update_completed_qty_in_material_request() self.update_planned_qty() @@ -364,7 +370,7 @@ class WorkOrder(Document): frappe.db.set_value('Sales Order Item', self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) - def update_work_order_qty_in_combined_so(self): + def update_work_order_qty_in_combined_so(self, cancel = None): total_bundle_qty = 1 if self.product_bundle_item: total_bundle_qty = frappe.db.sql(""" select sum(qty) from @@ -378,7 +384,7 @@ class WorkOrder(Document): pp_ref = prod_plan.prod_plan_ref for p in pp_ref: if p.item_ref == self.production_plan_item: - work_order_qty = int(p.qty) + work_order_qty = int(p.qty) if not cancel else 0 frappe.db.set_value('Sales Order Item', p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) From c9187b00af2d13ec3f5c1a696e66c74346778fa0 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 28 Apr 2021 13:32:27 +0530 Subject: [PATCH 0622/1264] fix: change coveralls version (#25499) --- .github/workflows/ci-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 824b74e013..4103bc68a9 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -85,8 +85,8 @@ jobs: run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} - pip install coveralls==3.0.1 - pip install coverage==5.5 + pip install coveralls==2.2.0 + pip install coverage==4.5.4 coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From aa4f750d8c9eae6cdf3d4b853ed3de3e08a5f38e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:42:49 +0530 Subject: [PATCH 0623/1264] fix: test --- erpnext/accounts/general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4c285ff088..f1717c50d8 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -175,7 +175,7 @@ def round_off_debit_credit(gl_map): frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.") .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff)) - elif abs(debit_credit_diff) > (1.0 / (10**precision)): + elif abs(debit_credit_diff) >= (1.0 / (10**precision)): make_round_off_gle(gl_map, debit_credit_diff, precision) def make_round_off_gle(gl_map, debit_credit_diff, precision): From da5b55c4f5fa89770ace9a400f7523d0e83550d0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:55:33 +0530 Subject: [PATCH 0624/1264] fix: test --- erpnext/controllers/accounts_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d7f89b6270..a61292491c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -944,7 +944,7 @@ class AccountsController(TransactionBase): for item in data: self.append("payment_schedule", item) else: - data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total) + data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) else: for d in self.get("payment_schedule"): @@ -1007,7 +1007,8 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - + print(total, base_total) + print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) From 44b07e4ef590d5f8a41b14c75291b86e251faeec Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 15:11:57 +0530 Subject: [PATCH 0625/1264] refactor: remove extra fields --- .../payment_schedule/payment_schedule.json | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e9f8ba29dc..6ed7a3154e 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -24,9 +24,7 @@ "paid_amount", "discounted_amount", "column_break_3", - "base_payment_amount", - "base_outstanding", - "base_paid_amount" + "base_payment_amount" ], "fields": [ { @@ -156,28 +154,12 @@ "fieldtype": "Currency", "label": "Payment Amount (Company Currency)", "options": "Company:company:default_currency" - }, - { - "default": "0", - "fieldname": "base_outstanding", - "fieldtype": "Currency", - "label": "Outstanding (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "base_paid_amount", - "fieldtype": "Currency", - "label": "Paid Amount (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-23 13:55:59.548043", + "modified": "2021-04-28 05:41:35.084233", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From a949480acf0e124e013dd0a936412b5593df7fe6 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 28 Apr 2021 12:38:23 +0200 Subject: [PATCH 0626/1264] fix: add translation to make semgrep pass --- erpnext/accounts/doctype/subscription/subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 175710c9f8..7c4ff73d90 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -276,7 +276,7 @@ class Subscription(Document): 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') + 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? From 64a38f52cf72a56568876bcc376e69aec3680007 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 28 Apr 2021 13:46:47 +0530 Subject: [PATCH 0627/1264] chore: remove frappe from requirements.txt Due to recent changes in pip dependency resolver, in some random cases pip thinks frappe is not installed and tries to fetch it from pypi, which results in errors like #25496 Until a better solution is available, frappe should not be part of requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 377fd7df6c..f1ffeb8f48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -frappe +# frappe # https://github.com/frappe/frappe is installed during bench-init gocardless-pro~=1.22.0 googlemaps # used in ERPNext, but dependency is defined in Frappe pandas~=1.1.5 From b7ca9139042784530157d104126f3569bcd1d1d8 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 28 Apr 2021 19:21:36 +0530 Subject: [PATCH 0628/1264] fix: Added Item Reference field to link tables and update work_order_qty --- .../doctype/production_plan/production_plan.py | 1 + .../production_plan_item/production_plan_item.json | 11 +++++++++-- .../manufacturing/doctype/work_order/work_order.py | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index b2bc21fb6d..088089f87f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -201,6 +201,7 @@ class ProductionPlan(Document): pi.sales_order = data.parent pi.sales_order_item = data.name pi.description = data.description + pi.item_reference = data.name elif self.get_items_from == "Material Request": diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 9ff1717e70..89ab7aa0a0 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -27,7 +27,8 @@ "column_break_19", "material_request", "material_request_item", - "product_bundle_item" + "product_bundle_item", + "item_reference" ], "fields": [ { @@ -206,12 +207,18 @@ "options": "Item", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "item_reference", + "fieldtype": "Data", + "hidden": 1, + "label": "Item Reference" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-22 12:10:01.102440", + "modified": "2021-04-28 19:14:57.772123", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 87d57ad42c..d77c46fb03 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -382,8 +382,10 @@ class WorkOrder(Document): prod_plan = frappe.get_doc('Production Plan', self.production_plan) pp_ref = prod_plan.prod_plan_ref + pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) + item_ref = pp_item.item_reference for p in pp_ref: - if p.item_ref == self.production_plan_item: + if p.item_ref == item_ref: work_order_qty = int(p.qty) if not cancel else 0 frappe.db.set_value('Sales Order Item', p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) From 56f697052cc01f3beab8a12d5b9978e2bf86ebbe Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 28 Apr 2021 19:25:22 +0530 Subject: [PATCH 0629/1264] test: added test case for combining items --- .../production_plan/test_production_plan.py | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 27335aa204..2f52ad9021 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -100,7 +100,7 @@ class TestProductionPlan(unittest.TestCase): def test_production_plan_sales_orders(self): item = 'Test Production Item 1' - so = make_sales_order(item_code=item, qty=5) + so = make_sales_order(item_code=item, qty=1) sales_order = so.name sales_order_item = so.items[0].name @@ -124,8 +124,8 @@ class TestProductionPlan(unittest.TestCase): wo_doc = frappe.get_doc('Work Order', work_order) wo_doc.update({ - 'wip_warehouse': '_Test Warehouse 1 - _TC', - 'fg_warehouse': '_Test Warehouse - _TC' + 'wip_warehouse': 'Work In Progress - _TC', + 'fg_warehouse': 'Finished Goods - _TC' }) wo_doc.submit() @@ -145,6 +145,57 @@ class TestProductionPlan(unittest.TestCase): self.assertEqual(sales_orders, []) + def test_production_plan_combine_items(self): + item = 'Test Production Item 1' + so = make_sales_order(item_code=item, qty=1) + sales_order = so.name + sales_order_item = so.items[0].name + + pln = frappe.new_doc('Production Plan') + pln.company = so.company + pln.get_items_from = 'Sales Order' + pln.append('sales_orders', { + 'sales_order': so.name, + 'sales_order_date': so.transaction_date, + 'customer': so.customer, + 'grand_total': so.grand_total + }) + so = make_sales_order(item_code=item, qty=2) + pln.append('sales_orders', { + 'sales_order': so.name, + 'sales_order_date': so.transaction_date, + 'customer': so.customer, + 'grand_total': so.grand_total + }) + pln.combine_items = 1 + pln.get_so_items() + pln.save() + pp = frappe.get_doc('Production Plan',pln.name) + for d in pp.prod_plan_ref: + d.item_ref = pp.po_items[0].name + pln.submit() + + self.assertTrue(pln.po_items[0].planned_qty,3) + + + pln.make_work_order() + + work_order = frappe.db.get_value('Work Order', {'production_plan_item': pln.po_items[0].name, + 'production_plan': pln.name,}, 'name') + + wo_doc = frappe.get_doc('Work Order', work_order) + wo_doc.update({ + 'wip_warehouse': 'Work In Progress - _TC', + }) + + wo_doc.submit() + for d in pln.prod_plan_ref: + so_wo_qty = frappe.db.get_value('Sales Order Item', d.sales_order_item, 'work_order_qty') + self.assertTrue(so_wo_qty,d.qty) + + + + def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) From ab052599c055789a5519c10cdd1567c4a86c78c4 Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 28 Apr 2021 20:21:04 +0530 Subject: [PATCH 0630/1264] fix: allow to cancel loan with cancelled replayment entry --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..20b44a15e3 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -44,6 +44,7 @@ class Loan(AccountsController): def on_cancel(self): self.unlink_loan_security_pledge() + self.ignore_linked_doctypes = ['GL Entry'] def set_missing_fields(self): if not self.company: @@ -359,4 +360,4 @@ def get_shortfall_applicants(): return { "value": len(applicants), "fieldtype": "Int" - } \ No newline at end of file + } From dc086dd52ffa6a06afbc965587f2a9c0f34f7edf Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 29 Apr 2021 11:03:27 +0530 Subject: [PATCH 0631/1264] fix: item stock levels displaying inconsistently (#25506) * fix: fixed stock levels dashboard not displaying after any interactions * fix: minor translation fix --- erpnext/stock/doctype/item/item.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 2079cf88dd..8aec89381a 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -46,9 +46,6 @@ frappe.ui.form.on("Item", { }, __("View")); } - if (!frm.doc.is_fixed_asset) { - erpnext.item.make_dashboard(frm); - } if (frm.doc.is_fixed_asset) { frm.trigger('is_fixed_asset'); @@ -96,6 +93,10 @@ frappe.ui.form.on("Item", { erpnext.item.edit_prices_button(frm); erpnext.item.toggle_attributes(frm); + + if (!frm.doc.is_fixed_asset) { + erpnext.item.make_dashboard(frm); + } frm.add_custom_button(__('Duplicate'), function() { var new_item = frappe.model.copy_doc(frm.doc); @@ -473,11 +474,15 @@ $.extend(erpnext.item, { me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants')); me.multiple_variant_dialog.disable_primary_action(); } else { + let no_of_combinations = lengths.reduce((a, b) => a * b, 1); - me.multiple_variant_dialog.get_primary_btn() - .html(__( - `Make ${no_of_combinations} Variant${no_of_combinations === 1 ? '' : 's'}` - )); + let msg; + if (no_of_combinations === 1) { + msg = __("Make {0} Variant", [no_of_combinations]); + } else { + msg = __("Make {0} Variants", [no_of_combinations]); + } + me.multiple_variant_dialog.get_primary_btn().html(msg); me.multiple_variant_dialog.enable_primary_action(); } } From 83e3820575a2706f7b6ca8b150e0455df716a9cb Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 29 Apr 2021 12:21:56 +0530 Subject: [PATCH 0632/1264] fix: remove print statement --- erpnext/controllers/accounts_controller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a61292491c..c409850734 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -952,7 +952,6 @@ class AccountsController(TransactionBase): d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount - d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -1007,7 +1006,6 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - print(total, base_total) print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): @@ -1278,7 +1276,6 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, base_gra term_details.discount_type = term.discount_type term_details.discount = term.discount term_details.outstanding = term_details.payment_amount - term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: From 93c22ebbb98324153b6644475478c62ee15f8ff8 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 29 Apr 2021 12:57:41 +0530 Subject: [PATCH 0633/1264] refactor: created separate function to update work_order on cancel --- .../doctype/work_order/work_order.py | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index d77c46fb03..d9956e5bca 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -242,8 +242,8 @@ class WorkOrder(Document): frappe.throw(_("For Warehouse is required before Submit")) prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_ref = prod_plan.prod_plan_ref - if pp_ref: + + if prod_plan.prod_plan_ref: self.update_work_order_qty_in_combined_so() else: self.update_work_order_qty_in_so() @@ -259,9 +259,9 @@ class WorkOrder(Document): frappe.db.set(self,'status', 'Cancelled') prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_ref = prod_plan.prod_plan_ref - if pp_ref: - self.update_work_order_qty_in_combined_so(cancel = True) + + if prod_plan.prod_plan_ref: + self.update_work_order_combined_on_cancel() else: self.update_work_order_qty_in_so() @@ -370,7 +370,7 @@ class WorkOrder(Document): frappe.db.set_value('Sales Order Item', self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) - def update_work_order_qty_in_combined_so(self, cancel = None): + def update_work_order_qty_in_combined_so(self): total_bundle_qty = 1 if self.product_bundle_item: total_bundle_qty = frappe.db.sql(""" select sum(qty) from @@ -381,14 +381,22 @@ class WorkOrder(Document): total_bundle_qty = 1 prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_ref = prod_plan.prod_plan_ref pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) - item_ref = pp_item.item_reference - for p in pp_ref: - if p.item_ref == item_ref: - work_order_qty = int(p.qty) if not cancel else 0 + + for p in prod_plan.prod_plan_ref: + if p.item_ref == pp_item.item_reference: + work_order_qty = int(p.qty) frappe.db.set_value('Sales Order Item', p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + + def update_work_order_combined_on_cancel(self): + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) + + for p in prod_plan.prod_plan_ref: + if p.item_ref == pp_item.item_reference: + frappe.db.set_value('Sales Order Item', + p.sales_order_item, 'work_order_qty', 0.0) def update_completed_qty_in_material_request(self): From 90c667205adce829d9e0f9c4d74c4dd9effaa065 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 29 Apr 2021 12:58:35 +0530 Subject: [PATCH 0634/1264] test: added on_cancel test --- .../production_plan/test_production_plan.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2f52ad9021..19b06bc8dd 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -148,8 +148,6 @@ class TestProductionPlan(unittest.TestCase): def test_production_plan_combine_items(self): item = 'Test Production Item 1' so = make_sales_order(item_code=item, qty=1) - sales_order = so.name - sales_order_item = so.items[0].name pln = frappe.new_doc('Production Plan') pln.company = so.company @@ -169,17 +167,13 @@ class TestProductionPlan(unittest.TestCase): }) pln.combine_items = 1 pln.get_so_items() - pln.save() - pp = frappe.get_doc('Production Plan',pln.name) - for d in pp.prod_plan_ref: - d.item_ref = pp.po_items[0].name + for d in pln.prod_plan_ref: + d.item_ref = pln.po_items[0].name pln.submit() - self.assertTrue(pln.po_items[0].planned_qty,3) - - - pln.make_work_order() + self.assertTrue(pln.po_items[0].planned_qty,3) + pln.make_work_order() work_order = frappe.db.get_value('Work Order', {'production_plan_item': pln.po_items[0].name, 'production_plan': pln.name,}, 'name') @@ -189,13 +183,19 @@ class TestProductionPlan(unittest.TestCase): }) wo_doc.submit() + so_items = [] for d in pln.prod_plan_ref: + so_items.append(d.sales_order_item) so_wo_qty = frappe.db.get_value('Sales Order Item', d.sales_order_item, 'work_order_qty') - self.assertTrue(so_wo_qty,d.qty) - - - - + self.assertEqual(so_wo_qty, d.qty) + wo_doc.cancel() + for s in so_items: + so_wo_qty = frappe.db.get_value('Sales Order Item', s, 'work_order_qty') + self.assertEqual(so_wo_qty, 0.0) + + lat_plan = frappe.get_doc('Production Plan',pln.name) + lat_plan.cancel() + def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) From 6fb417590fd4ed431aae585b5a30612ab5e900a0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 29 Apr 2021 14:05:20 +0530 Subject: [PATCH 0635/1264] fix: sider issues --- erpnext/public/js/controllers/transaction.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index dc731ca213..a2b95cb757 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1363,9 +1363,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.update_payment_schedule_grid_labels(company_currency); - - // set labels - var $wrapper = $(this.frm.wrapper); }, update_item_grid_labels: function(company_currency) { @@ -1382,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if(this.frm.fields_dict["payment_schedule"]) { + if (this.frm.fields_dict["payment_schedule"]) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], @@ -1390,7 +1387,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { - if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); } @@ -2045,6 +2042,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, payment_term: function(doc, cdt, cdn) { + const me = this; var row = locals[cdt][cdn]; if(row.payment_term) { frappe.call({ @@ -2061,7 +2059,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } } From 0dc00219e8e0dbc32f0b6b00c3b268c2f2fb908a Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 29 Apr 2021 15:21:16 +0530 Subject: [PATCH 0636/1264] fix: coveralls different services for different events (#25513) --- .github/workflows/ci-tests.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 4103bc68a9..84ecfb1457 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -80,8 +80,8 @@ jobs: env: TYPE: ${{ matrix.TYPE }} - - name: Coverage - if: matrix.TYPE == 'server' + - name: Coverage - Pull Request + if: matrix.TYPE == 'server' && github.event_name == 'pull_request' run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} @@ -91,3 +91,18 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_SERVICE_NAME: github + + - name: Coverage - Push + if: matrix.TYPE == 'server' && github.event_name == 'push' + run: | + cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + cd ${GITHUB_WORKSPACE} + pip install coveralls==2.2.0 + pip install coverage==4.5.4 + coveralls --service=github-actions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_SERVICE_NAME: github-actions + From dd1e7624bf62aaa6b7a4fa797bdde4d61ea6f215 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 29 Apr 2021 17:00:59 +0530 Subject: [PATCH 0637/1264] fix: changing paid amount should change allocated amount --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 830a7f25c0..b80e8ada38 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', { flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)); if(frm.doc.payment_type == "Pay") - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1); else frm.events.set_unallocated_amount(frm); From e70d9c925c5f56ca35d0da84f59e305d38481e35 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 12 Apr 2021 15:40:27 +0530 Subject: [PATCH 0638/1264] fix: payment amount showing in foreign currency --- .../accounts/doctype/payment_schedule/payment_schedule.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e362566af0..39eda331db 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -65,7 +65,6 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", - "options": "currency", "reqd": 1 }, { @@ -150,7 +149,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-15 21:03:12.540546", + "modified": "2021-04-12 15:39:47.363609", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From a91eebf6aa64851bb7223f39771019225f21fe7c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 14:46:52 +0530 Subject: [PATCH 0639/1264] feat: base payment amount in payment schedule --- .../payment_schedule/payment_schedule.json | 36 +++++++++++-- .../purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 53 ++++++++++++------- erpnext/public/js/controllers/transaction.js | 5 +- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 39eda331db..e9f8ba29dc 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -20,10 +20,13 @@ "discount", "section_break_9", "payment_amount", + "outstanding", + "paid_amount", "discounted_amount", "column_break_3", - "outstanding", - "paid_amount" + "base_payment_amount", + "base_outstanding", + "base_paid_amount" ], "fields": [ { @@ -65,6 +68,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", + "options": "currency", "reqd": 1 }, { @@ -77,7 +81,8 @@ "depends_on": "paid_amount", "fieldname": "paid_amount", "fieldtype": "Currency", - "label": "Paid Amount" + "label": "Paid Amount", + "options": "currency" }, { "fieldname": "column_break_3", @@ -96,6 +101,7 @@ "fieldname": "outstanding", "fieldtype": "Currency", "label": "Outstanding", + "options": "currency", "read_only": 1 }, { @@ -144,12 +150,34 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break" + }, + { + "fieldname": "base_payment_amount", + "fieldtype": "Currency", + "label": "Payment Amount (Company Currency)", + "options": "Company:company:default_currency" + }, + { + "default": "0", + "fieldname": "base_outstanding", + "fieldtype": "Currency", + "label": "Outstanding (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-12 15:39:47.363609", + "modified": "2021-04-23 13:55:59.548043", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 50492f50b5..66be11ff23 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase): pi.update({ "payment_schedule": get_payment_terms("_Test Payment Term Template", - pi.posting_date, pi.grand_total) + pi.posting_date, pi.grand_total, pi.base_grand_total) }) pi.save() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 97bcb00210..d7f89b6270 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -923,20 +923,24 @@ class AccountsController(TransactionBase): date = self.get("due_date") due_date = date or posting_date - if party_account_currency == self.company_currency: - grand_total = self.get("base_rounded_total") or self.base_grand_total - else: - grand_total = self.get("rounded_total") or self.grand_total + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) if self.get("total_advance"): - grand_total -= self.get("total_advance") + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): if self.get("payment_terms_template"): - data = get_payment_terms(self.payment_terms_template, posting_date, grand_total) + data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) else: @@ -946,7 +950,9 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount + d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -982,22 +988,28 @@ class AccountsController(TransactionBase): if self.get("payment_schedule"): total = 0 + base_total = 0 for d in self.get("payment_schedule"): total += flt(d.payment_amount) + base_total += flt(d.base_payment_amount) - if party_account_currency == self.company_currency: - total = flt(total, self.precision("base_grand_total")) - grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) - else: - total = flt(total, self.precision("grand_total")) - grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) - - if self.get("total_advance"): - grand_total -= self.get("total_advance") + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) - if total != flt(grand_total, self.precision("grand_total")): + + if self.get("total_advance"): + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) + + if total != flt(grand_total, self.precision("grand_total")) or \ + base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): @@ -1237,7 +1249,7 @@ def update_invoice_status(): @frappe.whitelist() -def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None): +def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): if not terms_template: return @@ -1245,14 +1257,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_ schedule = [] for d in terms_doc.get("terms"): - term_details = get_payment_term_details(d, posting_date, grand_total, bill_date) + term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date) schedule.append(term_details) return schedule @frappe.whitelist() -def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None): +def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): term_details = frappe._dict() if isinstance(term, text_type): term = frappe.get_doc("Payment Term", term) @@ -1261,10 +1273,11 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat term_details.description = term.description term_details.invoice_portion = term.invoice_portion term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 + term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100 term_details.discount_type = term.discount_type term_details.discount = term.discount - # term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount term_details.outstanding = term_details.payment_amount + term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a0398e718f..258262aed3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1995,6 +1995,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ terms_template: doc.payment_terms_template, posting_date: posting_date, grand_total: doc.rounded_total || doc.grand_total, + base_grand_total: doc.base_rounded_total || doc.base_grand_total, bill_date: doc.bill_date }, callback: function(r) { @@ -2009,13 +2010,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { + debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { term: row.payment_term, bill_date: this.frm.doc.bill_date, posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date, - grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total + grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total, + base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total }, callback: function(r) { if(r.message && !r.exc) { From 973afeac3cb7176fe8324ac2df007e9e089ac01c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 15:34:58 +0530 Subject: [PATCH 0640/1264] feat: set dynamic labels for payment schedule fields --- erpnext/public/js/controllers/transaction.js | 54 +++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 258262aed3..dc731ca213 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -640,6 +640,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ let key = item.name; me.apply_rule_on_other_items({key: item}); } + }, + () => { + var company_currency = me.get_company_currency(); + me.update_item_grid_labels(company_currency); } ]); } @@ -1321,11 +1325,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ change_grid_labels: function(company_currency) { var me = this; - this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"], - company_currency, "items"); + this.update_item_grid_labels(company_currency); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"], - this.frm.doc.currency, "items"); + this.toggle_item_grid_columns(company_currency); if(this.frm.fields_dict["operations"]) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); @@ -1360,6 +1362,42 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.doc.party_account_currency, "advances"); } + this.update_payment_schedule_grid_labels(company_currency); + + // set labels + var $wrapper = $(this.frm.wrapper); + }, + + update_item_grid_labels: function(company_currency) { + this.frm.set_currency_labels([ + "base_rate", "base_net_rate", "base_price_list_rate", + "base_amount", "base_net_amount", "base_rate_with_margin" + ], company_currency, "items"); + + this.frm.set_currency_labels([ + "rate", "net_rate", "price_list_rate", "amount", + "net_amount", "stock_uom_rate", "rate_with_margin" + ], this.frm.doc.currency, "items"); + }, + + update_payment_schedule_grid_labels: function(company_currency) { + const me = this; + if(this.frm.fields_dict["payment_schedule"]) { + this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], + company_currency, "payment_schedule"); + this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], + this.frm.doc.currency, "payment_schedule"); + + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; + $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { + if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); + }); + } + }, + + toggle_item_grid_columns: function(company_currency) { + const me = this; // toggle columns var item_grid = this.frm.fields_dict["items"].grid; $.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) { @@ -1379,9 +1417,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency))); }); - - // set labels - var $wrapper = $(this.frm.wrapper); }, recalculate: function() { @@ -2001,6 +2036,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if(r.message && !r.exc) { me.frm.set_value("payment_schedule", r.message); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } }) @@ -2010,7 +2047,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { - debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { @@ -2024,6 +2060,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } } From 3a5ecb75873e8e9be1fea6e2c134fec31f6b895c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:55:33 +0530 Subject: [PATCH 0641/1264] fix: test --- erpnext/controllers/accounts_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d7f89b6270..a61292491c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -944,7 +944,7 @@ class AccountsController(TransactionBase): for item in data: self.append("payment_schedule", item) else: - data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total) + data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) else: for d in self.get("payment_schedule"): @@ -1007,7 +1007,8 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - + print(total, base_total) + print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) From 6dd1c5cf922c5a73f87ca26a780b5885d755e180 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 15:11:57 +0530 Subject: [PATCH 0642/1264] refactor: remove extra fields --- .../payment_schedule/payment_schedule.json | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e9f8ba29dc..6ed7a3154e 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -24,9 +24,7 @@ "paid_amount", "discounted_amount", "column_break_3", - "base_payment_amount", - "base_outstanding", - "base_paid_amount" + "base_payment_amount" ], "fields": [ { @@ -156,28 +154,12 @@ "fieldtype": "Currency", "label": "Payment Amount (Company Currency)", "options": "Company:company:default_currency" - }, - { - "default": "0", - "fieldname": "base_outstanding", - "fieldtype": "Currency", - "label": "Outstanding (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "base_paid_amount", - "fieldtype": "Currency", - "label": "Paid Amount (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-23 13:55:59.548043", + "modified": "2021-04-28 05:41:35.084233", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From 2bde1baa38078477d8c3ffad98d2d9c5475ec1f1 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 29 Apr 2021 12:21:56 +0530 Subject: [PATCH 0643/1264] fix: remove print statement --- erpnext/controllers/accounts_controller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a61292491c..c409850734 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -952,7 +952,6 @@ class AccountsController(TransactionBase): d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount - d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -1007,7 +1006,6 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - print(total, base_total) print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): @@ -1278,7 +1276,6 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, base_gra term_details.discount_type = term.discount_type term_details.discount = term.discount term_details.outstanding = term_details.payment_amount - term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: From 3e83542360975d79e9bff6d5fed1ac8efc90632e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 29 Apr 2021 14:05:20 +0530 Subject: [PATCH 0644/1264] fix: sider issues --- erpnext/public/js/controllers/transaction.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index dc731ca213..a2b95cb757 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1363,9 +1363,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.update_payment_schedule_grid_labels(company_currency); - - // set labels - var $wrapper = $(this.frm.wrapper); }, update_item_grid_labels: function(company_currency) { @@ -1382,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if(this.frm.fields_dict["payment_schedule"]) { + if (this.frm.fields_dict["payment_schedule"]) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], @@ -1390,7 +1387,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { - if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); } @@ -2045,6 +2042,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, payment_term: function(doc, cdt, cdn) { + const me = this; var row = locals[cdt][cdn]; if(row.payment_term) { frappe.call({ @@ -2061,7 +2059,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } } From 76e1e68cf4d3bcbc29a8061c53e31c1960230071 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 29 Apr 2021 08:24:56 -0600 Subject: [PATCH 0645/1264] fix: Make strings translatable Make strings translatable --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index 44368a69fb..d5c20dce6b 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -16,7 +16,7 @@ frappe.ui.form.on('Salary Structure', { onload: function(frm) { let help_button = $(` - ${__(Condition and Formula Help)} + ${__("Condition and Formula Help")} `).click(()=>{ let d = new frappe.ui.Dialog({ From 7783a56c08d343baab2e583d7f90f6e25e69a8ac Mon Sep 17 00:00:00 2001 From: Vignesh S Date: Thu, 29 Apr 2021 22:39:42 +0530 Subject: [PATCH 0646/1264] fix: List invoices in Payment Reconciliation Payment Invoices are not listed in the Payment Reconciliation Payment table due to a typo in the code --- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 08103184d5..d1523cd7ac 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -234,7 +234,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }); if (invoices) { - this.frm.fields_dict.payment.grid.update_docfield_property( + this.frm.fields_dict.payments.grid.update_docfield_property( 'invoice_number', 'options', "\n" + invoices.join("\n") ); From 7ad66caf7f656347d2e9b3fe253ba64b6fd35ae0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 30 Apr 2021 11:42:16 +0530 Subject: [PATCH 0647/1264] refactor: migrated calculation and validation logic in js to py --- .../maintenance_schedule.js | 84 ++++--------------- .../maintenance_schedule.py | 60 ++++++++++++- 2 files changed, 74 insertions(+), 70 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 3c05526496..79167ae45f 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -73,31 +73,11 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } if (flag) { this.frm.add_custom_button(__('Create Maintenance Visit'), function () { - let items = me.frm.doc.items; - let s = me.frm.doc.schedules; let options = ""; - let dates = ""; - for (let i in items) { - for (let d in s) { - if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { - options = options + '\n' + items[i].item_name; - break; - } - } - } - function formatDate(date) { - var d = new Date(date), - month = '' + (d.getMonth() + 1), - day = '' + d.getDate(), - year = d.getFullYear(); - - if (month.length < 2) - month = '0' + month; - if (day.length < 2) - day = '0' + day; - - return [day, month, year].join('-'); - } + + me.frm.call('get_pending_data',{data_type:"items"}).then(r =>{ + options = r.message + var schedule_id = ""; var d = new frappe.ui.Dialog({ title: __("Enter Visit Details"), @@ -109,30 +89,23 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ reqd: 1, onchange: function () { let field = d.get_field("scheduled_date"); - dates = ""; - for (let i in s) { - if (s[i].item_name == this.value && s[i].completion_status == "Pending") { - dates = dates + '\n' + formatDate(s[i].scheduled_date); - } - - } - field.df.options = dates; - field.refresh(); + me.frm.call('get_pending_data',{item_name:this.value,data_type:"date"}).then(r =>{ + field.df.options = r.message; + field.refresh(); + }) } }, { label: __('Scheduled Date'), fieldname: 'scheduled_date', fieldtype: 'Select', - options: dates, + options: "", reqd: 1, onchange: function () { let field = d.get_field('item_name'); - for (let i in s) { - if (s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value) { - schedule_id = s[i].name; - } - } + me.frm.call('get_pending_data',{item_name:field.value,s_date:this.value,data_type:"id"}).then(r =>{ + schedule_id = r.message; + }) } }, ], @@ -159,7 +132,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } }); d.show(); - + }); }, __('Create')); } } @@ -185,35 +158,8 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ var item = frappe.get_doc(cdt, cdn); if (item.start_date && item.periodicity) { - - var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; - - var days_in_period = { - "Weekly": 7, - "Monthly": 30, - "Quarterly": 91, - "Half Yearly": 182, - "Yearly": 365 - } - - var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); - if (no_of_visits == 0 || !no_of_visits) { - - let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "end_date", end_date); - date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; - no_of_visits = cint(date_diff / days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); - - } else if (item.no_of_visits > no_of_visits) { - let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "end_date", end_date); - - } else if (item.no_of_visits < no_of_visits) { - let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "end_date", end_date); - - } + me.frm.call('validate_end_date_visits') + } }, }); diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index b89d540ebb..d11bf7e735 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import add_days, getdate, cint, cstr +from frappe.utils import add_days, getdate, cint, cstr, date_diff, formatdate from frappe import throw, _ from erpnext.utilities.transaction_base import TransactionBase, delete_events @@ -36,6 +36,39 @@ class MaintenanceSchedule(TransactionBase): child.completion_status = "Pending" child.item_ref = d.name + @frappe.whitelist() + def validate_end_date_visits(self): + days_in_period = { + "Weekly": 7, + "Monthly": 30, + "Quarterly": 91, + "Half Yearly": 182, + "Yearly": 365 + } + for i in self.items: + + if i.periodicity and i.start_date: + if not i.end_date: + if i.no_of_visits: + i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + else: + i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) + + diff = date_diff(i.end_date, i.start_date) + 1 + no_of_visits = cint(diff / days_in_period[i.periodicity]) + + if not i.no_of_visits or i.no_of_visits == 0: + i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) + diff = date_diff(i.end_date, i.start_date ) + 1 + i.no_of_visits = cint(diff / days_in_period[i.periodicity]) + + elif i.no_of_visits > no_of_visits: + i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + + elif i.no_of_visits < no_of_visits: + i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + + def on_submit(self): if not self.get('schedules'): throw(_("Please click on 'Generate Schedule' to get schedule")) @@ -166,6 +199,7 @@ class MaintenanceSchedule(TransactionBase): throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order)) def validate(self): + self.validate_end_date_visits() self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() @@ -246,6 +280,30 @@ class MaintenanceSchedule(TransactionBase): def on_trash(self): delete_events(self.doctype, self.name) + + @frappe.whitelist() + def get_pending_data(self,data_type,s_date = None, item_name = None): + if data_type == "date": + dates = "" + for i in self.schedules: + if i.item_name == item_name and i.completion_status == "Pending": + dates = dates + "\n" + formatdate(i.scheduled_date, "dd-MM-yyyy") + return dates + elif data_type == "items": + items = "" + for i in self.items: + for s in self.schedules: + if i.item_name == s.item_name and s.completion_status == "Pending": + items = items + "\n" + i.item_name + break + return items + elif data_type == "id": + for s in self.schedules: + if s.item_name == item_name and s_date == formatdate(s.scheduled_date,"dd-mm-yyyy"): + return s.name + + + @frappe.whitelist() def update_serial_nos(s_id): serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') From 435e93bda9efe882031bbd5ed4b4994b74888db1 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 30 Apr 2021 13:52:48 +0530 Subject: [PATCH 0648/1264] fix: allocated amount change wrt paid amount change --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 830a7f25c0..b80e8ada38 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', { flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)); if(frm.doc.payment_type == "Pay") - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1); else frm.events.set_unallocated_amount(frm); From c0352010d360870d7ae2a057a1edefb72e72399b Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 30 Apr 2021 14:09:30 +0530 Subject: [PATCH 0649/1264] test: creating schedule and visit --- .../test_maintenance_schedule.py | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 3c307e920f..834c05476e 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -2,7 +2,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals -from frappe.utils.data import get_datetime, add_days +from frappe.utils.data import add_days, today +from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import make_maintenance_visit import frappe import unittest @@ -21,6 +22,52 @@ class TestMaintenanceSchedule(unittest.TestCase): ms.cancel() events_after_cancel = get_events(ms) self.assertTrue(len(events_after_cancel) == 0) + + def test_make_schedule(self): + ms = make_maintenance_schedule() + ms.save() + i = ms.items[0] + expected_end_date = add_days(i.start_date, i.no_of_visits * 7) + self.assertEqual(i.end_date, expected_end_date) + + i.no_of_visits = 2 + ms.save() + expected_end_date = add_days(i.start_date, i.no_of_visits * 7) + self.assertEqual(i.end_date, expected_end_date) + + items = ms.get_pending_data(data_type = "items") + items = items.split('\n') + items.pop(0) + expected_items = ['_Test Item'] + self.assertTrue(items,expected_items) + + dates = ms.get_pending_data(data_type = "date",item_name = i.item_name) + dates = dates.split('\n') + dates.pop(0) + expected_dates = ['07-05-2021','14-05-2021'] + self.assertEqual(dates,expected_dates) + + + ms.submit() + s_id = ms.get_pending_data(data_type = "id",item_name = i.item_name, s_date = "14-05-2021") + test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) + visit = frappe.new_doc('Maintenance Visit') + visit = test + visit.completion_status = "Partially Completed" + + visit.set('purposes',[{ + 'item_code':i.item_code, + 'description':"test", + 'work_done':"test", + 'prevdoc_docname':ms.name, + 'prevdoc_doctype':ms.doctype, + 'prevdoc_detail_docname':s_id + }]) + visit.submit() + ms = frappe.get_doc('Maintenance Schedule',ms.name) + self.assertTrue(ms.schedules[1].completion_status,"Partially Completed") + + def get_events(ms): return frappe.get_all("Event Participants", filters={ @@ -29,16 +76,16 @@ def get_events(ms): "parenttype": "Event" }) + def make_maintenance_schedule(): ms = frappe.new_doc("Maintenance Schedule") ms.company = "_Test Company" ms.customer = "_Test Customer" - ms.transaction_date = get_datetime() + ms.transaction_date = today() ms.append("items", { "item_code": "_Test Item", - "start_date": get_datetime(), - "end_date": add_days(get_datetime(), 32), + "start_date": today(), "periodicity": "Weekly", "no_of_visits": 4, "sales_person": "Sales Team", From 178938b2f0e3f18b0652bfc320a499699888ef70 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Apr 2021 16:57:34 +0530 Subject: [PATCH 0650/1264] fix: Ageing errors in PSOA --- .../process_statement_of_accounts.html | 12 ++++++------ .../process_statement_of_accounts.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 94ae79a0c6..16d4ebe6cb 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -60,8 +60,8 @@

    -{% if aging %} -

    {{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}

    +{% if ageing %} +

    {{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

    {{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
    @@ -78,10 +78,10 @@ - {{ aging.range1 }} - {{ aging.range2 }} - {{ aging.range3 }} - {{ aging.range4 }} + {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 43fbb0600a..fd2102b87f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -38,7 +38,7 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): statement_dict = {} - aging = '' + ageing = '' base_template_path = "frappe/www/printview.html" template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" @@ -54,8 +54,10 @@ def get_report_pdf(doc, consolidated=True): 'range4': 120, 'customer': entry.customer }) - col1, aging = get_ageing(ageing_filters) - aging[0]['ageing_based_on'] = doc.ageing_based_on + col1, ageing = get_ageing(ageing_filters) + + if ageing: + ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id @@ -83,11 +85,14 @@ def get_report_pdf(doc, consolidated=True): if len(res) == 3: continue + html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None}) + {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None}) + html = frappe.render_template(base_template_path, {"body": html, \ "css": get_print_style(), "title": "Statement For " + entry.customer}) statement_dict[entry.customer] = html + if not bool(statement_dict): return False elif consolidated: From 9cbca06f4e533466978f63e4c3fbb78261e38628 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 12:19:31 +0530 Subject: [PATCH 0651/1264] fix: Updated filters for process statement of accounts --- .../process_statement_of_accounts.html | 2 +- .../process_statement_of_accounts.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 16d4ebe6cb..f61aacbce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -19,7 +19,7 @@ - {% for row in data %} + {% for row in data %} {% if(row.posting_date) %} {{ frappe.format(row.posting_date, 'Date') }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index fd2102b87f..948431bf5b 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -7,8 +7,9 @@ import frappe from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing -from frappe.core.doctype.communication.email import make +from erpnext import get_company_currency +from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -66,16 +67,16 @@ def get_report_pdf(doc, consolidated=True): 'to_date': doc.to_date, 'company': doc.company, 'finance_book': doc.finance_book if doc.finance_book else None, - "account": doc.account if doc.account else None, + 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], + 'presentation_currency': doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], 'project': [p.project_name for p in doc.project], 'show_opening_entries': 0, 'include_default_book_entries': 0, - 'show_cancelled_entries': 1, 'tax_id': tax_id if tax_id else None }) col, res = get_soa(filters) From bd5ee7d725c8583526fff0e93334e81812a36147 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 17:27:26 +0530 Subject: [PATCH 0652/1264] fix: Use party account currency --- .../process_statement_of_accounts.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 948431bf5b..f6724449b8 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -8,6 +8,7 @@ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency +from erpnext.accounts.party import get_party_account_currency from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf @@ -70,7 +71,8 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': doc.currency or get_company_currency(doc.company), + 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], From 1deef5229e36f5fc758c9980a8638ae222ca273d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Apr 2021 14:51:00 +0530 Subject: [PATCH 0653/1264] fix: Linting and translation issues --- .../process_statement_of_accounts.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index f6724449b8..62090c0255 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -4,13 +4,13 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency from erpnext.accounts.party import get_party_account_currency -from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -31,7 +31,7 @@ class ProcessStatementOfAccounts(Document): validate_template(self.body) if not self.customers: - frappe.throw(frappe._('Customers not selected.')) + frappe.throw(_('Customers not selected.')) if self.enable_auto_email: self.to_date = self.start_date @@ -62,6 +62,8 @@ def get_report_pdf(doc, consolidated=True): ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id + presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company) filters= frappe._dict({ 'from_date': doc.from_date, @@ -71,8 +73,7 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ - or doc.currency or get_company_currency(doc.company), + 'presentation_currency': presentation_currency, 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], @@ -175,7 +176,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): if customer_collection == 'Sales Person': customers = get_customers_based_on_sales_person(collection_name) if not bool(customers): - frappe.throw('No Customers found with selected options.') + frappe.throw(_('No Customers found with selected options.')) else: if customer_collection == 'Sales Partner': customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ @@ -207,14 +208,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: - frappe.throw('No billing email found for customer: '+ customer_name) + frappe.throw(_("No billing email found for customer: {0}").format(customer_name)) else: return '' if billing_and_primary: primary_email = frappe.get_value('Customer', customer_name, 'email_id') if primary_email is None and int(primary_mandatory): - frappe.throw('No primary email found for customer: '+ customer_name) + frappe.throw(_("No primary email found for customer: {0}").format(customer_name)) return [primary_email or '', billing_email[0][0]] else: return billing_email[0][0] or '' From 88d40e8dc477361ce257bf22d8f8cf5088e1f6a8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Apr 2021 15:33:33 +0530 Subject: [PATCH 0654/1264] fix: Remove extra space --- .../process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 62090c0255..a0dbff3db4 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -62,7 +62,7 @@ def get_report_pdf(doc, consolidated=True): ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id - presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ + presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ or doc.currency or get_company_currency(doc.company) filters= frappe._dict({ From a90c81626f662890a0ee7bcbf87878c1ffa0d8d6 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 30 Apr 2021 15:49:09 +0530 Subject: [PATCH 0655/1264] fix: updated item filters for material request --- .../doctype/material_request/material_request.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 7dfc5da50d..92c8d21387 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -433,13 +433,21 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten if (doc.material_request_type == "Customer Provided") { return{ query: "erpnext.controllers.queries.item_query", - filters:{ 'customer': me.frm.doc.customer } + filters:{ + 'customer': me.frm.doc.customer, + 'is_stock_item':1 + } } - } else if (doc.material_request_type != "Manufacture") { + } else if (doc.material_request_type == "Purchase") { return{ query: "erpnext.controllers.queries.item_query", filters: {'is_purchase_item': 1} } + } else { + return{ + query: "erpnext.controllers.queries.item_query", + filters: {'is_stock_item': 1} + } } }); }, From 204ea1027f84190b5a26e127b88be19f2945337c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Apr 2021 16:35:52 +0530 Subject: [PATCH 0656/1264] fix: Ignore validations for Tax Setup --- erpnext/regional/india/setup.py | 10 ++++-- erpnext/setup/doctype/company/company.py | 2 +- .../setup_wizard/operations/taxes_setup.py | 34 ++++++++++++++----- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index f70ba90506..aedd6c88ab 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -25,6 +25,7 @@ def setup_company_independent_fixtures(patch=False): frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) create_gratuity_rule() add_print_formats() + update_accounts_settings_for_taxes() def add_hsn_sac_codes(): # HSN codes @@ -698,6 +699,7 @@ def set_tax_withholding_category(company): for d in docs: if not frappe.db.exists("Tax Withholding Category", d.get("name")): doc = frappe.get_doc(d) + doc.flags.ignore_validate = True doc.flags.ignore_permissions = True doc.flags.ignore_mandatory = True doc.insert() @@ -714,11 +716,12 @@ def set_tax_withholding_category(company): doc.append("rates", d.get('rates')[0]) doc.flags.ignore_permissions = True + doc.flags.ignore_validdate = True doc.flags.ignore_mandatory = True + doc.flags.ignore_links = True doc.save() def set_tds_account(docs, company): - abbr = frappe.get_value("Company", company, "abbr") parent_account = frappe.db.get_value("Account", filters = {"account_name": "Duties and Taxes", "company": company}) if parent_account: docs.extend([ @@ -877,7 +880,6 @@ def get_tds_details(accounts, fiscal_year): ] def create_gratuity_rule(): - # Standard Indain Gratuity Rule if not frappe.db.exists("Gratuity Rule", "Indian Standard Gratuity Rule"): rule = frappe.new_doc("Gratuity Rule") @@ -895,3 +897,7 @@ def create_gratuity_rule(): rule.flags.ignore_mandatory = True rule.save() + +def update_accounts_settings_for_taxes(): + if frappe.db.count('Company') == 1: + frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 64e027dd28..aa4bc98875 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -110,7 +110,7 @@ class Company(NestedSet): self.create_default_warehouses() if frappe.flags.country_change: - install_country_fixtures(self.name) + install_country_fixtures(self.name, self.country) self.create_default_tax_template() if not frappe.db.get_value("Department", {"company": self.name}): diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index bbe301bb37..a644da9292 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -123,8 +123,11 @@ def make_taxes_and_charges_template(company_name, doctype, template): if fieldname not in tax_row: tax_row[fieldname] = default_value - return frappe.get_doc(template).insert(ignore_permissions=True) - + doc = frappe.get_doc(template) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) + return doc def make_item_tax_template(company_name, template): """Create an Item Tax Template. @@ -149,14 +152,21 @@ def make_item_tax_template(company_name, template): if 'tax_rate' not in tax_row: tax_row['tax_rate'] = account_data.get('tax_rate') - return frappe.get_doc(template).insert(ignore_permissions=True) + doc = frappe.get_doc(template) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) + return doc def make_tax_category(tax_category): """ Make tax category based on title if not already created """ doctype = 'Tax Category' if not frappe.db.exists(doctype, tax_category): tax_category['doctype'] = doctype - frappe.get_doc(tax_category).insert(ignore_permissions=True) + doc = frappe.get_doc(tax_category) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) def get_or_create_account(company_name, account): """ @@ -169,7 +179,8 @@ def get_or_create_account(company_name, account): existing_accounts = frappe.get_list('Account', filters={ 'account_name': account.get('account_name'), - 'account_number': account.get('account_number', '') + 'account_number': account.get('account_number', ''), + 'company': company_name }, or_filters={ 'company': company_name, @@ -191,8 +202,11 @@ def get_or_create_account(company_name, account): account['root_type'] = root_type account['is_group'] = 0 - return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True) - + doc = frappe.get_doc(account) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True, ignore_mandatory=True) + return doc def get_or_create_tax_group(company_name, root_type): # Look for a group account of type 'Tax' @@ -237,7 +251,11 @@ def get_or_create_tax_group(company_name, root_type): 'account_type': 'Tax', 'account_name': account_name, 'parent_account': root_account.name - }).insert(ignore_permissions=True) + }) + + tax_group_account.flags.ignore_links = True + tax_group_account.flags.ignore_validate = True + tax_group_account.insert(ignore_permissions=True) tax_group_name = tax_group_account.name From da0ba15cbf1d5c5b1e96d97f6b915c2ad508df1c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 30 Apr 2021 18:38:41 +0530 Subject: [PATCH 0657/1264] fix: Fetch total stock at company in PO (#25532) --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1a61f30b9a..3fc1df76bc 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -86,7 +86,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse)) + out.update(get_bin_details(args.item_code, out.warehouse, args.company)) # update args with out, if key or value not exists for key, value in iteritems(out): From a19c6a9278a13cf1b67306eb51627d8d53bc5ce2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 30 Apr 2021 18:38:49 +0530 Subject: [PATCH 0658/1264] fix: Fetch total stock at company in PO (#25533) --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1a61f30b9a..3fc1df76bc 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -86,7 +86,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse)) + out.update(get_bin_details(args.item_code, out.warehouse, args.company)) # update args with out, if key or value not exists for key, value in iteritems(out): From 5a4615fd4e74e258f32c9c2e9addb4915f800660 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 30 Apr 2021 18:40:43 +0530 Subject: [PATCH 0659/1264] fix: allow to cancel loan with cancelled replayment entry (#25508) --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..20b44a15e3 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -44,6 +44,7 @@ class Loan(AccountsController): def on_cancel(self): self.unlink_loan_security_pledge() + self.ignore_linked_doctypes = ['GL Entry'] def set_missing_fields(self): if not self.company: @@ -359,4 +360,4 @@ def get_shortfall_applicants(): return { "value": len(applicants), "fieldtype": "Int" - } \ No newline at end of file + } From e91b0021ac362ae46e894c1f954da743cc476e4b Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 30 Apr 2021 18:41:19 +0530 Subject: [PATCH 0660/1264] fix: fieldname when updating docfield property (#25516) From 1fd1e2bb60f6c6fd118f2843ba27c85b5ccd5ed9 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 30 Apr 2021 18:41:26 +0530 Subject: [PATCH 0661/1264] fix: fieldname when updating docfield property (#25517) --- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 08103184d5..d1523cd7ac 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -234,7 +234,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }); if (invoices) { - this.frm.fields_dict.payment.grid.update_docfield_property( + this.frm.fields_dict.payments.grid.update_docfield_property( 'invoice_number', 'options', "\n" + invoices.join("\n") ); From 00ea336b5241b2695f41c87378c2c34c89cb2143 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 1 May 2021 13:53:39 +0530 Subject: [PATCH 0662/1264] fix: stock ledger entry created against draft stock entry (#25540) --- erpnext/stock/stock_ledger.py | 2 +- erpnext/stock/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 985901fc44..bbfcb7ad7d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -416,7 +416,7 @@ class update_entries_after(object): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) + stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 0af3d90822..034d3ebbb5 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -172,7 +172,7 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = 1 bin_obj.insert() else: - bin_obj = frappe.get_cached_doc('Bin', bin) + bin_obj = frappe.get_doc('Bin', bin, for_update=True) bin_obj.flags.ignore_permissions = True return bin_obj From 2e38f1514fd67dd4ab80fe301668e0ee88d05e1c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 1 May 2021 13:53:46 +0530 Subject: [PATCH 0663/1264] fix: stock ledger entry created against draft stock entry (#25539) --- erpnext/stock/stock_ledger.py | 2 +- erpnext/stock/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 985901fc44..bbfcb7ad7d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -416,7 +416,7 @@ class update_entries_after(object): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) + stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 0af3d90822..034d3ebbb5 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -172,7 +172,7 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = 1 bin_obj.insert() else: - bin_obj = frappe.get_cached_doc('Bin', bin) + bin_obj = frappe.get_doc('Bin', bin, for_update=True) bin_obj.flags.ignore_permissions = True return bin_obj From 3c788f032558e4e709d9975311801478fb2dea54 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 1 May 2021 14:42:30 +0530 Subject: [PATCH 0664/1264] chore: Add release note for v13.2.0 --- erpnext/change_log/v13/v13_2_0.md | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 erpnext/change_log/v13/v13_2_0.md diff --git a/erpnext/change_log/v13/v13_2_0.md b/erpnext/change_log/v13/v13_2_0.md new file mode 100644 index 0000000000..eb9499d115 --- /dev/null +++ b/erpnext/change_log/v13/v13_2_0.md @@ -0,0 +1,56 @@ +# Version 13.2.0 Release Notes + +### Features & Enhancements + +- Employee Hours Utilization Report ([#25209](https://github.com/frappe/erpnext/pull/25209)) +- Delayed Tasks Summary Report ([#25024](https://github.com/frappe/erpnext/pull/25024)) +- Project Profitability Report ([#24944](https://github.com/frappe/erpnext/pull/24944)) +- Timer in LMS Quiz ([#24246](https://github.com/frappe/erpnext/pull/24246)) +- Role to allow over billing, delivery, receipt ([#24854](https://github.com/frappe/erpnext/pull/24854)) +- Auto calculate distance for e-way bill generations ([#25480](https://github.com/frappe/erpnext/pull/25480)) +- Add total available stock field in PO ([#24878](https://github.com/frappe/erpnext/pull/24878)) +- Refactored Setup Taxes and Charges ([#24805](https://github.com/frappe/erpnext/pull/24805)) +- Inpatient Occupancy Table Editable for Healthcare Admin ([#24989](https://github.com/frappe/erpnext/pull/24989)) +- Added Disable Rounded Total in sales transactions ([#25362](https://github.com/frappe/erpnext/pull/25362)) + + +### Fixes + +- Incorrect GL Entry validation ([#25474](https://github.com/frappe/erpnext/pull/25474)) +- Cannot create item variants ([#25433](https://github.com/frappe/erpnext/pull/25433)) +- Leave policy in leave allocation ([#25334](https://github.com/frappe/erpnext/pull/25334)) +- Let Administrator delete company transactions ([#25300](https://github.com/frappe/erpnext/pull/25300)) +- Display reconcile tool when closing balance 0 ([#25417](https://github.com/frappe/erpnext/pull/25417)) +- Bulk Salary Structure Assignment ([#25389](https://github.com/frappe/erpnext/pull/25389)) +- Payment amount showing in foreign currency ([#25518](https://github.com/frappe/erpnext/pull/25518)) +- Commit changes to shipment status in database ([#25374](https://github.com/frappe/erpnext/pull/25374)) +- Add amend perm for loan and system manager for loan doctypes ([#25393](https://github.com/frappe/erpnext/pull/25393)) +- Cashier query in POS Opening/Closing Entry ([#25398](https://github.com/frappe/erpnext/pull/25398)) +- Apply single transaction threshold on net_total instead of supplier credit amount ([#25243](https://github.com/frappe/erpnext/pull/25243)) +- Update allocated amount after paid amount is changed in PE ([#25528](https://github.com/frappe/erpnext/pull/25528)) +- Remove non-standard module cards from Home Workspace ([#25391](https://github.com/frappe/erpnext/pull/25391)) +- Cannot scan spacebar character in pos ([#25479](https://github.com/frappe/erpnext/pull/25479)) +- Permission error after submitting exchange rate revaluation ([#25432](https://github.com/frappe/erpnext/pull/25432)) +- Equality check instead of assignment in cart ([#25372](https://github.com/frappe/erpnext/pull/25372)) +- Disable auto naming of customer during import ([#25152](https://github.com/frappe/erpnext/pull/25152)) +- Additional Salary component amount not getting set ([#25355](https://github.com/frappe/erpnext/pull/25355)) +- Round off values near to zero ([#25304](https://github.com/frappe/erpnext/pull/25304)) +- Allow to cancel loan with cancelled repayment entry ([#25508](https://github.com/frappe/erpnext/pull/25508)) +- Currency symbol in bank transaction list view ([#25336](https://github.com/frappe/erpnext/pull/25336)) +- Incorrect batch picked in subcontracted purchase receipt ([#25186](https://github.com/frappe/erpnext/pull/25186)) +- Issue in project custom status ([#25452](https://github.com/frappe/erpnext/pull/25452)) +- Shipment pickup_to, pickup_from functionality. ([#25359](https://github.com/frappe/erpnext/pull/25359)) +- Stock ledger entry created against draft stock entry ([#25539](https://github.com/frappe/erpnext/pull/25539)) +- Ageing errors in PSOA ([#25529](https://github.com/frappe/erpnext/pull/25529)) +- Permission error while adding weekly holidays ([#25450](https://github.com/frappe/erpnext/pull/25450)) +- Filter for employees in salary slip ([#25360](https://github.com/frappe/erpnext/pull/25360)) +- Backward compatibility for GSTR-1 report ([#25444](https://github.com/frappe/erpnext/pull/25444)) +- Incorrect incoming rate for the sales return ([#25145](https://github.com/frappe/erpnext/pull/25145)) +- POS print receipt ([#25328](https://github.com/frappe/erpnext/pull/25328)) +- Laboratory Module patch ([#25431](https://github.com/frappe/erpnext/pull/25431)) +- Performance: fetching exchange rate on every line item slows down PO ([#25345](https://github.com/frappe/erpnext/pull/25345)) +- Presentation currency in statement of accounts ([#25367](https://github.com/frappe/erpnext/pull/25367)) +- Serial No not updated correctly via Inter Company Stock Transfer ([#25006](https://github.com/frappe/erpnext/pull/25006)) +- Ignore Customer Group Perm on All Products page ([#25396](https://github.com/frappe/erpnext/pull/25396)) +- Change subcontracted item display ([#25425](https://github.com/frappe/erpnext/pull/25425)) +- Add company validation for e-invoicing ([#25348](https://github.com/frappe/erpnext/pull/25348)) From d8cd5a95cb86e2589d6a6724bfc3c05d4e9db450 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 1 May 2021 15:12:44 +0550 Subject: [PATCH 0665/1264] bumped to version 13.2.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4da0605370..a988d7217d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.1.0' +__version__ = '13.2.0' def get_default_company(user=None): '''Get default company for user''' From 2fb573781df1bbbe0d45dc7a2a13585c836b620e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 May 2021 17:56:40 +0530 Subject: [PATCH 0666/1264] fix: rename field has not updated value of deposit and withdrawal fields --- .../patches/v13_0/delete_old_bank_reconciliation_doctypes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py index af1f6e7ec1..77a23cfc3f 100644 --- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py +++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py @@ -22,5 +22,7 @@ def execute(): frappe.delete_doc("Page", "bank-reconciliation", force=1) + frappe.reload_doc('accounts', 'doctype', 'bank_transaction') + rename_field("Bank Transaction", "debit", "deposit") rename_field("Bank Transaction", "credit", "withdrawal") From 1efafb3ce841948116c57f2203a880d9df796c6a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 1 May 2021 21:52:43 +0530 Subject: [PATCH 0667/1264] fix: Do not roundoff balance amount --- erpnext/loan_management/doctype/loan/loan.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..f480402c35 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -70,7 +70,6 @@ class Loan(AccountsController): frappe.throw(_("Repay From Salary can be selected only for term loans")) def make_repayment_schedule(self): - if not self.repayment_start_date: frappe.throw(_("Repayment Start Date is mandatory for term loans")) @@ -78,10 +77,9 @@ class Loan(AccountsController): payment_date = self.repayment_start_date balance_amount = self.loan_amount while(balance_amount > 0): - interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100)) + interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100)) principal_amount = self.monthly_repayment_amount - interest_amount - balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount) - + balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount) if balance_amount < 0: principal_amount += balance_amount balance_amount = 0.0 From 824f08956951de31839a79f27d35d055dc39ddee Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 1 May 2021 22:07:13 +0530 Subject: [PATCH 0668/1264] fix: Auto write off on loan closure --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- .../loan_management/doctype/loan_repayment/loan_repayment.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index f480402c35..c570e5efda 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -193,7 +193,8 @@ def request_loan_closure(loan, posting_date=None): posting_date = getdate() amounts = calculate_amounts(loan, posting_date) - pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest'] + pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \ + amounts['interest_amount'] + amounts['penalty_amount'] loan_type = frappe.get_value('Loan', loan, 'loan_type') write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount') diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 728eadf22a..3d99b1f304 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -435,7 +435,6 @@ def get_amounts(amounts, against_loan, posting_date): @frappe.whitelist() def calculate_amounts(against_loan, posting_date, payment_type=''): - amounts = { 'penalty_amount': 0.0, 'interest_amount': 0.0, From a66184fe3cd9527b8b13c9b4897b46ec0dcbed8c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 12:22:16 +0530 Subject: [PATCH 0669/1264] fix: Remove redundant get_doc --- erpnext/regional/india/setup.py | 2 +- erpnext/setup/doctype/company/company.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index aedd6c88ab..71b5661a4e 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -646,7 +646,7 @@ def make_custom_fields(update=True): def make_fixtures(company=None): docs = [] - company = company.name if company else frappe.db.get_value("Global Defaults", None, "default_company") + company = company or frappe.db.get_value("Global Defaults", None, "default_company") set_salary_components(docs) set_tds_account(docs, company) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index aa4bc98875..779e976181 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -435,13 +435,12 @@ def get_name_with_abbr(name, company): return " - ".join(parts) -def install_country_fixtures(company): - company_doc = frappe.get_doc("Company", company) - path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(company_doc.country)) +def install_country_fixtures(company, country): + path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) if os.path.exists(path.encode("utf-8")): try: - module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(company_doc.country)) - frappe.get_attr(module_name)(company_doc, False) + module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(country)) + frappe.get_attr(module_name)(company, False) except Exception as e: frappe.log_error() frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(company_doc.country))) From f6610c96dd6cf3585dcbadfaf90bc973a60bb40f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 12:23:11 +0530 Subject: [PATCH 0670/1264] fix: Gracefully handle duplicate bank account name to make setup faster --- erpnext/accounts/doctype/account/account.py | 6 +++++ .../chart_of_accounts/chart_of_accounts.py | 18 ------------- erpnext/public/js/setup_wizard.js | 26 ------------------- 3 files changed, 6 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 0606823821..60269943cf 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -28,6 +28,12 @@ class Account(NestedSet): from erpnext.accounts.utils import get_autoname_with_number self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company) + def before_insert(self): + # Update Bank account name if conflicting with any other account + if frappe.flags.in_install and self.account_type == 'Bank': + if frappe.db.get_value('Account', {'account_name': self.account_name}): + self.account_name = self.account_name + '-1' + def validate(self): from erpnext.accounts.utils import validate_field_number if frappe.local.flags.allow_unverified_charts: diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 0e3b24cda3..9d3174204b 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -188,24 +188,6 @@ def build_account_tree(tree, parent, all_accounts): # call recursively to build a subtree for current account build_account_tree(tree[child.account_name], child, all_accounts) -@frappe.whitelist() -def validate_bank_account(coa, bank_account): - accounts = [] - chart = get_chart(coa) - - if chart: - def _get_account_names(account_master): - for account_name, child in iteritems(account_master): - if account_name not in ["account_number", "account_type", - "root_type", "is_group", "tax_rate"]: - accounts.append(account_name) - - _get_account_names(child) - - _get_account_names(chart) - - return (bank_account in accounts) - @frappe.whitelist() def build_tree_from_json(chart_template, chart_data=None): ''' get chart template from its folder and parse the json to be rendered as tree ''' diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index ef03b01698..a3045724fe 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -139,36 +139,10 @@ erpnext.setup.slides_settings = [ }, validate: function () { - let me = this; - let exist; - if (!this.validate_fy_dates()) { return false; } - // Validate bank name - if(me.values.bank_account){ - frappe.call({ - async: false, - method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account", - args: { - "coa": me.values.chart_of_accounts, - "bank_account": me.values.bank_account - }, - callback: function (r) { - if(r.message){ - exist = r.message; - me.get_field("bank_account").set_value(""); - let message = __('Account {0} already exists. Please enter a different name for your bank account.', - [me.values.bank_account] - ); - frappe.msgprint(message); - } - } - }); - return !exist; // Return False if exist = true - } - return true; }, From c571141c1f4b0cac7a0e556e9aa7b47ee89ff5b4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 15:31:04 +0530 Subject: [PATCH 0671/1264] fix: Test cases --- .../loan_management/doctype/loan/test_loan.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 6f8da3166f..fae6f860b6 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -56,25 +56,25 @@ class TestLoan(unittest.TestCase): def test_loan(self): loan = frappe.get_doc("Loan", {"applicant":self.applicant1}) self.assertEquals(loan.monthly_repayment_amount, 15052) - self.assertEquals(loan.total_interest_payable, 21034) - self.assertEquals(loan.total_payment, 301034) + self.assertEquals(flt(loan.total_interest_payable, 0), 21034) + self.assertEquals(flt(loan.total_payment, 0), 301034) schedule = loan.repayment_schedule self.assertEqual(len(schedule), 20) - for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: - self.assertEqual(schedule[idx].principal_amount, principal_amount) - self.assertEqual(schedule[idx].interest_amount, interest_amount) - self.assertEqual(schedule[idx].balance_loan_amount, balance_loan_amount) + for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: + self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount) + self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount) + self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount) loan.repayment_method = "Repay Fixed Amount per Period" loan.monthly_repayment_amount = 14000 loan.save() self.assertEquals(len(loan.repayment_schedule), 22) - self.assertEquals(loan.total_interest_payable, 22712) - self.assertEquals(loan.total_payment, 302712) + self.assertEquals(flt(loan.total_interest_payable, 0), 22712) + self.assertEquals(flt(loan.total_payment, 0), 302712) def test_loan_with_security(self): From 35d4829383e278809bc64784b5d8a558db2ac61f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 May 2021 13:07:26 +0530 Subject: [PATCH 0672/1264] fix: serial no changed after saving stock reconciliation --- .../report/project_profitability/project_profitability.py | 5 +++-- .../doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 5ad2d85232..9139d84fac 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import flt def execute(filters=None): columns, data = [], [] @@ -52,8 +53,8 @@ def get_rows(filters): def calculate_cost_and_profit(data): for row in data: - row.fractional_cost = row.base_gross_pay * row.utilization - row.profit = row.base_grand_total - row.base_gross_pay * row.utilization + row.fractional_cost = flt(row.base_gross_pay) * flt(row.utilization) + row.profit = flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization) return data def get_conditions(filters): diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 1396f19d3f..e4cdcb4116 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -72,7 +72,7 @@ class StockReconciliation(StockController): if item_dict.get("serial_nos"): item.current_serial_no = item_dict.get("serial_nos") - if self.purpose == "Stock Reconciliation": + if self.purpose == "Stock Reconciliation" and not item.serial_no:: item.serial_no = item.current_serial_no item.current_qty = item_dict.get("qty") From 6a5a380c07893f45f72f57ff3ac135b48f207ed0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 2 May 2021 18:02:28 +0530 Subject: [PATCH 0673/1264] fix: total stock summary report not working --- erpnext/stock/report/total_stock_summary/total_stock_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py index ed52393923..59c253c425 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py @@ -51,7 +51,7 @@ def get_total_stock(filters): INNER JOIN `tabWarehouse` warehouse ON warehouse.name = ledger.warehouse WHERE - actual_qty != 0 %s""" % (columns, conditions)) + ledger.actual_qty != 0 %s""" % (columns, conditions)) def validate_filters(filters): if filters.get("group_by") == 'Company' and \ From 72e602ae548a4dd16ace8788f574c09374941b68 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 18:22:29 +0530 Subject: [PATCH 0674/1264] fix: Add validation for GST Settings --- .../regional/doctype/gst_settings/gst_settings.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.py b/erpnext/regional/doctype/gst_settings/gst_settings.py index bc956e9fa8..af3d92e59a 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.py +++ b/erpnext/regional/doctype/gst_settings/gst_settings.py @@ -19,6 +19,21 @@ class GSTSettings(Document): from tabAddress where country = "India" and ifnull(gstin, '')!='' ''') self.set_onload('data', data) + def validate(self): + # Validate duplicate accounts + self.validate_duplicate_accounts() + + def validate_duplicate_accounts(self): + account_list = [] + for account in self.get('gst_accounts'): + for fieldname in ['cgst_account', 'sgst_account', 'igst_account', 'cess_account']: + if account.get(fieldname) in account_list: + frappe.throw(_("Account {0} appears multiple times").format( + frappe.bold(account.get(fieldname)))) + + if account.get(fieldname): + account_list.append(account.get(fieldname)) + @frappe.whitelist() def send_reminder(): frappe.has_permission('GST Settings', throw=True) From 1bac72b74d41a7284ca5028015a386b6cdaa61b5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 22:29:48 +0530 Subject: [PATCH 0675/1264] fix: Add GST accounts to GST Settings --- erpnext/regional/india/setup.py | 52 ++++++++++++++++++++++++ erpnext/setup/doctype/company/company.py | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 71b5661a4e..2c0a285546 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -15,6 +15,7 @@ def setup(company=None, patch=True): setup_company_independent_fixtures(patch=patch) if not patch: make_fixtures(company) + setup_gst_settings(company) # TODO: for all countries def setup_company_independent_fixtures(patch=False): @@ -664,6 +665,57 @@ def make_fixtures(company=None): # create records for Tax Withholding Category set_tax_withholding_category(company) +def setup_gst_settings(company): + # Will only add default GST accounts if present + input_account_names = ['Input Tax CGST', 'Input Tax SGST', 'Input Tax IGST'] + output_account_names = ['Output Tax CGST', 'Output Tax SGST', 'Output Tax IGST'] + gst_settings = frappe.get_single('GST Settings') + existing_account_list = [] + + for account in gst_settings.get('gst_accounts'): + for key in ['cgst_account', 'sgst_account', 'igst_account']: + existing_account_list.append(account.get(key)) + + gst_accounts = frappe._dict(frappe.get_all("Account", + {'company': company, 'name': ('like', "%GST%")}, ['account_name', 'name'], as_list=1)) + + all_input_account_exists = 0 + all_output_account_exists = 0 + + for account in input_account_names: + if not gst_accounts.get(account): + all_input_account_exists = 1 + + # Check if already added in GST Settings + if gst_accounts.get(account) in existing_account_list: + all_input_account_exists = 1 + + for account in output_account_names: + if not gst_accounts.get(account): + all_output_account_exists = 1 + + # Check if already added in GST Settings + if gst_accounts.get(account) in existing_account_list: + all_output_account_exists = 1 + + if not all_input_account_exists: + gst_settings.append('gst_accounts', { + 'company': company, + 'cgst_account': gst_accounts.get(input_account_names[0]), + 'sgst_account': gst_accounts.get(input_account_names[1]), + 'igst_account': gst_accounts.get(input_account_names[2]) + }) + + if not all_output_account_exists: + gst_settings.append('gst_accounts', { + 'company': company, + 'cgst_account': gst_accounts.get(output_account_names[0]), + 'sgst_account': gst_accounts.get(output_account_names[1]), + 'igst_account': gst_accounts.get(output_account_names[2]) + }) + + gst_settings.save() + def set_salary_components(docs): docs.extend([ {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 779e976181..9a74a2e68e 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -443,7 +443,7 @@ def install_country_fixtures(company, country): frappe.get_attr(module_name)(company, False) except Exception as e: frappe.log_error() - frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(company_doc.country))) + frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(country))) def update_company_current_month_sales(company): From 9cc7c294e78b2580351b7e033bb6506143d3c263 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 3 May 2021 10:25:53 +0530 Subject: [PATCH 0676/1264] Update stock_reconciliation.py --- .../stock/doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e4cdcb4116..2029b0708a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -72,7 +72,7 @@ class StockReconciliation(StockController): if item_dict.get("serial_nos"): item.current_serial_no = item_dict.get("serial_nos") - if self.purpose == "Stock Reconciliation" and not item.serial_no:: + if self.purpose == "Stock Reconciliation" and not item.serial_no: item.serial_no = item.current_serial_no item.current_qty = item_dict.get("qty") From cdc99cdd49c152b00ecfce464917390feb323890 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 3 May 2021 11:54:55 +0530 Subject: [PATCH 0677/1264] fix(pos): incorrect expense account set in pos invoice (#25543) --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a2b95cb757..f91b432a39 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -562,7 +562,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ weight_uom: item.weight_uom, manufacturer: item.manufacturer, stock_uom: item.stock_uom, - pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', + pos_profile: cint(me.frm.doc.is_pos) ? me.frm.doc.pos_profile : '', cost_center: item.cost_center, tax_category: me.frm.doc.tax_category, item_tax_template: item.item_tax_template, From 8f34ca4ac6133c38f2d789e9b8bfd67c21555894 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 May 2021 15:32:13 +0530 Subject: [PATCH 0678/1264] fix: stock reconciliation getting time out error during submission --- .../stock/doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 1396f19d3f..0ee6dc7877 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -469,7 +469,7 @@ class StockReconciliation(StockController): def submit(self): if len(self.items) > 100: msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage")) - self.queue_action('submit') + self.queue_action('submit', timeout=2000) else: self._submit() From f1bdfac7a8855be07d101d99b6ca863d1f42ed3c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 3 May 2021 18:37:00 +0530 Subject: [PATCH 0679/1264] fix: Employee Separation (#25503) * fix: Employee Separation - add ignore_mandatory flag for project creation - form clean-up * fix: Employee Separation test --- .../employee_separation.json | 761 ++++-------------- .../test_employee_separation.py | 2 +- erpnext/hr/utils.py | 4 +- 3 files changed, 160 insertions(+), 607 deletions(-) diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json index f44d83060a..7af209887f 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.json +++ b/erpnext/hr/doctype/employee_separation/employee_separation.json @@ -1,626 +1,177 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "HR-EMP-SEP-.YYYY.-.#####", - "beta": 0, - "creation": "2018-05-10 02:29:16.740490", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "HR-EMP-SEP-.YYYY.-.#####", + "creation": "2018-05-10 02:29:16.740490", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "designation", + "employee_grade", + "column_break_7", + "company", + "boarding_status", + "resignation_letter_date", + "project", + "table_for_activity", + "employee_separation_template", + "activities", + "notify_users_by_email", + "section_break_14", + "exit_interview", + "amended_from" + ], "fields": [ { - "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": 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": "employee.employee_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": 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": "employee.resignation_letter_date", - "fieldname": "resignation_letter_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": "Resignation Letter 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": 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": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_bulk_edit": 0, - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_in_quick_entry": 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 - }, - { - "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": "employee_separation_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 Separation Template", - "length": 0, - "no_copy": 0, - "options": "Employee Separation 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": "employee.company", - "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 + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Employee Name", + "read_only": 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 - }, + "fetch_from": "employee.resignation_letter_date", + "fieldname": "resignation_letter_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Resignation Letter Date", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "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", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.designation", - "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" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.grade", - "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" + }, { - "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": "employee_separation_template", + "fieldtype": "Link", + "label": "Employee Separation Template", + "options": "Employee Separation Template" + }, { - "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 - }, + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "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 - }, + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "exit_interview", - "fieldtype": "Text Editor", - "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": "Exit Interview Summary", - "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": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Department", + "options": "Department", + "read_only": 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 Separation", - "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 + "fetch_from": "employee.designation", + "fieldname": "designation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fetch_from": "employee.grade", + "fieldname": "employee_grade", + "fieldtype": "Link", + "label": "Employee Grade", + "options": "Employee Grade", + "read_only": 1 + }, + { + "fieldname": "table_for_activity", + "fieldtype": "Section Break", + "label": "Separation Activities" + }, + { + "allow_on_submit": 1, + "fieldname": "activities", + "fieldtype": "Table", + "label": "Activities", + "options": "Employee Boarding Activity" + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, + { + "fieldname": "exit_interview", + "fieldtype": "Text Editor", + "label": "Exit Interview Summary" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Separation", + "print_hide": 1, + "read_only": 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-03 16:15:39.025898", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Separation", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2021-04-28 15:58:36.020196", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Separation", + "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": 1, - "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 + ], + "quick_entry": 1, + "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_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py index 2fa114d345..713fcf526b 100644 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py @@ -18,7 +18,7 @@ class TestEmployeeSeparation(unittest.TestCase): 'activity_name': 'Deactivate Employee', 'role': 'HR User' }) - separation.status = 'Pending' + separation.boarding_status = 'Pending' separation.insert() separation.submit() self.assertEqual(separation.docstatus, 1) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 190eb4f10a..2540b3db63 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -32,13 +32,15 @@ class EmployeeBoardingController(Document): project_name += self.job_applicant else: project_name += self.employee + project = frappe.get_doc({ "doctype": "Project", "project_name": project_name, "expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date, "department": self.department, "company": self.company - }).insert(ignore_permissions=True) + }).insert(ignore_permissions=True, ignore_mandatory=True) + self.db_set("project", project.name) self.db_set("boarding_status", "Pending") self.reload() From e36f3030422babc8aed6207c9534716bfb5ae921 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 3 May 2021 19:49:22 +0530 Subject: [PATCH 0680/1264] fix: use percent string templates for db.sql calls --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 3fc1df76bc..98d08c0a18 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -935,8 +935,8 @@ def get_bin_details(item_code, warehouse, company=None): def get_company_total_stock(item_code, company): return frappe.db.sql("""SELECT sum(actual_qty) from (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) - WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'""" - .format(company, item_code))[0][0] + WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""", + (company, item_code))[0][0] @frappe.whitelist() def get_serial_no_details(item_code, warehouse, stock_qty, serial_no): From 308905b1bee572c0161c26e0f89bf8a2a86c0c1f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 3 May 2021 23:34:34 +0530 Subject: [PATCH 0681/1264] fix: semgrep, refactor default mutable dict --- erpnext/stock/get_item_details.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 98d08c0a18..3832415db6 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -470,7 +470,9 @@ def get_item_tax_template(args, item, out): item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out) item_group = item_group_doc.parent_item_group -def _get_item_tax_template(args, taxes, out={}, for_validate=False): +def _get_item_tax_template(args, taxes, out=None, for_validate=False): + if out is None: + out = {} taxes_with_validity = [] taxes_with_no_validity = [] From 076020643d55252c881cdc20ff1e169af97fb897 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:26:49 +0530 Subject: [PATCH 0682/1264] fix: empty payment term column in accounts receivable report (#25556) --- .../report/accounts_receivable/accounts_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 444b40ed79..db605f7285 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -364,7 +364,7 @@ class ReceivablePayableReport(object): payment_terms_details = frappe.db.sql(""" select si.name, si.party_account_currency, si.currency, si.conversion_rate, - ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount + ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount from `tab{0}` si, `tabPayment Schedule` ps where si.name = ps.parent and @@ -394,7 +394,7 @@ class ReceivablePayableReport(object): "due_date": d.due_date, "invoiced": invoiced, "invoice_grand_total": row.invoiced, - "payment_term": d.description, + "payment_term": d.description or d.payment_term, "paid": d.paid_amount + d.discounted_amount, "credit_note": 0.0, "outstanding": invoiced - d.paid_amount - d.discounted_amount From 1e554378c7b79b03fc669056fc2653d993b3466a Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:28:24 +0530 Subject: [PATCH 0683/1264] fix(pos): incorrect expense account set in pos invoice (#25571) From 384f4b5b7e6d23436046d0f84888c5687cdca7f7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:33:49 +0530 Subject: [PATCH 0684/1264] fix: can't open general ledger from consolidated financial report (#25542) --- .../consolidated_financial_statement.js | 224 +++++++++--------- .../consolidated_financial_statement.py | 5 +- 2 files changed, 120 insertions(+), 109 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 09479221fb..1363b53746 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -2,118 +2,128 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Consolidated Financial Statement"] = { - "filters": [ - { - "fieldname":"company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname":"filter_based_on", - "label": __("Filter Based On"), - "fieldtype": "Select", - "options": ["Fiscal Year", "Date Range"], - "default": ["Fiscal Year"], - "reqd": 1, - on_change: function() { - let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); - frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); - frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); +frappe.require("assets/erpnext/js/financial_statements.js", function() { + frappe.query_reports["Consolidated Financial Statement"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"filter_based_on", + "label": __("Filter Based On"), + "fieldtype": "Select", + "options": ["Fiscal Year", "Date Range"], + "default": ["Fiscal Year"], + "reqd": 1, + on_change: function() { + let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); + frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); + frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); - frappe.query_report.refresh(); + frappe.query_report.refresh(); + } + }, + { + "fieldname":"period_start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"period_end_date", + "label": __("End Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"from_fiscal_year", + "label": __("Start Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname":"to_fiscal_year", + "label": __("End Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book" + }, + { + "fieldname":"report", + "label": __("Report"), + "fieldtype": "Select", + "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], + "default": "Balance Sheet", + "reqd": 1 + }, + { + "fieldname": "presentation_currency", + "label": __("Currency"), + "fieldtype": "Select", + "options": erpnext.get_presentation_currency_list(), + "default": frappe.defaults.get_user_default("Currency") + }, + { + "fieldname":"accumulated_in_group_company", + "label": __("Accumulated Values in Group Company"), + "fieldtype": "Check", + "default": 0 + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check", + "default": 1 + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data && column.fieldname=="account") { + value = data.account_name || value; + + column.link_onclick = + "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + column.is_tree = true; } - }, - { - "fieldname":"period_start_date", - "label": __("Start Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"period_end_date", - "label": __("End Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"from_fiscal_year", - "label": __("Start Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 - }, - { - "fieldname":"to_fiscal_year", - "label": __("End Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 - }, - { - "fieldname":"finance_book", - "label": __("Finance Book"), - "fieldtype": "Link", - "options": "Finance Book" - }, - { - "fieldname":"report", - "label": __("Report"), - "fieldtype": "Select", - "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], - "default": "Balance Sheet", - "reqd": 1 - }, - { - "fieldname": "presentation_currency", - "label": __("Currency"), - "fieldtype": "Select", - "options": erpnext.get_presentation_currency_list(), - "default": frappe.defaults.get_user_default("Currency") - }, - { - "fieldname":"accumulated_in_group_company", - "label": __("Accumulated Values in Group Company"), - "fieldtype": "Check", - "default": 0 - }, - { - "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), - "fieldtype": "Check", - "default": 1 - } - ], - "formatter": function(value, row, column, data, default_formatter) { - value = default_formatter(value, row, column, data); - if (!data.parent_account) { - value = $(`${value}`); + value = default_formatter(value, row, column, data); - var $value = $(value).css("font-weight", "bold"); + if (!data.parent_account) { + value = $(`${value}`); - value = $value.wrap("

    ").parent().html(); - } - return value; - }, - onload: function() { - let fiscal_year = frappe.defaults.get_user_default("fiscal_year") + var $value = $(value).css("font-weight", "bold"); - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - period_start_date: fy.year_start_date, - period_end_date: fy.year_end_date + value = $value.wrap("

    ").parent().html(); + } + return value; + }, + onload: function() { + let fiscal_year = frappe.defaults.get_user_default("fiscal_year") + + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + period_start_date: fy.year_start_date, + period_end_date: fy.year_end_date + }); }); - }); + } } -} +}); \ No newline at end of file diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 0c4a422440..094f5db89b 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -329,8 +329,9 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com has_value = False total = 0 row = frappe._dict({ - "account_name": _(d.account_name), - "account": _(d.account_name), + "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name)) + if d.account_number else _(d.account_name)), + "account": _(d.name), "parent_account": _(d.parent_account), "indent": flt(d.indent), "year_start_date": start_date, From ba8dc1ffbd8d02cb6d01e3b10c0c21a9956bcca3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 4 May 2021 15:03:10 +0530 Subject: [PATCH 0685/1264] fix: stock balance and batchwise balance history report showing different closing stock --- .../batch_wise_balance_history/batch_wise_balance_history.py | 2 +- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 087c12ed2d..01927c2d10 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -70,7 +70,7 @@ def get_stock_ledger_entries(filters): return frappe.db.sql(""" select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty from `tabStock Ledger Entry` - where docstatus < 2 and ifnull(batch_no, '') != '' %s + where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s group by voucher_no, batch_no, item_code, warehouse order by item_code, warehouse""" % conditions, as_dict=1) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 6dfede4590..bbd73e9112 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -165,7 +165,7 @@ def get_stock_ledger_entries(filters, items): select sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate, sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, - sle.item_code as name, sle.voucher_no, sle.stock_value + sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no from `tabStock Ledger Entry` sle force index (posting_sort_index) where sle.docstatus < 2 %s %s @@ -193,7 +193,7 @@ def get_item_warehouse_map(filters, sle): qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)] - if d.voucher_type == "Stock Reconciliation": + if d.voucher_type == "Stock Reconciliation" and not d.batch_no: qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty) else: qty_diff = flt(d.actual_qty) From eebc6e9277b3d461ae4da4a92fa9cc45c27eea55 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 4 May 2021 17:05:12 +0530 Subject: [PATCH 0686/1264] refactor: Show item's full name on hover over item in POS (#25554) Co-authored-by: Saqib --- .../page/point_of_sale/pos_item_selector.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 709fe57747..9384ae5542 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -81,13 +81,24 @@ erpnext.PointOfSale.ItemSelector = class { const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; + let qty_to_display = actual_qty; + + if (Math.round(qty_to_display) > 999) { + qty_to_display = Math.round(qty_to_display)/1000; + qty_to_display = qty_to_display.toFixed(1) + 'K'; + } + function get_item_image_html() { if (!me.hide_images && item_image) { - return `
    + return `
    + ${qty_to_display}
    +
    ${frappe.get_abbr(item.item_name)}
    `; } else { - return `
    ${frappe.get_abbr(item.item_name)}
    `; + return `
    + ${qty_to_display}
    +
    ${frappe.get_abbr(item.item_name)}
    `; } } @@ -95,13 +106,12 @@ erpnext.PointOfSale.ItemSelector = class { `
    + title="${item.item_name}"> ${get_item_image_html()}
    - ${frappe.ellipsis(item.item_name, 18)}
    ${format_currency(item.price_list_rate, item.currency, 0) || 0}
    @@ -316,4 +326,4 @@ erpnext.PointOfSale.ItemSelector = class { toggle_component(show) { show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } -}; \ No newline at end of file +}; From 18ad15ed160ce209cea7b6da7cc6fc07fb3ee152 Mon Sep 17 00:00:00 2001 From: Asharam Seervi Date: Tue, 4 May 2021 19:28:07 +0530 Subject: [PATCH 0687/1264] fix: designation insufficient permission on lead doctype. (#25331) --- erpnext/hr/doctype/designation/designation.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/designation/designation.json b/erpnext/hr/doctype/designation/designation.json index 4c3888be4a..bab6b90d1a 100644 --- a/erpnext/hr/doctype/designation/designation.json +++ b/erpnext/hr/doctype/designation/designation.json @@ -182,6 +182,10 @@ "share": 1, "submit": 0, "write": 1 + }, + { + "read": 1, + "role": "Sales User" } ], "quick_entry": 1, @@ -191,4 +195,4 @@ "track_changes": 0, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} From 04923d6a65637e7e6c50db826b46e92bbb491c2e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 4 May 2021 21:01:12 +0530 Subject: [PATCH 0688/1264] fix: function call to update payment schedule labels --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f91b432a39..d218a5ee5f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1384,7 +1384,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], this.frm.doc.currency, "payment_schedule"); - + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) @@ -2034,7 +2034,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { me.frm.set_value("payment_schedule", r.message); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } }) From 2a20a03c28c32944b5a5726241906010d42456af Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 5 May 2021 11:59:15 +0530 Subject: [PATCH 0689/1264] fix: check for None in item.schedule_date before setting --- erpnext/controllers/buying_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index b686dc026c..3f2d3390c0 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -838,9 +838,10 @@ class BuyingController(StockController): if not self.get("items"): return - earliest_schedule_date = min([d.schedule_date for d in self.get("items")]) - if earliest_schedule_date: - self.schedule_date = earliest_schedule_date + if any(d.schedule_date for d in self.get("items")): + # Select earliest schedule_date. + self.schedule_date = min(d.schedule_date for d in self.get("items") + if d.schedule_date is not None) if self.schedule_date: for d in self.get('items'): From 1bb7bb74adb4b07bd2efc6e2fb641ed84a262fae Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 12:19:57 +0530 Subject: [PATCH 0690/1264] fix: Check if payment schedule exits before updating label --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d218a5ee5f..10c802c6f0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1379,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if (this.frm.fields_dict["payment_schedule"]) { + if (this.frm.doc.payment_schedule.length > 0) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], From 136eb30081b7b74d3b500acf6731696b4692da8e Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 5 May 2021 12:26:29 +0530 Subject: [PATCH 0691/1264] fix: use get_serial_nos for splitting --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4461f29fe3..4de877353a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1111,7 +1111,7 @@ class SalesInvoice(SellingController): if not item.serial_no: continue - for serial_no in item.serial_no.split("\n"): + for serial_no in get_serial_nos(item.serial_no): if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code: frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice) From ffea9d4126c9ab544ac7a659874b9f41254404ec Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 12:28:40 +0530 Subject: [PATCH 0692/1264] fix: Check if payment schedule exists --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 10c802c6f0..0af8da77a0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1379,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if (this.frm.doc.payment_schedule.length > 0) { + if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], From 85b675a554cfcdf78cfa5255e0a12e06e7e02e44 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 20:57:31 +0530 Subject: [PATCH 0693/1264] fix: Invoices not fetch during payment reconciliation --- .../doctype/payment_reconciliation/payment_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cf6ec18f3b..6635128f9e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -114,7 +114,7 @@ class PaymentReconciliation(Document): 'party_type': self.party_type, 'voucher_type': voucher_type, 'account': self.receivable_payable_account - }, as_dict=1, debug=1) + }, as_dict=1) def add_payment_entries(self, entries): self.set('payments', []) From bb3e5d00f44869132d869fbead5046ba1d3ddfc6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 24 Apr 2021 17:28:33 +0530 Subject: [PATCH 0694/1264] fix: allow to receive same serial numbers multiple times --- .../purchase_receipt/test_purchase_receipt.py | 83 +++++++++++++------ erpnext/stock/doctype/serial_no/serial_no.py | 18 +--- .../stock/report/serial_no_ledger/__init__.py | 0 .../serial_no_ledger/serial_no_ledger.js | 52 ++++++++++++ .../serial_no_ledger/serial_no_ledger.json | 33 ++++++++ .../serial_no_ledger/serial_no_ledger.py | 53 ++++++++++++ erpnext/stock/stock_ledger.py | 53 ++++++++++-- 7 files changed, 247 insertions(+), 45 deletions(-) create mode 100644 erpnext/stock/report/serial_no_ledger/__init__.py create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.js create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.json create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.py diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 16eea24f84..f9b2c1d787 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -13,8 +13,9 @@ from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import make_item from six import iteritems +from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class TestPurchaseReceipt(unittest.TestCase): def setUp(self): @@ -144,6 +145,62 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})) self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no})) + def test_duplicate_serial_nos(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item = frappe.db.exists("Item", {'item_name': 'Test Serialized Item 123'}) + if not item: + item = create_item("Test Serialized Item 123") + item.has_serial_no = 1 + item.serial_no_series = "TSI123-.####" + item.save() + else: + item = frappe.get_doc("Item", {'item_name': 'Test Serialized Item 123'}) + + # First make purchase receipt + pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500) + pr.load_from_db() + + serial_nos = frappe.db.get_value('Stock Ledger Entry', + {'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no') + + serial_nos = get_serial_nos(serial_nos) + + self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos) + + # Then tried to receive same serial nos in difference company + pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, + warehouse = 'Stores - _TC1') + + self.assertRaises(SerialNoDuplicateError, pr_different_company.submit) + + # Then made delivery note to remove the serial nos from stock + dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos)) + dn.load_from_db() + self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos) + + posting_date = add_days(today(), -3) + + # Try to receive same serial nos again in the same company with backdated. + pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True) + + self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit) + + # Try to receive same serial nos with different company with backdated. + pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, + warehouse = 'Stores - _TC1') + + self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit) + + # Receive the same serial nos after the delivery note posting date and time + make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos)) + + # Raise the error for backdated deliver note entry cancel + self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel) + def test_purchase_receipt_gl_entry(self): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", @@ -562,30 +619,6 @@ class TestPurchaseReceipt(unittest.TestCase): new_pr_doc.cancel() - def test_not_accept_duplicate_serial_no(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note - - item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0, "has_batch_no": 0}) - if not item_code: - item = make_item("Test Serial Item 1", dict(has_serial_no=1, has_batch_no=0)) - item_code = item.name - - serial_no = random_string(5) - pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no) - dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no) - - pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True) - self.assertRaises(SerialNoDuplicateError, pr2.submit) - - se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, - serial_no=serial_no, basic_rate=100, do_not_submit=True) - se.submit() - - se.cancel() - dn.cancel() - pr1.cancel() - def test_auto_asset_creation(self): asset_item = "Test Asset Item" diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index c02dd2e518..5ecc9f8140 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -243,7 +243,7 @@ def validate_serial_no(sle, item_det): if frappe.db.exists("Serial No", serial_no): sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order", "delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type", - "purchase_document_no", "company"], as_dict=1) + "purchase_document_no", "company", "status"], as_dict=1) if sr.item_code!=sle.item_code: if not allow_serial_nos_with_different_item(serial_no, sle): @@ -266,6 +266,9 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse), SerialNoWarehouseError) + if not sr.purchase_document_no: + frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError) + if sle.voucher_type in ("Delivery Note", "Sales Invoice"): if sr.batch_no and sr.batch_no != sle.batch_no: @@ -382,19 +385,6 @@ def has_serial_no_exists(sn, sle): if sn.company != sle.company: return False - status = False - if sn.purchase_document_no: - if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and - sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]): - status = True - - # If status is receipt then system will allow to in-ward the delivered serial no - if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry", - sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")): - status = False - - return status - def allow_serial_nos_with_different_item(sle_serial_no, sle): """ Allows same serial nos for raw materials and finished goods diff --git a/erpnext/stock/report/serial_no_ledger/__init__.py b/erpnext/stock/report/serial_no_ledger/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js new file mode 100644 index 0000000000..616312e311 --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js @@ -0,0 +1,52 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Serial No Ledger"] = { + "filters": [ + { + 'label': __('Item Code'), + 'fieldtype': 'Link', + 'fieldname': 'item_code', + 'reqd': 1, + 'options': 'Item', + get_query: function() { + return { + filters: { + 'has_serial_no': 1 + } + } + } + }, + { + 'label': __('Serial No'), + 'fieldtype': 'Link', + 'fieldname': 'serial_no', + 'options': 'Serial No', + 'reqd': 1 + }, + { + 'label': __('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + get_query: function() { + let company = frappe.query_report.get_filter_value('company'); + + if (company) { + return { + filters: { + 'company': company + } + } + } + } + }, + { + 'label': __('As On Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date', + 'default': frappe.datetime.get_today() + }, + ] +}; diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json new file mode 100644 index 0000000000..e20e74c78b --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-04-20 13:32:41.523219", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2021-04-20 13:33:19.015829", + "modified_by": "Administrator", + "module": "Stock", + "name": "Serial No Ledger", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Serial No Ledger", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Purchase User" + }, + { + "role": "Sales User" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py new file mode 100644 index 0000000000..c3339fd341 --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -0,0 +1,53 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe import _ +from erpnext.stock.stock_ledger import get_stock_ledger_entries +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + +def execute(filters=None): + columns = get_columns(filters) + data = get_data(filters) + return columns, data + +def get_columns(filters): + columns = [{ + 'label': _('Posting Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date' + }, { + 'label': _('Posting Time'), + 'fieldtype': 'Time', + 'fieldname': 'posting_time' + }, { + 'label': _('Voucher Type'), + 'fieldtype': 'Link', + 'fieldname': 'voucher_type', + 'options': 'DocType', + 'width': 220 + }, { + 'label': _('Voucher No'), + 'fieldtype': 'Dynamic Link', + 'fieldname': 'voucher_no', + 'options': 'voucher_type', + 'width': 220 + }, { + 'label': _('Company'), + 'fieldtype': 'Link', + 'fieldname': 'company', + 'options': 'Company', + 'width': 220 + }, { + 'label': _('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + 'width': 220 + }] + + return columns + +def get_data(filters): + return get_stock_ledger_entries(filters, '<=', order="asc") or [] + diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bbfcb7ad7d..9729987d2d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2,9 +2,11 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, erpnext +import frappe +import erpnext +import copy from frappe import _ -from frappe.utils import cint, flt, cstr, now, now_datetime +from frappe.utils import cint, flt, cstr, now, get_link_to_form from frappe.model.meta import get_field_precision from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel from erpnext.stock.utils import get_bin @@ -13,6 +15,8 @@ from six import iteritems # future reposting class NegativeStockError(frappe.ValidationError): pass +class SerialNoExistsInFutureTransaction(frappe.ValidationError): + pass _exceptions = frappe.local('stockledger_exceptions') # _exceptions = [] @@ -27,6 +31,9 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) for sle in sl_entries: + if sle.serial_no: + validate_serial_no(sle) + if cancel: sle['actual_qty'] = -flt(sle.get('actual_qty')) @@ -46,6 +53,30 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc args = sle_doc.as_dict() update_bin(args, allow_negative_stock, via_landed_cost_voucher) +def validate_serial_no(sle): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + for sn in get_serial_nos(sle.serial_no): + args = copy.deepcopy(sle) + args.serial_no = sn + args.warehouse = '' + + vouchers = [] + for row in get_stock_ledger_entries(args, '>'): + voucher_type = frappe.bold(row.voucher_type) + voucher_no = frappe.bold(get_link_to_form(row.voucher_type, row.voucher_no)) + vouchers.append(f'{voucher_type} {voucher_no}') + + if vouchers: + serial_no = frappe.bold(sn) + msg = (f'''The serial no {serial_no} has been used in the future transactions so you need to cancel them first. + The list of the transactions are as below.''' + '

    • ') + + msg += '
    • '.join(vouchers) + msg += '
    ' + + title = 'Cannot Submit' if not sle.get('is_cancelled') else 'Cannot Cancel' + frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransaction) + def validate_cancellation(args): if args[0].get("is_cancelled"): repost_entry = frappe.db.get_value("Repost Item Valuation", { @@ -718,7 +749,17 @@ def get_stock_ledger_entries(previous_sle, operator=None, conditions += " and " + previous_sle.get("warehouse_condition") if check_serial_no and previous_sle.get("serial_no"): - conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no")))) + # conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no")))) + serial_no = previous_sle.get("serial_no") + conditions += (""" and + ( + serial_no = {0} + or serial_no like {1} + or serial_no like {2} + or serial_no like {3} + ) + """).format(frappe.db.escape(serial_no), frappe.db.escape('{}\n%'.format(serial_no)), + frappe.db.escape('%\n{}'.format(serial_no)), frappe.db.escape('%\n{}\n%'.format(serial_no))) if not previous_sle.get("posting_date"): previous_sle["posting_date"] = "1900-01-01" @@ -793,12 +834,12 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \ and cint(erpnext.is_perpetual_inventory_enabled(company)): frappe.local.message_log = [] - form_link = frappe.utils.get_link_to_form("Item", item_code) + form_link = get_link_to_form("Item", item_code) message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no) - message += "

    " + _(" Here are the options to proceed:") + message += "

    " + _("Here are the options to proceed:") solutions = "
  • " + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "
  • " - solutions += "
  • " + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "
  • " + solutions += "
  • " + _("If not, you can Cancel / Submit this entry") + " {0} ".format(frappe.bold("after")) + _("performing either one below:") + "
  • " sub_solutions = "
    • " + _("Create an incoming stock transaction for the Item.") + "
    • " sub_solutions += "
    • " + _("Mention Valuation Rate in the Item master.") + "
    " msg = message + solutions + sub_solutions + "" From d502f763197b9d3891c4c2e498c132a87d35ee19 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 6 May 2021 16:10:55 +0530 Subject: [PATCH 0695/1264] feat(e-invoicing): e-way bill validity field (#25555) --- erpnext/patches.txt | 1 + .../patches/v12_0/add_ewaybill_validity_field.py | 16 ++++++++++++++++ erpnext/regional/india/e_invoice/utils.py | 12 ++++++++---- erpnext/regional/india/setup.py | 3 +++ 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v12_0/add_ewaybill_validity_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 23f9fd8ecb..7faaf26158 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -774,5 +774,6 @@ erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 erpnext.patches.v13_0.update_shipment_status erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting +erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py new file mode 100644 index 0000000000..87d98f1a56 --- /dev/null +++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill') + ] + } + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 699441be7e..b4e7a8889e 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -71,13 +71,14 @@ def validate_einvoice_fields(doc): def raise_document_name_too_long_error(): title = _('Document ID Too Long') - msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ') - msg += _('document id {} exceed 16 letters. ').format(bold(_('should not'))) + msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice') + msg += ', ' + msg += _('document id {} exceed 16 letters.').format(bold(_('should not'))) msg += '

    ' - msg += _('You must {} your {} in order to have document id of {} length 16. ').format( + msg += _('You must {} your {} in order to have document id of {} length 16.').format( bold(_('modify')), bold(_('naming series')), bold(_('maximum')) ) - msg += _('Please account for ammended documents too. ') + msg += _('Please account for ammended documents too.') frappe.throw(msg, title=title) def read_json(name): @@ -847,6 +848,7 @@ class GSPConnector(): res = self.make_request('post', self.generate_ewaybill_url, headers, data) if res.get('success'): self.invoice.ewaybill = res.get('result').get('EwbNo') + self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill') self.invoice.eway_bill_cancelled = 0 self.invoice.update(args) self.invoice.flags.updater_reference = { @@ -944,6 +946,7 @@ class GSPConnector(): self.invoice.irn = res.get('Irn') self.invoice.ewaybill = res.get('EwbNo') + self.invoice.eway_bill_validity = res.get('EwbValidTill') self.invoice.ack_no = res.get('AckNo') self.invoice.ack_date = res.get('AckDt') self.invoice.signed_einvoice = dec_signed_invoice @@ -960,6 +963,7 @@ class GSPConnector(): 'label': _('IRN Generated') } self.update_invoice() + def attach_qrcode_image(self): qrcode = self.invoice.signed_qr_code doctype = self.invoice.doctype diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 9ded8dab5b..b12e152b14 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -422,6 +422,9 @@ def make_custom_fields(update=True): dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), From f43a86d90ffb4029dcaee6768c6974f4e90e3026 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 6 May 2021 16:40:06 +0530 Subject: [PATCH 0696/1264] perf: significant reduction in time taken to save a delivery note (#25475) --- erpnext/selling/doctype/customer/customer.py | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 49ca9423e8..51d86ff0bf 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -490,7 +490,7 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0 # Outstanding based on Sales Order - outstanding_based_on_so = 0.0 + outstanding_based_on_so = 0 # if credit limit check is bypassed at sales order level, # we should not consider outstanding Sales Orders, when customer credit balance report is run @@ -501,9 +501,11 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F where customer=%s and docstatus = 1 and company=%s and per_billed < 100 and status != 'Closed'""", (customer, company)) - outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0 + outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0 # Outstanding based on Delivery Note, which are not created against Sales Order + outstanding_based_on_dn = 0 + unmarked_delivery_note_items = frappe.db.sql("""select dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item @@ -515,15 +517,29 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F and ifnull(dn_item.against_sales_invoice, '') = '' """, (customer, company), as_dict=True) - outstanding_based_on_dn = 0.0 + if not unmarked_delivery_note_items: + return outstanding_based_on_gle + outstanding_based_on_so + + si_amounts = frappe.db.sql(""" + SELECT + dn_detail, sum(amount) from `tabSales Invoice Item` + WHERE + docstatus = 1 + and dn_detail in ({}) + GROUP BY dn_detail""".format(", ".join( + frappe.db.escape(dn_item.name) + for dn_item in unmarked_delivery_note_items + )) + ) + + si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts} for dn_item in unmarked_delivery_note_items: - si_amount = frappe.db.sql("""select sum(amount) - from `tabSales Invoice Item` - where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0] + dn_amount = flt(dn_item.amount) + si_amount = flt(si_amounts.get(dn_item.name)) - if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total: - outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \ + if dn_amount > si_amount and dn_item.base_net_total: + outstanding_based_on_dn += ((dn_amount - si_amount) / dn_item.base_net_total) * dn_item.base_grand_total return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn From 900a8fb21a9a22d4f683912035777c33341730d6 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 6 May 2021 17:02:47 +0530 Subject: [PATCH 0697/1264] feat(pos): ability to retry on pos closing failure (#25595) * feat(pos): ability to retry on pos closing failure * fix: sider issues * fix: sider issues * fix: mark all queued closing entry as failed * feat: add headline message --- .../pos_closing_entry/pos_closing_entry.js | 104 ++++++++++-------- .../pos_closing_entry/pos_closing_entry.json | 21 +++- .../pos_closing_entry/pos_closing_entry.py | 4 + .../pos_closing_entry_list.js | 1 + .../pos_invoice_merge_log.py | 87 +++++++++++---- erpnext/controllers/status_updater.py | 1 + erpnext/patches.txt | 1 + .../v13_0/set_pos_closing_as_failed.py | 7 ++ 8 files changed, 158 insertions(+), 68 deletions(-) create mode 100644 erpnext/patches/v13_0/set_pos_closing_as_failed.py diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 9ea616f8e7..aa0c53e228 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -22,7 +22,43 @@ frappe.ui.form.on('POS Closing Entry', { }); if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime()); - if (frm.doc.docstatus === 1) set_html_data(frm); + + frappe.realtime.on('closing_process_complete', async function(data) { + await frm.reload_doc(); + if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) { + frappe.msgprint({ + title: __('POS Closing Failed'), + message: frm.doc.error_message, + indicator: 'orange', + clear: true + }); + } + }); + + set_html_data(frm); + }, + + refresh: function(frm) { + if (frm.doc.docstatus == 1 && frm.doc.status == 'Failed') { + const issue = 'issue'; + frm.dashboard.set_headline( + __('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue])); + + $('#jump_to_error').on('click', (e) => { + e.preventDefault(); + frappe.utils.scroll_to( + cur_frm.get_field("error_message").$wrapper, + true, + 30 + ); + }); + + frm.add_custom_button(__('Retry'), function () { + frm.call('retry', {}, () => { + frm.reload_doc(); + }); + }); + } }, pos_opening_entry(frm) { @@ -61,44 +97,24 @@ frappe.ui.form.on('POS Closing Entry', { refresh_fields(frm); set_html_data(frm); } - }) + }); + }, + + before_save: function(frm) { + for (let row of frm.doc.pos_transactions) { + frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => { + cur_frm.doc.grand_total -= flt(doc.grand_total); + cur_frm.doc.net_total -= flt(doc.net_total); + cur_frm.doc.total_quantity -= flt(doc.total_qty); + refresh_payments(doc, cur_frm, 1); + refresh_taxes(doc, cur_frm, 1); + refresh_fields(cur_frm); + set_html_data(cur_frm); + }); + } } }); -cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) { - const removed_row = locals[cdt][cdn]; - - if (!removed_row.pos_invoice) return; - - frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => { - cur_frm.doc.grand_total -= flt(doc.grand_total); - cur_frm.doc.net_total -= flt(doc.net_total); - cur_frm.doc.total_quantity -= flt(doc.total_qty); - refresh_payments(doc, cur_frm, 1); - refresh_taxes(doc, cur_frm, 1); - refresh_fields(cur_frm); - set_html_data(cur_frm); - }); -} - -frappe.ui.form.on('POS Invoice Reference', { - pos_invoice(frm, cdt, cdn) { - const added_row = locals[cdt][cdn]; - - if (!added_row.pos_invoice) return; - - frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => { - frm.doc.grand_total += flt(doc.grand_total); - frm.doc.net_total += flt(doc.net_total); - frm.doc.total_quantity += flt(doc.total_qty); - refresh_payments(doc, frm); - refresh_taxes(doc, 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]; @@ -177,11 +193,13 @@ function refresh_fields(frm) { } 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); - } - }) + if (frm.doc.docstatus === 1 && frm.doc.status == 'Submitted') { + 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 index a9b91e02a9..4d6e4a2ba0 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -30,6 +30,8 @@ "total_quantity", "column_break_16", "taxes", + "failure_description_section", + "error_message", "section_break_14", "amended_from" ], @@ -195,7 +197,7 @@ "fieldtype": "Select", "hidden": 1, "label": "Status", - "options": "Draft\nSubmitted\nQueued\nCancelled", + "options": "Draft\nSubmitted\nQueued\nFailed\nCancelled", "print_hide": 1, "read_only": 1 }, @@ -203,6 +205,21 @@ "fieldname": "period_details_section", "fieldtype": "Section Break", "label": "Period Details" + }, + { + "collapsible": 1, + "collapsible_depends_on": "error_message", + "depends_on": "error_message", + "fieldname": "failure_description_section", + "fieldtype": "Section Break", + "label": "Failure Description" + }, + { + "depends_on": "error_message", + "fieldname": "error_message", + "fieldtype": "Small Text", + "label": "Error", + "read_only": 1 } ], "is_submittable": 1, @@ -212,7 +229,7 @@ "link_fieldname": "pos_closing_entry" } ], - "modified": "2021-02-01 13:47:20.722104", + "modified": "2021-05-05 16:59:49.723261", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 1065168a50..82528728dd 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -60,6 +60,10 @@ class POSClosingEntry(StatusUpdater): def on_cancel(self): unconsolidate_pos_invoices(closing_entry=self) + @frappe.whitelist() + def retry(self): + consolidate_pos_invoices(closing_entry=self) + def update_opening_entry(self, for_cancel=False): opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry) opening_entry.pos_closing_entry = self.name if not for_cancel else None diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js index 20fd610899..cffeb4d535 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js @@ -8,6 +8,7 @@ frappe.listview_settings['POS Closing Entry'] = { "Draft": "red", "Submitted": "blue", "Queued": "orange", + "Failed": "red", "Cancelled": "red" }; 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 index 4d5472df4b..bc7874305c 100644 --- 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 @@ -13,8 +13,7 @@ from frappe.model.mapper import map_doc, map_child_doc from frappe.utils.scheduler import is_scheduler_inactive from frappe.core.page.background_jobs.background_jobs import get_info import json - -from six import iteritems +import six class POSInvoiceMergeLog(Document): def validate(self): @@ -239,7 +238,7 @@ def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() invoice_by_customer = get_invoice_customer_map(invoices) - if len(invoices) >= 1 and closing_entry: + if len(invoices) >= 10 and closing_entry: closing_entry.set_status(update=True, status='Queued') enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: @@ -252,36 +251,68 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 1: + if len(merge_logs) >= 10: closing_entry.set_status(update=True, status='Queued') enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) def create_merge_logs(invoice_by_customer, closing_entry=None): - for customer, invoices in iteritems(invoice_by_customer): - merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() - merge_log.customer = customer - merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None + try: + for customer, invoices in six.iteritems(invoice_by_customer): + merge_log = frappe.new_doc('POS Invoice Merge Log') + merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() + merge_log.customer = customer + merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None - merge_log.set('pos_invoices', invoices) - merge_log.save(ignore_permissions=True) - merge_log.submit() + merge_log.set('pos_invoices', invoices) + merge_log.save(ignore_permissions=True) + merge_log.submit() - if closing_entry: - closing_entry.set_status(update=True, status='Submitted') - closing_entry.update_opening_entry() + if closing_entry: + closing_entry.set_status(update=True, status='Submitted') + closing_entry.db_set('error_message', '') + closing_entry.update_opening_entry() + + except Exception: + frappe.db.rollback() + message_log = frappe.message_log.pop() + error_message = safe_load_json(message_log) + + if closing_entry: + closing_entry.set_status(update=True, status='Failed') + closing_entry.db_set('error_message', error_message) + raise + + finally: + frappe.db.commit() + frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user}) def cancel_merge_logs(merge_logs, closing_entry=None): - for log in merge_logs: - merge_log = frappe.get_doc('POS Invoice Merge Log', log) - merge_log.flags.ignore_permissions = True - merge_log.cancel() + try: + for log in merge_logs: + merge_log = frappe.get_doc('POS Invoice Merge Log', log) + merge_log.flags.ignore_permissions = True + merge_log.cancel() - if closing_entry: - closing_entry.set_status(update=True, status='Cancelled') - closing_entry.update_opening_entry(for_cancel=True) + if closing_entry: + closing_entry.set_status(update=True, status='Cancelled') + closing_entry.db_set('error_message', '') + closing_entry.update_opening_entry(for_cancel=True) + + except Exception: + frappe.db.rollback() + message_log = frappe.message_log.pop() + error_message = safe_load_json(message_log) + + if closing_entry: + closing_entry.set_status(update=True, status='Submitted') + closing_entry.db_set('error_message', error_message) + raise + + finally: + frappe.db.commit() + frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user}) def enqueue_job(job, **kwargs): check_scheduler_status() @@ -314,4 +345,14 @@ def check_scheduler_status(): def job_already_enqueued(job_name): enqueued_jobs = [d.get("job_name") for d in get_info()] if job_name in enqueued_jobs: - return True \ No newline at end of file + return True + +def safe_load_json(message): + JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError + + try: + json_message = json.loads(message).get('message') + except JSONDecodeError: + json_message = message + + return json_message \ No newline at end of file diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 5276da9720..4bb6138e5d 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -98,6 +98,7 @@ status_map = { ["Draft", None], ["Submitted", "eval:self.docstatus == 1"], ["Queued", "eval:self.status == 'Queued'"], + ["Failed", "eval:self.status == 'Failed'"], ["Cancelled", "eval:self.docstatus == 2"], ] } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7faaf26158..82d223cada 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -777,3 +777,4 @@ erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number +erpnext.patches.v13_0.set_pos_closing_as_failed diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py new file mode 100644 index 0000000000..1c576db1c7 --- /dev/null +++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry') + + frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'") \ No newline at end of file From 695becdd0569aac2bb57e0c87b9d4234b3ca9647 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 6 May 2021 18:03:32 +0530 Subject: [PATCH 0698/1264] fix: added validation in stock entry to check duplicate serial nos --- .../stock/doctype/stock_entry/stock_entry.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 48cfa51041..2f76bc7d56 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -76,6 +76,7 @@ class StockEntry(StockController): self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() + self.validate_duplicate_serial_no() if not self.from_bom: self.fg_completed_qty = 0.0 @@ -587,6 +588,22 @@ class StockEntry(StockController): self.purpose = frappe.get_cached_value('Stock Entry Type', self.stock_entry_type, 'purpose') + def validate_duplicate_serial_no(self): + warehouse_wise_serial_nos = {} + + # In case of repack the source and target serial nos could be same + for warehouse in ['s_warehouse', 't_warehouse']: + serial_nos = [] + for row in self.items: + if not (row.serial_no and row.get(warehouse)): continue + + for sn in get_serial_nos(row.serial_no): + if sn in serial_nos: + frappe.throw(_('The serial no {0} has added multiple times in the stock entry {1}') + .format(frappe.bold(sn), self.name)) + + serial_nos.append(sn) + def validate_purchase_order(self): """Throw exception if more raw material is transferred against Purchase Order than in the raw materials supplied table""" From 134eaa5786745fc9930a795b74f0461e353363ac Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 6 May 2021 19:13:54 +0530 Subject: [PATCH 0699/1264] perf: Performance enhancement on setup wizard (#25605) * perf: Performance enhancement on setup wizard * fix: create departments without updating nsm --- erpnext/accounts/doctype/account/account.py | 2 +- .../chart_of_accounts/chart_of_accounts.py | 4 +- .../accounts_settings/accounts_settings.py | 4 +- .../education_settings/education_settings.py | 4 +- erpnext/hr/doctype/department/department.py | 3 +- .../payroll_settings/payroll_settings.py | 4 +- .../selling_settings/selling_settings.py | 4 +- .../global_defaults/global_defaults.py | 12 +- .../doctype/naming_series/naming_series.py | 16 +-- .../operations/install_fixtures.py | 134 ++++++++++-------- erpnext/setup/setup_wizard/setup_wizard.py | 9 -- .../doctype/stock_settings/stock_settings.py | 10 +- 12 files changed, 105 insertions(+), 101 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 0606823821..1be2fbf5c8 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -13,7 +13,7 @@ class BalanceMismatchError(frappe.ValidationError): pass class Account(NestedSet): nsm_parent_field = 'parent_account' def on_update(self): - if frappe.local.flags.ignore_on_update: + if frappe.local.flags.ignore_update_nsm: return else: super(Account, self).on_update() diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 0e3b24cda3..927adc7086 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -57,10 +57,10 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch # Rebuild NestedSet HSM tree for Account Doctype # after all accounts are already inserted. - frappe.local.flags.ignore_on_update = True + frappe.local.flags.ignore_update_nsm = True _import_accounts(chart, None, None, root_account=True) rebuild_tree("Account", "parent_account") - frappe.local.flags.ignore_on_update = False + frappe.local.flags.ignore_update_nsm = False def add_suffix_if_duplicate(account_name, account_number, accounts): if account_number: diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 5593466fc2..4d3388090d 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -30,5 +30,5 @@ class AccountsSettings(Document): def enable_payment_schedule_in_print(self): show_in_print = cint(self.show_payment_schedule_in_print) 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") + make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/education/doctype/education_settings/education_settings.py b/erpnext/education/doctype/education_settings/education_settings.py index a85d3e70f3..658380ea42 100644 --- a/erpnext/education/doctype/education_settings/education_settings.py +++ b/erpnext/education/doctype/education_settings/education_settings.py @@ -31,9 +31,9 @@ class EducationSettings(Document): def validate(self): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if self.get('instructor_created_by')=='Naming Series': - make_property_setter('Instructor', "naming_series", "hidden", 0, "Check") + make_property_setter('Instructor', "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) else: - make_property_setter('Instructor', "naming_series", "hidden", 1, "Check") + make_property_setter('Instructor', "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False) def update_website_context(context): context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms \ No newline at end of file diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py index 2cef509276..539a360269 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -31,7 +31,8 @@ class Department(NestedSet): return new def on_update(self): - NestedSet.on_update(self) + if not frappe.local.flags.ignore_update_nsm: + super(Department, self).on_update() def on_trash(self): super(Department, self).on_trash() diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py index 5efa41db1f..459b7eacb4 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py @@ -28,5 +28,5 @@ class PayrollSettings(Document): def toggle_rounded_total(self): self.disable_rounded_total = cint(self.disable_rounded_total) - make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check") + make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index d297883876..b219e7ecce 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -30,8 +30,8 @@ class SellingSettings(Document): # Make property setters to hide tax_id fields for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"): - make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check") - make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check") + make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False) def set_default_customer_group_and_territory(self): if not self.customer_group: diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index 76a8450829..e587217181 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -60,11 +60,11 @@ class GlobalDefaults(Document): # Make property setters to hide rounded total fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", "Supplier Quotation", "Purchase Order", "Purchase Invoice"): - make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check") + make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False) - make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check") + make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) def toggle_in_words(self): self.disable_in_words = cint(self.disable_in_words) @@ -72,5 +72,5 @@ class GlobalDefaults(Document): # Make property setters to hide in words fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"): - make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check") - make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check") + make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index 373b0a58c9..c1f9433b41 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -183,8 +183,8 @@ class NamingSeries(Document): def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if naming_series: - make_property_setter(doctype, "naming_series", "hidden", 0, "Check") - make_property_setter(doctype, "naming_series", "reqd", 1, "Check") + make_property_setter(doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "reqd", 1, "Check", validate_fields_for_doctype=False) # set values for mandatory try: @@ -195,15 +195,15 @@ def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True pass if hide_name_field: - make_property_setter(doctype, fieldname, "reqd", 0, "Check") - make_property_setter(doctype, fieldname, "hidden", 1, "Check") + make_property_setter(doctype, fieldname, "reqd", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, fieldname, "hidden", 1, "Check", validate_fields_for_doctype=False) else: - make_property_setter(doctype, "naming_series", "reqd", 0, "Check") - make_property_setter(doctype, "naming_series", "hidden", 1, "Check") + make_property_setter(doctype, "naming_series", "reqd", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False) if hide_name_field: - make_property_setter(doctype, fieldname, "hidden", 0, "Check") - make_property_setter(doctype, fieldname, "reqd", 1, "Check") + make_property_setter(doctype, fieldname, "hidden", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, fieldname, "reqd", 1, "Check", validate_fields_for_doctype=False) # set values for mandatory frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=`name` where diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 5053c6a512..5c725d332d 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -12,6 +12,7 @@ from frappe.desk.doctype.global_search_settings.global_search_settings import up from erpnext.accounts.doctype.account.account import RootNotEditable from erpnext.regional.address_template.setup import set_up_address_templates +from frappe.utils.nestedset import rebuild_tree default_lead_sources = ["Existing Customer", "Reference", "Advertisement", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", @@ -280,13 +281,15 @@ def install(country=None): set_more_defaults() update_global_search_doctypes() - # path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) - # if os.path.exists(path.encode("utf-8")): - # frappe.get_attr("erpnext.regional.{0}.setup.setup_company_independent_fixtures".format(frappe.scrub(country)))() - - def set_more_defaults(): # Do more setup stuff that can be done here with no dependencies + update_selling_defaults() + update_buying_defaults() + update_hr_defaults() + add_uom_data() + update_item_variant_settings() + +def update_selling_defaults(): selling_settings = frappe.get_doc("Selling Settings") selling_settings.set_default_customer_group_and_territory() selling_settings.cust_master_name = "Customer Name" @@ -296,13 +299,7 @@ def set_more_defaults(): selling_settings.sales_update_frequency = "Each Transaction" selling_settings.save() - add_uom_data() - - # set no copy fields of an item doctype to item variant settings - doc = frappe.get_doc('Item Variant Settings') - doc.set_default_fields() - doc.save() - +def update_buying_defaults(): buying_settings = frappe.get_doc("Buying Settings") buying_settings.supp_master_name = "Supplier Name" buying_settings.po_required = "No" @@ -311,12 +308,19 @@ def set_more_defaults(): buying_settings.allow_multiple_items = 1 buying_settings.save() +def update_hr_defaults(): hr_settings = frappe.get_doc("HR Settings") hr_settings.emp_created_by = "Naming Series" hr_settings.leave_approval_notification_template = _("Leave Approval Notification") hr_settings.leave_status_notification_template = _("Leave Status Notification") hr_settings.save() +def update_item_variant_settings(): + # set no copy fields of an item doctype to item variant settings + doc = frappe.get_doc('Item Variant Settings') + doc.set_default_fields() + doc.save() + def add_uom_data(): # add UOMs uoms = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_data.json")).read()) @@ -327,7 +331,7 @@ def add_uom_data(): "uom_name": _(d.get("uom_name")), "name": _(d.get("uom_name")), "must_be_whole_number": d.get("must_be_whole_number") - }).insert(ignore_permissions=True) + }).db_insert() # bootstrap uom conversion factors uom_conversions = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_conversion_data.json")).read()) @@ -336,7 +340,7 @@ def add_uom_data(): frappe.get_doc({ "doctype": "UOM Category", "category_name": _(d.get("category")) - }).insert(ignore_permissions=True) + }).db_insert() if not frappe.db.exists("UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}): uom_conversion = frappe.get_doc({ @@ -369,8 +373,8 @@ def add_sale_stages(): {"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")}, {"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")} ] - - make_records(records) + for sales_stage in records: + frappe.get_doc(sales_stage).db_insert() def install_company(args): records = [ @@ -418,7 +422,14 @@ def install_post_company_fixtures(args=None): {'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': args.company_name}, ] - make_records(records) + # Make root department with NSM updation + make_records(records[:1]) + + frappe.local.flags.ignore_update_nsm = True + make_records(records[1:]) + frappe.local.flags.ignore_update_nsm = False + + rebuild_tree("Department", "parent_department") def install_defaults(args=None): @@ -432,7 +443,15 @@ def install_defaults(args=None): # enable default currency frappe.db.set_value("Currency", args.get("currency"), "enabled", 1) + frappe.db.set_value("Stock Settings", None, "email_footer_address", args.get("company_name")) + set_global_defaults(args) + set_active_domains(args) + update_stock_settings() + update_shopping_cart_settings(args) + create_bank_account(args) + +def set_global_defaults(args): global_defaults = frappe.get_doc("Global Defaults", "Global Defaults") current_fiscal_year = frappe.get_all("Fiscal Year")[0] @@ -445,13 +464,10 @@ def install_defaults(args=None): global_defaults.save() - system_settings = frappe.get_doc("System Settings") - system_settings.email_footer_address = args.get("company_name") - system_settings.save() - - domain_settings = frappe.get_single('Domain Settings') - domain_settings.set_active_domains(args.get('domains')) +def set_active_domains(args): + frappe.get_single('Domain Settings').set_active_domains(args.get('domains')) +def update_stock_settings(): stock_settings = frappe.get_doc("Stock Settings") stock_settings.item_naming_by = "Item Code" stock_settings.valuation_method = "FIFO" @@ -463,48 +479,44 @@ def install_defaults(args=None): stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1 stock_settings.save() - if args.bank_account: - company_name = args.company_name - bank_account_group = frappe.db.get_value("Account", - {"account_type": "Bank", "is_group": 1, "root_type": "Asset", - "company": company_name}) - if bank_account_group: - bank_account = frappe.get_doc({ - "doctype": "Account", - 'account_name': args.bank_account, - 'parent_account': bank_account_group, - 'is_group':0, - 'company': company_name, - "account_type": "Bank", - }) - try: - doc = bank_account.insert() +def create_bank_account(args): + if not args.bank_account: + return - frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) + company_name = args.company_name + bank_account_group = frappe.db.get_value("Account", + {"account_type": "Bank", "is_group": 1, "root_type": "Asset", + "company": company_name}) + if bank_account_group: + bank_account = frappe.get_doc({ + "doctype": "Account", + 'account_name': args.bank_account, + 'parent_account': bank_account_group, + 'is_group':0, + 'company': company_name, + "account_type": "Bank", + }) + try: + doc = bank_account.insert() - except RootNotEditable: - frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) - except frappe.DuplicateEntryError: - # bank account same as a CoA entry - pass + frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) - # Now, with fixtures out of the way, onto concrete stuff - records = [ - - # Shopping cart: needs price lists - { - "doctype": "Shopping Cart Settings", - "enabled": 1, - 'company': args.company_name, - # uh oh - 'price_list': frappe.db.get_value("Price List", {"selling": 1}), - 'default_customer_group': _("Individual"), - 'quotation_series': "QTN-", - }, - ] - - make_records(records) + except RootNotEditable: + frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) + except frappe.DuplicateEntryError: + # bank account same as a CoA entry + pass +def update_shopping_cart_settings(args): + shopping_cart = frappe.get_doc("Shopping Cart Settings") + shopping_cart.update({ + "enabled": 1, + 'company': args.company_name, + 'price_list': frappe.db.get_value("Price List", {"selling": 1}), + 'default_customer_group': _("Individual"), + 'quotation_series': "QTN-", + }) + shopping_cart.update_single(shopping_cart.get_valid_dict()) def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index e74d837ef5..f63d2695aa 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -51,11 +51,6 @@ def get_setup_stages(args=None): 'status': _('Setting defaults'), 'fail_msg': 'Failed to set defaults', 'tasks': [ - { - 'fn': setup_post_company_fixtures, - 'args': args, - 'fail_msg': _("Failed to setup post company fixtures") - }, { 'fn': setup_defaults, 'args': args, @@ -94,9 +89,6 @@ def stage_fixtures(args): def setup_company(args): fixtures.install_company(args) -def setup_post_company_fixtures(args): - fixtures.install_post_company_fixtures(args) - def setup_defaults(args): fixtures.install_defaults(frappe._dict(args)) @@ -129,7 +121,6 @@ def login_as_first_user(args): def setup_complete(args=None): stage_fixtures(args) setup_company(args) - setup_post_company_fixtures(args) setup_defaults(args) stage_four(args) fin(args) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 3b9608b805..2dd7c6f35b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -30,7 +30,7 @@ class StockSettings(Document): # show/hide barcode field for name in ["barcode", "barcodes", "scan_barcode"]: frappe.make_property_setter({'fieldname': name, 'property': 'hidden', - 'value': 0 if self.show_barcode_field else 1}) + 'value': 0 if self.show_barcode_field else 1}, validate_fields_for_doctype=False) self.validate_warehouses() self.cant_change_valuation_method() @@ -67,10 +67,10 @@ class StockSettings(Document): self.toggle_warehouse_field_for_inter_warehouse_transfer() def toggle_warehouse_field_for_inter_warehouse_transfer(self): - make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check") - make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check") - make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check") - make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check") + make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False) + make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False) + make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False) + make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False) def clean_all_descriptions(): From 4ecae62194d00f455dd757ce45bb80148d750977 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 6 May 2021 19:42:01 +0530 Subject: [PATCH 0700/1264] fix: added is_stock_item filter (#25530) --- erpnext/manufacturing/doctype/bom/bom.js | 5 ++++- erpnext/manufacturing/doctype/bom/bom.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index fbfd801a11..a09a5e3430 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -29,7 +29,10 @@ frappe.ui.form.on("BOM", { frm.set_query("item", function() { return { - query: "erpnext.manufacturing.doctype.bom.bom.item_query" + query: "erpnext.manufacturing.doctype.bom.bom.item_query", + filters: { + "is_stock_item": 1 + } }; }); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 979f7ca312..d1f63854c7 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -973,6 +973,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if not has_variants: query_filters["has_variants"] = 0 + if filters and filters.get("is_stock_item"): + query_filters["is_stock_item"] = 1 + return frappe.get_all("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, From 0e0de6baa14683b10b18e43d1b2dab03c9a94f37 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 7 May 2021 11:40:02 +0530 Subject: [PATCH 0701/1264] fix: prevent spurious defaults for items when making prec from dnote (#25559) * fix: prevent spurious defaults for items when making prec from dnote * refactor: make concise, use dict comp --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4de877353a..bb74a02606 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1755,15 +1755,10 @@ def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, wa item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item)) def get_delivery_note_details(internal_reference): - so_item_map = {} - si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'], filters={'parent': internal_reference}) - for d in si_item_details: - so_item_map.setdefault(d.name, d.so_detail) - - return so_item_map + return {d.name: d.so_detail for d in si_item_details if d.so_detail} def get_sales_invoice_details(internal_reference): dn_item_map = {} From f132ed4335996a7848a386d017ed6cfbc93c8197 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Fri, 7 May 2021 12:11:09 +0530 Subject: [PATCH 0702/1264] fix: update item level cost center from POS (#25609) --- erpnext/stock/get_item_details.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 3832415db6..d1dcdc21c8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -79,7 +79,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): - out.update(get_pos_profile_item_details(args.company, args)) + out.update(get_pos_profile_item_details(args.company, args, update_data=True)) if (args.get("doctype") == "Material Request" and args.get("material_request_type") == "Material Transfer"): @@ -935,8 +935,8 @@ def get_bin_details(item_code, warehouse, company=None): return bin_details def get_company_total_stock(item_code, company): - return frappe.db.sql("""SELECT sum(actual_qty) from - (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) + return frappe.db.sql("""SELECT sum(actual_qty) from + (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""", (company, item_code))[0][0] From 996f7e53a19e2ab741f9709f09f774d04498cc81 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 12:14:14 +0530 Subject: [PATCH 0703/1264] fix: update shopify api version (#25600) --- .../erpnext_integrations/connectors/shopify_connection.py | 6 +++--- .../doctype/shopify_settings/shopify_settings.py | 4 ++-- .../doctype/shopify_settings/sync_product.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index f0a05ed192..5d5b2e19ce 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -335,13 +335,13 @@ def get_url(shopify_settings): if not last_order_id: if shopify_settings.sync_based_on == 'Date': - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format( + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format( get_datetime(shopify_settings.from_date)), shopify_settings) else: - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format( + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format( shopify_settings.from_order_id), shopify_settings) else: - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) return url diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index cbdf90681d..7634fd0caf 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -30,7 +30,7 @@ class ShopifySettings(Document): webhooks = ["orders/create", "orders/paid", "orders/fulfilled"] # url = get_shopify_url('admin/webhooks.json', self) created_webhooks = [d.method for d in self.webhooks] - url = get_shopify_url('admin/api/2020-04/webhooks.json', self) + url = get_shopify_url('admin/api/2021-04/webhooks.json', self) for method in webhooks: session = get_request_session() try: @@ -56,7 +56,7 @@ class ShopifySettings(Document): deleted_webhooks = [] for d in self.webhooks: - url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self) + url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self) try: res = session.delete(url, headers=get_header(self)) res.raise_for_status() diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py index f9f0bb3cec..16efb6caee 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py @@ -8,7 +8,7 @@ from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings impo shopify_variants_attr_list = ["option1", "option2", "option3"] def sync_item_from_shopify(shopify_settings, item): - url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings) + url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings) session = get_request_session() try: From f648d2d7c4a23061f4b0a03afa060d0d061eea95 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Fri, 7 May 2021 12:15:19 +0530 Subject: [PATCH 0704/1264] fix: added tax_types list (#25587) --- .../v12_0/move_item_tax_to_item_tax_template.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 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 06331d7ff7..a6471eb53c 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 @@ -44,9 +44,11 @@ def execute(): # make current item's tax map item_tax_map = {} for d in old_item_taxes[item_code]: - item_tax_map[d.tax_type] = d.tax_rate + if d.tax_type not in item_tax_map: + item_tax_map[d.tax_type] = d.tax_rate - item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code) + tax_types = [] + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code, tax_types=tax_types) # update the item tax table frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code) @@ -68,7 +70,7 @@ def execute(): and item_tax_template is NULL""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) item_tax_template_name = get_item_tax_template(item_tax_templates, - item_tax_map, d.item_code, d.parenttype, d.parent) + item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types) frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name) frappe.db.auto_commit_on_many_writes = False @@ -78,7 +80,7 @@ def execute(): settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None): +def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: @@ -126,7 +128,9 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp account_type = frappe.get_cached_value("Account", tax_type, "account_type") if tax_type and account_type in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'): - item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) + if tax_type not in tax_types: + item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) + tax_types.append(tax_type) item_tax_templates.setdefault(item_tax_template.title, {}) item_tax_templates[item_tax_template.title][tax_type] = tax_rate if item_tax_template.get("taxes"): From 00e00e4e903ac9f6e37520903b0e7ca97e1c388b Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 7 May 2021 12:16:44 +0530 Subject: [PATCH 0705/1264] fix: Report summary showing inflated values when values are accumulated in Group Company (#25577) * fix: Report summary showing inflated values when values are accumullated in Group Company * fix: Remove extra space * fix: Translate strings * fix: Remove unintended changes --- .../report/balance_sheet/balance_sheet.py | 7 ++++++- erpnext/accounts/report/cash_flow/cash_flow.py | 17 ++++++++++++----- .../consolidated_financial_statement.py | 6 +++--- erpnext/accounts/report/financial_statements.py | 14 +++++++++++--- .../profit_and_loss_statement.py | 11 ++++++++--- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 1729abce9e..287b8a7484 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt, cint -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, + get_filtered_list_for_consolidated_report) def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, @@ -132,6 +133,10 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit if filters.get('accumulated_values'): period_list = [period_list[-1]] + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(period_list) + for period in period_list: key = period if consolidated else period.key if asset: diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index cf0946beab..3577457c98 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, get_filtered_list_for_consolidated_report) from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss from erpnext.accounts.utils import get_fiscal_year from six import iteritems @@ -67,9 +67,9 @@ def execute(filters=None): section_data.append(account_data) add_total_row_account(data, section_data, cash_flow_account['section_footer'], - period_list, company_currency, summary_data) + period_list, company_currency, summary_data, filters) - add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data) + add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters) columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company) chart = get_chart_data(columns, data) @@ -162,18 +162,26 @@ def get_start_date(period, accumulated_values, company): return start_date -def add_total_row_account(out, data, label, period_list, currency, summary_data, consolidated = False): +def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False): total_row = { "account_name": "'" + _("{0}").format(label) + "'", "account": "'" + _("{0}").format(label) + "'", "currency": currency } + + summary_data[label] = 0 + + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(filters, period_list) + for row in data: if row.get("parent_account"): for period in period_list: key = period if consolidated else period['key'] total_row.setdefault(key, 0.0) total_row[key] += row.get(key, 0.0) + summary_data[label] += row.get(key) total_row.setdefault("total", 0.0) total_row["total"] += row["total"] @@ -181,7 +189,6 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data, out.append(total_row) out.append({}) - summary_data[label] = total_row["total"] def get_report_summary(summary_data, currency): report_summary = [] diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 094f5db89b..7793af737f 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -94,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters): chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) - report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True) + report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True) return data, None, chart, report_summary @@ -149,9 +149,9 @@ def get_cash_flow_data(fiscal_year, companies, filters): section_data.append(account_data) add_total_row_account(data, section_data, cash_flow_account['section_footer'], - companies, company_currency, summary_data, True) + companies, company_currency, summary_data, filters, True) - add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, True) + add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True) report_summary = get_cash_flow_summary(summary_data, company_currency) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 14efa1f8fc..d20ddbde5c 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -119,10 +119,10 @@ def validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year): def validate_dates(from_date, to_date): if not from_date or not to_date: - frappe.throw("From Date and To Date are mandatory") + frappe.throw(_("From Date and To Date are mandatory")) if to_date < from_date: - frappe.throw("To Date cannot be less than From Date") + frappe.throw(_("To Date cannot be less than From Date")) def get_months(start_date, end_date): diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month) @@ -522,4 +522,12 @@ def get_columns(periodicity, period_list, accumulated_values=1, company=None): "width": 150 }) - return columns \ No newline at end of file + return columns + +def get_filtered_list_for_consolidated_report(filters, period_list): + filtered_summary_list = [] + for period in period_list: + if period == filters.get('company'): + filtered_summary_list.append(period) + + return filtered_summary_list diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index fe261b30b4..5d04824b57 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, + get_filtered_list_for_consolidated_report) def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, @@ -33,13 +34,17 @@ def execute(filters=None): chart = get_chart_data(filters, columns, income, expense, net_profit_loss) currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency") - report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency) + report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters) return columns, data, None, chart, report_summary -def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False): +def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False): net_income, net_expense, net_profit = 0.0, 0.0, 0.0 + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(filters, period_list) + for period in period_list: key = period if consolidated else period.key if income: From 735fbdc350d0449720db36e684c85ca56161442f Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 7 May 2021 12:26:32 +0530 Subject: [PATCH 0706/1264] fix: Updating Standard Notification's channel field (#25564) --- .../notification_for_new_fiscal_year.json | 1 + erpnext/hr/notification/training_feedback/training_feedback.json | 1 + .../payroll/notification/retention_bonus/retention_bonus.json | 1 + 3 files changed, 3 insertions(+) diff --git a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json index bd7a126517..4c7faf4f65 100644 --- a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json +++ b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "condition": "doc.auto_created", "creation": "2018-04-25 14:19:05.440361", "days_in_advance": 0, diff --git a/erpnext/hr/notification/training_feedback/training_feedback.json b/erpnext/hr/notification/training_feedback/training_feedback.json index 2cc064f34a..92b68a98a9 100644 --- a/erpnext/hr/notification/training_feedback/training_feedback.json +++ b/erpnext/hr/notification/training_feedback/training_feedback.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "creation": "2017-08-11 03:17:11.769210", "days_in_advance": 0, "docstatus": 0, diff --git a/erpnext/payroll/notification/retention_bonus/retention_bonus.json b/erpnext/payroll/notification/retention_bonus/retention_bonus.json index 50db0338c4..37381fa942 100644 --- a/erpnext/payroll/notification/retention_bonus/retention_bonus.json +++ b/erpnext/payroll/notification/retention_bonus/retention_bonus.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "condition": "doc.docstatus==1", "creation": "2018-05-15 18:52:36.362838", "date_changed": "bonus_payment_date", From 7f79d463f69237189f9393c820e1bae58054493a Mon Sep 17 00:00:00 2001 From: Umair Sayed Date: Fri, 7 May 2021 12:28:57 +0530 Subject: [PATCH 0707/1264] fix: Stock and Accounts Settings form refactor (#25534) * stock and accounts settings page * fix: Stock and accounts settings page cleanup Co-authored-by: Umair Sayed --- .../accounts_settings/accounts_settings.json | 61 ++++++++++------ .../stock_settings/stock_settings.json | 70 ++++++++++++------- 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index e1276e7da3..781f94e203 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -7,26 +7,30 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "auto_accounting_for_stock", - "acc_frozen_upto", - "frozen_accounts_modifier", - "determine_address_tax_category_from", + "accounts_transactions_settings_section", "over_billing_allowance", "role_allowed_to_over_bill", - "column_break_4", - "credit_controller", - "check_supplier_invoice_uniqueness", "make_payment_via_journal_entry", + "column_break_11", + "check_supplier_invoice_uniqueness", "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", "delete_linked_ledger_entries", + "book_asset_depreciation_entry_automatically", + "unlink_advance_payment_on_cancelation_of_order", + "tax_settings_section", + "determine_address_tax_category_from", + "column_break_19", + "add_taxes_from_item_tax_template", + "period_closing_settings_section", + "acc_frozen_upto", + "frozen_accounts_modifier", + "column_break_4", + "credit_controller", "deferred_accounting_settings_section", - "automatically_process_deferred_accounting_entry", "book_deferred_entries_based_on", "column_break_18", + "automatically_process_deferred_accounting_entry", "book_deferred_entries_via_journal_entry", "submit_journal_entries", "print_settings", @@ -40,15 +44,6 @@ "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 entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below", "fieldname": "acc_frozen_upto", @@ -94,6 +89,7 @@ "default": "0", "fieldname": "make_payment_via_journal_entry", "fieldtype": "Check", + "hidden": 1, "label": "Make Payment via Journal Entry" }, { @@ -234,6 +230,29 @@ "fieldtype": "Link", "label": "Role Allowed to Over Bill ", "options": "Role" + }, + { + "fieldname": "period_closing_settings_section", + "fieldtype": "Section Break", + "label": "Period Closing Settings" + }, + { + "fieldname": "accounts_transactions_settings_section", + "fieldtype": "Section Break", + "label": "Transactions Settings" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "tax_settings_section", + "fieldtype": "Section Break", + "label": "Tax Settings" + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" } ], "icon": "icon-cog", @@ -241,7 +260,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-11 18:52:05.601996", + "modified": "2021-04-30 15:25:10.381008", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index f18eabc84b..cf5d98d092 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -5,40 +5,44 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "item_defaults_section", "item_naming_by", "item_group", "stock_uom", "default_warehouse", - "sample_retention_warehouse", "column_break_4", "valuation_method", + "sample_retention_warehouse", + "use_naming_series", + "naming_series_prefix", + "section_break_9", "over_delivery_receipt_allowance", "role_allowed_to_over_deliver_receive", - "action_if_quality_inspection_is_not_submitted", - "show_barcode_field", - "clean_description_html", - "disable_serial_no_and_batch_selector", - "section_break_7", + "column_break_12", "auto_insert_price_list_rate_if_missing", "allow_negative_stock", - "column_break_10", + "show_barcode_field", + "clean_description_html", + "action_if_quality_inspection_is_not_submitted", + "section_break_7", "automatically_set_serial_nos_based_on_fifo", "set_qty_in_transactions_based_on_serial_no_input", + "column_break_10", + "disable_serial_no_and_batch_selector", "auto_material_request", "auto_indent", + "column_break_27", "reorder_email_notify", "inter_warehouse_transfer_settings_section", "allow_from_dn", + "column_break_31", "allow_from_pr", "control_historical_stock_transactions_section", - "role_allowed_to_create_edit_back_dated_transactions", - "column_break_26", "stock_frozen_upto", "stock_frozen_upto_days", - "stock_auth_role", - "batch_id_sb", - "use_naming_series", - "naming_series_prefix" + "column_break_26", + "role_allowed_to_create_edit_back_dated_transactions", + "stock_auth_role" ], "fields": [ { @@ -102,23 +106,24 @@ "default": "1", "fieldname": "show_barcode_field", "fieldtype": "Check", - "label": "Show Barcode Field" + "label": "Show Barcode Field in Stock Transactions" }, { "default": "1", "fieldname": "clean_description_html", "fieldtype": "Check", - "label": "Convert Item Description to Clean HTML" + "label": "Convert Item Description to Clean HTML in Transactions" }, { "fieldname": "section_break_7", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Serialised and Batch Setting" }, { "default": "0", "fieldname": "auto_insert_price_list_rate_if_missing", "fieldtype": "Check", - "label": "Auto Insert Price List Rate If Missing" + "label": "Auto Insert Item Price If Missing" }, { "default": "0", @@ -179,16 +184,11 @@ "label": "Role Allowed to Edit Frozen Stock", "options": "Role" }, - { - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "label": "Batch Identification" - }, { "default": "0", "fieldname": "use_naming_series", "fieldtype": "Check", - "label": "Use Naming Series" + "label": "Have Default Naming Series for Batch ID?" }, { "default": "BATCH-", @@ -242,6 +242,28 @@ "fieldtype": "Link", "label": "Role Allowed to Over Deliver/Receive", "options": "Role" + }, + { + "fieldname": "item_defaults_section", + "fieldtype": "Section Break", + "label": "Item Defaults" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Stock Transactions Settings" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" } ], "icon": "icon-cog", @@ -249,7 +271,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-11 18:48:14.513055", + "modified": "2021-04-30 17:27:42.709231", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 062d30146f967a28672a76f9f8d286c0d28470ca Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 7 May 2021 13:31:14 +0530 Subject: [PATCH 0708/1264] fix: Include search fields in Project Link field query (#25505) * fix: Include search fields in Project Link field query * fix: add project_name to Project search fields --- erpnext/controllers/queries.py | 10 +++++++--- erpnext/projects/doctype/project/project.json | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index bc1ac5ea06..b31724fa48 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -292,11 +292,14 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = """(`tabProject`.customer = %s or ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) - fields = get_fields("Project", ["name"]) + fields = get_fields("Project", ["name", "project_name"]) + searchfields = frappe.get_meta("Project").get_search_fields() + searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) return frappe.db.sql("""select {fields} from `tabProject` - where `tabProject`.status not in ("Completed", "Cancelled") - and {cond} `tabProject`.name like %(txt)s {match_cond} + where + `tabProject`.status not in ("Completed", "Cancelled") + and {cond} {match_cond} {scond} order by if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), idx desc, @@ -304,6 +307,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): limit {start}, {page_len}""".format( fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]), cond=cond, + scond=searchfields, match_cond=get_match_cond(doctype), start=start, page_len=page_len), { diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 3cdfcb212f..2570df7026 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -458,7 +458,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2020-09-02 11:54:01.223620", + "modified": "2021-04-28 16:36:11.654632", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -495,11 +495,11 @@ } ], "quick_entry": 1, - "search_fields": "customer, status, priority, is_active", + "search_fields": "project_name,customer, status, priority, is_active", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", "timeline_field": "customer", "title_field": "project_name", "track_seen": 1 -} +} \ No newline at end of file From 5618ce3852e0dbad1460d31dae834cf19d61a197 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 7 May 2021 13:35:09 +0530 Subject: [PATCH 0709/1264] fix(Material Request): Add 'Partially Received' to Status drop-down list (#24857) Co-authored-by: Ganga Manoj Co-authored-by: Nabin Hait --- erpnext/stock/doctype/material_request/material_request.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 8d7b238c17..4e2d9e6170 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -181,7 +181,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred\nReceived", + "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nPartially Received\nOrdered\nIssued\nTransferred\nReceived", "print_hide": 1, "print_width": "100px", "read_only": 1, From 27cf19a19f09169a0f5fc08d537555471cc466e2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 7 May 2021 13:37:42 +0530 Subject: [PATCH 0710/1264] feat(pos): show POS reserved stock in stock projected qty report (#25593) * feat(pos): consider POS reserved stock in stock projected qty report * chore: remove unwanted string formats --- .../doctype/pos_invoice/pos_invoice.py | 22 +++++++++++-------- .../report/pos_register/pos_register.py | 13 +++++------ .../stock_projected_qty.js | 9 +++++++- .../stock_projected_qty.py | 13 ++++++++--- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 1e6a3d1b3b..473db565fa 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -461,7 +461,17 @@ def get_stock_availability(item_code, warehouse): 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 + pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) + + sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 + + if sle_qty and pos_sales_qty: + return sle_qty - pos_sales_qty + else: + return sle_qty + +def get_pos_reserved_qty(item_code, warehouse): + reserved_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 @@ -470,14 +480,8 @@ def get_stock_availability(item_code, warehouse): 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: - return sle_qty - pos_sales_qty - else: - return sle_qty + + return reserved_qty[0].qty or 0 if reserved_qty else 0 @frappe.whitelist() def make_sales_return(source_name, target_doc=None): diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py index 52f7fe238e..cfbd7fd0c8 100644 --- a/erpnext/accounts/report/pos_register/pos_register.py +++ b/erpnext/accounts/report/pos_register/pos_register.py @@ -116,22 +116,19 @@ def validate_filters(filters): frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method")) def get_conditions(filters): - conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format( - company=filters.get("company"), - from_date=filters.get("from_date"), - to_date=filters.get("to_date")) + conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s" if filters.get("pos_profile"): - conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile")) + conditions += " AND pos_profile = %(pos_profile)s" if filters.get("owner"): - conditions += " AND owner = %(owner)s".format(owner=filters.get("owner")) + conditions += " AND owner = %(owner)s" if filters.get("customer"): - conditions += " AND customer = %(customer)s".format(customer=filters.get("customer")) + conditions += " AND customer = %(customer)s" if filters.get("is_return"): - conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return")) + conditions += " AND is_return = %(is_return)s" if filters.get("mode_of_payment"): conditions += """ diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js index babc6dc960..cb109f8050 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js @@ -14,7 +14,14 @@ frappe.query_reports["Stock Projected Qty"] = { "fieldname":"warehouse", "label": __("Warehouse"), "fieldtype": "Link", - "options": "Warehouse" + "options": "Warehouse", + "get_query": () => { + return { + filters: { + company: frappe.query_report.get_filter_value('company') + } + } + } }, { "fieldname":"item_code", diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index 1183e41d04..808d279170 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.utils import flt, today from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress +from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty def execute(filters=None): is_reposting_item_valuation_in_progress() @@ -49,9 +50,13 @@ def execute(filters=None): if (re_order_level or re_order_qty) and re_order_level > bin.projected_qty: shortage_qty = re_order_level - flt(bin.projected_qty) + reserved_qty_for_pos = get_pos_reserved_qty(bin.item_code, bin.warehouse) + if reserved_qty_for_pos: + bin.projected_qty -= reserved_qty_for_pos + data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse, item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, - bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, + bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, reserved_qty_for_pos, bin.projected_qty, re_order_level, re_order_qty, shortage_qty]) if include_uom: @@ -74,9 +79,11 @@ def get_columns(): {"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"}, {"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float", + {"label": _("Reserved for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", + {"label": _("Reserved for Sub Contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", + "width": 100, "convertible": "qty"}, + {"label": _("Reserved for POS Transactions"), "fieldname": "reserved_qty_for_pos", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"}, From da7fefe29d82886a90263064b309b1fa0a9b02d9 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 7 May 2021 20:26:50 +0530 Subject: [PATCH 0711/1264] fix: timesheet filter date exclusive issue (#25626) --- erpnext/projects/doctype/timesheet/timesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index ed02f79c2d..8d99b48b59 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -209,7 +209,7 @@ def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time if parent: condition = "AND parent = %(parent)s" if from_time and to_time: - condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s" + condition += "AND CAST(from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1 From e28165ea871720a68ebdc00cfb7b97d6bf775d73 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 20:27:51 +0530 Subject: [PATCH 0712/1264] fix: force https for shopify webhook registration (#25630) --- .../doctype/shopify_settings/shopify_settings.py | 2 +- erpnext/erpnext_integrations/utils.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index 7634fd0caf..381c5e5dec 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -37,7 +37,7 @@ class ShopifySettings(Document): res = session.post(url, data=json.dumps({ "webhook": { "topic": method, - "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'), + "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True), "format": "json" } }), headers=get_header(self)) diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 362f6cf88e..3840e781b4 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -28,7 +28,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key='secret'): return innerfn -def get_webhook_address(connector_name, method, exclude_uri=False): +def get_webhook_address(connector_name, method, exclude_uri=False, force_https=False): endpoint = "erpnext.erpnext_integrations.connectors.{0}.{1}".format(connector_name, method) if exclude_uri: @@ -39,7 +39,11 @@ def get_webhook_address(connector_name, method, exclude_uri=False): except RuntimeError: url = "http://localhost:8000" - server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint) + url_data = urlparse(url) + scheme = "https" if force_https else url_data.scheme + netloc = url_data.netloc + + server_url = f"{scheme}://{netloc}/api/method/{endpoint}" return server_url From 90e671905a9a4e8b496a84a8315ceba25e10d9ed Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 20:28:51 +0530 Subject: [PATCH 0713/1264] chore: replace assertEquals with alias assertEqual (#25613) * chore: replace assertEquals with alias assertEqual assertEquals has been deprecated. ref: https://docs.python.org/3/library/unittest.html#deprecated-aliases * chore: sider fixes --- .../accounts/doctype/dunning/test_dunning.py | 6 +- .../doctype/gl_entry/test_gl_entry.py | 2 +- .../payment_order/test_payment_order.py | 8 +- .../doctype/pricing_rule/test_pricing_rule.py | 36 +++---- erpnext/assets/doctype/asset/test_asset.py | 4 +- .../purchase_order/test_purchase_order.py | 42 ++++---- .../mpesa_settings/test_mpesa_settings.py | 26 ++--- .../test_clinical_procedure.py | 2 +- .../doctype/lab_test/test_lab_test.py | 4 +- .../test_patient_appointment.py | 8 +- .../doctype/therapy_plan/test_therapy_plan.py | 12 +-- .../doctype/therapy_type/test_therapy_type.py | 2 +- .../test_compensatory_leave_request.py | 16 ++-- .../expense_claim/test_expense_claim.py | 12 +-- .../hr/doctype/job_offer/test_job_offer.py | 4 +- .../leave_allocation/test_leave_allocation.py | 14 +-- .../test_leave_application.py | 78 +++++++-------- .../leave_encashment/test_leave_encashment.py | 8 +- .../loan_management/doctype/loan/test_loan.py | 96 +++++++++---------- .../test_loan_disbursement.py | 6 +- .../test_loan_interest_accrual.py | 6 +- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../bom_update_tool/test_bom_update_tool.py | 6 +- .../doctype/work_order/test_work_order.py | 10 +- .../doctype/donation/test_donation.py | 2 +- .../portal/doctype/homepage/test_homepage.py | 2 +- .../homepage_section/test_homepage_section.py | 4 +- .../test_tax_exemption_80g_certificate.py | 12 +-- .../doctype/quotation/test_quotation.py | 2 +- .../doctype/sales_order/test_sales_order.py | 4 +- .../delivery_note/test_delivery_note.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 10 +- erpnext/support/doctype/issue/test_issue.py | 28 +++--- 33 files changed, 240 insertions(+), 240 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index cb18309e3c..c5ce514cdd 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -42,9 +42,9 @@ class TestDunning(unittest.TestCase): ['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) + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) def test_payment_entry(self): dunning = create_dunning() diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index b4a547b21b..4167ca70df 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -54,4 +54,4 @@ class TestGLEntry(unittest.TestCase): self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries))) new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0] - self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value) + self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value) diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py index 1c23e2a0ec..5fdde07faa 100644 --- a/erpnext/accounts/doctype/payment_order/test_payment_order.py +++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py @@ -31,10 +31,10 @@ class TestPaymentOrder(unittest.TestCase): doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry") reference_doc = doc.get("references")[0] - self.assertEquals(reference_doc.reference_name, payment_entry.name) - self.assertEquals(reference_doc.reference_doctype, "Payment Entry") - self.assertEquals(reference_doc.supplier, "_Test Supplier") - self.assertEquals(reference_doc.amount, 250) + self.assertEqual(reference_doc.reference_name, payment_entry.name) + self.assertEqual(reference_doc.reference_doctype, "Payment Entry") + self.assertEqual(reference_doc.supplier, "_Test Supplier") + self.assertEqual(reference_doc.amount, 250) def create_payment_order_against_payment_entry(ref_doc, order_type): payment_order = frappe.get_doc(dict( diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ef9aad562d..ffe8be1162 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -99,7 +99,7 @@ class TestPricingRule(unittest.TestCase): args.item_code = "_Test Item 2" details = get_item_details(args) - self.assertEquals(details.get("discount_percentage"), 15) + self.assertEqual(details.get("discount_percentage"), 15) def test_pricing_rule_for_margin(self): from erpnext.stock.get_item_details import get_item_details @@ -145,8 +145,8 @@ class TestPricingRule(unittest.TestCase): "name": None }) details = get_item_details(args) - self.assertEquals(details.get("margin_type"), "Percentage") - self.assertEquals(details.get("margin_rate_or_amount"), 10) + self.assertEqual(details.get("margin_type"), "Percentage") + self.assertEqual(details.get("margin_rate_or_amount"), 10) def test_mixed_conditions_for_item_group(self): for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]: @@ -192,7 +192,7 @@ class TestPricingRule(unittest.TestCase): "name": None }) details = get_item_details(args) - self.assertEquals(details.get("discount_percentage"), 10) + self.assertEqual(details.get("discount_percentage"), 10) def test_pricing_rule_for_variants(self): from erpnext.stock.get_item_details import get_item_details @@ -322,11 +322,11 @@ class TestPricingRule(unittest.TestCase): si.insert(ignore_permissions=True) item = si.items[0] - self.assertEquals(item.margin_rate_or_amount, 10) - self.assertEquals(item.rate_with_margin, 1100) + self.assertEqual(item.margin_rate_or_amount, 10) + self.assertEqual(item.rate_with_margin, 1100) self.assertEqual(item.discount_percentage, 10) - self.assertEquals(item.discount_amount, 110) - self.assertEquals(item.rate, 990) + self.assertEqual(item.discount_amount, 110) + self.assertEqual(item.rate, 990) def test_pricing_rule_with_margin_and_discount_amount(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') @@ -338,10 +338,10 @@ class TestPricingRule(unittest.TestCase): si.insert(ignore_permissions=True) item = si.items[0] - self.assertEquals(item.margin_rate_or_amount, 10) - self.assertEquals(item.rate_with_margin, 1100) - self.assertEquals(item.discount_amount, 110) - self.assertEquals(item.rate, 990) + self.assertEqual(item.margin_rate_or_amount, 10) + self.assertEqual(item.rate_with_margin, 1100) + self.assertEqual(item.discount_amount, 110) + self.assertEqual(item.rate, 990) def test_pricing_rule_for_product_discount_on_same_item(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') @@ -458,21 +458,21 @@ class TestPricingRule(unittest.TestCase): si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 100) + self.assertEqual(item.rate, 100) # Correct Customer and Incorrect is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 100) + self.assertEqual(item.rate, 100) # Correct Customer and correct is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 900) + self.assertEqual(item.rate, 900) def test_multiple_pricing_rules(self): make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1, @@ -545,11 +545,11 @@ class TestPricingRule(unittest.TestCase): apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10) si = create_sales_invoice(qty=5, do_not_submit=True) - self.assertEquals(len(si.items), 2) - self.assertEquals(si.items[1].rate, 10) + self.assertEqual(len(si.items), 2) + self.assertEqual(si.items[1].rate, 10) si1 = create_sales_invoice(qty=2, do_not_submit=True) - self.assertEquals(len(si1.items), 1) + self.assertEqual(len(si1.items), 1) for doc in [si, si1]: doc.delete() diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a0d76031fc..40a8f85d8d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -78,7 +78,7 @@ class TestAsset(unittest.TestCase): }) doc.set_missing_values() - self.assertEquals(doc.items[0].is_fixed_asset, 1) + self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_schedule_for_straight_line_method(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -565,7 +565,7 @@ class TestAsset(unittest.TestCase): doc = make_invoice(pr.name) - self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 42f4472f29..aaa98f2f1f 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -187,7 +187,7 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() - self.assertEquals(len(po.get('items')), 2) + self.assertEqual(len(po.get('items')), 2) self.assertEqual(po.status, 'To Receive and Bill') # ordered qty should increase on row addition self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7) @@ -234,7 +234,7 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() - self.assertEquals(len(po.get('items')), 1) + self.assertEqual(len(po.get('items')), 1) self.assertEqual(po.status, 'To Receive and Bill') # ordered qty should decrease (back to initial) on row deletion @@ -448,13 +448,13 @@ class TestPurchaseOrder(unittest.TestCase): pi.load_from_db() - self.assertEquals(pi.per_received, 100.00) - self.assertEquals(pi.items[0].qty, pi.items[0].received_qty) + self.assertEqual(pi.per_received, 100.00) + self.assertEqual(pi.items[0].qty, pi.items[0].received_qty) po.load_from_db() - self.assertEquals(po.per_received, 100.00) - self.assertEquals(po.per_billed, 100.00) + self.assertEqual(po.per_received, 100.00) + self.assertEqual(po.per_billed, 100.00) pr.cancel() @@ -674,8 +674,8 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1) - self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) - self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10) + self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10) # Create stock transfer rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item", @@ -690,7 +690,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # close PO po.update_status("Closed") @@ -698,7 +698,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Re-open PO po.update_status("Submitted") @@ -706,7 +706,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100) @@ -723,7 +723,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Cancel PR pr.cancel() @@ -731,7 +731,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # Make Purchase Invoice pi = make_pi_from_po(po.name) @@ -743,7 +743,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Cancel PR pi.cancel() @@ -751,7 +751,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # Cancel Stock Entry se.cancel() @@ -759,7 +759,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) # Cancel PO po.reload() @@ -768,7 +768,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) def test_exploded_items_in_subcontracted(self): item_code = "_Test Subcontracted FG Item 1" @@ -782,7 +782,7 @@ class TestPurchaseOrder(unittest.TestCase): exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) - self.assertEquals(exploded_items, supplied_items) + self.assertEqual(exploded_items, supplied_items) po1 = create_purchase_order(item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0) @@ -790,7 +790,7 @@ class TestPurchaseOrder(unittest.TestCase): supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items]) bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')]) - self.assertEquals(supplied_items1, bom_items) + self.assertEqual(supplied_items1, bom_items) def test_backflush_based_on_stock_entry(self): item_code = "_Test Subcontracted FG Item 1" @@ -840,8 +840,8 @@ class TestPurchaseOrder(unittest.TestCase): transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name]) issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) - self.assertEquals(transferred_items, issued_items) - self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000) + self.assertEqual(transferred_items, issued_items) + self.assertEqual(pr.get('items')[0].rm_supp_cost, 2000) transferred_rm_map = frappe._dict() diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 29487962f6..d370fbcda7 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -19,7 +19,7 @@ class TestMpesaSettings(unittest.TestCase): mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test") self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"})) self.assertTrue(mode_of_payment.name) - self.assertEquals(mode_of_payment.type, "Phone") + self.assertEqual(mode_of_payment.type, "Phone") def test_processing_of_account_balance(self): mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance") @@ -31,11 +31,11 @@ class TestMpesaSettings(unittest.TestCase): # test integration request creation and successful update of the status on receiving callback response self.assertTrue(integration_request) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") # test formatting of account balance received as string to json with appropriate currency symbol mpesa_doc.reload() - self.assertEquals(mpesa_doc.account_balance, dumps({ + self.assertEqual(mpesa_doc.account_balance, dumps({ "Working Account": { "current_balance": "Sh 481,000.00", "available_balance": "Sh 481,000.00", @@ -60,7 +60,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -75,12 +75,12 @@ class TestMpesaSettings(unittest.TestCase): # test integration request creation and successful update of the status on receiving callback response self.assertTrue(integration_request) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") pos_invoice.reload() integration_request.reload() - self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") + self.assertEqual(integration_request.status, "Completed") frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") integration_request.delete() @@ -104,7 +104,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -126,12 +126,12 @@ class TestMpesaSettings(unittest.TestCase): verify_transaction(**callback_response) # test completion of integration request integration_request = frappe.get_doc("Integration Request", integration_req_ids[i]) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") integration_requests.append(integration_request) # check receipt number once all the integration requests are completed pos_invoice.reload() - self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers)) + self.assertEqual(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers)) frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") [d.delete() for d in integration_requests] @@ -155,7 +155,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -175,7 +175,7 @@ class TestMpesaSettings(unittest.TestCase): verify_transaction(**callback_response) # test completion of integration request integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") # now one request is completed # second integration request fails @@ -187,7 +187,7 @@ class TestMpesaSettings(unittest.TestCase): 'name': ['not in', integration_req_ids] }, pluck="name") - self.assertEquals(len(new_integration_req_ids), 1) + self.assertEqual(len(new_integration_req_ids), 1) frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'") diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index fb72073a07..03e96a4b3b 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -17,7 +17,7 @@ class TestClinicalProcedure(unittest.TestCase): procedure_template.disabled = 1 procedure_template.save() - self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) def test_consumables(self): patient, medical_department, practitioner = create_healthcare_docs() diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py index 79ab8a4d7f..c9f0029ed8 100644 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py @@ -18,7 +18,7 @@ class TestLabTest(unittest.TestCase): lab_template.disabled = 1 lab_template.save() - self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1) lab_template.reload() @@ -57,7 +57,7 @@ class TestLabTest(unittest.TestCase): # sample collection should not be created lab_test.reload() - self.assertEquals(lab_test.sample, None) + self.assertEqual(lab_test.sample, None) def test_create_lab_tests_from_sales_invoice(self): sales_invoice = create_sales_invoice() diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 2bb8a53c45..5f2dc480a1 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -20,13 +20,13 @@ class TestPatientAppointment(unittest.TestCase): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) appointment = create_appointment(patient, practitioner, nowdate()) - self.assertEquals(appointment.status, 'Open') + self.assertEqual(appointment.status, 'Open') appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2)) - self.assertEquals(appointment.status, 'Scheduled') + self.assertEqual(appointment.status, 'Scheduled') encounter = create_encounter(appointment) - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') encounter.cancel() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_start_encounter(self): patient, medical_department, practitioner = create_healthcare_docs() diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index 7fb159d6b5..d079bedb42 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -18,24 +18,24 @@ class TestTherapyPlan(unittest.TestCase): def test_status(self): plan = create_therapy_plan() - self.assertEquals(plan.status, 'Not Started') + self.assertEqual(plan.status, 'Not Started') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') frappe.get_doc(session).submit() - self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') + self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') frappe.get_doc(session).submit() - self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') + self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') patient, medical_department, practitioner = create_healthcare_docs() appointment = create_appointment(patient, practitioner, nowdate()) session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) session = frappe.get_doc(session) session.submit() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') session.cancel() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_therapy_plan_from_template(self): patient = create_patient() @@ -49,7 +49,7 @@ class TestTherapyPlan(unittest.TestCase): si.save() therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount') - self.assertEquals(si.items[0].amount, therapy_plan_template_amt) + self.assertEqual(si.items[0].amount, therapy_plan_template_amt) def create_therapy_plan(template=None): diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py index 03a1be8a4e..21f6369975 100644 --- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py @@ -13,7 +13,7 @@ class TestTherapyType(unittest.TestCase): therapy_type.disabled = 1 therapy_type.save() - self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1) def create_therapy_type(): exercise = create_exercise_type() 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 74ce30108f..3b99c57051 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 @@ -68,19 +68,19 @@ class TestCompensatoryLeaveRequest(unittest.TestCase): filters = dict(transaction_name=compensatory_leave_request.leave_allocation) leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, 1) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, 1) # check reverse leave ledger entry on cancellation compensatory_leave_request.cancel() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc') - self.assertEquals(len(leave_ledger_entry), 2) - self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, -1) + self.assertEqual(len(leave_ledger_entry), 2) + self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, -1) def get_compensatory_leave_request(employee, leave_date=today()): prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request', diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 3f22ca2141..578eccf787 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -88,9 +88,9 @@ class TestExpenseClaim(unittest.TestCase): ]) 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) + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) def test_rejected_expense_claim(self): payable_account = get_payable_account(company_name) @@ -104,11 +104,11 @@ class TestExpenseClaim(unittest.TestCase): }) expense_claim.submit() - self.assertEquals(expense_claim.status, 'Rejected') - self.assertEquals(expense_claim.total_sanctioned_amount, 0.0) + self.assertEqual(expense_claim.status, 'Rejected') + self.assertEqual(expense_claim.total_sanctioned_amount, 0.0) gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name}) - self.assertEquals(len(gl_entry), 0) + self.assertEqual(len(gl_entry), 0) def test_expense_approver_perms(self): user = "test_approver_perm_emp@example.com" diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index 690a692ddc..b3e1dc8d87 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -35,13 +35,13 @@ class TestJobOffer(unittest.TestCase): job_offer = create_job_offer(job_applicant=job_applicant.name) job_offer.submit() job_applicant.reload() - self.assertEquals(job_applicant.status, "Accepted") + self.assertEqual(job_applicant.status, "Accepted") # status update after rejection job_offer.status = "Rejected" job_offer.submit() job_applicant.reload() - self.assertEquals(job_applicant.status, "Rejected") + self.assertEqual(job_applicant.status, "Rejected") def create_job_offer(**args): args = frappe._dict(args) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 0b71036c86..6e7ae87d08 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -96,7 +96,7 @@ class TestLeaveAllocation(unittest.TestCase): carry_forward=1) leave_allocation_1.submit() - self.assertEquals(leave_allocation_1.unused_leaves, 10) + self.assertEqual(leave_allocation_1.unused_leaves, 10) leave_allocation_1.cancel() @@ -108,7 +108,7 @@ class TestLeaveAllocation(unittest.TestCase): new_leaves_allocated=25) leave_allocation_2.submit() - self.assertEquals(leave_allocation_2.unused_leaves, 5) + self.assertEqual(leave_allocation_2.unused_leaves, 5) def test_carry_forward_leaves_expiry(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -145,7 +145,7 @@ class TestLeaveAllocation(unittest.TestCase): to_date=add_months(nowdate(), 12)) leave_allocation_1.submit() - self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) + self.assertEqual(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) def test_creation_of_leave_ledger_entry_on_submit(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -155,10 +155,10 @@ class TestLeaveAllocation(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name)) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, leave_allocation.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) # check if leave ledger entry is deleted on cancellation leave_allocation.cancel() diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index b54c9712c8..a4a96b813e 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -16,36 +16,36 @@ from erpnext.hr.doctype.employee.test_employee import make_employee test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"] _test_records = [ - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00001", - "from_date": "2013-05-01", - "description": "_Test Reason", - "leave_type": "_Test Leave Type", - "posting_date": "2013-01-02", - "to_date": "2013-05-05" - }, - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00002", - "from_date": "2013-05-01", - "description": "_Test Reason", - "leave_type": "_Test Leave Type", - "posting_date": "2013-01-02", - "to_date": "2013-05-05" - }, - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00001", - "from_date": "2013-01-15", - "description": "_Test Reason", - "leave_type": "_Test Leave Type LWP", - "posting_date": "2013-01-02", - "to_date": "2013-01-15" - } + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00001", + "from_date": "2013-05-01", + "description": "_Test Reason", + "leave_type": "_Test Leave Type", + "posting_date": "2013-01-02", + "to_date": "2013-05-05" + }, + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00002", + "from_date": "2013-05-01", + "description": "_Test Reason", + "leave_type": "_Test Leave Type", + "posting_date": "2013-01-02", + "to_date": "2013-05-05" + }, + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00001", + "from_date": "2013-01-15", + "description": "_Test Reason", + "leave_type": "_Test Leave Type LWP", + "posting_date": "2013-01-02", + "to_date": "2013-01-15" + } ] @@ -516,9 +516,9 @@ class TestLeaveApplication(unittest.TestCase): leave_application.submit() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) - self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) + self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) # check if leave ledger entry is deleted on cancellation leave_application.cancel() @@ -549,11 +549,11 @@ class TestLeaveApplication(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name)) - self.assertEquals(len(leave_ledger_entry), 2) - self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, -9) - self.assertEquals(leave_ledger_entry[1].leaves, -2) + self.assertEqual(len(leave_ledger_entry), 2) + self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, -9) + self.assertEqual(leave_ledger_entry[1].leaves, -2) def test_leave_application_creation_after_expiry(self): # test leave balance for carry forwarded allocation @@ -566,7 +566,7 @@ class TestLeaveApplication(unittest.TestCase): create_carry_forwarded_allocation(employee, leave_type) - self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) + self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) def test_leave_approver_perms(self): employee = get_employee() diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index aafc9642d4..e0ffa5dd41 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -88,10 +88,10 @@ class TestLeaveEncashment(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name)) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, leave_encashment.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) # check if leave ledger entry is deleted on cancellation diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index fae6f860b6..fa4707ce2b 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -55,9 +55,9 @@ class TestLoan(unittest.TestCase): def test_loan(self): loan = frappe.get_doc("Loan", {"applicant":self.applicant1}) - self.assertEquals(loan.monthly_repayment_amount, 15052) - self.assertEquals(flt(loan.total_interest_payable, 0), 21034) - self.assertEquals(flt(loan.total_payment, 0), 301034) + self.assertEqual(loan.monthly_repayment_amount, 15052) + self.assertEqual(flt(loan.total_interest_payable, 0), 21034) + self.assertEqual(flt(loan.total_payment, 0), 301034) schedule = loan.repayment_schedule @@ -72,9 +72,9 @@ class TestLoan(unittest.TestCase): loan.monthly_repayment_amount = 14000 loan.save() - self.assertEquals(len(loan.repayment_schedule), 22) - self.assertEquals(flt(loan.total_interest_payable, 0), 22712) - self.assertEquals(flt(loan.total_payment, 0), 302712) + self.assertEqual(len(loan.repayment_schedule), 22) + self.assertEqual(flt(loan.total_interest_payable, 0), 22712) + self.assertEqual(flt(loan.total_payment, 0), 302712) def test_loan_with_security(self): @@ -89,7 +89,7 @@ class TestLoan(unittest.TestCase): loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application) - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) def test_loan_disbursement(self): pledge = [{ @@ -102,7 +102,7 @@ class TestLoan(unittest.TestCase): create_pledge(loan_application) loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application) - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) loan.submit() @@ -120,8 +120,8 @@ class TestLoan(unittest.TestCase): filters = {'voucher_type': 'Loan Disbursement', 'voucher_no': loan_disbursement_entry2.name} ) - self.assertEquals(loan.status, "Disbursed") - self.assertEquals(loan.disbursed_amount, 1000000) + self.assertEqual(loan.status, "Disbursed") + self.assertEqual(loan.disbursed_amount, 1000000) self.assertTrue(gl_entries1) self.assertTrue(gl_entries2) @@ -137,7 +137,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -156,15 +156,15 @@ class TestLoan(unittest.TestCase): repayment_entry.submit() penalty_amount = (accrued_interest_amount * 5 * 25) / 100 - self.assertEquals(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0)) amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount']) loan.load_from_db() total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount'] - self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) - self.assertEquals(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid - + self.assertEqual(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) + self.assertEqual(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid - penalty_amount - total_interest_paid, 0)) def test_loan_closure(self): @@ -179,7 +179,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -204,12 +204,12 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") def test_loan_repayment_for_term_loan(self): pledges = [{ @@ -241,8 +241,8 @@ class TestLoan(unittest.TestCase): amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', 'paid_principal_amount']) - self.assertEquals(amounts[0], 11250.00) - self.assertEquals(amounts[1], 78303.00) + self.assertEqual(amounts[0], 11250.00) + self.assertEqual(amounts[1], 78303.00) def test_security_shortfall(self): pledges = [{ @@ -268,17 +268,17 @@ class TestLoan(unittest.TestCase): loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name}) self.assertTrue(loan_security_shortfall) - self.assertEquals(loan_security_shortfall.loan_amount, 1000000.00) - self.assertEquals(loan_security_shortfall.security_value, 800000.00) - self.assertEquals(loan_security_shortfall.shortfall_amount, 600000.00) + self.assertEqual(loan_security_shortfall.loan_amount, 1000000.00) + self.assertEqual(loan_security_shortfall.security_value, 800000.00) + self.assertEqual(loan_security_shortfall.shortfall_amount, 600000.00) frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250 where loan_security='Test Security 2'""") create_process_loan_security_shortfall() loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name}) - self.assertEquals(loan_security_shortfall.status, "Completed") - self.assertEquals(loan_security_shortfall.shortfall_amount, 0) + self.assertEqual(loan_security_shortfall.status, "Completed") + self.assertEqual(loan_security_shortfall.shortfall_amount, 0) def test_loan_security_unpledge(self): pledge = [{ @@ -292,7 +292,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -312,7 +312,7 @@ class TestLoan(unittest.TestCase): request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") unpledge_request = unpledge_security(loan=loan.name, save=1) unpledge_request.submit() @@ -323,11 +323,11 @@ class TestLoan(unittest.TestCase): pledged_qty = get_pledged_security_qty(loan.name) self.assertEqual(loan.status, 'Closed') - self.assertEquals(sum(pledged_qty.values()), 0) + self.assertEqual(sum(pledged_qty.values()), 0) amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(amounts['pending_principal_amount'], 0) - self.assertEquals(amounts['payable_principal_amount'], 0.0) + self.assertEqual(amounts['payable_principal_amount'], 0.0) self.assertEqual(amounts['interest_amount'], 0) def test_partial_loan_security_unpledge(self): @@ -346,7 +346,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -379,7 +379,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) unpledge_map = {'Test Security 1': 4000} unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1) @@ -450,7 +450,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -475,7 +475,7 @@ class TestLoan(unittest.TestCase): request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(amounts['pending_principal_amount'], 0.0) @@ -492,7 +492,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -533,8 +533,8 @@ class TestLoan(unittest.TestCase): calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount') - self.assertEquals(loan.loan_amount, 1000000) - self.assertEquals(calculated_penalty_amount, penalty_amount) + self.assertEqual(loan.loan_amount, 1000000) + self.assertEqual(calculated_penalty_amount, penalty_amount) def test_penalty_repayment(self): loan, dummy = create_loan_scenario_for_penalty(self) @@ -547,13 +547,13 @@ class TestLoan(unittest.TestCase): repayment_entry.submit() amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01') - self.assertEquals(amounts['penalty_amount'], second_penalty) + self.assertEqual(amounts['penalty_amount'], second_penalty) repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty) repayment_entry.submit() amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02') - self.assertEquals(amounts['penalty_amount'], 0) + self.assertEqual(amounts['penalty_amount'], 0) def test_loan_write_off_limit(self): pledge = [{ @@ -567,7 +567,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -589,15 +589,15 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 50) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 50) request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") def test_loan_amount_write_off(self): pledge = [{ @@ -611,7 +611,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -633,17 +633,17 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 100) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 100) we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount']) we.submit() amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 0) def create_loan_scenario_for_penalty(doc): pledge = [{ diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index a8753877a6..da56710c67 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -87,7 +87,7 @@ class TestLoanDisbursement(unittest.TestCase): loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -114,5 +114,5 @@ class TestLoanDisbursement(unittest.TestCase): per_day_interest = get_per_day_interest(1500000, 13.5, '2019-10-30') interest = per_day_interest * 15 - self.assertEquals(amounts['pending_principal_amount'], 1500000) - self.assertEquals(amounts['interest_amount'], flt(interest + previous_interest, 2)) + self.assertEqual(amounts['pending_principal_amount'], 1500000) + self.assertEqual(amounts['interest_amount'], flt(interest + previous_interest, 2)) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py index 85e008ac29..eb626f3eee 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py @@ -52,7 +52,7 @@ class TestLoanInterestAccrual(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date=last_date) loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name}) - self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0)) + self.assertEqual(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0)) def test_accumulated_amounts(self): pledge = [{ @@ -76,7 +76,7 @@ class TestLoanInterestAccrual(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date=last_date) loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name}) - self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0)) + self.assertEqual(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0)) next_start_date = '2019-10-31' next_end_date = '2019-11-29' @@ -90,4 +90,4 @@ class TestLoanInterestAccrual(unittest.TestCase): loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name, 'process_loan_interest_accrual': process}) - self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount) + self.assertEqual(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 7108338dab..e1cca9e3ef 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -223,7 +223,7 @@ class TestBOM(unittest.TestCase): is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) - self.assertEquals(bom_items, supplied_items) + self.assertEqual(bom_items, supplied_items) def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index ac9a409bcb..80d1cdfc8f 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -45,16 +45,16 @@ class TestBOMUpdateTool(unittest.TestCase): else: doc = frappe.get_doc("BOM", bom_no) - self.assertEquals(doc.total_cost, 200) + self.assertEqual(doc.total_cost, 200) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200) update_cost() doc.load_from_db() - self.assertEquals(doc.total_cost, 300) + self.assertEqual(doc.total_cost, 300) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100) update_cost() doc.load_from_db() - self.assertEquals(doc.total_cost, 200) + self.assertEqual(doc.total_cost, 200) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 6b1fafe5f4..cb1ee92196 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -473,7 +473,7 @@ class TestWorkOrder(unittest.TestCase): def test_cost_center_for_manufacture(self): wo_order = make_wo_order_test_record() ste = make_stock_entry(wo_order.name, "Material Transfer for Manufacture", wo_order.qty) - self.assertEquals(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC") + self.assertEqual(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC") def test_operation_time_with_batch_size(self): fg_item = "Test Batch Size Item For BOM" @@ -539,11 +539,11 @@ class TestWorkOrder(unittest.TestCase): ste_cancel_list.append(ste1) ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2)) - self.assertEquals(ste3.fg_completed_qty, 2) + self.assertEqual(ste3.fg_completed_qty, 2) expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4} for row in ste3.items: - self.assertEquals(row.qty, expected_qty.get(row.item_code)) + self.assertEqual(row.qty, expected_qty.get(row.item_code)) ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() @@ -577,7 +577,7 @@ class TestWorkOrder(unittest.TestCase): ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) for ste_row in ste3.items: if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: - self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) ste3.submit() ste_cancel_list.append(ste3) @@ -585,7 +585,7 @@ class TestWorkOrder(unittest.TestCase): ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) for ste_row in ste2.items: if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: - self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py index c6a534dac3..bbe9bf5228 100644 --- a/erpnext/non_profit/doctype/donation/test_donation.py +++ b/erpnext/non_profit/doctype/donation/test_donation.py @@ -39,7 +39,7 @@ class TestDonation(unittest.TestCase): donation.on_payment_authorized() donation.reload() - self.assertEquals(donation.paid, 1) + self.assertEqual(donation.paid, 1) self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name})) diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py index bf5c4025a0..b717491a82 100644 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -13,7 +13,7 @@ class TestHomepage(unittest.TestCase): set_request(method='GET', path='home') response = render() - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) html = frappe.safe_decode(response.get_data()) self.assertTrue('
    Date: Fri, 7 May 2021 20:30:04 +0530 Subject: [PATCH 0714/1264] feat!: add pick batch button (#25413) * feat!: add pick batch button BREAKING CHANGE: replaces setup_serial_no with setup_serial_or_batch_no. * refactor: use setup_serial_or_batch_no instead of setup_serial_no * refactor: use setup_serial_or_batch_no instead of setup_serial_no * refactor: use setup_serial_or_batch_no instead of setup_serial_no * style: add sider review changes * refactor: make consice, extract function * refactor: camel to snake casing --- .../doctype/sales_invoice/sales_invoice.js | 4 +- erpnext/public/js/utils.js | 48 ++++++++++--------- .../doctype/delivery_note/delivery_note.js | 4 +- .../stock/doctype/stock_entry/stock_entry.js | 2 +- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8a42d9e13c..7c73ad6c90 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -356,11 +356,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }, items_on_form_rendered: function() { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, packed_items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, make_sales_return: function() { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 19c9073090..472746ab84 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -48,31 +48,24 @@ $.extend(erpnext, { return cint(frappe.boot.sysdefaults.allow_stale); }, - setup_serial_no: function() { - var grid_row = cur_frm.open_grid_row(); - if(!grid_row || !grid_row.grid_form.fields_dict.serial_no || - grid_row.grid_form.fields_dict.serial_no.get_status()!=="Write") return; + setup_serial_or_batch_no: function() { + let grid_row = cur_frm.open_grid_row(); + if (!grid_row || !grid_row.grid_form.fields_dict.serial_no || + grid_row.grid_form.fields_dict.serial_no.get_status() !== "Write") return; - var $btn = $('') - .appendTo($("
    ") - .css({"margin-bottom": "10px", "margin-top": "10px"}) - .appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper)); + frappe.model.get_value('Item', {'name': grid_row.doc.item_code}, + ['has_serial_no', 'has_batch_no'], ({has_serial_no, has_batch_no}) => { + Object.assign(grid_row.doc, {has_serial_no, has_batch_no}); - var me = this; - $btn.on("click", function() { - let callback = ''; - let on_close = ''; - - frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no', - (data) => { - if(data) { - grid_row.doc.has_serial_no = data.has_serial_no; - me.show_serial_batch_selector(grid_row.frm, grid_row.doc, - callback, on_close, true); - } + if (has_serial_no) { + attach_selector_button(__("Add Serial No"), + grid_row.grid_form.fields_dict.serial_no.$wrapper, this, grid_row); + } else if (has_batch_no) { + attach_selector_button(__("Pick Batch No"), + grid_row.grid_form.fields_dict.batch_no.$wrapper, this, grid_row); } - ); - }); + } + ); }, route_to_adjustment_jv: (args) => { @@ -743,3 +736,14 @@ $(document).on('app_ready', function() { }); } }); + +function attach_selector_button(inner_text, append_loction, context, grid_row) { + let $btn_div = $("
    ").css({"margin-bottom": "10px", "margin-top": "10px"}) + .appendTo(append_loction); + let $btn = $(``) + .appendTo($btn_div); + + $btn.on("click", function() { + context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true); + }); +} diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 334bdeac9d..7875b9cd87 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -273,11 +273,11 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( }, items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, packed_items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, close_delivery_note: function(doc){ diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ef7d54ac96..772c8df96e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -996,7 +996,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, toggle_related_fields: function(doc) { From aa9e1720913527aef4193a053606d5cf38a4f1cf Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Sat, 8 May 2021 17:15:33 +0530 Subject: [PATCH 0715/1264] feat: Add Create Expense Claim button in Delivery Trip (#25526) * feat(Delivery Trip): Add employee_code field * feat(Expense Claim): Add Delivery Trip Number field * feat(Delivery Trip): Add Create Expense Claim button * feat(Delivery Trip): Make Create Expense Claim button show up after save * fix(Delivery Trip): Fix Sider issues * fix(Delivery Trip): Display button after submit * fix(Delivery Trip & Expense Claim): Rename new fields * fix(Delivery Trip): Add button in refresh * fix(Delivery Trip): Remove redundant line * fix(Expense Claim): Display delivery_trip only if non-empty * fix(Delivery Trip): Add test for Create Expense Claim * fix(Delivery Trip): Fix Sider Issue * fix(Delivery Trip): Only display Create Expense Claim if the driver is an employee * fix(Delivery Trip): Fix test * fix(Delivery Trip): Fix make_expense_claim() * fix: sider Co-authored-by: Saqib --- erpnext/hr/doctype/expense_claim/expense_claim.json | 10 +++++++++- .../stock/doctype/delivery_trip/delivery_trip.js | 9 +++++++++ .../stock/doctype/delivery_trip/delivery_trip.json | 11 ++++++++++- .../stock/doctype/delivery_trip/delivery_trip.py | 13 +++++++++++++ .../doctype/delivery_trip/test_delivery_trip.py | 6 +++++- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index e3e6e80616..a268c15c70 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -14,6 +14,7 @@ "column_break_5", "expense_approver", "approval_status", + "delivery_trip", "is_paid", "expense_details", "expenses", @@ -365,13 +366,20 @@ "label": "Total Taxes and Charges", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "depends_on": "eval: doc.delivery_trip", + "fieldname": "delivery_trip", + "fieldtype": "Link", + "label": "Delivery Trip", + "options": "Delivery Trip" } ], "icon": "fa fa-money", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-05-04 05:35:12.040199", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index a6fbb66aa2..68cba2993c 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -41,6 +41,15 @@ frappe.ui.form.on('Delivery Trip', { }, refresh: function (frm) { + if (frm.doc.docstatus == 1 && frm.doc.employee) { + frm.add_custom_button(__('Expense Claim'), function() { + frappe.model.open_mapped_doc({ + method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.make_expense_claim', + frm: cur_frm, + }); + }, __("Create")); + } + if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) { frm.add_custom_button(__("Notify Customers via Email"), function () { frm.trigger('notify_customers'); diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json index 879901f6a8..11b71c2076 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json @@ -21,6 +21,7 @@ "column_break_4", "vehicle", "departure_time", + "employee", "delivery_service_stops", "delivery_stops", "calculate_arrival_time", @@ -176,11 +177,19 @@ "fieldtype": "Data", "label": "Driver Email", "read_only": 1 + }, + { + "fetch_from": "driver.employee", + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-01-26 22:37:14.824021", + "modified": "2021-04-30 21:21:36.610142", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Trip", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index de85bc3922..81e730126e 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -11,6 +11,7 @@ from frappe import _ from frappe.contacts.doctype.address.address import get_address_display from frappe.model.document import Document from frappe.utils import cint, get_datetime, get_link_to_form +from frappe.model.mapper import get_mapped_doc class DeliveryTrip(Document): @@ -394,3 +395,15 @@ def get_driver_email(driver): employee = frappe.db.get_value("Driver", driver, "employee") email = frappe.db.get_value("Employee", employee, "prefered_email") return {"email": email} + +@frappe.whitelist() +def make_expense_claim(source_name, target_doc=None): + doc = get_mapped_doc("Delivery Trip", source_name, + {"Delivery Trip": { + "doctype": "Expense Claim", + "field_map": { + "name" : "delivery_trip" + } + }}, target_doc) + + return doc \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index eeea6da7a4..1e71603175 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -7,7 +7,7 @@ import unittest import erpnext import frappe -from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers +from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers, make_expense_claim from erpnext.tests.utils import create_test_contact_and_address from frappe.utils import add_days, flt, now_datetime, nowdate @@ -28,6 +28,10 @@ class TestDeliveryTrip(unittest.TestCase): frappe.db.sql("delete from `tabEmail Template`") frappe.db.sql("delete from `tabDelivery Trip`") + def test_expense_claim_fields_are_fetched_properly(self): + expense_claim = make_expense_claim(self.delivery_trip.name) + self.assertEqual(self.delivery_trip.name, expense_claim.delivery_trip) + def test_delivery_trip_notify_customers(self): notify_customers(delivery_trip=self.delivery_trip.name) self.delivery_trip.load_from_db() From 77154418422074e9c125d6f5505b7891746d7efe Mon Sep 17 00:00:00 2001 From: noahjacob Date: Sun, 9 May 2021 20:02:23 +0530 Subject: [PATCH 0716/1264] feat: added supplier item group doctype --- .../doctype/supplier_item_group/__init__.py | 0 .../supplier_item_group.js | 8 +++ .../supplier_item_group.json | 51 +++++++++++++++++++ .../supplier_item_group.py | 10 ++++ .../test_supplier_item_group.py | 10 ++++ 5 files changed, 79 insertions(+) create mode 100644 erpnext/buying/doctype/supplier_item_group/__init__.py create mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.js create mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.json create mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.py create mode 100644 erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py diff --git a/erpnext/buying/doctype/supplier_item_group/__init__.py b/erpnext/buying/doctype/supplier_item_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js new file mode 100644 index 0000000000..f7da90d98d --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Supplier Item Group', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json new file mode 100644 index 0000000000..1417ec23cf --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json @@ -0,0 +1,51 @@ +{ + "actions": [], + "creation": "2021-05-07 18:16:40.621421", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "supplier", + "item_group" + ], + "fields": [ + { + "fieldname": "supplier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Group", + "options": "Item Group" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-05-07 18:16:40.621421", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Item Group", + "owner": "Administrator", + "permissions": [ + { + "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/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py new file mode 100644 index 0000000000..6fbeb37242 --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 SupplierItemGroup(Document): + pass diff --git a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py new file mode 100644 index 0000000000..c75044d44e --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestSupplierItemGroup(unittest.TestCase): + pass From 50f52dfbcbf3124c1f9468807f1f22e6df2cb10c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 7 May 2021 17:18:48 +0530 Subject: [PATCH 0717/1264] refactor: variable names and refactored cancel function into submit function --- .../production_plan/production_plan.json | 18 +++++------ .../production_plan/production_plan.py | 16 +++++----- .../production_plan/test_production_plan.py | 17 +++++----- .../production_plan_item_reference.json | 16 +++++----- .../doctype/work_order/work_order.py | 32 ++++++------------- 5 files changed, 44 insertions(+), 55 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 5c73992d1b..3041507caf 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -31,7 +31,7 @@ "combine_items", "po_items", "section_break_25", - "prod_plan_ref", + "prod_plan_references", "material_request_planning", "include_non_stock_items", "include_subcontracted_items", @@ -320,13 +320,6 @@ "fieldtype": "Check", "label": "Include Safety Stock in Required Qty Calculation" }, - { - "fieldname": "prod_plan_ref", - "fieldtype": "Table", - "hidden": 1, - "label": "Production Plan Item Reference", - "options": "Production Plan Item Reference" - }, { "default": "0", "fieldname": "combine_items", @@ -336,13 +329,20 @@ { "fieldname": "section_break_25", "fieldtype": "Section Break" + }, + { + "fieldname": "prod_plan_references", + "fieldtype": "Table", + "hidden": 1, + "label": "Production Plan Item Reference", + "options": "Production Plan Item Reference" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-26 14:11:43.564957", + "modified": "2021-05-07 16:56:00.255001", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 088089f87f..8d578fd935 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -172,7 +172,7 @@ class ProductionPlan(Document): item_details = get_item_details(data.item_code) if self.combine_items: if item_details.bom_no in refs.keys(): - refs[item_details.bom_no]['qty'] = refs[item_details.bom_no]['qty'] + data.pending_qty + refs[item_details.bom_no]['qty'] += data.pending_qty refs[item_details.bom_no]['so'].append(data.parent) refs[item_details.bom_no]['so_items'].append(data.name) refs[item_details.bom_no]['planned_qty'].append(data.pending_qty) @@ -217,15 +217,15 @@ class ProductionPlan(Document): self.add_pp_ref(refs) def add_pp_ref(self, refs): - for r in refs: + for bom_no in refs: idx = 0 - for so in refs[r]['so']: - self.append('prod_plan_ref', { - 'item_ref': refs[r]['ref'], + for so in refs[bom_no]['so']: + self.append('prod_plan_references', { + 'item_reference': refs[bom_no]['ref'], 'sales_order': so, - 'sales_order_item':refs[r]['so_items'][idx], - 'qty':refs[r]['planned_qty'][idx] - }) + 'sales_order_item':refs[bom_no]['so_items'][idx], + 'qty':refs[bom_no]['planned_qty'][idx] + }) idx+=1 diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 19b06bc8dd..ec5c5e0e13 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -167,8 +167,8 @@ class TestProductionPlan(unittest.TestCase): }) pln.combine_items = 1 pln.get_so_items() - for d in pln.prod_plan_ref: - d.item_ref = pln.po_items[0].name + for plan_reference in pln.prod_plan_references: + plan_reference.item_reference = pln.po_items[0].name pln.submit() self.assertTrue(pln.po_items[0].planned_qty,3) @@ -184,13 +184,14 @@ class TestProductionPlan(unittest.TestCase): wo_doc.submit() so_items = [] - for d in pln.prod_plan_ref: - so_items.append(d.sales_order_item) - so_wo_qty = frappe.db.get_value('Sales Order Item', d.sales_order_item, 'work_order_qty') - self.assertEqual(so_wo_qty, d.qty) + for plan_reference in pln.prod_plan_references: + so_items.append(plan_reference.sales_order_item) + so_wo_qty = frappe.db.get_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty') + self.assertEqual(so_wo_qty, plan_reference.qty) + wo_doc.cancel() - for s in so_items: - so_wo_qty = frappe.db.get_value('Sales Order Item', s, 'work_order_qty') + for so_item in so_items: + so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty') self.assertEqual(so_wo_qty, 0.0) lat_plan = frappe.get_doc('Production Plan',pln.name) diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json index 19e813c5b3..84dee4ad28 100644 --- a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json @@ -5,18 +5,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "item_ref", + "item_reference", "sales_order", "sales_order_item", "qty" ], "fields": [ - { - "fieldname": "item_ref", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Reference" - }, { "fieldname": "sales_order", "fieldtype": "Link", @@ -35,12 +29,18 @@ "fieldtype": "Data", "in_list_view": 1, "label": "qty" + }, + { + "fieldname": "item_reference", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Reference" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-23 16:55:22.161418", + "modified": "2021-05-07 17:03:49.707487", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item Reference", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index d9956e5bca..bb6450b775 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -240,10 +240,8 @@ class WorkOrder(Document): frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) - - prod_plan = frappe.get_doc('Production Plan', self.production_plan) - if prod_plan.prod_plan_ref: + if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}): self.update_work_order_qty_in_combined_so() else: self.update_work_order_qty_in_so() @@ -256,12 +254,10 @@ class WorkOrder(Document): def on_cancel(self): self.validate_cancel() - frappe.db.set(self,'status', 'Cancelled') - prod_plan = frappe.get_doc('Production Plan', self.production_plan) - if prod_plan.prod_plan_ref: - self.update_work_order_combined_on_cancel() + if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}): + self.update_work_order_qty_in_combined_so() else: self.update_work_order_qty_in_so() @@ -381,24 +377,16 @@ class WorkOrder(Document): total_bundle_qty = 1 prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) + item_reference = frappe.get_value('Production Plan Item', self.production_plan_item,'item_reference') - for p in prod_plan.prod_plan_ref: - if p.item_ref == pp_item.item_reference: - work_order_qty = int(p.qty) + for plan_reference in prod_plan.prod_plan_references: + work_order_qty = 0.0 + if plan_reference.item_reference == item_reference: + if self.docstatus == 1: + work_order_qty = cint(plan_reference.qty) / total_bundle_qty frappe.db.set_value('Sales Order Item', - p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + plan_reference.sales_order_item, 'work_order_qty', work_order_qty) - def update_work_order_combined_on_cancel(self): - prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) - - for p in prod_plan.prod_plan_ref: - if p.item_ref == pp_item.item_reference: - frappe.db.set_value('Sales Order Item', - p.sales_order_item, 'work_order_qty', 0.0) - - def update_completed_qty_in_material_request(self): if self.material_request: frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item]) From 9226cd3932e3a087c7e474c43b7b8d0535221c3c Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 10 May 2021 12:36:56 +0530 Subject: [PATCH 0718/1264] feat(india): reduced rate of depreciation as per IT Act (#25648) * feat(india): reduced rate of depreciation as per IT Act * refactor: check date difference instead of month difference * feat: add test for regional feature --- erpnext/assets/doctype/asset/asset.py | 51 ++++++++++------------ erpnext/assets/doctype/asset/test_asset.py | 39 +++++++++++++++++ erpnext/hooks.py | 3 +- erpnext/regional/india/utils.py | 21 +++++++++ 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9aff1440d6..8799275fc4 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -195,8 +195,7 @@ class Asset(AccountsController): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = self.get_depreciation_amount(value_after_depreciation, - d.total_number_of_depreciations, d) + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d) if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: schedule_date = add_months(d.depreciation_start_date, @@ -208,7 +207,7 @@ class Asset(AccountsController): # For first row if has_pro_rata and n==0: - depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, self.available_for_use_date, d.depreciation_start_date) # For first depr schedule date will be the start date @@ -220,7 +219,7 @@ class Asset(AccountsController): to_date = add_months(self.available_for_use_date, n * cint(d.frequency_of_depreciation)) - depreciation_amount, days, months = get_pro_rata_amt(d, + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, schedule_date, to_date) monthly_schedule_date = add_months(schedule_date, 1) @@ -365,24 +364,6 @@ class Asset(AccountsController): def get_value_after_depreciation(self, idx): return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation) - def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): - precision = self.precision("gross_purchase_amount") - - if row.depreciation_method in ("Straight Line", "Manual"): - depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked)) - - if not depreciation_left: - frappe.msgprint(_("All the depreciations has been booked")) - depreciation_amount = flt(row.expected_value_after_useful_life) - return depreciation_amount - - depreciation_amount = (flt(row.value_after_depreciation) - - flt(row.expected_value_after_useful_life)) / depreciation_left - else: - depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) - - return depreciation_amount - def validate_expected_value_after_useful_life(self): for row in self.get('finance_books'): accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount @@ -575,6 +556,13 @@ class Asset(AccountsController): return 100 * (1 - flt(depreciation_rate, float_precision)) + def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date): + days = date_diff(to_date, from_date) + months = month_diff(to_date, from_date) + total_days = get_total_days(to_date, row.frequency_of_depreciation) + + return (depreciation_amount * flt(days)) / flt(total_days), days, months + def update_maintenance_status(): assets = frappe.get_all( "Asset", filters={"docstatus": 1, "maintenance_required": 1} @@ -758,15 +746,20 @@ def make_asset_movement(assets, purpose=None): def is_cwip_accounting_enabled(asset_category): return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting")) -def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): - days = date_diff(to_date, from_date) - months = month_diff(to_date, from_date) - total_days = get_total_days(to_date, row.frequency_of_depreciation) - - return (depreciation_amount * flt(days)) / flt(total_days), days, months - def get_total_days(date, frequency): period_start_date = add_months(date, cint(frequency) * -1) return date_diff(date, period_start_date) + +@erpnext.allow_regional +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + + if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + else: + depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) + + return depreciation_amount \ 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 40a8f85d8d..30a270c204 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -635,6 +635,45 @@ class TestAsset(unittest.TestCase): frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc) + def test_discounted_wdv_depreciation_rate_for_indian_region(self): + # set indian company + company_flag = frappe.flags.company + frappe.flags.company = "_Test Company" + + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-12' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 1106.85, 1106.85], + ["2031-12-31", 3446.58, 4553.43], + ["2032-12-31", 1723.29, 6276.72], + ["2033-06-12", 723.28, 7000.00] + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + # reset indian company + frappe.flags.company = company_flag + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bb6cd8bdc2..9d1ce9bbbf 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -426,7 +426,8 @@ regional_overrides = { '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.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', - 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields' + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', + 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount' }, '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 6338056698..052d7bdedf 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -879,3 +879,24 @@ def update_taxable_values(doc, method): if total_charges != additional_taxes: diff = additional_taxes - total_charges doc.get('items')[item_count - 1].taxable_value += diff + +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + + if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + else: + rate_of_depreciation = row.rate_of_depreciation + # if its the first depreciation + if depreciable_value == asset.gross_purchase_amount: + # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 + diff = date_diff(asset.available_for_use_date, row.depreciation_start_date) + if diff <= 180: + rate_of_depreciation = rate_of_depreciation / 2 + frappe.msgprint( + _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) + + depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) + + return depreciation_amount \ No newline at end of file From 6e179c3092c5f31f43ed61610a654e8d61487993 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 10 May 2021 13:24:26 +0530 Subject: [PATCH 0719/1264] fix: sync shopify customer addresses (#25481) --- .../doctype/shopify_settings/sync_customer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py index 7866fdea31..2af57f4c89 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py @@ -32,10 +32,12 @@ def create_customer(shopify_customer, shopify_settings): raise e def create_customer_address(customer, shopify_customer): - if not shopify_customer.get("addresses"): - return + addresses = shopify_customer.get("addresses", []) - for i, address in enumerate(shopify_customer.get("addresses")): + if not addresses and "default_address" in shopify_customer: + addresses.append(shopify_customer["default_address"]) + + for i, address in enumerate(addresses): address_title, address_type = get_address_title_and_type(customer.customer_name, i) try : frappe.get_doc({ From f2eb8dd1d5b6a156f2d6df9a4eb4d41ca497738b Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 10 May 2021 14:02:58 +0530 Subject: [PATCH 0720/1264] feat: Transaction Deletion Record (#25354) Co-authored-by: Saqib --- erpnext/controllers/status_updater.py | 4 + erpnext/setup/doctype/company/company.js | 4 +- erpnext/setup/doctype/company/company.py | 11 +- .../company/delete_company_transactions.py | 117 -------------- erpnext/setup/doctype/company/test_company.py | 9 -- .../transaction_deletion_record/__init__.py | 0 .../test_transaction_deletion_record.py | 68 ++++++++ .../transaction_deletion_record.js | 40 +++++ .../transaction_deletion_record.json | 79 ++++++++++ .../transaction_deletion_record.py | 147 ++++++++++++++++++ .../transaction_deletion_record_list.js | 12 ++ .../__init__.py | 0 .../transaction_deletion_record_item.json | 39 +++++ .../transaction_deletion_record_item.py | 10 ++ 14 files changed, 411 insertions(+), 129 deletions(-) delete mode 100644 erpnext/setup/doctype/company/delete_company_transactions.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/__init__.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/__init__.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 4bb6138e5d..ed3aee5c1a 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -100,6 +100,10 @@ status_map = { ["Queued", "eval:self.status == 'Queued'"], ["Failed", "eval:self.status == 'Failed'"], ["Cancelled", "eval:self.docstatus == 2"], + ], + "Transaction Deletion Record": [ + ["Draft", None], + ["Completed", "eval:self.docstatus == 1"], ] } diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index c2b5e4f9a9..9957aad019 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -169,9 +169,9 @@ frappe.ui.form.on("Company", { return; } frappe.call({ - method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions", + method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request", args: { - company_name: data.company_name + company: data.company_name }, freeze: true, callback: function(r, rt) { diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 64e027dd28..077538d479 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -613,4 +613,13 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad if out: return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0] else: - return None \ No newline at end of file + return None + +@frappe.whitelist() +def create_transaction_deletion_request(company): + tdr = frappe.get_doc({ + 'doctype': 'Transaction Deletion Record', + 'company': company + }) + tdr.insert() + tdr.submit() diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py deleted file mode 100644 index 8367a257ea..0000000000 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ /dev/null @@ -1,117 +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 frappe - -from frappe.utils import cint -from frappe import _ -from frappe.desk.notifications import clear_notifications - -import functools - -@frappe.whitelist() -def delete_company_transactions(company_name): - frappe.only_for("System Manager") - doc = frappe.get_doc("Company", company_name) - - if frappe.session.user != doc.owner and frappe.session.user != 'Administrator': - frappe.throw(_("Transactions can only be deleted by the creator of the Company"), - frappe.PermissionError) - - delete_bins(company_name) - delete_lead_addresses(company_name) - - for doctype in frappe.db.sql_list("""select parent from - tabDocField where fieldtype='Link' and options='Company'"""): - if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", - "Party Account", "Employee", "Sales Taxes and Charges Template", - "Purchase Taxes and Charges Template", "POS Profile", "BOM", - "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account", - "Item Default", "Customer", "Supplier", "GST Account"): - delete_for_doctype(doctype, company_name) - - # reset company values - doc.total_monthly_sales = 0 - doc.sales_monthly_history = None - doc.save() - # Clear notification counts - clear_notifications() - -def delete_for_doctype(doctype, company_name): - meta = frappe.get_meta(doctype) - company_fieldname = meta.get("fields", {"fieldtype": "Link", - "options": "Company"})[0].fieldname - - if not meta.issingle: - if not meta.istable: - # delete communication - delete_communications(doctype, company_name, company_fieldname) - - # delete children - for df in meta.get_table_fields(): - frappe.db.sql("""delete from `tab{0}` where parent in - (select name from `tab{1}` where `{2}`=%s)""".format(df.options, - doctype, company_fieldname), company_name) - - #delete version log - frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in - (select name from `tab{0}` where `{1}`=%s)""".format(doctype, - company_fieldname), (doctype, company_name)) - - # delete parent - frappe.db.sql("""delete from `tab{0}` - where {1}= %s """.format(doctype, company_fieldname), company_name) - - # reset series - naming_series = meta.get_field("naming_series") - if naming_series and naming_series.options: - prefixes = sorted(naming_series.options.split("\n"), - key=functools.cmp_to_key(lambda a, b: len(b) - len(a))) - - for prefix in prefixes: - if prefix: - last = frappe.db.sql("""select max(name) from `tab{0}` - where name like %s""".format(doctype), prefix + "%") - if last and last[0][0]: - last = cint(last[0][0].replace(prefix, "")) - else: - last = 0 - - frappe.db.sql("""update tabSeries set current = %s - where name=%s""", (last, prefix)) - -def delete_bins(company_name): - frappe.db.sql("""delete from tabBin where warehouse in - (select name from tabWarehouse where company=%s)""", company_name) - -def delete_lead_addresses(company_name): - """Delete addresses to which leads are linked""" - leads = frappe.get_all("Lead", filters={"company": company_name}) - leads = [ "'%s'"%row.get("name") for row in leads ] - addresses = [] - if leads: - addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name - in ({leads})""".format(leads=",".join(leads))) - - if addresses: - addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - - frappe.db.sql("""delete from tabAddress where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) - - frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) - - frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) - -def delete_communications(doctype, company_name, company_fieldname): - reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) - reference_doc_names = [r.name for r in reference_docs] - - communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) - communication_names = [c.name for c in communications] - - frappe.delete_doc("Communication", communication_names, ignore_permissions=True) diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 29f6c3731d..e1c803a038 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -86,15 +86,6 @@ class TestCompany(unittest.TestCase): self.delete_mode_of_payment(template) frappe.delete_doc("Company", template) - def test_delete_communication(self): - from erpnext.setup.doctype.company.delete_company_transactions import delete_communications - company = create_child_company() - lead = create_test_lead_in_company(company) - communication = create_company_communication("Lead", lead) - delete_communications("Lead", "Test Company", "company") - self.assertFalse(frappe.db.exists("Communcation", communication)) - self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication})) - def delete_mode_of_payment(self, company): frappe.db.sql(""" delete from `tabMode of Payment Account` where company =%s """, (company)) diff --git a/erpnext/setup/doctype/transaction_deletion_record/__init__.py b/erpnext/setup/doctype/transaction_deletion_record/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py new file mode 100644 index 0000000000..bbe68369ff --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestTransactionDeletionRecord(unittest.TestCase): + def setUp(self): + create_company('Dunder Mifflin Paper Co') + + def tearDown(self): + frappe.db.rollback() + + def test_doctypes_contain_company_field(self): + tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co') + for doctype in tdr.doctypes: + contains_company = False + doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields'] + for doctype_field in doctype_fields: + if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company': + contains_company = True + break + self.assertTrue(contains_company) + + def test_no_of_docs_is_correct(self): + for i in range(5): + create_task('Dunder Mifflin Paper Co') + tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co') + for doctype in tdr.doctypes: + if doctype.doctype_name == 'Task': + self.assertEqual(doctype.no_of_docs, 5) + + def test_deletion_is_successful(self): + create_task('Dunder Mifflin Paper Co') + create_transaction_deletion_request('Dunder Mifflin Paper Co') + tasks_containing_company = frappe.get_all('Task', + filters = { + 'company' : 'Dunder Mifflin Paper Co' + }) + self.assertEqual(tasks_containing_company, []) + +def create_company(company_name): + company = frappe.get_doc({ + 'doctype': 'Company', + 'company_name': company_name, + 'default_currency': 'INR' + }) + company.insert(ignore_if_duplicate = True) + +def create_transaction_deletion_request(company): + tdr = frappe.get_doc({ + 'doctype': 'Transaction Deletion Record', + 'company': company + }) + tdr.insert() + tdr.submit() + return tdr + + +def create_task(company): + task = frappe.get_doc({ + 'doctype': 'Task', + 'company': company, + 'subject': 'Delete' + }) + task.insert() diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js new file mode 100644 index 0000000000..20caa15ee4 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -0,0 +1,40 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Transaction Deletion Record', { + onload: function(frm) { + if (frm.doc.docstatus == 0) { + let doctypes_to_be_ignored_array; + frappe.call({ + method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored', + callback: function(r) { + doctypes_to_be_ignored_array = r.message; + populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm); + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + } + }); + } + + frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true; + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + }, + + refresh: function(frm) { + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + } + +}); + +function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) { + if (!(frm.doc.doctypes_to_be_ignored)) { + var i; + for (i = 0; i < doctypes_to_be_ignored_array.length; i++) { + frm.add_child('doctypes_to_be_ignored', { + doctype_name: doctypes_to_be_ignored_array[i] + }); + } + } +} diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json new file mode 100644 index 0000000000..9313f95516 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -0,0 +1,79 @@ +{ + "actions": [], + "autoname": "TDL.####", + "creation": "2021-04-06 20:17:18.404716", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "doctypes", + "doctypes_to_be_ignored", + "amended_from", + "status" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "doctypes", + "fieldtype": "Table", + "label": "Summary", + "options": "Transaction Deletion Record Item", + "read_only": 1 + }, + { + "fieldname": "doctypes_to_be_ignored", + "fieldtype": "Table", + "label": "Excluded DocTypes", + "options": "Transaction Deletion Record Item" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Transaction Deletion Record", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "label": "Status", + "options": "Draft\nCompleted" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-05-08 23:13:48.049879", + "modified_by": "Administrator", + "module": "Setup", + "name": "Transaction Deletion Record", + "owner": "Administrator", + "permissions": [ + { + "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/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py new file mode 100644 index 0000000000..38f8de7a66 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.utils import cint +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.desk.notifications import clear_notifications + +class TransactionDeletionRecord(Document): + def validate(self): + frappe.only_for('System Manager') + company_obj = frappe.get_doc('Company', self.company) + if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator': + frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'), + frappe.PermissionError) + doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() + for doctype in self.doctypes_to_be_ignored: + if doctype.doctype_name not in doctypes_to_be_ignored_list: + frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed")) + + def before_submit(self): + if not self.doctypes_to_be_ignored: + self.populate_doctypes_to_be_ignored_table() + + self.delete_bins() + self.delete_lead_addresses() + + company_obj = frappe.get_doc('Company', self.company) + # reset company values + company_obj.total_monthly_sales = 0 + company_obj.sales_monthly_history = None + company_obj.save() + # Clear notification counts + clear_notifications() + + singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') + tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') + doctypes_to_be_ignored_list = singles + for doctype in self.doctypes_to_be_ignored: + doctypes_to_be_ignored_list.append(doctype.doctype_name) + + docfields = frappe.get_all('DocField', + filters = { + 'fieldtype': 'Link', + 'options': 'Company', + 'parent': ['not in', doctypes_to_be_ignored_list]}, + fields=['parent', 'fieldname']) + + for docfield in docfields: + if docfield['parent'] != self.doctype: + no_of_docs = frappe.db.count(docfield['parent'], { + docfield['fieldname'] : self.company + }) + + if no_of_docs > 0: + self.delete_version_log(docfield['parent'], docfield['fieldname']) + self.delete_communications(docfield['parent'], docfield['fieldname']) + + # populate DocTypes table + if docfield['parent'] not in tables: + self.append('doctypes', { + 'doctype_name' : docfield['parent'], + 'no_of_docs' : no_of_docs + }) + + # delete the docs linked with the specified company + frappe.db.delete(docfield['parent'], { + docfield['fieldname'] : self.company + }) + + naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') + if naming_series: + if '#' in naming_series: + self.update_naming_series(naming_series, docfield['parent']) + + def populate_doctypes_to_be_ignored_table(self): + doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() + for doctype in doctypes_to_be_ignored_list: + self.append('doctypes_to_be_ignored', { + 'doctype_name' : doctype + }) + + def update_naming_series(self, naming_series, doctype_name): + if '.' in naming_series: + prefix, hashes = naming_series.rsplit('.', 1) + else: + prefix, hashes = naming_series.rsplit('{', 1) + last = frappe.db.sql("""select max(name) from `tab{0}` + where name like %s""".format(doctype_name), prefix + '%') + if last and last[0][0]: + last = cint(last[0][0].replace(prefix, '')) + else: + last = 0 + + frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix)) + + def delete_version_log(self, doctype, company_fieldname): + frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in + (select name from `tab{0}` where `{1}`=%s)""".format(doctype, + company_fieldname), (doctype, self.company)) + + def delete_communications(self, doctype, company_fieldname): + reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company}) + reference_doc_names = [r.name for r in reference_docs] + + communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]}) + communication_names = [c.name for c in communications] + + frappe.delete_doc('Communication', communication_names, ignore_permissions=True) + + def delete_bins(self): + frappe.db.sql("""delete from tabBin where warehouse in + (select name from tabWarehouse where company=%s)""", self.company) + + def delete_lead_addresses(self): + """Delete addresses to which leads are linked""" + leads = frappe.get_all('Lead', filters={'company': self.company}) + leads = ["'%s'" % row.get("name") for row in leads] + addresses = [] + if leads: + addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name + in ({leads})""".format(leads=",".join(leads))) + + if addresses: + addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] + + frappe.db.sql("""delete from tabAddress where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) + + frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' + and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) + + frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) + +@frappe.whitelist() +def get_doctypes_to_be_ignored(): + doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget', + 'Party Account', 'Employee', 'Sales Taxes and Charges Template', + 'Purchase Taxes and Charges Template', 'POS Profile', 'BOM', + 'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment', + 'Item Default', 'Customer', 'Supplier', 'GST Account'] + return doctypes_to_be_ignored_list diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js new file mode 100644 index 0000000000..d7175ddac4 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js @@ -0,0 +1,12 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.listview_settings['Transaction Deletion Record'] = { + get_indicator: function(doc) { + if (doc.docstatus == 0) { + return [__("Draft"), "red"]; + } else { + return [__("Completed"), "green"]; + } + } +}; \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py b/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json new file mode 100644 index 0000000000..be0be945c4 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json @@ -0,0 +1,39 @@ +{ + "actions": [], + "creation": "2021-04-07 07:34:00.124124", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "doctype_name", + "no_of_docs" + ], + "fields": [ + { + "fieldname": "doctype_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "no_of_docs", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Number of Docs" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-08 23:10:46.166744", + "modified_by": "Administrator", + "module": "Setup", + "name": "Transaction Deletion Record Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py new file mode 100644 index 0000000000..2176cb10de --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 TransactionDeletionRecordItem(Document): + pass From 1a48eb49cf912223913698383fce7568c52d510b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 14:37:10 +0530 Subject: [PATCH 0721/1264] fix: Client script breaking while settings tax labels --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f91b432a39..43eea1357a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1351,7 +1351,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.fields_dict["taxes"]) { + if(this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); From 13dfb9734cb8a32a88b425177b0803fc7a838505 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 15:38:32 +0530 Subject: [PATCH 0722/1264] fix: Lable for transaction child tables --- erpnext/public/js/controllers/transaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 43eea1357a..a3f4de48b8 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.toggle_item_grid_columns(company_currency); - if(this.frm.fields_dict["operations"]) { + if(this.frm.doc.operations && this.frm.doc.operations.length > 0) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations"); @@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.fields_dict["scrap_items"]) { + if(this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items"); this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items"); @@ -1357,7 +1357,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); } - if(this.frm.fields_dict["advances"]) { + if(this.frm.doc.advances && this.frm.doc.advances.length > 0) { this.frm.set_currency_labels(["advance_amount", "allocated_amount"], this.frm.doc.party_account_currency, "advances"); } @@ -1384,7 +1384,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], this.frm.doc.currency, "payment_schedule"); - + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) From 55d47a2baaeb9d10c991ecfc048fba5ff853869b Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 10 May 2021 15:59:37 +0530 Subject: [PATCH 0723/1264] fix(pos): UI fixes related to overflowing payment section (#25652) * fix: additional fields overflowing in payment section * fix: pos profile filter in pos opening dialog * fix: item quantity pill --- erpnext/public/scss/point-of-sale.scss | 29 ++++++++++++++++++- .../page/point_of_sale/pos_controller.js | 2 +- .../page/point_of_sale/pos_item_selector.js | 10 ++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 0bb8e68b69..9bdaa8d1ee 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -129,11 +129,20 @@ @extend .pointer-no-select; border-radius: var(--border-radius-md); box-shadow: var(--shadow-base); + position: relative; &:hover { transform: scale(1.02, 1.02); } + .item-qty-pill { + position: absolute; + display: flex; + margin: var(--margin-sm); + justify-content: flex-end; + right: 0px; + } + .item-display { display: flex; align-items: center; @@ -766,9 +775,10 @@ > .payment-modes { display: flex; padding-bottom: var(--padding-sm); - margin-bottom: var(--margin-xs); + margin-bottom: var(--margin-sm); overflow-x: scroll; overflow-y: hidden; + flex-shrink: 0; > .payment-mode-wrapper { min-width: 40%; @@ -825,9 +835,24 @@ > .fields-numpad-container { display: flex; flex: 1; + height: 100%; + position: relative; + justify-content: flex-end; > .fields-section { flex: 1; + position: absolute; + display: flex; + flex-direction: column; + width: 50%; + height: 100%; + top: 0; + left: 0; + padding-bottom: var(--margin-md); + + .invoice-fields { + overflow-y: scroll; + } } > .number-pad { @@ -835,6 +860,7 @@ display: flex; justify-content: flex-end; align-items: flex-end; + max-width: 50%; .numpad-container { display: grid; @@ -861,6 +887,7 @@ margin-bottom: var(--margin-sm); justify-content: center; flex-direction: column; + flex-shrink: 0; > .totals { display: flex; diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 8adf5bf747..8e0a1e1c18 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -58,7 +58,7 @@ erpnext.PointOfSale.Controller = class { } const pos_profile_query = { query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { company: frappe.defaults.get_default('company') } + filters: { company: dialog.fields_dict.company.get_value() } } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 9384ae5542..b8a82a9eda 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -90,14 +90,16 @@ erpnext.PointOfSale.ItemSelector = class { function get_item_image_html() { if (!me.hide_images && item_image) { - return `
    - ${qty_to_display}
    + return `
    + ${qty_to_display} +
    ${frappe.get_abbr(item.item_name)}
    `; } else { - return `
    - ${qty_to_display}
    + return `
    + ${qty_to_display} +
    ${frappe.get_abbr(item.item_name)}
    `; } } From 9f0823a164e43c740f4aac0e9cb93559b5b06d13 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 16:07:41 +0530 Subject: [PATCH 0724/1264] fix: Linting issues --- erpnext/public/js/controllers/transaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a3f4de48b8..7cfd939e95 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.toggle_item_grid_columns(company_currency); - if(this.frm.doc.operations && this.frm.doc.operations.length > 0) { + if (this.frm.doc.operations && this.frm.doc.operations.length > 0) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations"); @@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { + if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items"); this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items"); @@ -1351,13 +1351,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { + if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); } - if(this.frm.doc.advances && this.frm.doc.advances.length > 0) { + if (this.frm.doc.advances && this.frm.doc.advances.length > 0) { this.frm.set_currency_labels(["advance_amount", "allocated_amount"], this.frm.doc.party_account_currency, "advances"); } From d2520680bc2f64d5a4692e63c70b129b2b46a8b7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 10 May 2021 21:17:06 +0530 Subject: [PATCH 0725/1264] fix: Error on applying TDS without party (#25632) * fix: Error on applying TDS without party * fix: Add placeholder value --- .../tax_withholding_category/tax_withholding_category.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 09db7fee2b..5c1cbaa4aa 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -21,7 +21,10 @@ def get_party_details(inv): else: party_type = 'Supplier' party = inv.supplier - + + if not party: + frappe.throw(_("Please select {0} first").format(party_type)) + return party_type, party def get_party_tax_withholding_details(inv, tax_withholding_category=None): @@ -324,7 +327,7 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post net_total, ldc.certificate_limit ): tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details) - + return tds_amount def get_debit_note_amount(suppliers, fiscal_year_details, company=None): From a60c3081cf54b61757c1196c957bdbf3f5738a99 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 May 2021 16:38:33 +0530 Subject: [PATCH 0726/1264] fix: Breaking cost center validation --- erpnext/controllers/accounts_controller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c409850734..996c4ed11b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -368,6 +368,11 @@ class AccountsController(TransactionBase): if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'): item.set('is_fixed_asset', ret.get('is_fixed_asset', 0)) + # Double check for cost center + # Items add via promotional scheme may not have cost center set + if hasattr(item, 'cost_center') and not item.get('cost_center'): + item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company)) + if ret.get("pricing_rules"): self.apply_pricing_rule_on_items(item, ret) self.set_pricing_rule_details(item, ret) From a665f14620b453db7b144f4f260fd47ed7d99682 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 11 May 2021 17:33:59 +0530 Subject: [PATCH 0727/1264] fix: Error on adding bank account to plaid (#25658) --- .../doctype/plaid_settings/plaid_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 16c65733f0..21f1db619e 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -90,9 +90,9 @@ def add_bank_accounts(response, bank, company): "bank": bank["bank_name"], "account": default_gl_account.account, "account_name": account["name"], - "account_type": account["type"] or "", - "account_subtype": account["subtype"] or "", - "mask": account["mask"] or "", + "account_type": account.get("type", ""), + "account_subtype": account.get("subtype", ""), + "mask": account.get("mask", ""), "integration_id": account["id"], "is_company_account": 1, "company": company From b1f8c80be3a80a59f4389a5350411c0c4a4ca318 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 May 2021 18:27:20 +0530 Subject: [PATCH 0728/1264] ci: enable semgrep check on v13 branches and update rules (#25647) * ci: enable semgrep on v13 branches * ci: break semgrep steps for nicer output * ci: update semgrep rules inline with frappe repo --- .flake8 | 3 +- .../semgrep_rules/frappe_correctness.py | 64 ++++++++++--- .../semgrep_rules/frappe_correctness.yml | 89 ++++++++++++++++--- .github/helper/semgrep_rules/translate.js | 7 ++ .github/helper/semgrep_rules/translate.yml | 9 +- .github/workflows/semgrep.yml | 12 ++- 6 files changed, 150 insertions(+), 34 deletions(-) diff --git a/.flake8 b/.flake8 index 399b176e1d..56c9b9a369 100644 --- a/.flake8 +++ b/.flake8 @@ -29,4 +29,5 @@ ignore = B950, W191, -max-line-length = 200 \ No newline at end of file +max-line-length = 200 +exclude=.github/helper/semgrep_rules diff --git a/.github/helper/semgrep_rules/frappe_correctness.py b/.github/helper/semgrep_rules/frappe_correctness.py index 4798b927f8..745e6463b8 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.py +++ b/.github/helper/semgrep_rules/frappe_correctness.py @@ -4,25 +4,61 @@ from frappe import _, flt from frappe.model.document import Document +# ruleid: frappe-modifying-but-not-comitting def on_submit(self): if self.value_of_goods == 0: frappe.throw(_('Value of goods cannot be 0')) - # ruleid: frappe-modifying-after-submit self.status = 'Submitted' + +# ok: frappe-modifying-but-not-comitting def on_submit(self): - if flt(self.per_billed) < 100: - self.update_billing_status() - else: - # todook: frappe-modifying-after-submit - self.status = "Completed" - self.db_set("status", "Completed") + if self.value_of_goods == 0: + frappe.throw(_('Value of goods cannot be 0')) + self.status = 'Submitted' + self.db_set('status', 'Submitted') -class TestDoc(Document): - pass +# ok: frappe-modifying-but-not-comitting +def on_submit(self): + if self.value_of_goods == 0: + frappe.throw(_('Value of goods cannot be 0')) + x = "y" + self.status = x + self.db_set('status', x) - def validate(self): - #ruleid: frappe-modifying-child-tables-while-iterating - for item in self.child_table: - if item.value < 0: - self.remove(item) + +# ok: frappe-modifying-but-not-comitting +def on_submit(self): + x = "y" + self.status = x + self.save() + +# ruleid: frappe-modifying-but-not-comitting-other-method +class DoctypeClass(Document): + def on_submit(self): + self.good_method() + self.tainted_method() + + def tainted_method(self): + self.status = "uptate" + + +# ok: frappe-modifying-but-not-comitting-other-method +class DoctypeClass(Document): + def on_submit(self): + self.good_method() + self.tainted_method() + + def tainted_method(self): + self.status = "update" + self.db_set("status", "update") + +# ok: frappe-modifying-but-not-comitting-other-method +class DoctypeClass(Document): + def on_submit(self): + self.good_method() + self.tainted_method() + self.save() + + def tainted_method(self): + self.status = "uptate" diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index 54df062480..faab3344a6 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -1,32 +1,93 @@ # This file specifies rules for correctness according to how frappe doctype data model works. rules: -- id: frappe-modifying-after-submit +- id: frappe-modifying-but-not-comitting patterns: - - pattern: self.$ATTR = ... - - pattern-inside: | - def on_submit(self, ...): + - pattern: | + def $METHOD(self, ...): ... + self.$ATTR = ... + - pattern-not: | + def $METHOD(self, ...): + ... + self.$ATTR = ... + ... + self.db_set(..., self.$ATTR, ...) + - pattern-not: | + def $METHOD(self, ...): + ... + self.$ATTR = $SOME_VAR + ... + self.db_set(..., $SOME_VAR, ...) + - pattern-not: | + def $METHOD(self, ...): + ... + self.$ATTR = $SOME_VAR + ... + self.save() - metavariable-regex: metavariable: '$ATTR' # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me) - regex: '^(?!status_updater)(.*)$' + regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$' + - metavariable-regex: + metavariable: "$METHOD" + regex: "(on_submit|on_cancel)" message: | - Doctype modified after submission. Please check if modification of self.$ATTR is commited to database. + DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database. languages: [python] severity: ERROR -- id: frappe-modifying-after-cancel +- id: frappe-modifying-but-not-comitting-other-method patterns: - - pattern: self.$ATTR = ... - - pattern-inside: | - def on_cancel(self, ...): + - pattern: | + class $DOCTYPE(...): + def $METHOD(self, ...): ... - - metavariable-regex: - metavariable: '$ATTR' - regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$' + self.$ANOTHER_METHOD() + ... + + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = ... + - pattern-not: | + class $DOCTYPE(...): + def $METHOD(self, ...): + ... + self.$ANOTHER_METHOD() + ... + + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = ... + ... + self.db_set(..., self.$ATTR, ...) + - pattern-not: | + class $DOCTYPE(...): + def $METHOD(self, ...): + ... + self.$ANOTHER_METHOD() + ... + + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = $SOME_VAR + ... + self.db_set(..., $SOME_VAR, ...) + - pattern-not: | + class $DOCTYPE(...): + def $METHOD(self, ...): + ... + self.$ANOTHER_METHOD() + ... + self.save() + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = ... + - metavariable-regex: + metavariable: "$METHOD" + regex: "(on_submit|on_cancel)" message: | - Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database. + self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database. languages: [python] severity: ERROR diff --git a/.github/helper/semgrep_rules/translate.js b/.github/helper/semgrep_rules/translate.js index 7b92fe2dff..9cdfb75d0b 100644 --- a/.github/helper/semgrep_rules/translate.js +++ b/.github/helper/semgrep_rules/translate.js @@ -35,3 +35,10 @@ __('You have' + 'subscribers in your mailing list.') // ruleid: frappe-translation-js-splitting __('You have {0} subscribers' + 'in your mailing list', [subscribers.length]) + +// ok: frappe-translation-js-splitting +__("Ctrl+Enter to add comment") + +// ruleid: frappe-translation-js-splitting +__('You have {0} subscribers \ + in your mailing list', [subscribers.length]) diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml index 3737da5a7e..fa4ec9e15d 100644 --- a/.github/helper/semgrep_rules/translate.yml +++ b/.github/helper/semgrep_rules/translate.yml @@ -42,9 +42,10 @@ rules: - id: frappe-translation-python-splitting pattern-either: - - pattern: _(...) + ... + _(...) + - pattern: _(...) + _(...) - pattern: _("..." + "...") - - pattern-regex: '_\([^\)]*\\\s*' + - pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\` + - pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( ) message: | Do not split strings inside translate function. Do not concatenate using translate functions. Please refer: https://frappeframework.com/docs/user/en/translations @@ -53,8 +54,8 @@ rules: - id: frappe-translation-js-splitting pattern-either: - - pattern-regex: '__\([^\)]*[\+\\]\s*' - - pattern: __('...' + '...') + - pattern-regex: '__\([^\)]*[\\]\s+' + - pattern: __('...' + '...', ...) - pattern: __('...') + __('...') message: | Do not split strings inside translate function. Do not concatenate using translate functions. diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index df08263236..389524e968 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -4,6 +4,8 @@ on: pull_request: branches: - develop + - version-13-hotfix + - version-13-pre-release jobs: semgrep: name: Frappe Linter @@ -14,11 +16,19 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.8 - - name: Run semgrep + + - name: Setup semgrep run: | python -m pip install -q semgrep git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q + + - name: Semgrep errors + run: | files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files semgrep --config="r/python.lang.correctness" --quiet --error $files + + - name: Semgrep warnings + run: | + files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files From d984be0ccd892989f3b1135f29c3bf4b8288cf9a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 12:32:14 +0530 Subject: [PATCH 0729/1264] fix: don't map set warehouse from delivery note to purchase receipt --- erpnext/stock/doctype/delivery_note/delivery_note.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d326a04173..cce51cb9b1 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -732,7 +732,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): "doctype": target_doctype, "postprocess": update_details, "field_no_map": [ - "taxes_and_charges" + "taxes_and_charges", + "set_warehouse" ] }, doctype +" Item": { From e7a2fdd81a0b054b19d4eebc39a91cf34975ddb9 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 12 May 2021 13:02:32 +0530 Subject: [PATCH 0730/1264] fix: change links in workspace (#25674) --- .../workspace/accounting/accounting.json | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 9ffa481c1c..df68318052 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "accounting", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Accounting", "links": [ @@ -625,9 +626,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Bank Reconciliation", - "link_to": "bank-reconciliation", - "link_type": "Page", + "label": "Bank Reconciliation Tool", + "link_to": "Bank Reconciliation Tool", + "link_type": "DocType", "onboard": 0, "type": "Link" }, @@ -641,26 +642,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bank Statement Transaction Entry", - "link_to": "Bank Statement Transaction Entry", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bank Statement Settings", - "link_to": "Bank Statement Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -1071,7 +1052,7 @@ "type": "Link" } ], - "modified": "2021-03-04 00:38:35.349024", + "modified": "2021-05-12 11:48:01.905144", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", From 9f017a351ba58e4f47e2af78234962d4b002fb2d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 16:35:09 +0530 Subject: [PATCH 0731/1264] fix: updated modified time to pull new fields --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 24e67febca..d3d3ffa17f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1380,7 +1380,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-03-30 22:45:58.334107", + "modified": "2021-04-30 22:45:58.334107", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From dc205e805f5875dd2e74bea80f70a9910319b87e Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 12 May 2021 17:42:06 +0530 Subject: [PATCH 0732/1264] fix: Dialog variable assignment after definition in POS (#25681) --- erpnext/selling/page/point_of_sale/pos_controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 8e0a1e1c18..4f4f1b2240 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -56,10 +56,6 @@ erpnext.PointOfSale.Controller = class { dialog.fields_dict.balance_details.grid.refresh(); }); } - const pos_profile_query = { - query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { company: dialog.fields_dict.company.get_value() } - } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), static: true, @@ -105,6 +101,10 @@ erpnext.PointOfSale.Controller = class { primary_action_label: __('Submit') }); dialog.show(); + const pos_profile_query = { + query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', + filters: { company: dialog.fields_dict.company.get_value() } + }; } async prepare_app_defaults(data) { From c3c54fe05819a040fd45411fab5308c0afb5a53b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 19:42:04 +0530 Subject: [PATCH 0733/1264] fix: Woocommerce order sync issue --- .../connectors/woocommerce_connection.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 6dedaa8c53..a505ee09d2 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import frappe, base64, hashlib, hmac, json +from frappe.utils import cstr from frappe import _ def verify_request(): @@ -146,22 +147,19 @@ def rename_address(address, customer): def link_items(items_list, woocommerce_settings, sys_lang): for item_data in items_list: - item_woo_com_id = item_data.get("product_id") + item_woo_com_id = cstr(item_data.get("product_id")) - if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}): - #Edit Item - item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id}) - else: + if not frappe.db.get_value("Item", {"woocommerce_id": item_woo_com_id}, 'name'): #Create Item item = frappe.new_doc("Item") + item.item_code = _("woocommerce - {0}", sys_lang).format(item_woo_com_id) + item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang) + item.item_group = _("WooCommerce Products", sys_lang) - item.item_name = item_data.get("name") - item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id")) - item.woocommerce_id = item_data.get("product_id") - item.item_group = _("WooCommerce Products", sys_lang) - item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang) - item.flags.ignore_mandatory = True - item.save() + item.item_name = item_data.get("name") + item.woocommerce_id = item_woo_com_id + item.flags.ignore_mandatory = True + item.save() def create_sales_order(order, woocommerce_settings, customer_name, sys_lang): new_sales_order = frappe.new_doc("Sales Order") @@ -194,12 +192,12 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l for item in order.get("line_items"): woocomm_item_id = item.get("product_id") - found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id}) + found_item = frappe.get_doc("Item", {"woocommerce_id": cstr(woocomm_item_id)}) ordered_items_tax = item.get("total_tax") - new_sales_order.append("items",{ - "item_code": found_item.item_code, + new_sales_order.append("items", { + "item_code": found_item.name, "item_name": found_item.item_name, "description": found_item.item_name, "delivery_date": new_sales_order.delivery_date, @@ -207,7 +205,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l "qty": item.get("quantity"), "rate": item.get("price"), "warehouse": woocommerce_settings.warehouse or default_warehouse - }) + }) add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account) From 0c482fde5f14a1454cd5aa6a6b28fec245bf72d6 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 13 May 2021 13:20:14 +0530 Subject: [PATCH 0734/1264] feat: Leave Policy Assignment Refactor (#24327) * feat: Leave Policy Assignment Refactor * fix: Changes Requested * fix: sider * fix: changes requested * test: fixed * test: fixed wrong set query * fix: remove commented code * fix(style): extra space Co-authored-by: Rucha Mahabal --- erpnext/hooks.py | 2 - .../hr/doctype/employee/employee_dashboard.py | 16 +++--- .../hr/doctype/hr_settings/hr_settings.json | 9 +--- .../test_leave_application.py | 2 - .../leave_encashment/test_leave_encashment.py | 4 -- .../leave_policy/leave_policy_dashboard.py | 2 +- .../leave_policy_assignment.js | 43 ++++++---------- .../leave_policy_assignment.py | 49 +++++-------------- .../leave_policy_assignment_dashboard.py | 13 +++++ .../leave_policy_assignment_list.js | 32 +----------- .../test_leave_policy_assignment.py | 2 - erpnext/hr/utils.py | 7 --- 12 files changed, 53 insertions(+), 128 deletions(-) create mode 100644 erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9d1ce9bbbf..2a70f2bd39 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -365,10 +365,8 @@ scheduler_events = { "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", - "erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy", "erpnext.hr.utils.generate_leave_encashment", "erpnext.hr.utils.allocate_earned_leaves", - "erpnext.hr.utils.grant_leaves_automatically", "erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall", "erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans", "erpnext.crm.doctype.lead.lead.daily_open_lead" diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py index 0203332164..285374d9f6 100644 --- a/erpnext/hr/doctype/employee/employee_dashboard.py +++ b/erpnext/hr/doctype/employee/employee_dashboard.py @@ -11,8 +11,12 @@ def get_data(): }, 'transactions': [ { - 'label': _('Leave and Attendance'), - 'items': ['Attendance', 'Attendance Request', 'Leave Application', 'Leave Allocation', 'Employee Checkin'] + 'label': _('Attendance'), + 'items': ['Attendance', 'Attendance Request', 'Employee Checkin'] + }, + { + 'label': _('Leave'), + 'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment'] }, { 'label': _('Lifecycle'), @@ -30,10 +34,6 @@ def get_data(): 'label': _('Benefit'), 'items': ['Employee Benefit Application', 'Employee Benefit Claim'] }, - { - 'label': _('Evaluation'), - 'items': ['Appraisal'] - }, { 'label': _('Payroll'), 'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account'] @@ -42,5 +42,9 @@ def get_data(): 'label': _('Training'), 'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map'] }, + { + 'label': _('Evaluation'), + 'items': ['Appraisal'] + }, ] } diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 3db6c239ef..2396a8eee9 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -23,7 +23,6 @@ "show_leaves_of_all_department_members_in_calendar", "auto_leave_encashment", "restrict_backdated_leave_application", - "automatically_allocate_leaves_based_on_leave_policy", "hiring_settings", "check_vacancies" ], @@ -133,12 +132,6 @@ "label": "Role Allowed to Create Backdated Leave Application", "options": "Role" }, - { - "default": "0", - "fieldname": "automatically_allocate_leaves_based_on_leave_policy", - "fieldtype": "Check", - "label": "Automatically Allocate Leaves Based On Leave Policy" - }, { "default": "1", "fieldname": "send_leave_notification", @@ -155,7 +148,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2021-04-26 10:52:56.192773", + "modified": "2021-05-11 10:52:56.192773", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index a4a96b813e..2832e2fad3 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -446,8 +446,6 @@ class TestLeaveApplication(unittest.TestCase): leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data)) - frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee() - from erpnext.hr.utils import allocate_earned_leaves i = 0 while(i<14): diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index e0ffa5dd41..c1da8b47ff 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -44,10 +44,6 @@ class TestLeaveEncashment(unittest.TestCase): salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee, other_details={"leave_encashment_amount_per_day": 50}) - #grant Leaves - frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee() - - def tearDown(self): for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]: frappe.db.sql("delete from `tab%s`" % dt) diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index e0ec4be2dc..ff7f0422e0 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -7,7 +7,7 @@ def get_data(): 'transactions': [ { 'label': _('Leaves'), - 'items': ['Leave Allocation'] + 'items': ['Leave Policy Assignment', 'Leave Allocation'] }, ] } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js index 7c32a0dde0..0aaf4cf616 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js @@ -4,35 +4,22 @@ frappe.ui.form.on('Leave Policy Assignment', { onload: function(frm) { frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; - }, - refresh: function(frm) { - if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) { - frm.add_custom_button(__("Grant Leave"), function() { - - frappe.call({ - doc: frm.doc, - method: "grant_leave_alloc_for_employee", - callback: function(r) { - let leave_allocations = r.message; - let msg = frm.events.get_success_message(leave_allocations); - frappe.msgprint(msg); - cur_frm.refresh(); - } - }); - }); - } - }, - - get_success_message: function(leave_allocations) { - let msg = __("Leaves has been granted successfully"); - msg += "
    "; - msg += ""; - for (let key in leave_allocations) { - msg += ""; - } - msg += "
    "+__('Leave Type')+""+__("Leave Allocation")+""+__("Leaves Granted")+"
    "+key+""+leave_allocations[key]["name"]+""+leave_allocations[key]["leaves"]+"
    "; - return msg; + frm.set_query('leave_policy', function() { + return { + filters: { + "docstatus": 1 + } + }; + }); + frm.set_query('leave_period', function() { + return { + filters: { + "is_active": 1, + "company": frm.doc.company + } + }; + }); }, assignment_based_on: function(frm) { diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index 462b81df1d..d7cb1c88c9 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -17,6 +17,9 @@ class LeavePolicyAssignment(Document): self.validate_policy_assignment_overlap() self.set_dates() + def on_submit(self): + self.grant_leave_alloc_for_employee() + def set_dates(self): if self.assignment_based_on == "Leave Period": self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"]) @@ -75,7 +78,7 @@ class LeavePolicyAssignment(Document): from_date=self.effective_from, to_date=self.effective_to, new_leaves_allocated=new_leaves_allocated, - leave_period=self.leave_period or None, + leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else '', leave_policy_assignment = self.name, leave_policy = self.leave_policy, carry_forward=carry_forward @@ -131,22 +134,6 @@ class LeavePolicyAssignment(Document): return new_leaves_allocated -@frappe.whitelist() -def grant_leave_for_multiple_employees(leave_policy_assignments): - leave_policy_assignments = json.loads(leave_policy_assignments) - not_granted = [] - for assignment in leave_policy_assignments: - try: - frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee() - except Exception: - not_granted.append(assignment) - - if len(not_granted): - msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents") - else: - msg = _("Leave granted Successfully") - frappe.msgprint(msg) - @frappe.whitelist() def create_assignment_for_multiple_employees(employees, data): @@ -166,29 +153,18 @@ def create_assignment_for_multiple_employees(employees, data): assignment.effective_to = getdate(data.effective_to) or None assignment.leave_period = data.leave_period or None assignment.carry_forward = data.carry_forward - assignment.save() - assignment.submit() + try: + assignment.submit() + except frappe.exceptions.ValidationError: + continue + + frappe.db.commit() + docs_name.append(assignment.name) + return docs_name - -def automatically_allocate_leaves_based_on_leave_policy(): - today = getdate() - automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value( - 'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy' - ) - - pending_assignments = frappe.get_list( - "Leave Policy Assignment", - filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today} - ) - - if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy: - for assignment in pending_assignments: - frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee() - - def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all("Leave Type", @@ -197,4 +173,3 @@ def get_leave_type_details(): for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details - diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py new file mode 100644 index 0000000000..4bb0535cf8 --- /dev/null +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'leave_policy_assignment', + 'transactions': [ + { + 'label': _('Leaves'), + 'items': ['Leave Allocation'] + }, + ] + } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js index 468f243885..8fe4b8f8ef 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js @@ -6,6 +6,7 @@ frappe.listview_settings['Leave Policy Assignment'] = { doctype: "Employee", target: cur_list, setters: { + employee_name: '', company: '', department: '', }, @@ -92,37 +93,6 @@ frappe.listview_settings['Leave Policy Assignment'] = { } }); }); - - list_view.page.add_inner_button(__("Grant Leaves"), function () { - me.dialog = new frappe.ui.form.MultiSelectDialog({ - doctype: "Leave Policy Assignment", - target: cur_list, - setters: { - company: '', - employee: '', - }, - get_query() { - return { - filters: { - docstatus: ['=', 1], - leaves_allocated: ['=', 0] - } - }; - }, - add_filters_group: 1, - primary_action_label: "Grant Leaves", - action(leave_policy_assignments) { - frappe.call({ - method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees', - async: false, - args: { - leave_policy_assignments: leave_policy_assignments - } - }); - me.dialog.hide(); - } - }); - }); }, set_effective_date: function () { diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py index 838e794795..9a14e3588d 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py @@ -35,7 +35,6 @@ class TestLeavePolicyAssignment(unittest.TestCase): leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data)) leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]) - leave_policy_assignment_doc.grant_leave_alloc_for_employee() leave_policy_assignment_doc.reload() self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1) @@ -73,7 +72,6 @@ class TestLeavePolicyAssignment(unittest.TestCase): leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data)) leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]) - leave_policy_assignment_doc.grant_leave_alloc_for_employee() leave_policy_assignment_doc.reload() diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 2540b3db63..80189e87b7 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -500,13 +500,6 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co total_claimed_amount = sum_of_claimed_amount[0].total_amount return total_claimed_amount -def grant_leaves_automatically(): - automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy") - if automatically_allocate_leaves_based_on_leave_policy: - lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0}) - for assignment in lpa: - frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee() - def share_doc_with_approver(doc, user): # if approver does not have permissions, share if not frappe.has_permission(doc=doc, ptype="submit", user=user): From 95e05fbdac1398c0818e78d37aa93b90054ac346 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 May 2021 14:59:28 +0530 Subject: [PATCH 0735/1264] fix: Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet --- erpnext/accounts/report/balance_sheet/balance_sheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 287b8a7484..26bb44f4f7 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -135,7 +135,7 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit # from consolidated financial statement if filters.get('accumulated_in_group_company'): - period_list = get_filtered_list_for_consolidated_report(period_list) + period_list = get_filtered_list_for_consolidated_report(filters, period_list) for period in period_list: key = period if consolidated else period.key From 27f50d5852d9f2525488193ce694b101b011966e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 13 May 2021 01:37:12 +0530 Subject: [PATCH 0736/1264] fix: timeout error while loading warehouse tree --- erpnext/stock/doctype/warehouse/warehouse.py | 46 ++++++++++++++++--- .../stock/doctype/warehouse/warehouse_tree.js | 2 +- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 6c84f168fd..2062bddc7c 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import cint, nowdate +from frappe.utils import cint, flt from frappe import throw, _ +from collections import defaultdict from frappe.utils.nestedset import NestedSet from erpnext.stock import get_warehouse_account from frappe.contacts.address_and_contact import load_address_and_contact @@ -139,8 +140,6 @@ class Warehouse(NestedSet): @frappe.whitelist() def get_children(doctype, parent=None, company=None, is_root=False): - from erpnext.stock.utils import get_stock_value_from_bin - if is_root: parent = "" @@ -153,13 +152,48 @@ def get_children(doctype, parent=None, company=None, is_root=False): warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by='name') + company_currency = '' + if company: + company_currency = frappe.get_cached_value('Company', company, 'default_currency') + + warehouse_wise_value = get_warehouse_wise_stock_value(company) + # return warehouses for wh in warehouses: - wh["balance"] = get_stock_value_from_bin(warehouse=wh.value) - if company: - wh["company_currency"] = frappe.db.get_value('Company', company, 'default_currency') + wh["balance"] = warehouse_wise_value.get(wh.value) + if company_currency: + wh["company_currency"] = company_currency return warehouses +def get_warehouse_wise_stock_value(company): + warehouses = frappe.get_all('Warehouse', + fields = ['name', 'parent_warehouse'], filters = {'company': company}) + parent_warehouse = {d.name : d.parent_warehouse for d in warehouses} + + filters = {'warehouse': ('in', [data.name for data in warehouses])} + bin_data = frappe.get_all('Bin', fields = ['sum(stock_value) as stock_value', 'warehouse'], + filters = filters, group_by = 'warehouse') + + warehouse_wise_stock_value = defaultdict(float) + for row in bin_data: + if not row.stock_value: + continue + + warehouse_wise_stock_value[row.warehouse] = row.stock_value + update_value_in_parent_warehouse(warehouse_wise_stock_value, + parent_warehouse, row.warehouse, row.stock_value) + + return warehouse_wise_stock_value + +def update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value): + parent_warehouse = parent_warehouse_dict.get(warehouse) + if not parent_warehouse: + return + + warehouse_wise_stock_value[parent_warehouse] += flt(stock_value) + update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict, + parent_warehouse, stock_value) + @frappe.whitelist() def add_node(): from frappe.desk.treeview import make_tree_args diff --git a/erpnext/stock/doctype/warehouse/warehouse_tree.js b/erpnext/stock/doctype/warehouse/warehouse_tree.js index 3665c0530f..407d7d1ccd 100644 --- a/erpnext/stock/doctype/warehouse/warehouse_tree.js +++ b/erpnext/stock/doctype/warehouse/warehouse_tree.js @@ -20,7 +20,7 @@ frappe.treeview_settings['Warehouse'] = { onrender: function(node) { if (node.data && node.data.balance!==undefined) { $('' - + format_currency(Math.abs(node.data.balance), node.data.company_currency) + + format_currency((node.data.balance), node.data.company_currency) + '').insertBefore(node.$ul); } } From e85770fe3f7d64380b84d5c5b82412045459e5fe Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 13 May 2021 17:33:18 +0530 Subject: [PATCH 0737/1264] fix: bank statement import via google sheet (#25676) * fix: google sheet bank statement import * fix: quotes Co-authored-by: Ankush Menat * chore: add translation Co-authored-by: Ankush Menat * chore: grammar Co-authored-by: Ankush Menat * fix: remove comment Co-authored-by: Ankush Menat --- .../bank_statement_import.js | 1 + .../bank_statement_import.json | 6 +++--- .../bank_statement_import.py | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 3dbd605344..016f29a7b5 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -239,6 +239,7 @@ frappe.ui.form.on("Bank Statement Import", { "withdrawal", "description", "reference_number", + "bank_account" ], }, }); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index 5e913cc2aa..7ffff02850 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -146,7 +146,7 @@ }, { "depends_on": "eval:!doc.__islocal && !doc.import_file\n", - "description": "Must be a publicly accessible Google Sheets URL", + "description": "Must be a publicly accessible Google Sheets URL and adding Bank Account column is necessary for importing via Google Sheets", "fieldname": "google_sheets_url", "fieldtype": "Data", "label": "Import from Google Sheets" @@ -202,7 +202,7 @@ ], "hide_toolbar": 1, "links": [], - "modified": "2021-02-10 19:29:59.027325", + "modified": "2021-05-12 14:17:37.777246", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", @@ -224,4 +224,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 9f41b13f4b..5f110e2727 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -47,6 +47,13 @@ class BankStatementImport(DataImport): def start_import(self): + preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template( + self.import_file, self.google_sheets_url + ) + + if 'Bank Account' not in json.dumps(preview): + frappe.throw(_("Please add the Bank Account column")) + from frappe.core.page.background_jobs.background_jobs import get_info from frappe.utils.scheduler import is_scheduler_inactive @@ -67,6 +74,7 @@ class BankStatementImport(DataImport): data_import=self.name, bank_account=self.bank_account, import_file_path=self.import_file, + google_sheets_url=self.google_sheets_url, bank=self.bank, template_options=self.template_options, now=frappe.conf.developer_mode or frappe.flags.in_test, @@ -90,18 +98,20 @@ def download_errored_template(data_import_name): data_import = frappe.get_doc("Bank Statement Import", data_import_name) data_import.export_errored_rows() -def start_import(data_import, bank_account, import_file_path, bank, template_options): +def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options): """This method runs in background job""" update_mapping_db(bank, template_options) data_import = frappe.get_doc("Bank Statement Import", data_import) + file = import_file_path if import_file_path else google_sheets_url - import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records") + import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records") data = import_file.raw_data - add_bank_account(data, bank_account) - write_files(import_file, data) + if import_file_path: + add_bank_account(data, bank_account) + write_files(import_file, data) try: i = Importer(data_import.reference_doctype, data_import=data_import) From af1376c1dfa501b5f93d06788ffe08a86116a33a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 13 May 2021 17:39:49 +0530 Subject: [PATCH 0738/1264] chore: change today to now to get data for reposting --- .../doctype/repost_item_valuation/repost_item_valuation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 3f83780569..0971d6fdb9 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, add_to_date, today +from frappe.utils import cint, get_link_to_form, add_to_date, now from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -127,7 +127,7 @@ def repost_entries(): check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): - date = add_to_date(today(), hours=-3) + date = add_to_date(now(), hours=-3) return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 From fc44478810e40bae8bb2a3e3d17978b87344a4ce Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 13 May 2021 17:42:33 +0530 Subject: [PATCH 0739/1264] Update repost_item_valuation.py --- .../doctype/repost_item_valuation/repost_item_valuation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 0971d6fdb9..27b8729ea0 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, add_to_date, now +from frappe.utils import cint, get_link_to_form, add_to_date, now, today from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -132,4 +132,4 @@ def get_repost_item_valuation_entries(): return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, date, as_dict=1) \ No newline at end of file + """, date, as_dict=1) From 55fe85d850ca7db434177d3dd540a590c6f05b5e Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 14 May 2021 12:17:41 +0530 Subject: [PATCH 0740/1264] feat(India): Multiple GST enhancement and fixes (#25249) * fix: RCM tax calculation * feat(India): ITC Reversal via Journal Entry * fix: Reverse Charge booking logic and validation * fix: Addd patch for availed ITC fields * fix: Hooks method to update availed ITC field * fix: Cleanup and fixes in GSTR3B report * fix: Update params in GSTR-1 report * fix: Debit note using Sales Invoice * fix: Setup and patch * fix: GSTR 3B report cleanup and updates * fix: Add method to get invoices liable to reverse charge * fix: Add taxable value in Purchase Invoice Item * fix: Inward supplies liable to reverse charge * fix: Linting issues * fix: GSTR3B report test --- .../doctype/gst_account/gst_account.json | 260 ++------ .../doctype/sales_invoice/sales_invoice.js | 14 + .../doctype/sales_invoice/sales_invoice.json | 13 +- erpnext/hooks.py | 9 +- erpnext/patches.txt | 1 + .../create_itc_reversal_custom_fields.py | 115 ++++ .../gstr_3b_report/gstr_3b_report.html | 2 +- .../doctype/gstr_3b_report/gstr_3b_report.py | 621 ++++++++---------- .../gstr_3b_report_template.json | 127 ++++ .../gstr_3b_report/test_gstr_3b_report.py | 3 +- erpnext/regional/india/setup.py | 44 +- erpnext/regional/india/utils.py | 146 ++-- erpnext/regional/report/gstr_1/gstr_1.js | 8 +- erpnext/regional/report/gstr_1/gstr_1.py | 106 ++- 14 files changed, 809 insertions(+), 660 deletions(-) create mode 100644 erpnext/patches/v12_0/create_itc_reversal_custom_fields.py create mode 100644 erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json diff --git a/erpnext/accounts/doctype/gst_account/gst_account.json b/erpnext/accounts/doctype/gst_account/gst_account.json index 70673387fe..b6ec8844e1 100644 --- a/erpnext/accounts/doctype/gst_account/gst_account.json +++ b/erpnext/accounts/doctype/gst_account/gst_account.json @@ -1,196 +1,82 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-01-02 15:48:58.768352", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-01-02 15:48:58.768352", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "cgst_account", + "sgst_account", + "igst_account", + "cess_account", + "is_reverse_charge_account" + ], "fields": [ { - "allow_bulk_edit": 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": 1, - "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, - "unique": 0 - }, + "columns": 1, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cgst_account", - "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": "CGST Account", - "length": 0, - "no_copy": 0, - "options": "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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "cgst_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "CGST Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sgst_account", - "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": "SGST Account", - "length": 0, - "no_copy": 0, - "options": "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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "sgst_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "SGST Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "igst_account", - "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": "IGST Account", - "length": 0, - "no_copy": 0, - "options": "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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "igst_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "IGST Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cess_account", - "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": "CESS Account", - "length": 0, - "no_copy": 0, - "options": "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, - "unique": 0 + "columns": 2, + "fieldname": "cess_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "CESS Account", + "options": "Account" + }, + { + "columns": 1, + "default": "0", + "fieldname": "is_reverse_charge_account", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Reverse Charge Account" } - ], - "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-01-02 15:52:22.335988", - "modified_by": "Administrator", - "module": "Accounts", - "name": "GST Account", - "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 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-09 12:30:25.889993", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST Account", + "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/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7c73ad6c90..5538568de4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -582,6 +582,16 @@ frappe.ui.form.on('Sales Invoice', { }; }); + frm.set_query("adjustment_against", function() { + return { + filters: { + company: frm.doc.company, + customer: frm.doc.customer, + docstatus: 1 + } + }; + }); + frm.custom_make_buttons = { 'Delivery Note': 'Delivery', 'Sales Invoice': 'Return / Credit Note', @@ -867,6 +877,10 @@ frappe.ui.form.on('Sales Invoice', { }) } + if (frm.doc.is_debit_note) { + frm.set_df_property('return_against', 'label', 'Adjustment Against'); + } + if (frappe.boot.active_domains.includes("Healthcare")) { frm.set_df_property("patient", "hidden", 0); frm.set_df_property("patient_name", "hidden", 0); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index c6c67b4ddc..7ae20892d2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -16,6 +16,7 @@ "is_pos", "is_consolidated", "is_return", + "is_debit_note", "update_billed_amount_in_sales_order", "column_break1", "company", @@ -392,7 +393,7 @@ "read_only": 1 }, { - "depends_on": "return_against", + "depends_on": "eval:doc.return_against || doc.is_debit_note", "fieldname": "return_against", "fieldtype": "Link", "hide_days": 1, @@ -401,7 +402,7 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1, + "read_only_depends_on": "eval:doc.is_return", "search_index": 1 }, { @@ -1953,6 +1954,12 @@ }, { "default": "0", + "fieldname": "is_debit_note", + "fieldtype": "Check", + "label": "Is Debit Note" + }, + { + "default": 0, "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", @@ -1969,7 +1976,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-04-15 23:57:58.766651", + "modified": "2021-04-23 22:36:32.916354", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2a70f2bd39..55169dffba 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -268,10 +268,12 @@ doc_events = { }, "Purchase Invoice": { "validate": [ - "erpnext.regional.india.utils.update_grand_total_for_rcm", + "erpnext.regional.india.utils.validate_reverse_charge_transaction", + "erpnext.regional.india.utils.update_itc_availed_fields", "erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm", - "erpnext.regional.united_arab_emirates.utils.validate_returns" - ] + "erpnext.regional.united_arab_emirates.utils.validate_returns", + "erpnext.regional.india.utils.update_taxable_values" + ] }, "Payment Entry": { "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"], @@ -423,7 +425,6 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts', '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.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount' }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 82d223cada..9b3ddd09e8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -769,6 +769,7 @@ erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status +erpnext.patches.v12_0.create_itc_reversal_custom_fields erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 diff --git a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py new file mode 100644 index 0000000000..0078a53cd6 --- /dev/null +++ b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py @@ -0,0 +1,115 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.custom.doctype.property_setter.property_setter import make_property_setter +from erpnext.regional.india.utils import get_gst_accounts + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name']) + if not company: + return + + frappe.reload_doc("regional", "doctype", "gst_settings") + frappe.reload_doc("accounts", "doctype", "gst_account") + + journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] + make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') + + custom_fields = { + 'Journal Entry': [ + dict(fieldname='reversal_type', label='Reversal Type', + fieldtype='Select', insert_after='voucher_type', print_hide=1, + options="As per rules 42 & 43 of CGST Rules\nOthers", + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_address', label='Company Address', + fieldtype='Link', options='Address', insert_after='reversal_type', + print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1, + fetch_from='company_address.gstin', + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'") + ], + 'Purchase Invoice': [ + dict(fieldname='eligibility_for_itc', label='Eligibility For ITC', + fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1, + options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC', + default="All Other ITC") + ], + 'Purchase Invoice Item': [ + dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + ] + } + + create_custom_fields(custom_fields, update=True) + + # Patch ITC Availed fields from Data to Currency + # Patch Availed ITC for current fiscal_year + + gst_accounts = get_gst_accounts(only_non_reverse_charge=1) + + frappe.db.sql(""" + UPDATE `tabCustom Field` SET fieldtype='Currency', options='Company:company:default_currency' + WHERE dt = 'Purchase Invoice' and fieldname in ('itc_integrated_tax', 'itc_state_tax', 'itc_central_tax', + 'itc_cess_amount') + """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0' + WHERE trim(coalesce(itc_integrated_tax, '')) = '' """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_state_tax = '0' + WHERE trim(coalesce(itc_state_tax, '')) = '' """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_central_tax = '0' + WHERE trim(coalesce(itc_central_tax, '')) = '' """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_cess_amount = '0' + WHERE trim(coalesce(itc_cess_amount, '')) = '' """) + + # Get purchase invoices + invoices = frappe.get_all('Purchase Invoice', + {'posting_date': ('>=', '2021-04-01'), 'eligibility_for_itc': ('!=', 'Ineligible')}, + ['name']) + + amount_map = {} + + if invoices: + invoice_list = set([d.name for d in invoices]) + + # Get GST applied + amounts = frappe.db.sql(""" + SELECT parent, account_head, sum(base_tax_amount_after_discount_amount) as amount + FROM `tabPurchase Taxes and Charges` + where parent in %s + GROUP BY parent, account_head + """, (invoice_list), as_dict=1) + + for d in amounts: + amount_map.setdefault(d.parent, + { + 'itc_integrated_tax': 0, + 'itc_state_tax': 0, + 'itc_central_tax': 0, + 'itc_cess_amount': 0 + }) + + if d.account_head in gst_accounts.get('igst_account'): + amount_map[d.parent]['itc_integrated_tax'] += d.amount + if d.account_head in gst_accounts.get('cgst_account'): + amount_map[d.parent]['itc_central_tax'] += d.amount + if d.account_head in gst_accounts.get('sgst_account'): + amount_map[d.parent]['itc_state_tax'] += d.amount + if d.account_head in gst_accounts.get('cess_account'): + amount_map[d.parent]['itc_cess_amount'] += d.amount + + for invoice, values in amount_map.items(): + frappe.db.set_value('Purchase Invoice', invoice, { + 'itc_integrated_tax': values.get('itc_integrated_tax'), + 'itc_central_tax': values.get('itc_central_tax'), + 'itc_state_tax': values['itc_state_tax'], + 'itc_cess_amount': values['itc_cess_amount'], + }) \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html index 369a4001ef..3b6a45a3b4 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html @@ -172,7 +172,7 @@ - (A) {{__("ITC Available (whether in full op part)")}} + (A) {{__("ITC Available (whether in full or part)")}} 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 a5dd5a2e09..3ddcc58867 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -3,148 +3,21 @@ # For license information, please see license.txt from __future__ import unicode_literals +import os +import json import frappe +from six import iteritems from frappe import _ from frappe.model.document import Document -import json -from six import iteritems -from frappe.utils import flt, getdate +from frappe.utils import flt, cstr from erpnext.regional.india import state_numbers class GSTR3BReport(Document): - def before_save(self): - + def validate(self): self.get_data() def get_data(self): - - self.report_dict = { - "gstin": "", - "ret_period": "", - "inward_sup": { - "isup_details": [ - { - "ty": "GST", - "intra": 0, - "inter": 0 - }, - { - "ty": "NONGST", - "inter": 0, - "intra": 0 - } - ] - }, - "sup_details": { - "osup_zero": { - "csamt": 0, - "txval": 0, - "iamt": 0 - }, - "osup_nil_exmp": { - "txval": 0 - }, - "osup_det": { - "samt": 0, - "csamt": 0, - "txval": 0, - "camt": 0, - "iamt": 0 - }, - "isup_rev": { - "samt": 0, - "csamt": 0, - "txval": 0, - "camt": 0, - "iamt": 0 - }, - "osup_nongst": { - "txval": 0, - } - }, - "inter_sup": { - "unreg_details": [], - "comp_details": [], - "uin_details": [] - }, - "itc_elg": { - "itc_avl": [ - { - "csamt": 0, - "samt": 0, - "ty": "IMPG", - "camt": 0, - "iamt": 0 - }, - { - "csamt": 0, - "samt": 0, - "ty": "IMPS", - "camt": 0, - "iamt": 0 - }, - { - "samt": 0, - "csamt": 0, - "ty": "ISRC", - "camt": 0, - "iamt": 0 - }, - { - "ty": "ISD", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - }, - { - "samt": 0, - "csamt": 0, - "ty": "OTH", - "camt": 0, - "iamt": 0 - } - ], - "itc_rev": [ - { - "ty": "RUL", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - }, - { - "ty": "OTH", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - } - ], - "itc_net": { - "samt": 0, - "csamt": 0, - "camt": 0, - "iamt": 0 - }, - "itc_inelg": [ - { - "ty": "RUL", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - }, - { - "ty": "OTH", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - } - ] - } - } + self.report_dict = json.loads(get_json('gstr_3b_report_template')) self.gst_details = self.get_company_gst_details() self.report_dict["gstin"] = self.gst_details.get("gstin") @@ -152,23 +25,19 @@ class GSTR3BReport(Document): self.month_no = get_period(self.month) self.account_heads = self.get_account_heads() - outward_supply_tax_amounts = self.get_tax_amounts("Sales Invoice") - inward_supply_tax_amounts = self.get_tax_amounts("Purchase Invoice", reverse_charge="Y") + self.get_outward_supply_details("Sales Invoice") + self.set_outward_taxable_supplies() + + self.get_outward_supply_details("Purchase Invoice", reverse_charge=True) + self.set_supplies_liable_to_reverse_charge() + itc_details = self.get_itc_details() - - self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"]) - self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"]) - self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y") - self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2) self.set_itc_details(itc_details) - - inter_state_supplies = self.get_inter_state_supplies(self.gst_details.get("gst_state_number")) + self.get_itc_reversal_entries() inward_nil_exempt = self.get_inward_nil_exempt(self.gst_details.get("gst_state")) - self.set_inter_state_supply(inter_state_supplies) self.set_inward_nil_exempt(inward_nil_exempt) self.missing_field_invoices = self.get_missing_field_invoices() - self.json_output = frappe.as_json(self.report_dict) def set_inward_nil_exempt(self, inward_nil_exempt): @@ -178,189 +47,95 @@ class GSTR3BReport(Document): self.report_dict["inward_sup"]["isup_details"][1]["intra"] = flt(inward_nil_exempt.get("non_gst").get("intra"), 2) def set_itc_details(self, itc_details): - - itc_type_map = { + itc_eligible_type_map = { 'IMPG': 'Import Of Capital Goods', 'IMPS': 'Import Of Service', + 'ISRC': 'ITC on Reverse Charge', 'ISD': 'Input Service Distributor', 'OTH': 'All Other ITC' } + itc_ineligible_map = { + 'RUL': 'Ineligible As Per Section 17(5)', + 'OTH': 'Ineligible Others' + } + net_itc = self.report_dict["itc_elg"]["itc_net"] for d in self.report_dict["itc_elg"]["itc_avl"]: - - itc_type = itc_type_map.get(d["ty"]) - - if d["ty"] == 'ISRC': - reverse_charge = ["Y"] - itc_type = 'All Other ITC' - gst_category = ['Unregistered', 'Overseas'] - else: - gst_category = ['Unregistered', 'Overseas', 'Registered Regular'] - reverse_charge = ["N", "Y"] - - for account_head in self.account_heads: - for category in gst_category: - for charge_type in reverse_charge: - for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: - d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2) - + itc_type = itc_eligible_type_map.get(d["ty"]) for key in ['iamt', 'camt', 'samt', 'csamt']: + d[key] = flt(itc_details.get(itc_type, {}).get(key)) net_itc[key] += flt(d[key], 2) - for account_head in self.account_heads: - itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1] - for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: - itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2) + for d in self.report_dict["itc_elg"]["itc_inelg"]: + itc_type = itc_ineligible_map.get(d["ty"]) + for key in ['iamt', 'camt', 'samt', 'csamt']: + d[key] = flt(itc_details.get(itc_type, {}).get(key)) - def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"): + def get_itc_reversal_entries(self): + reversal_entries = frappe.db.sql(""" + SELECT ja.account, j.reversal_type, sum(credit_in_account_currency) as amount + FROM `tabJournal Entry` j, `tabJournal Entry Account` ja + where j.docstatus = 1 + and j.is_opening = 'No' + and ja.parent = j.name + and j.voucher_type = 'Reversal Of ITC' + and month(j.posting_date) = %s and year(j.posting_date) = %s + and j.company = %s and j.company_gstin = %s + GROUP BY ja.account, j.reversal_type""", (self.month_no, self.year, self.company, + self.gst_details.get("gstin")), as_dict=1) - account_map = { - 'sgst_account': 'samt', - 'cess_account': 'csamt', - 'cgst_account': 'camt', - 'igst_account': 'iamt' - } + net_itc = self.report_dict["itc_elg"]["itc_net"] - txval = 0 - total_taxable_value = self.get_total_taxable_value(doctype, reverse_charge) + for entry in reversal_entries: + if entry.reversal_type == 'As per rules 42 & 43 of CGST Rules': + index = 0 + else: + index = 1 - for gst_category in gst_category_list: - txval += total_taxable_value.get(gst_category,0) - for account_head in self.account_heads: - for account_type, account_name in iteritems(account_head): - if account_map.get(account_type) in self.report_dict.get(supply_type).get(supply_category): - self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \ - flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2) - - self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2) - - def set_inter_state_supply(self, inter_state_supply): - osup_det = self.report_dict["sup_details"]["osup_det"] - - for key, value in iteritems(inter_state_supply): - if key[0] == "Unregistered": - self.report_dict["inter_sup"]["unreg_details"].append(value) - - if key[0] == "Registered Composition": - self.report_dict["inter_sup"]["comp_details"].append(value) - - if key[0] == "UIN Holders": - self.report_dict["inter_sup"]["uin_details"].append(value) - - def get_total_taxable_value(self, doctype, reverse_charge): - - return frappe._dict(frappe.db.sql(""" - select gst_category, sum(net_total) as total - from `tab{doctype}` - where docstatus = 1 and month(posting_date) = %s - and year(posting_date) = %s and reverse_charge = %s - and company = %s and company_gstin = %s - group by gst_category - """ #nosec - .format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin")))) + for key in ['camt', 'samt', 'iamt', 'csamt']: + if entry.account in self.account_heads.get(key): + self.report_dict["itc_elg"]["itc_rev"][index][key] += flt(entry.amount) + net_itc[key] -= flt(entry.amount) def get_itc_details(self): - itc_amount = frappe.db.sql(""" - select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, - t.account_head, s.eligibility_for_itc, s.reverse_charge - from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t - where s.docstatus = 1 and t.parent = s.name - and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s - and s.company_gstin = %s - group by t.account_head, s.gst_category, s.eligibility_for_itc - """, - (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + itc_amounts = frappe.db.sql(""" + SELECT eligibility_for_itc, sum(itc_integrated_tax) as itc_integrated_tax, + sum(itc_central_tax) as itc_central_tax, + sum(itc_state_tax) as itc_state_tax, + sum(itc_cess_amount) as itc_cess_amount + FROM `tabPurchase Invoice` + WHERE docstatus = 1 + and is_opening = 'No' + and month(posting_date) = %s and year(posting_date) = %s and company = %s + and company_gstin = %s + GROUP BY eligibility_for_itc + """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) itc_details = {} - - for d in itc_amount: - itc_details.setdefault((d.gst_category, d.eligibility_for_itc, d.reverse_charge, d.account_head),{ - "amount": d.tax_amount + for d in itc_amounts: + itc_details.setdefault(d.eligibility_for_itc, { + 'iamt': d.itc_integrated_tax, + 'camt': d.itc_central_tax, + 'samt': d.itc_state_tax, + 'csamt': d.itc_cess_amount }) return itc_details - def get_nil_rated_supply_value(self): - - return frappe.db.sql(""" - select sum(i.base_amount) as total from - `tabSales Invoice Item` i, `tabSales Invoice` s - where s.docstatus = 1 and i.parent = s.name and i.is_nil_exempt = 1 - and month(s.posting_date) = %s and year(s.posting_date) = %s - and s.company = %s and s.company_gstin = %s""", - (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_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') - """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - - inter_state_supply_tax_mapping = {} - inter_state_supply_details = {} - - for d in inter_state_supply_tax: - inter_state_supply_tax_mapping.setdefault(d.name, { - 'place_of_supply': d.place_of_supply, - 'taxable_value': d.net_total, - 'gst_category': d.gst_category, - 'camt': 0.0, - 'samt': 0.0, - 'iamt': 0.0, - 'csamt': 0.0 - }) - - if d.account_head in [a.cgst_account for a in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount - - if d.account_head in [a.sgst_account for a in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount - - if d.account_head in [a.igst_account for a in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount - - if d.account_head in [a.cess_account for a 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 value.get('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) - - if state_number != value.get('place_of_supply').split("-")[0]: - inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), { - "txval": 0.0, - "pos": value.get('place_of_supply').split("-")[0], - "iamt": 0.0 - }) - - inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value'] - inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt'] - - return inter_state_supply_details - def get_inward_nil_exempt(self, state): - inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, - i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i - where p.docstatus = 1 and p.name = i.parent + inward_nil_exempt = frappe.db.sql(""" + SELECT p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst + FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i + WHERE p.docstatus = 1 and p.name = i.parent + and p.is_opening = 'No' and p.gst_category != 'Registered Composition' - and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and - month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s - group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - - inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply - FROM `tabPurchase Invoice` - WHERE docstatus = 1 and gst_category = 'Registered Composition' - and month(posting_date) = %s and year(posting_date) = %s - and company = %s and company_gstin = %s - group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and + month(p.posting_date) = %s and year(p.posting_date) = %s + and p.company = %s and p.company_gstin = %s + GROUP BY p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", + (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) inward_nil_exempt_details = { "gst": { @@ -388,37 +163,193 @@ class GSTR3BReport(Document): return inward_nil_exempt_details - def get_tax_amounts(self, doctype, reverse_charge="N"): + def get_outward_supply_details(self, doctype, reverse_charge=None): + self.get_outward_tax_invoices(doctype, reverse_charge=reverse_charge) + self.get_outward_items(doctype) + self.get_outward_tax_details(doctype) + def get_outward_tax_invoices(self, doctype, reverse_charge=None): + self.invoices = [] + self.invoice_detail_map = {} + condition = '' + + if reverse_charge: + condition += "AND reverse_charge = 'Y'" + + invoice_details = frappe.db.sql(""" + SELECT + name, gst_category, export_type, place_of_supply + FROM + `tab{doctype}` + WHERE + docstatus = 1 + AND month(posting_date) = %s + AND year(posting_date) = %s + AND company = %s + AND company_gstin = %s + AND is_opening = 'No' + {reverse_charge} + ORDER BY name + """.format(doctype=doctype, reverse_charge=condition), (self.month_no, self.year, + self.company, self.gst_details.get("gstin")), as_dict=1) + + for d in invoice_details: + self.invoice_detail_map.setdefault(d.name, d) + self.invoices.append(d.name) + + def get_outward_items(self, doctype): + self.invoice_items = frappe._dict() + self.is_nil_exempt = [] + self.is_non_gst = [] + + if self.get('invoices'): + item_details = frappe.db.sql(""" + SELECT + item_code, parent, taxable_value, base_net_amount, item_tax_rate, + is_nil_exempt, is_non_gst + FROM + `tab%s Item` + WHERE parent in (%s) + """ % (doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) + + for d in item_details: + if d.item_code not in self.invoice_items.get(d.parent, {}): + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, + sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details + if i.item_code == d.item_code and i.parent == d.parent)) + + if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: + self.is_nil_exempt.append(d.item_code) + + if d.is_non_gst and d.item_code not in self.is_non_gst: + self.is_non_gst.append(d.item_code) + + def get_outward_tax_details(self, doctype): if doctype == "Sales Invoice": tax_template = 'Sales Taxes and Charges' elif doctype == "Purchase Invoice": tax_template = 'Purchase Taxes and Charges' - tax_amounts = frappe.db.sql(""" - select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head - from `tab{doctype}` s , `tab{template}` t - where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s - and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s - and s.company_gstin = %s - group by t.account_head, s.gst_category - """ #nosec - .format(doctype=doctype, template=tax_template), - (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + self.items_based_on_tax_rate = {} + self.invoice_cess = frappe._dict() + self.cgst_sgst_invoices = [] - tax_details = {} + if self.get('invoices'): + tax_details = frappe.db.sql(""" + SELECT + parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount + FROM `tab%s` + WHERE + parenttype = %s and docstatus = 1 + and parent in (%s) + ORDER BY account_head + """ % (tax_template, '%s', ', '.join(['%s']*len(self.invoices))), + tuple([doctype] + list(self.invoices))) - for d in tax_amounts: - tax_details.setdefault( - (d.account_head,d.gst_category),{ - "amount": d.get("tax_amount"), - } - ) + for parent, account, item_wise_tax_detail, tax_amount in tax_details: + if account in self.account_heads.get('csamt'): + self.invoice_cess.setdefault(parent, tax_amount) + else: + if item_wise_tax_detail: + try: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + cgst_or_sgst = False + if account in self.account_heads.get('camt') \ + or account in self.account_heads.get('samt'): + cgst_or_sgst = True - return tax_details + for item_code, tax_amounts in item_wise_tax_detail.items(): + if not (cgst_or_sgst or account in self.account_heads.get('iamt') or + (item_code in self.is_non_gst + self.is_nil_exempt)): + continue + + tax_rate = tax_amounts[0] + if tax_rate: + if cgst_or_sgst: + tax_rate *= 2 + 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, []) + if item_code not in rate_based_dict: + rate_based_dict.append(item_code) + except ValueError: + continue + + + if self.get('invoice_items'): + # Build itemised tax for export invoices, nil and exempted where tax table is blank + for invoice, items in iteritems(self.invoice_items): + if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type') + == "Without Payment of Tax"): + self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) + + def set_outward_taxable_supplies(self): + inter_state_supply_details = {} + + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + for rate, items in items_based_on_rate.items(): + for item_code, taxable_value in self.invoice_items.get(inv).items(): + if item_code in items: + if item_code in self.is_nil_exempt: + self.report_dict['sup_details']['osup_nil_exmp']['txval'] += taxable_value + elif item_code in self.is_non_gst: + self.report_dict['sup_details']['osup_nongst']['txval'] += taxable_value + elif rate == 0: + self.report_dict['sup_details']['osup_zero']['txval'] += taxable_value + #self.report_dict['sup_details']['osup_zero'][key] += tax_amount + else: + if inv in self.cgst_sgst_invoices: + tax_rate = rate/2 + self.report_dict['sup_details']['osup_det']['camt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['osup_det']['samt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['osup_det']['txval'] += taxable_value + else: + self.report_dict['sup_details']['osup_det']['iamt'] += (taxable_value * rate /100) + self.report_dict['sup_details']['osup_det']['txval'] += taxable_value + + gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category') + place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply', '00-Other Territory') + + if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \ + self.gst_details.get("gst_state") != place_of_supply.split("-")[1]: + inter_state_supply_details.setdefault((gst_category, place_of_supply), { + "txval": 0.0, + "pos": place_of_supply.split("-")[0], + "iamt": 0.0 + }) + inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value + inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100) + + self.set_inter_state_supply(inter_state_supply_details) + + def set_supplies_liable_to_reverse_charge(self): + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + for rate, items in items_based_on_rate.items(): + for item_code, taxable_value in self.invoice_items.get(inv).items(): + if item_code in items: + if inv in self.cgst_sgst_invoices: + tax_rate = rate/2 + self.report_dict['sup_details']['isup_rev']['camt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['isup_rev']['samt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value + else: + self.report_dict['sup_details']['isup_rev']['iamt'] += (taxable_value * rate /100) + self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value + + def set_inter_state_supply(self, inter_state_supply): + for key, value in iteritems(inter_state_supply): + if key[0] == "Unregistered": + self.report_dict["inter_sup"]["unreg_details"].append(value) + + if key[0] == "Registered Composition": + self.report_dict["inter_sup"]["comp_details"].append(value) + + if key[0] == "UIN Holders": + self.report_dict["inter_sup"]["uin_details"].append(value) def get_company_gst_details(self): - gst_details = frappe.get_all("Address", fields=["gstin", "gst_state", "gst_state_number"], filters={ @@ -431,20 +362,28 @@ class GSTR3BReport(Document): frappe.throw(_("Please enter GSTIN and state for the Company Address {0}").format(self.company_address)) def get_account_heads(self): + account_map = { + 'sgst_account': 'samt', + 'cess_account': 'csamt', + 'cgst_account': 'camt', + 'igst_account': 'iamt' + } - account_heads = frappe.get_all("GST Account", - fields=["cgst_account", "sgst_account", "igst_account", "cess_account"], - filters={ - "company":self.company - }) + account_heads = {} + gst_settings_accounts = frappe.get_all("GST Account", + filters={'company': self.company, 'is_reverse_charge_account': 0}, + fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) - if account_heads: - return account_heads - else: - frappe.throw(_("Please set account heads in GST Settings for Compnay {0}").format(self.company)) + if not gst_settings_accounts: + frappe.throw(_("Please set GST Accounts in GST Settings")) + + for d in gst_settings_accounts: + for acc, val in d.items(): + account_heads.setdefault(account_map.get(acc), []).append(val) + + return account_heads def get_missing_field_invoices(self): - missing_field_invoices = [] for doctype in ["Sales Invoice", "Purchase Invoice"]: @@ -456,26 +395,32 @@ class GSTR3BReport(Document): party_type = 'Supplier' party = 'supplier' - docnames = frappe.db.sql(""" - select t1.name from `tab{doctype}` t1, `tab{party_type}` t2 - where t1.docstatus = 1 and month(t1.posting_date) = %s and year(t1.posting_date) = %s + docnames = frappe.db.sql( + """ + SELECT t1.name FROM `tab{doctype}` t1, `tab{party_type}` t2 + WHERE t1.docstatus = 1 and t1.is_opening = 'No' + and month(t1.posting_date) = %s and year(t1.posting_date) = %s and t1.company = %s and t1.place_of_supply IS NULL and t1.{party} = t2.name and t2.gst_category != 'Overseas' - """.format(doctype = doctype, party_type = party_type, party=party), (self.month_no, self.year, self.company), as_dict=1) #nosec + """.format(doctype = doctype, party_type = party_type, + party=party) ,(self.month_no, self.year, self.company), as_dict=1) #nosec for d in docnames: missing_field_invoices.append(d.name) return ",".join(missing_field_invoices) -def get_state_code(state): +def get_json(template): + file_path = os.path.join(os.path.dirname(__file__), '{template}.json'.format(template=template)) + with open(file_path, 'r') as f: + return cstr(f.read()) +def get_state_code(state): state_code = state_numbers.get(state) return state_code def get_period(month, year=None): - month_no = { "January": 1, "February": 2, @@ -499,13 +444,11 @@ def get_period(month, year=None): @frappe.whitelist() def view_report(name): - json_data = frappe.get_value("GSTR 3B Report", name, 'json_output') return json.loads(json_data) @frappe.whitelist() def make_json(name): - json_data = frappe.get_value("GSTR 3B Report", name, 'json_output') file_name = "GST3B.json" frappe.local.response.filename = file_name diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json new file mode 100644 index 0000000000..a68bd6a6e5 --- /dev/null +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json @@ -0,0 +1,127 @@ +{ + "gstin": "", + "ret_period": "", + "inward_sup": { + "isup_details": [ + { + "ty": "GST", + "intra": 0, + "inter": 0 + }, + { + "ty": "NONGST", + "inter": 0, + "intra": 0 + } + ] + }, + "sup_details": { + "osup_zero": { + "csamt": 0, + "txval": 0, + "iamt": 0 + }, + "osup_nil_exmp": { + "txval": 0 + }, + "osup_det": { + "samt": 0, + "csamt": 0, + "txval": 0, + "camt": 0, + "iamt": 0 + }, + "isup_rev": { + "samt": 0, + "csamt": 0, + "txval": 0, + "camt": 0, + "iamt": 0 + }, + "osup_nongst": { + "txval": 0 + } + }, + "inter_sup": { + "unreg_details": [], + "comp_details": [], + "uin_details": [] + }, + "itc_elg": { + "itc_avl": [ + { + "csamt": 0, + "samt": 0, + "ty": "IMPG", + "camt": 0, + "iamt": 0 + }, + { + "csamt": 0, + "samt": 0, + "ty": "IMPS", + "camt": 0, + "iamt": 0 + }, + { + "samt": 0, + "csamt": 0, + "ty": "ISRC", + "camt": 0, + "iamt": 0 + }, + { + "ty": "ISD", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + }, + { + "samt": 0, + "csamt": 0, + "ty": "OTH", + "camt": 0, + "iamt": 0 + } + ], + "itc_rev": [ + { + "ty": "RUL", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + }, + { + "ty": "OTH", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + } + ], + "itc_net": { + "samt": 0, + "csamt": 0, + "camt": 0, + "iamt": 0 + }, + "itc_inelg": [ + { + "ty": "RUL", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + }, + { + "ty": "OTH", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + } + ] + } +} \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index ef8af24c42..3857ce1cdb 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -60,8 +60,7 @@ class TestGSTR3BReport(unittest.TestCase): output = json.loads(report.json_output) - self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 36), - self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18), + self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 54) self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18), self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100), self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index b12e152b14..229e0c031e 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -114,9 +114,12 @@ def add_print_formats(): def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters + journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] + if not patch: make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', @@ -198,15 +201,20 @@ def make_custom_fields(update=True): purchase_invoice_itc_fields = [ dict(fieldname='eligibility_for_itc', label='Eligibility For ITC', fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1, - options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nIneligible\nAll Other ITC', default="All Other ITC"), + options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC', + default="All Other ITC"), dict(fieldname='itc_integrated_tax', label='Availed ITC Integrated Tax', - fieldtype='Data', insert_after='eligibility_for_itc', print_hide=1), + fieldtype='Currency', insert_after='eligibility_for_itc', + options='Company:company:default_currency', print_hide=1), dict(fieldname='itc_central_tax', label='Availed ITC Central Tax', - fieldtype='Data', insert_after='itc_integrated_tax', print_hide=1), + fieldtype='Currency', insert_after='itc_integrated_tax', + options='Company:company:default_currency', print_hide=1), dict(fieldname='itc_state_tax', label='Availed ITC State/UT Tax', - fieldtype='Data', insert_after='itc_central_tax', print_hide=1), + fieldtype='Currency', insert_after='itc_central_tax', + options='Company:company:default_currency', print_hide=1), dict(fieldname='itc_cess_amount', label='Availed ITC Cess', - fieldtype='Data', insert_after='itc_state_tax', print_hide=1), + fieldtype='Currency', insert_after='itc_state_tax', + options='Company:company:default_currency', print_hide=1), ] sales_invoice_gst_fields = [ @@ -236,6 +244,23 @@ def make_custom_fields(update=True): depends_on="eval:doc.gst_category=='Overseas' "), ] + journal_entry_fields = [ + dict(fieldname='reversal_type', label='Reversal Type', + fieldtype='Select', insert_after='voucher_type', print_hide=1, + options="As per rules 42 & 43 of CGST Rules\nOthers", + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_address', label='Company Address', + fieldtype='Link', options='Address', insert_after='reversal_type', + print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1, + fetch_from='company_address.gstin', + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'") + ] + inter_state_gst_field = [ dict(fieldname='is_inter_state', label='Is Inter State', fieldtype='Check', insert_after='disabled', print_hide=1), @@ -430,13 +455,13 @@ def make_custom_fields(update=True): dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', print_hide=1, hidden=1), - + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', no_copy=1, print_hide=1), - + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', no_copy=1, print_hide=1), dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', @@ -469,6 +494,7 @@ def make_custom_fields(update=True): 'Purchase Receipt': purchase_invoice_gst_fields, 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, + 'Journal Entry': journal_entry_fields, 'Sales Order': sales_invoice_gst_fields, 'Tax Category': inter_state_gst_field, 'Item': [ @@ -486,7 +512,7 @@ def make_custom_fields(update=True): 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], - 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Salary Component': [ dict(fieldname= 'component_type', diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 052d7bdedf..ca679e43d2 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -204,8 +204,6 @@ def get_regional_address_details(party_details, doctype, company): if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" - - get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) if party_details.get('taxes_and_charges'): @@ -216,7 +214,6 @@ def get_regional_address_details(party_details, doctype, company): elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" - get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) if party_details.get('taxes_and_charges'): @@ -283,20 +280,6 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code): {'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name') return default_tax -def get_tax_template_for_sez(party_details, master_doctype, company, party_type): - - gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))}, - ['gst_category', 'export_type'], as_dict=1) - - if gst_details: - if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax': - default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0, - "gst_state": number_state_mapping[party_details.company_gstin[:2]]}) - - party_details["taxes_and_charges"] = default_tax - party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - - def calculate_annual_eligible_hra_exemption(doc): basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"]) if not (basic_component and hra_component): @@ -697,10 +680,19 @@ def validate_state_code(state_code, address): return int(state_code) @frappe.whitelist() -def get_gst_accounts(company, account_wise=False): +def get_gst_accounts(company=None, account_wise=False, only_reverse_charge=0, only_non_reverse_charge=0): + filters={"parent": "GST Settings"} + + if company: + filters.update({'company': company}) + if only_reverse_charge: + filters.update({'is_reverse_charge_account': 1}) + elif only_non_reverse_charge: + filters.update({'is_reverse_charge_account': 0}) + gst_accounts = frappe._dict() gst_settings_accounts = frappe.get_all("GST Account", - filters={"parent": "GST Settings", "company": company}, + filters=filters, fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) if not gst_settings_accounts and not frappe.flags.in_test: @@ -715,101 +707,63 @@ def get_gst_accounts(company, account_wise=False): return gst_accounts -def update_grand_total_for_rcm(doc, method): +def validate_reverse_charge_transaction(doc, method): country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'India': return - gst_tax, base_gst_tax = get_gst_tax_amount(doc) - - if not base_gst_tax: - return + base_gst_tax = 0 + base_reverse_charge_booked = 0 if doc.reverse_charge == 'Y': - doc.taxes_and_charges_added -= gst_tax - doc.total_taxes_and_charges -= gst_tax - doc.base_taxes_and_charges_added -= base_gst_tax - doc.base_total_taxes_and_charges -= base_gst_tax + gst_accounts = get_gst_accounts(doc.company, only_reverse_charge=1) + reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') - update_totals(gst_tax, base_gst_tax, doc) - -def update_totals(gst_tax, base_gst_tax, doc): - doc.base_grand_total -= base_gst_tax - 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.rounded_total or doc.grand_total - - doc.in_words = money_in_words(doc.grand_total, doc.currency) - doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company)) - doc.set_payment_schedule() - -def make_regional_gl_entries(gl_entries, doc): - country = frappe.get_cached_value('Company', doc.company, 'country') - - if country != 'India': - return gl_entries - - gst_tax, base_gst_tax = get_gst_tax_amount(doc) - - if not base_gst_tax: - return gl_entries - - 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_gst_accounts(doc.company, only_non_reverse_charge=1) + non_reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + gst_accounts.get('igst_account') for tax in doc.get('taxes'): - if tax.category not in ("Total", "Valuation and Total"): - continue + if tax.account_head in non_reverse_charge_accounts: + if tax.add_deduct_tax == 'Add': + base_gst_tax += tax.base_tax_amount_after_discount_amount + else: + base_gst_tax += tax.base_tax_amount_after_discount_amount + elif tax.account_head in reverse_charge_accounts: + if tax.add_deduct_tax == 'Add': + base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount + else: + base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: - account_currency = get_account_currency(tax.account_head) + if base_gst_tax != base_reverse_charge_booked: + msg = _("Booked reverse charge is not equal to applied tax amount") + msg += "
    " + msg += _("Please refer {gst_document_link} to learn more about how to setup and create reverse charge invoice").format( + gst_document_link='GST Documentation') - gl_entries.append(doc.get_gl_dict( - { - "account": tax.account_head, - "cost_center": tax.cost_center, - "posting_date": doc.posting_date, - "against": doc.supplier, - dr_or_cr: tax.base_tax_amount_after_discount_amount, - dr_or_cr + "_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=tax) - ) + frappe.throw(msg) - return gl_entries +def update_itc_availed_fields(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') -def get_gst_tax_amount(doc): - 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', []) + if country != 'India': + return - base_gst_tax = 0 - gst_tax = 0 + # Initialize values + doc.itc_integrated_tax = doc.itc_state_tax = doc.itc_central_tax = doc.itc_cess_amount = 0 + gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1) 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: - base_gst_tax += tax.base_tax_amount_after_discount_amount - gst_tax += tax.tax_amount_after_discount_amount - - return gst_tax, base_gst_tax + if tax.account_head in gst_accounts.get('igst_account', []): + doc.itc_integrated_tax += flt(tax.base_tax_amount_after_discount_amount) + if tax.account_head in gst_accounts.get('sgst_account', []): + doc.itc_state_tax += flt(tax.base_tax_amount_after_discount_amount) + if tax.account_head in gst_accounts.get('cgst_account', []): + doc.itc_central_tax += flt(tax.base_tax_amount_after_discount_amount) + if tax.account_head in gst_accounts.get('cess_account', []): + doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount) @frappe.whitelist() def get_regional_round_off_accounts(company, account_list): diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 1a7ff2bf5a..444f5dbb8c 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -46,7 +46,13 @@ frappe.query_reports["GSTR-1"] = { "label": __("Type of Business"), "fieldtype": "Select", "reqd": 1, - "options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"], + "options": [ + { "value": "B2B", "label": __("B2B Invoices - 4A, 4B, 4C, 6B, 6C") }, + { "value": "B2C Large", "label": __("B2C(Large) Invoices - 5A, 5B") }, + { "value": "B2C Small", "label": __("B2C(Small) Invoices - 7") }, + { "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") }, + { "value": "EXPORT", "label": __("Export Invoice - 6A") } + ], "default": "B2B" } ], diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 808fd3a2cc..1e28a40f81 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -32,6 +32,7 @@ class Gstr1Report(object): reverse_charge, return_against, is_return, + is_debit_note, gst_category, export_type, port_code, @@ -42,7 +43,7 @@ class Gstr1Report(object): def run(self): self.get_columns() - self.gst_accounts = get_gst_accounts(self.filters.company) + self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1) self.get_invoice_data() if self.invoices: @@ -62,9 +63,9 @@ class Gstr1Report(object): for rate, items in items_based_on_rate.items(): row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - if self.filters.get("type_of_business") == "CDNR": + if self.filters.get("type_of_business") == "CDNR-REG": 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.append("C" if invoice_details.is_return else "D") if taxable_value: self.data.append(row) @@ -105,7 +106,7 @@ class Gstr1Report(object): def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items): row = [] for fieldname in self.invoice_fields: - if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value": + if self.filters.get("type_of_business") == "CDNR-REG" and fieldname == "invoice_value": row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total)) elif fieldname == "invoice_value": row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total) @@ -171,7 +172,7 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2B": - conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1" + conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1" if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') @@ -179,19 +180,19 @@ class Gstr1Report(object): frappe.throw(_("Please set B2C Limit in GST Settings.")) if self.filters.get("type_of_business") == "B2C Large": - conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') - and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') + AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "B2C Small": - conditions += """ and ( + conditions += """ AND ( SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2) - or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + OR grand_total <= {0}) and is_return != 1 AND gst_category ='Unregistered' """.format(flt(b2c_limit)) - elif self.filters.get("type_of_business") == "CDNR": - conditions += """ and is_return = 1 """ + elif self.filters.get("type_of_business") == "CDNR-REG": + conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')""" elif self.filters.get("type_of_business") == "EXPORT": - conditions += """ and is_return !=1 and gst_category = 'Overseas' """ + conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ return conditions def get_invoice_items(self): @@ -403,7 +404,7 @@ class Gstr1Report(object): "width": 100 } ] - elif self.filters.get("type_of_business") == "CDNR": + elif self.filters.get("type_of_business") == "CDNR-REG": self.invoice_columns = [ { "fieldname": "customer_gstin", @@ -437,6 +438,17 @@ class Gstr1Report(object): "options": "Sales Invoice", "width":120 }, + { + "fieldname": "reverse_charge", + "label": "Reverse Charge", + "fieldtype": "Data" + }, + { + "fieldname": "export_type", + "label": "Export Type", + "fieldtype": "Data", + "hidden": 1 + }, { "fieldname": "reason_for_issuing_document", "label": "Reason For Issuing document", @@ -449,6 +461,11 @@ class Gstr1Report(object): "fieldtype": "Data", "width": 120 }, + { + "fieldname": "gst_category", + "label": "GST Category", + "fieldtype": "Data" + }, { "fieldname": "invoice_value", "label": "Invoice Value", @@ -458,10 +475,10 @@ class Gstr1Report(object): ] self.other_columns = [ { - "fieldname": "cess_amount", - "label": "Cess Amount", - "fieldtype": "Currency", - "width": 100 + "fieldname": "cess_amount", + "label": "Cess Amount", + "fieldtype": "Currency", + "width": 100 }, { "fieldname": "pre_gst", @@ -589,6 +606,12 @@ def get_json(filters, report_name, data): out = get_export_json(res) gst_json["exp"] = out + elif filters["type_of_business"] == 'CDNR-REG': + for item in report_data[:-1]: + res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item) + + out = get_cdnr_reg_json(res, gstin) + gst_json["cdnr"] = out return { 'report_name': report_name, @@ -628,7 +651,6 @@ def get_b2b_json(res, gstin): return out def get_b2cs_json(data, gstin): - company_state_number = gstin[0:2] out = [] @@ -713,6 +735,54 @@ def get_export_json(res): return out +def get_cdnr_reg_json(res, gstin): + out = [] + + for gst_in in res: + cdnr_item, inv = {"ctin": gst_in, "nt": []}, [] + if not gst_in: continue + + for number, invoice in iteritems(res[gst_in]): + if not invoice[0]["place_of_supply"]: + frappe.throw(_("""{0} not entered in Invoice {1}. + Please update and try again""").format(frappe.bold("Place Of Supply"), + frappe.bold(invoice[0]['invoice_number']))) + + inv_item = { + "nt_num": invoice[0]["invoice_number"], + "nt_dt": getdate(invoice[0]["posting_date"]).strftime('%d-%m-%Y'), + "val": abs(flt(invoice[0]["invoice_value"])), + "ntty": invoice[0]["document_type"], + "pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]), + "rchrg": invoice[0]["reverse_charge"], + "inv_type": get_invoice_type_for_cdnr(invoice[0]) + } + + inv_item["itms"] = [] + for item in invoice: + inv_item["itms"].append(get_rate_and_tax_details(item, gstin)) + + inv.append(inv_item) + + if not inv: continue + cdnr_item["nt"] = inv + out.append(cdnr_item) + + return out + +def get_invoice_type_for_cdnr(row): + if row.get('gst_category') == 'SEZ': + if row.get('export_type') == 'WPAY': + invoice_type = 'SEWP' + else: + invoice_type = 'SEWOP' + elif row.get('gst_category') == 'Deemed Export': + row.invoice_type = 'DE' + elif row.get('gst_category') == 'Registered Regular': + invoice_type = 'R' + + return invoice_type + def get_basic_invoice_detail(row): return { "inum": row["invoice_number"], From be3cde931303df533e673082954604ab2cca6ce8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 14 May 2021 12:20:38 +0530 Subject: [PATCH 0741/1264] chore: Code clean up for purchase receipt GL (#25379) * chore: Code clean up for purchase receipt GL * fix: add params for debit and credit in account curreny * chore: Asset GL entry code cleanup * fix: Syntax error * fix: Update variable names * fix: function naming * fix: Add undefined variables * fix: Supplier warehouse fetching * fix: Linting issues --- .../purchase_receipt/purchase_receipt.py | 204 ++++++++---------- 1 file changed, 87 insertions(+), 117 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 61e60f3922..f1292d8cbd 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -243,16 +243,23 @@ class PurchaseReceipt(BuyingController): def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import process_gl_map + gl_entries = [] + self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) + self.make_tax_gl_entries(gl_entries) + self.get_asset_gl_entry(gl_entries) + + return process_gl_map(gl_entries) + + def make_item_gl_entries(self, gl_entries, warehouse_account=None): stock_rbnb = self.get_company_default("stock_received_but_not_billed") landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) - gl_entries = [] warehouse_with_no_account = [] - negative_expense_to_be_booked = 0.0 stock_items = self.get_stock_items() + for d in self.get("items"): if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): if warehouse_account.get(d.warehouse): @@ -263,21 +270,22 @@ class PurchaseReceipt(BuyingController): if not stock_value_diff: continue + warehouse_account_name = warehouse_account[d.warehouse]["account"] + warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"] + supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") + supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get("account_currency") + remarks = self.get("remarks") or _("Accounting Entry for Stock") + # If PR is sub-contracted and fg item rate is zero - # in that case if account for shource and target warehouse are same, + # in that case if account for source and target warehouse are same, # then GL entries should not be posted if flt(stock_value_diff) == flt(d.rm_supp_cost) \ and warehouse_account.get(self.supplier_warehouse) \ - and warehouse_account[d.warehouse]["account"] == warehouse_account[self.supplier_warehouse]["account"]: + and warehouse_account_name == supplier_warehouse_account: continue - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[d.warehouse]["account"], - "against": stock_rbnb, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": stock_value_diff - }, warehouse_account[d.warehouse]["account_currency"], item=d)) + self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, + stock_rbnb, account_currency=warehouse_account_currency, item=d) # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation @@ -287,43 +295,28 @@ class PurchaseReceipt(BuyingController): credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \ if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount")) if credit_amount: - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[d.from_warehouse]['account'] \ - if d.from_warehouse else stock_rbnb, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")), - "debit_in_account_currency": -1 * credit_amount - }, credit_currency, item=d)) + account = warehouse_account[d.from_warehouse]['account'] \ + if d.from_warehouse else stock_rbnb - negative_expense_to_be_booked += flt(d.item_tax_amount) + self.add_gl_entry(gl_entries, account, d.cost_center, + -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, + debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) - # Amount added through landed-cost-voucher + # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]): account_currency = get_account_currency(account) - gl_entries.append(self.get_gl_dict({ - "account": account, - "account_currency": account_currency, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": (flt(amount["base_amount"]) if (amount["base_amount"] or - account_currency!=self.company_currency) else flt(amount["amount"])), - "credit_in_account_currency": flt(amount["amount"]), - "project": d.project - }, item=d)) + credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or + account_currency!=self.company_currency) else flt(amount["amount"])) + + self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks, + warehouse_account_name, credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, project=d.project, item=d) # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[self.supplier_warehouse]["account"], - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(d.rm_supp_cost) - }, warehouse_account[self.supplier_warehouse]["account_currency"], item=d)) + self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost), + remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d) # divisional loss adjustment valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \ @@ -340,46 +333,32 @@ class PurchaseReceipt(BuyingController): cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") - gl_entries.append(self.get_gl_dict({ - "account": loss_account, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": divisional_loss, - "project": d.project - }, credit_currency, item=d)) + self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks, + warehouse_account_name, account_currency=credit_currency, project=d.project, item=d) elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(d.warehouse) elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items: - service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") credit_currency = get_account_currency(service_received_but_not_billed_account) - - gl_entries.append(self.get_gl_dict({ - "account": service_received_but_not_billed_account, - "against": d.expense_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Service"), - "project": d.project, - "credit": d.amount, - "voucher_detail_no": d.name - }, credit_currency, item=d)) - debit_currency = get_account_currency(d.expense_account) + remarks = self.get("remarks") or _("Accounting Entry for Service") - gl_entries.append(self.get_gl_dict({ - "account": d.expense_account, - "against": service_received_but_not_billed_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Service"), - "project": d.project, - "debit": d.amount, - "voucher_detail_no": d.name - }, debit_currency, item=d)) + self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount, + remarks, d.expense_account, account_currency=credit_currency, project=d.project, + voucher_detail_no=d.name, item=d) - self.get_asset_gl_entry(gl_entries) + self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account, + account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d) + + if warehouse_with_no_account: + frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + + "\n".join(warehouse_with_no_account)) + + def make_tax_gl_entries(self, gl_entries): + expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')]) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): @@ -420,23 +399,33 @@ class PurchaseReceipt(BuyingController): applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) amount_including_divisional_loss -= applicable_amount - gl_entries.append( - self.get_gl_dict({ - "account": account, - "cost_center": tax.cost_center, - "credit": applicable_amount, - "remarks": self.remarks or _("Accounting Entry for Stock"), - "against": against_account - }, item=tax) - ) + self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"), + against_account, item=tax) i += 1 - if warehouse_with_no_account: - frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + - "\n".join(warehouse_with_no_account)) + def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account, + debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None, + project=None, voucher_detail_no=None, item=None): + gl_entry = { + "account": account, + "cost_center": cost_center, + "debit": debit, + "credit": credit, + "against_account": against_account, + "remarks": remarks, + } - return process_gl_map(gl_entries) + if voucher_detail_no: + gl_entry.update({"voucher_detail_no": voucher_detail_no}) + + if debit_in_account_currency: + gl_entry.update({"debit_in_account_currency": debit_in_account_currency}) + + if credit_in_account_currency: + gl_entry.update({"credit_in_account_currency": credit_in_account_currency}) + + gl_entries.append(self.get_gl_dict(gl_entry, item=item)) def get_asset_gl_entry(self, gl_entries): for item in self.get("items"): @@ -458,30 +447,21 @@ class PurchaseReceipt(BuyingController): asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) + remarks = self.get("remarks") or _("Accounting Entry for Asset") cwip_account_currency = get_account_currency(cwip_account) # debit cwip account - gl_entries.append(self.get_gl_dict({ - "account": cwip_account, - "against": arbnb_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": (base_asset_amount - if cwip_account_currency == self.company_currency else asset_amount) - }, item=item)) + debit_in_account_currency = (base_asset_amount + if cwip_account_currency == self.company_currency else asset_amount) + self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks, + arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item) asset_rbnb_currency = get_account_currency(arbnb_account) # credit arbnb account - gl_entries.append(self.get_gl_dict({ - "account": arbnb_account, - "against": cwip_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "credit": base_asset_amount, - "credit_in_account_currency": (base_asset_amount - if asset_rbnb_currency == self.company_currency else asset_amount) - }, item=item)) + credit_in_account_currency = (base_asset_amount + if asset_rbnb_currency == self.company_currency else asset_amount) + self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks, + cwip_account, credit_in_account_currency=credit_in_account_currency, item=item) def add_lcv_gl_entries(self, item, gl_entries): expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") @@ -492,23 +472,13 @@ class PurchaseReceipt(BuyingController): # This returns company's default cwip account asset_account = get_asset_account("capital_work_in_progress_account", company=self.company) - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_asset_valuation, - "against": asset_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project - }, item=item)) + remarks = self.get("remarks") or _("Accounting Entry for Stock") - gl_entries.append(self.get_gl_dict({ - "account": asset_account, - "against": expenses_included_in_asset_valuation, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project - }, item=item)) + self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), + remarks, asset_account, project=item.project, item=item) + + self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), + remarks, expenses_included_in_asset_valuation, project=item.project, item=item) def update_assets(self, item, valuation_rate): assets = frappe.db.get_all('Asset', From eca86290bc33773288af2110f214aca7993c497b Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 14 May 2021 12:21:38 +0530 Subject: [PATCH 0742/1264] fix: show uom for item in selector dialog (#25697) --- erpnext/public/js/utils/serial_no_batch_selector.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 3333d569a7..a289ec415b 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -77,6 +77,14 @@ erpnext.SerialNoBatchSelector = Class.extend({ label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'), default: flt(me.item.stock_qty), }, + { + fieldname: 'uom', + read_only: 1, + fieldtype: 'Link', + options: 'UOM', + label: __('UOM'), + default: me.item.uom + }, { fieldname: 'auto_fetch_button', fieldtype:'Button', From 98fc4195b34f5ff47c4062352244105e8074913b Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 14 May 2021 12:22:28 +0530 Subject: [PATCH 0743/1264] fix: send emails on rfq submit (#25695) * fix: send emails on rfq submit * fix: check if email is present for supplier --- .../doctype/request_for_quotation/request_for_quotation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index b530d1ab24..180ba93666 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -62,6 +62,7 @@ class RequestforQuotation(BuyingController): for supplier in self.suppliers: supplier.email_sent = 0 supplier.quote_status = 'Pending' + self.send_to_supplier() def on_cancel(self): frappe.db.set(self, 'status', 'Cancelled') @@ -81,7 +82,7 @@ class RequestforQuotation(BuyingController): def send_to_supplier(self): """Sends RFQ mail to involved suppliers.""" for rfq_supplier in self.suppliers: - if rfq_supplier.send_email: + if rfq_supplier.email_id is not None and rfq_supplier.send_email: self.validate_email_id(rfq_supplier) # make new user if required From 2aa401826e1ac2b8627bb11e94b13fbe568c6eb7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 14 May 2021 12:34:50 +0530 Subject: [PATCH 0744/1264] fix: validation message of quality inspection in purchase receipt (#25666) --- erpnext/controllers/stock_controller.py | 3 +-- .../doctype/quality_inspection/test_quality_inspection.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b14c274515..41ca404d9b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -379,8 +379,7 @@ class StockController(AccountsController): link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection) frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError) - qa_failed = any([r.status=="Rejected" for r in qa_doc.readings]) - if qa_failed: + if qa_doc.status != 'Accepted': frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}") .format(d.idx, d.item_code), QualityInspectionRejectedError) elif qa_required : diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index a7dfc9ee28..56b046a92e 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -27,10 +27,11 @@ class TestQualityInspection(unittest.TestCase): dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) - frappe.db.set_value("Quality Inspection Reading", {"parent": qa.name}, "status", "Accepted") + frappe.db.set_value("Quality Inspection", qa.name, "status", "Accepted") dn.reload() dn.submit() + qa.reload() qa.cancel() dn.reload() dn.cancel() From 55c2fec6832152813f1abd9b2b8093cd0880e9fe Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 14 May 2021 12:36:41 +0530 Subject: [PATCH 0745/1264] feat: add pending qty section to batch/serial selector dialog (#25519) * feat: add pending qty section to batch/serial selector dialog * fix: call attach in setup and refresh, fix conditional * refactor: camel to snake casing --- .../js/utils/serial_no_batch_selector.js | 76 ++++++++++++++++++- .../stock/doctype/stock_entry/stock_entry.js | 21 +++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index a289ec415b..b5d3981ba7 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -74,9 +74,10 @@ erpnext.SerialNoBatchSelector = Class.extend({ fieldname: 'qty', fieldtype:'Float', read_only: me.has_batch && !me.has_serial_no, - label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'), + label: __(me.has_batch && !me.has_serial_no ? 'Selected Qty' : 'Qty'), default: flt(me.item.stock_qty), }, + ...get_pending_qty_fields(me), { fieldname: 'uom', read_only: 1, @@ -181,6 +182,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ if (this.has_batch && !this.has_serial_no) { this.update_total_qty(); + this.update_pending_qtys(); } this.dialog.show(); @@ -321,7 +323,21 @@ erpnext.SerialNoBatchSelector = Class.extend({ qty_field.set_input(total_qty); }, + update_pending_qtys: function() { + const pending_qty_field = this.dialog.fields_dict.pending_qty; + const total_selected_qty_field = this.dialog.fields_dict.total_selected_qty; + if (!pending_qty_field || !total_selected_qty_field) return; + + const me = this; + const required_qty = this.dialog.fields_dict.required_qty.value; + const selected_qty = this.dialog.fields_dict.qty.value; + const total_selected_qty = selected_qty + calc_total_selected_qty(me); + const pending_qty = required_qty - total_selected_qty; + + pending_qty_field.set_input(pending_qty); + total_selected_qty_field.set_input(total_selected_qty); + }, get_batch_fields: function() { var me = this; @@ -423,6 +439,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ } me.update_total_qty(); + me.update_pending_qtys(); } }, ], @@ -519,3 +536,60 @@ erpnext.SerialNoBatchSelector = Class.extend({ ]; } }); + +function get_pending_qty_fields(me) { + if (!check_can_calculate_pending_qty(me)) return []; + const { frm: { doc: { fg_completed_qty }}, item: { item_code, stock_qty }} = me; + const { qty_consumed_per_unit } = erpnext.stock.bom.items[item_code]; + + const total_selected_qty = calc_total_selected_qty(me); + const required_qty = flt(fg_completed_qty) * flt(qty_consumed_per_unit); + const pending_qty = required_qty - (flt(stock_qty) + total_selected_qty); + + const pending_qty_fields = [ + { fieldtype: 'Section Break', label: __('Pending Quantity') }, + { + fieldname: 'required_qty', + read_only: 1, + fieldtype: 'Float', + label: __('Required Qty'), + default: required_qty + }, + { fieldtype: 'Column Break' }, + { + fieldname: 'total_selected_qty', + read_only: 1, + fieldtype: 'Float', + label: __('Total Selected Qty'), + default: total_selected_qty + }, + { fieldtype: 'Column Break' }, + { + fieldname: 'pending_qty', + read_only: 1, + fieldtype: 'Float', + label: __('Pending Qty'), + default: pending_qty + }, + ]; + return pending_qty_fields; +} + +function calc_total_selected_qty(me) { + const { frm: { doc: { items }}, item: { name, item_code }} = me; + const totalSelectedQty = items + .filter( item => ( item.name !== name ) && ( item.item_code === item_code ) ) + .map( item => flt(item.qty) ) + .reduce( (i, j) => i + j, 0); + return totalSelectedQty; +} + +function check_can_calculate_pending_qty(me) { + const { frm: { doc }, item } = me; + const docChecks = doc.bom_no + && doc.fg_completed_qty + && erpnext.stock.bom + && erpnext.stock.bom.name === doc.bom_no; + const itemChecks = !!item; + return docChecks && itemChecks; +} diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 772c8df96e..daa1e51182 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -107,6 +107,7 @@ frappe.ui.form.on('Stock Entry', { frappe.flags.hide_serial_batch_dialog = true; } }); + attach_bom_items(frm.doc.bom_no); }, setup_quality_inspection: function(frm) { @@ -311,6 +312,7 @@ frappe.ui.form.on('Stock Entry', { } frm.trigger("setup_quality_inspection"); + attach_bom_items(frm.doc.bom_no) }, stock_entry_type: function(frm){ @@ -919,6 +921,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ method: "get_items", callback: function(r) { if(!r.exc) refresh_field("items"); + if(me.frm.doc.bom_no) attach_bom_items(me.frm.doc.bom_no) } }); } @@ -1064,4 +1067,22 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => { } +function attach_bom_items(bom_no) { + if (check_should_not_attach_bom_items(bom_no)) return + frappe.db.get_doc("BOM",bom_no).then(bom => { + const {name, items} = bom + erpnext.stock.bom = {name, items:{}} + items.forEach(item => { + erpnext.stock.bom.items[item.item_code] = item; + }); + }); +} + +function check_should_not_attach_bom_items(bom_no) { + return ( + bom_no === undefined || + (erpnext.stock.bom && erpnext.stock.bom.name === bom_no) + ); +} + $.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); From d8de7fccc2f27efe3b45b6ae1ba89ba8ab96521a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 14 May 2021 19:17:28 +0530 Subject: [PATCH 0746/1264] feat: Show net values in Party Accounts --- .../report/general_ledger/general_ledger.js | 5 ++++ .../report/general_ledger/general_ledger.py | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index fb0d359926..84f786814d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -166,6 +166,11 @@ frappe.query_reports["General Ledger"] = { "fieldname": "show_cancelled_entries", "label": __("Show Cancelled Entries"), "fieldtype": "Check" + }, + { + "fieldname": "show_net_values_in_party_account", + "label": __("Show Net Values in Party Account"), + "fieldtype": "Check" } ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index b5d7992604..562df4f6f7 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -344,6 +344,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): consolidated_gle = OrderedDict() group_by = group_by_field(filters.get('group_by')) + if filters.get('show_net_values_in_party_account'): + account_type_map = get_account_type_map(filters.get('company')) + def update_value_in_dict(data, key, gle): data[key].debit += flt(gle.debit) data[key].credit += flt(gle.credit) @@ -351,6 +354,24 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) + if filters.get('show_net_values_in_party_account') and \ + account_type_map.get(data[key].account) in ('Receivable', 'Payable'): + net_value = flt(data[key].debit) - flt(data[key].credit) + net_value_in_account_currency = flt(data[key].debit_in_account_currency) \ + - flt(data[key].credit_in_account_currency) + + if net_value < 0: + dr_or_cr = 'credit' + rev_dr_or_cr = 'debit' + else: + dr_or_cr = 'debit' + rev_dr_or_cr = 'credit' + + data[key][dr_or_cr] = abs(net_value) + data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency) + data[key][rev_dr_or_cr] = 0 + data[key][rev_dr_or_cr+'_in_account_currency'] = 0 + if data[key].against_voucher and gle.against_voucher: data[key].against_voucher += ', ' + gle.against_voucher @@ -388,6 +409,12 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): return totals, entries +def get_account_type_map(company): + account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'], + filters={'company': company}, as_list=1)) + + return account_type_map + def get_result_as_list(data, filters): balance, balance_in_account_currency = 0, 0 inv_details = get_supplier_invoice_details() From fd4743cc314c4c1f89ec2d6f7968f9fd478ae9b4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 12 May 2021 16:17:32 +0530 Subject: [PATCH 0747/1264] refactor: timesheet --- .../doctype/sales_invoice/sales_invoice.js | 51 +- .../doctype/sales_invoice/sales_invoice.json | 3 +- .../doctype/sales_invoice/sales_invoice.py | 24 +- .../sales_invoice_timesheet.json | 233 +--- erpnext/hooks.py | 3 +- erpnext/patches.txt | 1 + .../v7_0/convert_timelog_to_timesheet.py | 2 +- .../doctype/timesheet/test_timesheet.py | 14 +- .../projects/doctype/timesheet/timesheet.js | 87 +- .../projects/doctype/timesheet/timesheet.json | 24 +- .../projects/doctype/timesheet/timesheet.py | 65 +- .../timesheet_detail/timesheet_detail.json | 1160 +++-------------- erpnext/projects/report/billing_summary.py | 4 +- ...ee_hours_utilization_based_on_timesheet.py | 6 +- .../test_employee_util.py | 4 +- .../test_project_profitability.py | 2 +- erpnext/public/js/utils.js | 12 + 17 files changed, 498 insertions(+), 1197 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7c73ad6c90..2b79e18358 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -685,14 +685,16 @@ frappe.ui.form.on('Sales Invoice', { }, project: function(frm){ - frm.call({ - method: "add_timesheet_data", - doc: frm.doc, - callback: function(r, rt) { - refresh_field(['timesheets']) - } - }) - frm.refresh(); + if (!frm.doc.is_return) { + frm.call({ + method: "add_timesheet_data", + doc: frm.doc, + callback: function(r, rt) { + refresh_field(['timesheets']) + } + }) + frm.refresh(); + } }, onload: function(frm) { @@ -808,27 +810,45 @@ frappe.ui.form.on('Sales Invoice', { }, refresh: function(frm) { - if (frm.doc.project) { + if (frm.doc.project && frm.doc.docstatus===0 && !frm.doc.is_return) { frm.add_custom_button(__('Fetch Timesheet'), function() { let d = new frappe.ui.Dialog({ title: __('Fetch Timesheet'), fields: [ { - "label" : "From", + "label" : __("From"), "fieldname": "from_time", "fieldtype": "Date", "reqd": 1, }, + { + "label" : __("Currency"), + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "default": frm.doc.currency, + "reqd": 1, + "read_only": 1 + }, { fieldtype: 'Column Break', fieldname: 'col_break_1', }, { - "label" : "To", + "label" : __("To"), "fieldname": "to_time", "fieldtype": "Date", "reqd": 1, - } + }, + { + "label" : __("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "default": frm.doc.project, + "reqd": 1, + "read_only": 1 + }, ], primary_action: function() { let data = d.get_values(); @@ -837,7 +857,8 @@ frappe.ui.form.on('Sales Invoice', { args: { from_time: data.from_time, to_time: data.to_time, - project: frm.doc.project + project: data.project, + currency: data.currency }, callback: function(r) { if(!r.exc) { @@ -845,9 +866,11 @@ frappe.ui.form.on('Sales Invoice', { frm.clear_table('timesheets') r.message.forEach((d) => { frm.add_child('timesheets',{ + 'activity_type': d.activity_type, + 'description': d.description, 'time_sheet': d.parent, 'billing_hours': d.billing_hours, - 'billing_amount': d.billing_amt, + 'billing_amount': d.billing_amount, 'timesheet_detail': d.name }); }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index c6c67b4ddc..607e4c43d0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -748,6 +748,7 @@ { "collapsible": 1, "collapsible_depends_on": "eval:doc.total_billing_amount > 0", + "depends_on": "eval: !doc.is_return", "fieldname": "time_sheet_list", "fieldtype": "Section Break", "hide_days": 1, @@ -1969,7 +1970,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-04-15 23:57:58.766651", + "modified": "2021-05-13 17:53:26.185370", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bb74a02606..a008742390 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -125,6 +125,8 @@ class SalesInvoice(SellingController): self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") if not self.is_return: self.validate_serial_numbers() + else: + self.timesheets = [] self.update_packing_list() self.set_billing_hours_and_amount() self.update_timesheet_billing_for_project() @@ -337,7 +339,7 @@ class SalesInvoice(SellingController): if "Healthcare" in active_domains: manage_invoice_submit_cancel(self, "on_cancel") - + self.unlink_sales_invoice_from_timesheets() self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') def update_status_updater_args(self): @@ -393,6 +395,18 @@ class SalesInvoice(SellingController): if validate_against_credit_limit: check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order) + def unlink_sales_invoice_from_timesheets(self): + for row in self.timesheets: + timesheet = frappe.get_doc('Timesheet', row.time_sheet) + for time_log in timesheet.time_logs: + if time_log.sales_invoice == self.name: + time_log.sales_invoice = None + timesheet.calculate_total_amounts() + timesheet.calculate_percentage_billed() + timesheet.flags.ignore_validate_update_after_submit = True + timesheet.set_status() + timesheet.db_update_all() + @frappe.whitelist() def set_missing_values(self, for_validate=False): pos = self.set_pos_fields(for_validate) @@ -427,7 +441,7 @@ class SalesInvoice(SellingController): timesheet.calculate_percentage_billed() timesheet.flags.ignore_validate_update_after_submit = True timesheet.set_status() - timesheet.save() + timesheet.db_update_all() def update_time_sheet_detail(self, timesheet, args, sales_invoice): for data in timesheet.time_logs: @@ -741,8 +755,10 @@ class SalesInvoice(SellingController): self.append('timesheets', { 'time_sheet': data.parent, 'billing_hours': data.billing_hours, - 'billing_amount': data.billing_amt, - 'timesheet_detail': data.name + 'billing_amount': data.billing_amount, + 'timesheet_detail': data.name, + 'activity_type': data.activity_type, + 'description': data.description }) self.calculate_billing_amount_for_timesheet() diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json index f7b9aef96c..9321630829 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -1,172 +1,77 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-06-14 19:21:34.321662", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2016-06-14 19:21:34.321662", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "activity_type", + "description", + "billing_hours", + "billing_amount", + "time_sheet", + "timesheet_detail" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "time_sheet", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Time Sheet", - "length": 0, - "no_copy": 0, - "options": "Timesheet", - "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": "time_sheet", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Time Sheet", + "options": "Timesheet", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_hours", - "fieldtype": "Float", - "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": "Billing Hours", - "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 - }, + "fieldname": "billing_hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Billing Hours", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_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": "Billing 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 - }, + "fieldname": "billing_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Billing Amount", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timesheet_detail", - "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": "Timesheet Detail", - "length": 0, - "no_copy": 1, - "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 + "allow_on_submit": 1, + "fieldname": "timesheet_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Timesheet Detail", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "activity_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Activity Type", + "options": "Activity Type", + "read_only": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "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-02-18 18:50:44.770361", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Invoice Timesheet", - "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, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-05-13 16:52:32.995266", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Timesheet", + "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/hooks.py b/erpnext/hooks.py index bb6cd8bdc2..ca8ca871bd 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -304,7 +304,8 @@ doc_events = { # if payment entry not in auto cancel exempted doctypes it will cancel payment entry. auto_cancel_exempted_doctypes= [ "Payment Entry", - "Inpatient Medication Entry" + "Inpatient Medication Entry", + "Timesheet" ] after_migrate = ["erpnext.setup.install.update_select_perm_after_install"] diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 82d223cada..cec2dd5341 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -778,3 +778,4 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed +erpnext.patches.v13_0.rename_billable_to_is_billable_in_timesheet diff --git a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py index 3af6622d96..8c60b5b71e 100644 --- a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py +++ b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py @@ -51,7 +51,7 @@ def execute(): def get_timelog_data(data): return { - 'billable': data.billable, + 'is_billable': data.billable, 'from_time': data.from_time, 'hours': data.hours, 'to_time': data.to_time, diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index d21ac0f2f0..2b0c3abdd7 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -37,7 +37,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate=True, billable=1) + timesheet = make_timesheet(emp, simulate=True, is_billable=1) self.assertEqual(timesheet.total_hours, 2) self.assertEqual(timesheet.total_billable_hours, 2) @@ -49,7 +49,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate=True, billable=0) + timesheet = make_timesheet(emp, simulate=True, is_billable=0) self.assertEqual(timesheet.total_hours, 2) self.assertEqual(timesheet.total_billable_hours, 0) @@ -61,7 +61,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com", company="_Test Company") salary_structure = make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate = True, billable=1) + timesheet = make_timesheet(emp, simulate = True, is_billable=1) salary_slip = make_salary_slip(timesheet.name) salary_slip.submit() @@ -82,7 +82,7 @@ class TestTimesheet(unittest.TestCase): def test_sales_invoice_from_timesheet(self): emp = make_employee("test_employee_6@salary.com") - timesheet = make_timesheet(emp, simulate=True, billable=1) + timesheet = make_timesheet(emp, simulate=True, is_billable=1) sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer') sales_invoice.due_date = nowdate() sales_invoice.submit() @@ -100,7 +100,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") project = frappe.get_value("Project", {"project_name": "_Test Project"}) - timesheet = make_timesheet(emp, simulate=True, billable=1, project=project, company='_Test Company') + timesheet = make_timesheet(emp, simulate=True, is_billable=1, project=project, company='_Test Company') sales_invoice = create_sales_invoice(do_not_save=True) sales_invoice.project = project sales_invoice.submit() @@ -171,13 +171,13 @@ def make_salary_structure_for_timesheet(employee, company=None): return salary_structure -def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None): +def make_timesheet(employee, simulate=False, is_billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None): update_activity_type(activity_type) timesheet = frappe.new_doc("Timesheet") timesheet.employee = employee timesheet.company = company or '_Test Company' timesheet_detail = timesheet.append('time_logs', {}) - timesheet_detail.billable = billable + timesheet_detail.is_billable = is_billable timesheet_detail.activity_type = activity_type timesheet_detail.from_time = now_datetime() timesheet_detail.hours = 2 diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index b123af5d18..5554ed9264 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -90,17 +90,50 @@ frappe.ui.form.on("Timesheet", { } if(frm.doc.per_billed > 0) { frm.fields_dict["time_logs"].grid.toggle_enable("billing_hours", false); - frm.fields_dict["time_logs"].grid.toggle_enable("billable", false); + frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false); } + frm.trigger('setup_filters'); + }, + + customer: function(frm) { + frm.set_query('parent_project', function(doc) { + return { + filters: { + "customer": doc.customer + } + }; + }); + frm.set_query('project', 'time_logs', function(doc) { + return { + filters: { + "customer": doc.customer + } + }; + }); + frm.refresh(); }, make_invoice: function(frm) { + let fields = [{ + "fieldtype": "Link", + "label": __("Item Code"), + "fieldname": "item_code", + "options": "Item" + }] + + if (!frm.doc.customer) { + fields.push({ + "fieldtype": "Link", + "label": __("Customer"), + "fieldname": "customer", + "options": "Customer", + "default": frm.doc.customer + }); + } + let dialog = new frappe.ui.Dialog({ - title: __("Select Item (optional)"), - fields: [ - {"fieldtype": "Link", "label": __("Item Code"), "fieldname": "item_code", "options":"Item"}, - {"fieldtype": "Link", "label": __("Customer"), "fieldname": "customer", "options":"Customer"} - ] + title: __("Create Sales Invoice"), + fields: fields }); dialog.set_primary_action(__('Create Sales Invoice'), () => { @@ -113,7 +146,8 @@ frappe.ui.form.on("Timesheet", { args: { "source_name": frm.doc.name, "item_code": args.item_code, - "customer": args.customer + "customer": frm.doc.customer || args.customer, + "currency": frm.doc.currency }, freeze: true, callback: function(r) { @@ -136,8 +170,7 @@ frappe.ui.form.on("Timesheet", { parent_project: function(frm) { set_project_in_timelog(frm); - }, - + } }); frappe.ui.form.on("Timesheet Detail", { @@ -196,7 +229,7 @@ frappe.ui.form.on("Timesheet Detail", { calculate_billing_costing_amount(frm, cdt, cdn); }, - billable: function(frm, cdt, cdn) { + is_billable: function(frm, cdt, cdn) { update_billing_hours(frm, cdt, cdn); update_time_rates(frm, cdt, cdn); calculate_billing_costing_amount(frm, cdt, cdn); @@ -239,9 +272,9 @@ var calculate_end_time = function(frm, cdt, cdn) { } }; -var update_billing_hours = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - if(!child.billable) { +var update_billing_hours = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + if (!child.is_billable) { frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0); } else { // bill all hours by default @@ -249,19 +282,19 @@ var update_billing_hours = function(frm, cdt, cdn){ } }; -var update_time_rates = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - if(!child.billable){ +var update_time_rates = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + if (!child.is_billable) { frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0); } }; -var calculate_billing_costing_amount = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - var billing_amount = 0.0; - var costing_amount = 0.0; +var calculate_billing_costing_amount = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + let billing_amount = 0.0; + let costing_amount = 0.0; - if(child.billing_hours && child.billable){ + if (child.billing_hours && child.is_billable) { billing_amount = (child.billing_hours * child.billing_rate); } costing_amount = flt(child.costing_rate * child.hours); @@ -271,18 +304,18 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){ }; var calculate_time_and_amount = function(frm) { - var tl = frm.doc.time_logs || []; - var total_working_hr = 0; - var total_billing_hr = 0; - var total_billable_amount = 0; - var total_costing_amount = 0; + let tl = frm.doc.time_logs || []; + let total_working_hr = 0; + let total_billing_hr = 0; + let total_billable_amount = 0; + let total_costing_amount = 0; for(var i=0; i Date: Sat, 15 May 2021 20:40:20 +0530 Subject: [PATCH 0748/1264] adding patch --- .../v13_0/rename_billable_to_is_billable_in_timesheet.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py diff --git a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py new file mode 100644 index 0000000000..6860a37559 --- /dev/null +++ b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if "billable" in frappe.db.get_table_columns("Timesheet Detail"): + rename_field("Timesheet Detail", "billable", "is_billable") \ No newline at end of file From 42d2f663fa9f22542b2ef29a39f667774d56a907 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 17 May 2021 11:08:26 +0530 Subject: [PATCH 0749/1264] fix: sider fixes --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 5554ed9264..28535d7a34 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -119,7 +119,7 @@ frappe.ui.form.on("Timesheet", { "label": __("Item Code"), "fieldname": "item_code", "options": "Item" - }] + }]; if (!frm.doc.customer) { fields.push({ From 41ac8be6f20c490c2700de884257314a07a6ba63 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 17 May 2021 13:58:27 +0530 Subject: [PATCH 0750/1264] refactor: base_amount field moved below amount field and renamed --- .../landed_cost_taxes_and_charges.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json index 4fcdb4c10c..9c59c13ac0 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json @@ -10,8 +10,8 @@ "exchange_rate", "description", "col_break3", - "base_amount", - "amount" + "amount", + "base_amount" ], "fields": [ { @@ -59,7 +59,7 @@ { "fieldname": "base_amount", "fieldtype": "Currency", - "label": "Base Amount", + "label": "Amount (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 } @@ -67,7 +67,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-26 01:07:23.233604", + "modified": "2021-05-17 13:57:10.807980", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Taxes and Charges", From f3b3d81e0bba3996c11b8e5aa93a2aec3a04ac5b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 17 May 2021 16:43:38 +0530 Subject: [PATCH 0751/1264] fix: escape company name in deferred_revenue --- erpnext/accounts/deferred_revenue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index d5ab1c1704..0f47b953af 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -41,7 +41,7 @@ def build_conditions(process_type, account, company): if account: conditions += "AND %s='%s'"%(deferred_account, account) elif company: - conditions += "AND p.company='%s'"%(company) + conditions += f"AND p.company = {frappe.db.escape(company)}" return conditions From b6783b158f1f4346fb5fab3a9f0110e91bdff221 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 17 May 2021 17:06:12 +0530 Subject: [PATCH 0752/1264] chore: translation fixes --- erpnext/accounts/deferred_revenue.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 0f47b953af..dd346bc240 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -360,12 +360,10 @@ def make_gl_entries(doc, credit_account, debit_account, against, frappe.flags.deferred_accounting_error = True def send_mail(deferred_process): - title = _("Error while processing deferred accounting for {0}".format(deferred_process)) - content = _(""" - Deferred accounting failed for some invoices: - Please check Process Deferred Accounting {0} - and submit manually after resolving errors - """).format(get_link_to_form('Process Deferred Accounting', deferred_process)) + title = _("Error while processing deferred accounting for {0}").format(deferred_process) + link = get_link_to_form('Process Deferred Accounting', deferred_process) + content = _("Deferred accounting failed for some invoices:") + "\n" + content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link) sendmail_to_system_managers(title, content) def book_revenue_via_journal_entry(doc, credit_account, debit_account, against, From ca2fb47d445a6cfeacf67fcd5538ed385105dd83 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 12 May 2021 16:25:07 +0530 Subject: [PATCH 0753/1264] feat: updates item_code filters if item_group is linked to supplier --- erpnext/controllers/queries.py | 15 +++++++++++++-- erpnext/public/js/controllers/buying.js | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index bc1ac5ea06..e71b056c6e 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -204,7 +204,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if "description" in searchfields: searchfields.remove("description") - + columns = '' extra_searchfields = [field for field in searchfields if not field in ["name", "item_group", "description"]] @@ -216,11 +216,22 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if not field in searchfields] searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) + if filters.get('supplier'): + item_group_list = frappe.get_list('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + + item_groups = [] + for i in item_group_list: + item_groups.append(i.item_group) + + del filters['supplier'] + + if item_groups: + filters['item_group'] = ['in', item_groups] + description_cond = '' if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 description_cond = 'or tabItem.description LIKE %(txt)s' - return frappe.db.sql("""select tabItem.name, if(length(tabItem.item_name) > 40, concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index cdfd909b04..e7dcd41068 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -84,13 +84,13 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ if (me.frm.doc.is_subcontracted == "Yes") { return{ query: "erpnext.controllers.queries.item_query", - filters:{ 'is_sub_contracted_item': 1 } + filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 } } } else { return{ query: "erpnext.controllers.queries.item_query", - filters: {'is_purchase_item': 1} + filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1 } } } }); From 0ab0fcdd51cd4b982c67518217d5ca77fa4ea4f0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Tue, 18 May 2021 15:21:24 +0530 Subject: [PATCH 0754/1264] feat: added supplier item group link in supplier dashboard --- erpnext/buying/doctype/supplier/supplier.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 4cc5753cbd..38b8dfdf48 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -383,8 +383,14 @@ "icon": "fa fa-user", "idx": 370, "image_field": "image", - "links": [], - "modified": "2021-01-06 19:51:40.939087", + "links": [ + { + "group": "Item Group", + "link_doctype": "Supplier Item Group", + "link_fieldname": "supplier" + } + ], + "modified": "2021-05-18 15:10:11.087191", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", From bdba064fa6f76a98d5d0d7822c7648a6f24d8491 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Tue, 18 May 2021 06:42:53 -0400 Subject: [PATCH 0755/1264] fix: duplicate stock entry (#25486) Co-authored-by: Ankush Menat --- erpnext/stock/dashboard/item_dashboard.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 933ca8ab3d..db0573954f 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -230,6 +230,7 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call }, ], }); + var submitted = false; dialog.show(); dialog.get_field('item_code').set_input(item); @@ -253,6 +254,7 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call } dialog.set_primary_action(__('Submit'), function () { + if(submitted) return; var values = dialog.get_values(); if (!values) { return; @@ -265,6 +267,7 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call frappe.msgprint(__('Source and target warehouse must be different')); } + submitted = true; frappe.call({ method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, From 042b8524ccb25b0930313fd13fbf2844c2ac5341 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 May 2021 16:31:55 +0530 Subject: [PATCH 0756/1264] fix: disable submit button to avoid multiple calls --- erpnext/stock/dashboard/item_dashboard.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index db0573954f..dfb9e44283 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -271,7 +271,9 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call frappe.call({ method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, + btn: dialog.get_primary_btn(), freeze: true, + freeze_message: __('Creating Stock Entry'), callback: function (r) { frappe.show_alert(__('Stock Entry {0} created', ['' + r.message.name + ''])); From ecbb8cbc844ca451b76fb0fa4fdc30fd7592aad2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 May 2021 16:33:15 +0530 Subject: [PATCH 0757/1264] revert: "fix: duplicate stock entry (#25486)" Not required anymore, submit button is disabled. --- erpnext/stock/dashboard/item_dashboard.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index dfb9e44283..a657ecf105 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -230,7 +230,6 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call }, ], }); - var submitted = false; dialog.show(); dialog.get_field('item_code').set_input(item); @@ -254,7 +253,6 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call } dialog.set_primary_action(__('Submit'), function () { - if(submitted) return; var values = dialog.get_values(); if (!values) { return; @@ -267,7 +265,6 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call frappe.msgprint(__('Source and target warehouse must be different')); } - submitted = true; frappe.call({ method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, From 426b04003cba3935ca1bf4f369f9f4b8b2ffed84 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Sat, 15 May 2021 12:12:26 +0530 Subject: [PATCH 0758/1264] fix: Project filter for Kanban Board Signed-off-by: Syed Mujeer Hashmi --- erpnext/projects/doctype/project/project.js | 2 +- erpnext/projects/doctype/project/project.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index c5265e23c0..31460f66ea 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -87,7 +87,7 @@ frappe.ui.form.on("Project", { frm.add_custom_button(__("Kanban Board"), () => { frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { - project: frm.doc.project_name + project: frm.doc.name }).then(() => { frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); }); diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 55c5149a9c..c8fbe0bf7b 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -523,8 +523,9 @@ def update_project_sales_billing(): 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', project) + project = frappe.get_doc('Project', project) + if not frappe.db.exists('Kanban Board', project.project_name): + quick_kanban_board('Task', project.project_name, 'status', project.name) return True From 8d7d4b0ba7d92551957505923f57eef0e47a2840 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 18 May 2021 18:39:35 +0530 Subject: [PATCH 0759/1264] fix: expected amount in pos closing payments table (#25737) --- .../pos_closing_entry/pos_closing_entry.js | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index aa0c53e228..8c5a34a0d8 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -101,15 +101,24 @@ frappe.ui.form.on('POS Closing Entry', { }, before_save: function(frm) { + frm.set_value("grand_total", 0); + frm.set_value("net_total", 0); + frm.set_value("total_quantity", 0); + frm.set_value("taxes", []); + + for (let row of frm.doc.payment_reconciliation) { + row.expected_amount = 0; + } + for (let row of frm.doc.pos_transactions) { frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => { - cur_frm.doc.grand_total -= flt(doc.grand_total); - cur_frm.doc.net_total -= flt(doc.net_total); - cur_frm.doc.total_quantity -= flt(doc.total_qty); - refresh_payments(doc, cur_frm, 1); - refresh_taxes(doc, cur_frm, 1); - refresh_fields(cur_frm); - set_html_data(cur_frm); + frm.doc.grand_total += flt(doc.grand_total); + frm.doc.net_total += flt(doc.net_total); + frm.doc.total_quantity += flt(doc.total_qty); + refresh_payments(doc, frm); + refresh_taxes(doc, frm); + refresh_fields(frm); + set_html_data(frm); }); } } @@ -118,7 +127,7 @@ frappe.ui.form.on('POS Closing Entry', { 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)) + frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount)); } }) @@ -142,28 +151,28 @@ function add_to_pos_transaction(d, frm) { }) } -function refresh_payments(d, frm, remove) { +function refresh_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) { - if (!remove) payment.expected_amount += flt(p.amount); - else payment.expected_amount -= flt(p.amount); + payment.expected_amount += flt(p.amount); + payment.difference = payment.closing_amount - payment.expected_amount; } else { frm.add_child("payment_reconciliation", { mode_of_payment: p.mode_of_payment, opening_amount: 0, - expected_amount: p.amount + expected_amount: p.amount, + closing_amount: 0 }) } }) } -function refresh_taxes(d, frm, remove) { +function refresh_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) { - if (!remove) tax.amount += flt(t.tax_amount); - else tax.amount -= flt(t.tax_amount); + tax.amount += flt(t.tax_amount); } else { frm.add_child("taxes", { account_head: t.account_head, From 90f7ec840cf97ffdc8c523a33f3c3ba667eb7b0e Mon Sep 17 00:00:00 2001 From: anushka19 <37659765+anushka19@users.noreply.github.com> Date: Tue, 18 May 2021 22:21:42 +0530 Subject: [PATCH 0760/1264] fix: Accumulated depreciation (#25748) * fix: Accumulated depreciation * fix: Sider issues --- erpnext/assets/doctype/asset_category/asset_category.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_category/asset_category.js b/erpnext/assets/doctype/asset_category/asset_category.js index 74963c2aa9..51ce157a81 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.js +++ b/erpnext/assets/doctype/asset_category/asset_category.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Asset Category', { onload: function(frm) { frm.add_fetch('company_name', 'accumulated_depreciation_account', 'accumulated_depreciation_account'); - frm.add_fetch('company_name', 'depreciation_expense_account', 'accumulated_depreciation_account'); + frm.add_fetch('company_name', 'depreciation_expense_account', 'depreciation_expense_account'); frm.set_query('fixed_asset_account', 'accounts', function(doc, cdt, cdn) { var d = locals[cdt][cdn]; From 9bd779401d23902d161c70502ce091548989a37e Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 18 May 2021 22:41:28 +0530 Subject: [PATCH 0761/1264] added multi-currency fields --- .../doctype/activity_type/activity_type.js | 4 + .../projects/doctype/timesheet/timesheet.js | 81 +++++++++++++++---- .../projects/doctype/timesheet/timesheet.py | 17 +++- .../timesheet_detail/timesheet_detail.json | 36 ++++++++- 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/erpnext/projects/doctype/activity_type/activity_type.js b/erpnext/projects/doctype/activity_type/activity_type.js index 7eb3571af1..f1ba882812 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.js +++ b/erpnext/projects/doctype/activity_type/activity_type.js @@ -1,4 +1,8 @@ frappe.ui.form.on("Activity Type", { + onload: function(frm) { + frm.set_currency_labels(["billing_rate", "costing_rate"], frappe.defaults.get_global_default('currency')); + }, + refresh: function(frm) { frm.add_custom_button(__("Activity Cost per Employee"), function() { frappe.route_options = {"activity_type": frm.doc.name}; diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 28535d7a34..532d64994f 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -93,6 +93,7 @@ frappe.ui.form.on("Timesheet", { frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false); } frm.trigger('setup_filters'); + frm.trigger('set_dynamic_field_label'); }, customer: function(frm) { @@ -113,6 +114,48 @@ frappe.ui.form.on("Timesheet", { frm.refresh(); }, + currency: function(frm) { + let base_currency = frappe.defaults.get_global_default('currency'); + if (base_currency != frm.doc.company) { + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency: frm.doc.currency, + to_currency: base_currency + }, + callback: function(r) { + if (r.message) { + frm.set_value('exchange_rate', flt(r.message)); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + " = [?] " + base_currency); + } + } + }); + } + frm.trigger('set_dynamic_field_label'); + }, + + exchange_rate: function(frm) { + $.each(frm.doc.time_logs, function(i, d) { + calculate_billing_costing_amount(frm, d.doctype, d.name); + }); + calculate_time_and_amount(frm); + }, + + set_dynamic_field_label: function(frm) { + let base_currency = frappe.defaults.get_global_default('currency'); + frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); + frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); + frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); + frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); + + let time_logs_grid = frm.fields_dict.time_logs.grid; + $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { + if(frappe.meta.get_docfield(time_logs_grid.doctype, d)) + time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); + }); + frm.refresh_fields(); + }, + make_invoice: function(frm) { let fields = [{ "fieldtype": "Link", @@ -204,35 +247,34 @@ frappe.ui.form.on("Timesheet Detail", { if(frm.doc.parent_project) { frappe.model.set_value(cdt, cdn, 'project', frm.doc.parent_project); } - - var $trigger_again = $('.form-grid').find('.grid-row').find('.btn-open-row'); - $trigger_again.on('click', () => { - $('.form-grid') - .find('[data-fieldname="timer"]') - .append(frappe.render_template("timesheet")); - frm.trigger("control_timer"); - }); }, + hours: function(frm, cdt, cdn) { calculate_end_time(frm, cdt, cdn); + calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, billing_hours: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, billing_rate: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, costing_rate: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, is_billable: function(frm, cdt, cdn) { update_billing_hours(frm, cdt, cdn); update_time_rates(frm, cdt, cdn); calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, activity_type: function(frm, cdt, cdn) { @@ -240,7 +282,8 @@ frappe.ui.form.on("Timesheet Detail", { method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost", args: { employee: frm.doc.employee, - activity_type: frm.selected_doc.activity_type + activity_type: frm.selected_doc.activity_type, + currency: frm.doc.currency }, callback: function(r){ if(r.message){ @@ -290,17 +333,21 @@ var update_time_rates = function(frm, cdt, cdn) { }; var calculate_billing_costing_amount = function(frm, cdt, cdn) { - let child = frappe.get_doc(cdt, cdn); + let row = frappe.get_doc(cdt, cdn); let billing_amount = 0.0; - let costing_amount = 0.0; - - if (child.billing_hours && child.is_billable) { - billing_amount = (child.billing_hours * child.billing_rate); + let base_billing_amount = 0.0; + let exchange_rate = flt(frm.doc.exchange_rate); + frappe.model.set_value(cdt, cdn, 'base_billing_rate', flt(row.billing_rate) * exchange_rate); + frappe.model.set_value(cdt, cdn, 'base_costing_rate', flt(row.costing_rate) * exchange_rate); + if (row.billing_hours && row.is_billable) { + base_billing_amount = flt(row.billing_hours) * flt(row.base_billing_rate); + billing_amount = flt(row.billing_hours) * flt(row.billing_rate); } - costing_amount = flt(child.costing_rate * child.hours); + + frappe.model.set_value(cdt, cdn, 'base_billing_amount', base_billing_amount); + frappe.model.set_value(cdt, cdn, 'base_costing_amount', flt(row.base_costing_rate) * flt(row.hours)); frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount); - frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount); - calculate_time_and_amount(frm); + frappe.model.set_value(cdt, cdn, 'costing_amount', flt(row.costing_rate) * flt(row.hours)); }; var calculate_time_and_amount = function(frm) { diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d864c75279..1ee59aef8b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -14,6 +14,7 @@ from frappe.model.document import Document from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours, WorkstationHolidayError) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations +from erpnext.setup.utils import get_exchange_rate class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass @@ -37,9 +38,9 @@ class Timesheet(Document): self.total_hours = 0.0 self.total_billable_hours = 0.0 self.total_billed_hours = 0.0 - self.total_billable_amount = 0.0 - self.total_costing_amount = 0.0 - self.total_billed_amount = 0.0 + self.total_billable_amount = self.base_total_billable_amount = 0.0 + self.total_costing_amount = self.base_total_costing_amount = 0.0 + self.total_billed_amount = self.base_total_billed_amount = 0.0 for d in self.get("time_logs"): self.update_billing_hours(d) @@ -47,10 +48,13 @@ class Timesheet(Document): self.total_hours += flt(d.hours) self.total_costing_amount += flt(d.costing_amount) + self.base_total_costing_amount += flt(d.base_costing_amount) if d.is_billable: self.total_billable_hours += flt(d.billing_hours) self.total_billable_amount += flt(d.billing_amount) + self.base_total_billable_amount += flt(d.base_billing_amount) self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0 + self.base_total_billed_amount += flt(d.base_billing_amount) if d.sales_invoice else 0.0 self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0 def calculate_percentage_billed(self): @@ -330,12 +334,17 @@ def set_missing_values(time_sheet, target): }) @frappe.whitelist() -def get_activity_cost(employee=None, activity_type=None): +def get_activity_cost(employee=None, activity_type=None, currency=None): + base_currency = frappe.defaults.get_global_default('currency') rate = frappe.db.get_values("Activity Cost", {"employee": employee, "activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) if not rate: rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) + if rate and currency and currency!=base_currency: + exchnage_rate = get_exchange_rate(base_currency, currency) + rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchnage_rate + rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchnage_rate return rate[0] if rate else {} diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json index 0c9ed0bf20..ee04c612c9 100644 --- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json +++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json @@ -31,9 +31,13 @@ "column_break_8", "billing_hours", "section_break_11", + "base_billing_rate", + "base_billing_amount", + "base_costing_rate", + "base_costing_amount", + "column_break_14", "billing_rate", "billing_amount", - "column_break_14", "costing_rate", "costing_amount" ], @@ -230,12 +234,40 @@ "fieldname": "description", "fieldtype": "Small Text", "label": "Description" + }, + { + "fieldname": "base_billing_rate", + "fieldtype": "Currency", + "label": "Billing Rate", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_billing_amount", + "fieldtype": "Currency", + "label": "Billing Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_costing_rate", + "fieldtype": "Currency", + "label": "Costing Rate", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_costing_amount", + "fieldtype": "Currency", + "label": "Costing Amount", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-15 16:16:10.688694", + "modified": "2021-05-18 12:19:33.205940", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet Detail", From 5456873641be6ef354eefe01c7e37dd36962a336 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 16 May 2021 16:48:44 +0530 Subject: [PATCH 0762/1264] fix: run scheduler for reposting if there is no scheduler is running for the reposting --- .../repost_item_valuation/repost_item_valuation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 27b8729ea0..5b626ea345 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -4,8 +4,9 @@ from __future__ import unicode_literals import frappe, erpnext +from rq.timeouts import JobTimeoutException from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, add_to_date, now, today +from frappe.utils import cint, get_link_to_form, add_to_date, now, today, time_diff_in_hours from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -57,7 +58,8 @@ def repost(doc): repost_gl_entries(doc) doc.set_status('Completed') - except Exception: + + except (Exception, JobTimeoutException): frappe.db.rollback() traceback = frappe.get_traceback() frappe.log_error(traceback) @@ -113,6 +115,12 @@ def notify_error_to_stock_managers(doc, traceback): frappe.sendmail(recipients=recipients, subject=subject, message=message) def repost_entries(): + job_log = frappe.get_all('Scheduled Job Log', fields = ['status', 'creation'], + filters = {'scheduled_job_type': 'repost_item_valuation.repost_entries'}, order_by='creation desc', limit=1) + + if job_log and job_log[0]['status'] == 'Start' and time_diff_in_hours(now(), job_log[0]['creation']) < 2: + return + riv_entries = get_repost_item_valuation_entries() for row in riv_entries: From f79ef5d8cf91d9c4a3d7d390e024c47cb1ec963c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 19 May 2021 13:04:44 +0530 Subject: [PATCH 0763/1264] fix: missing cost center message on creating gl entries --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 78febf9c2e..948c51364e 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -75,8 +75,13 @@ class GLEntry(Document): def pl_must_have_cost_center(self): if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": 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)) + msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( + self.voucher_type, self.voucher_no, self.account) + msg += " " + msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( + self.voucher_type) + + frappe.throw(msg, title=_("Missing Cost Center")) def validate_dimensions_for_pl_and_bs(self): account_type = frappe.db.get_value("Account", self.account, "report_type") From 3768216dca811aea0bddcffcf08ef8199c42747d Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:50:35 +0530 Subject: [PATCH 0764/1264] refactor: updated permissions and mandatory fields --- .../supplier_item_group.json | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json index 1417ec23cf..1971458f61 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json @@ -14,19 +14,21 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Supplier", - "options": "Supplier" + "options": "Supplier", + "reqd": 1 }, { "fieldname": "item_group", "fieldtype": "Link", "in_list_view": 1, "label": "Item Group", - "options": "Item Group" + "options": "Item Group", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-07 18:16:40.621421", + "modified": "2021-05-19 13:48:16.742303", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Item Group", @@ -43,6 +45,30 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, + "write": 1 } ], "sort_field": "modified", From 8e34c49ac9130a7b523c83cb8cc0c005421e545c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:51:36 +0530 Subject: [PATCH 0765/1264] refactor: using get_all instead of get_list --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index e71b056c6e..f5668c51fe 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -217,7 +217,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) if filters.get('supplier'): - item_group_list = frappe.get_list('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + item_group_list = frappe.get_all('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) item_groups = [] for i in item_group_list: From bf7f0530e6cc14cb7722c767259c0c814fb1a8b8 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:53:22 +0530 Subject: [PATCH 0766/1264] fix: added error handling if entry already exists --- .../supplier_item_group/supplier_item_group.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py index 6fbeb37242..3a2e5d6dce 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py @@ -3,8 +3,16 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ from frappe.model.document import Document class SupplierItemGroup(Document): - pass + def validate(self): + exists = frappe.db.exists({ + 'doctype': 'Supplier Item Group', + 'supplier': self.supplier, + 'item_group': self.item_group + }) + if exists: + frappe.throw(_("Item Group has already been linked to this supplier.")) \ No newline at end of file From 4e73c8a79f1838de0f0d828da71aaad5110b4e3c Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 19 May 2021 14:58:12 +0530 Subject: [PATCH 0767/1264] restructuring timesheet fields --- .../projects/doctype/timesheet/timesheet.js | 20 +++++++---- .../projects/doctype/timesheet/timesheet.json | 36 +++++++++++++++++-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 532d64994f..4512244027 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -145,14 +145,20 @@ frappe.ui.form.on("Timesheet", { let base_currency = frappe.defaults.get_global_default('currency'); frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); - frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); - frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); - let time_logs_grid = frm.fields_dict.time_logs.grid; - $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { - if(frappe.meta.get_docfield(time_logs_grid.doctype, d)) - time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); - }); + frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], + frm.doc.currency != base_currency) + + if (frm.doc?.time_logs.length > 0) { + frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); + frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); + + let time_logs_grid = frm.fields_dict.time_logs.grid; + $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { + if (frappe.meta.get_docfield(time_logs_grid.doctype, d)) + time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); + }); + } frm.refresh_fields(); }, diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index 23e6ede967..75f7478ed1 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -13,6 +13,7 @@ "company", "customer", "currency", + "exchange_rate", "sales_invoice", "column_break_3", "salary_slip", @@ -32,11 +33,14 @@ "total_hours", "billing_details", "total_billable_hours", - "total_billed_hours", - "total_costing_amount", + "base_total_billable_amount", + "base_total_billed_amount", + "base_total_costing_amount", "column_break_10", + "total_billed_hours", "total_billable_amount", "total_billed_amount", + "total_costing_amount", "per_billed", "section_break_18", "note", @@ -283,13 +287,39 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "base_total_costing_amount", + "fieldtype": "Currency", + "label": "Total Costing Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_total_billable_amount", + "fieldtype": "Currency", + "label": "Total Billable Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_total_billed_amount", + "fieldtype": "Currency", + "label": "Total Billed Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "label": "Exchange Rate" } ], "icon": "fa fa-clock-o", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-13 17:13:29.954960", + "modified": "2021-05-18 16:10:08.249619", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet", From 516c789127d8514368fc35392368b197534d5e0b Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:29:30 +0530 Subject: [PATCH 0768/1264] refactor: variable names and suggested changes --- .../production_plan/production_plan.js | 6 +-- .../production_plan/production_plan.py | 51 ++++++++++--------- .../production_plan/test_production_plan.py | 16 +++--- .../doctype/work_order/work_order.py | 2 +- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 29c3d5b18e..64d584118f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -212,7 +212,7 @@ frappe.ui.form.on('Production Plan', { }, get_items: function (frm) { - frm.clear_table('prod_plan_ref'); + frm.clear_table('prod_plan_references'); frappe.call({ method: "get_items", @@ -224,15 +224,13 @@ frappe.ui.form.on('Production Plan', { }); }, combine_items: function (frm) { - frm.clear_table('prod_plan_ref'); + frm.clear_table('prod_plan_references'); frappe.call({ method: "get_items", freeze: true, doc: frm.doc, }); - - }, get_items_for_mr: function(frm) { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 8d578fd935..46e047654b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -171,20 +171,28 @@ class ProductionPlan(Document): for data in items: item_details = get_item_details(data.item_code) if self.combine_items: - if item_details.bom_no in refs.keys(): + if item_details.bom_no in refs: + refs[item_details.bom_no]['so_details'].append({ + 'sales_order': data.parent, + 'sales_order_item': data.name, + 'qty': data.pending_qty + }) refs[item_details.bom_no]['qty'] += data.pending_qty - refs[item_details.bom_no]['so'].append(data.parent) - refs[item_details.bom_no]['so_items'].append(data.name) - refs[item_details.bom_no]['planned_qty'].append(data.pending_qty) continue + else: - refs[item_details.bom_no] = {'qty': data.pending_qty, 'ref': data.name} - refs[item_details.bom_no]['so'] = [data.parent] - refs[item_details.bom_no]['so_items'] = [data.name] - refs[item_details.bom_no]['planned_qty'] = [data.pending_qty] - + refs[item_details.bom_no] = { + 'qty': data.pending_qty, + 'po_item_ref': data.name, + 'so_details': [] + } + refs[item_details.bom_no]['so_details'].append({ + 'sales_order': data.parent, + 'sales_order_item': data.name, + 'qty': data.pending_qty + }) + pi = self.append('po_items', { - 'name': data.name, 'include_exploded_items': 1, 'warehouse': data.warehouse, 'item_code': data.item_code, @@ -201,8 +209,6 @@ class ProductionPlan(Document): pi.sales_order = data.parent pi.sales_order_item = data.name pi.description = data.description - pi.item_reference = data.name - elif self.get_items_from == "Material Request": pi.material_request = data.parent @@ -210,24 +216,21 @@ class ProductionPlan(Document): pi.description = data.description if refs: - for d in self.po_items: - d.planned_qty = refs[d.bom_no]['qty'] - d.pending_qty = refs[d.bom_no]['qty'] - d.sales_order = '' + for po_item in self.po_items: + po_item.planned_qty = refs[po_item.bom_no]['qty'] + po_item.pending_qty = refs[po_item.bom_no]['qty'] + po_item.sales_order = '' self.add_pp_ref(refs) def add_pp_ref(self, refs): for bom_no in refs: - idx = 0 - for so in refs[bom_no]['so']: + for so_detail in refs[bom_no]['so_details']: self.append('prod_plan_references', { - 'item_reference': refs[bom_no]['ref'], - 'sales_order': so, - 'sales_order_item':refs[bom_no]['so_items'][idx], - 'qty':refs[bom_no]['planned_qty'][idx] + 'item_reference': refs[bom_no]['po_item_ref'], + 'sales_order': so_detail['sales_order'], + 'sales_order_item': so_detail['sales_order_item'], + 'qty': so_detail['qty'] }) - idx+=1 - def calculate_total_planned_qty(self): self.total_planned_qty = 0 diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index ec5c5e0e13..768f99eb43 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -166,16 +166,16 @@ class TestProductionPlan(unittest.TestCase): 'grand_total': so.grand_total }) pln.combine_items = 1 - pln.get_so_items() - for plan_reference in pln.prod_plan_references: - plan_reference.item_reference = pln.po_items[0].name + pln.get_items() pln.submit() - self.assertTrue(pln.po_items[0].planned_qty,3) + self.assertTrue(pln.po_items[0].planned_qty, 3) pln.make_work_order() - work_order = frappe.db.get_value('Work Order', {'production_plan_item': pln.po_items[0].name, - 'production_plan': pln.name,}, 'name') + work_order = frappe.db.get_value('Work Order', { + 'production_plan_item': pln.po_items[0].name, + 'production_plan': pln.name + }, 'name') wo_doc = frappe.get_doc('Work Order', work_order) wo_doc.update({ @@ -194,8 +194,8 @@ class TestProductionPlan(unittest.TestCase): so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty') self.assertEqual(so_wo_qty, 0.0) - lat_plan = frappe.get_doc('Production Plan',pln.name) - lat_plan.cancel() + latest_plan = frappe.get_doc('Production Plan', pln.name) + latest_plan.cancel() def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index bb6450b775..a154464a8b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -377,7 +377,7 @@ class WorkOrder(Document): total_bundle_qty = 1 prod_plan = frappe.get_doc('Production Plan', self.production_plan) - item_reference = frappe.get_value('Production Plan Item', self.production_plan_item,'item_reference') + item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item') for plan_reference in prod_plan.prod_plan_references: work_order_qty = 0.0 From 4bd641367b24a49fd8c4e2e896a960455c560f00 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 19 May 2021 16:38:53 +0530 Subject: [PATCH 0769/1264] fix: address template with upper filter throws jinja error (#25756) --- erpnext/regional/address_template/templates/united_states.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/address_template/templates/united_states.html b/erpnext/regional/address_template/templates/united_states.html index 089315e4e4..77fce46b9d 100644 --- a/erpnext/regional/address_template/templates/united_states.html +++ b/erpnext/regional/address_template/templates/united_states.html @@ -1,4 +1,4 @@ {{ address_line1 }}
    {% if address_line2 %}{{ address_line2 }}
    {% endif -%} {{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}
    {% endif -%} -{% if country != "United States" %}{{ country|upper }}{% endif -%} +{% if country != "United States" %}{{ country }}{% endif -%} From 7fb385a89f12f4ed22d01282020793686c415ce2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 19 May 2021 18:47:06 +0530 Subject: [PATCH 0770/1264] fix: cannot bypass e-invoicing for non gst item invoices --- erpnext/regional/india/e_invoice/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index b4e7a8889e..0315c83fd1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -43,8 +43,9 @@ def validate_eligibility(doc): invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') + has_non_gst_item = any(d for d in doc.get('items') if frappe.db.get_value('Item', d.get('item_code'), 'is_non_gst')) - if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied: + if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: return False return True From b9ad385232d5e6a66fa418bdb879b3eb85c263b3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 19 May 2021 19:13:06 +0530 Subject: [PATCH 0771/1264] fix: remove uncessary query --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 0315c83fd1..70e6d07eab 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -43,7 +43,7 @@ def validate_eligibility(doc): invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - has_non_gst_item = any(d for d in doc.get('items') if frappe.db.get_value('Item', d.get('item_code'), 'is_non_gst')) + has_non_gst_item = any(d for d in doc.get('items') if d.get('is_non_gst')) if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: return False From 08598238d73f4f033048baeedf9c96b4e7fc5102 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 20 May 2021 13:23:10 +0530 Subject: [PATCH 0772/1264] refactor: suggested changes --- .../maintenance_schedule.js | 26 +++--- .../maintenance_schedule.py | 79 +++++++++---------- .../maintenance_visit/maintenance_visit.js | 9 +-- 3 files changed, 52 insertions(+), 62 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 79167ae45f..075bd40660 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -64,22 +64,17 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }); }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { - var s = me.frm.doc.schedules; - let flag = 0; - for (let i in s) { - if (s[i].completion_status == "Pending") { - flag = 1; - } - } + let schedules = me.frm.doc.schedules; + let flag = schedules.some(schedule => schedule.completion_status === "Pending"); if (flag) { this.frm.add_custom_button(__('Create Maintenance Visit'), function () { let options = ""; - me.frm.call('get_pending_data',{data_type:"items"}).then(r =>{ + me.frm.call('get_pending_data', {data_type: "items"}).then(r =>{ options = r.message - var schedule_id = ""; - var d = new frappe.ui.Dialog({ + let schedule_id = ""; + let d = new frappe.ui.Dialog({ title: __("Enter Visit Details"), fields: [{ fieldtype: "Select", @@ -103,7 +98,13 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ reqd: 1, onchange: function () { let field = d.get_field('item_name'); - me.frm.call('get_pending_data',{item_name:field.value,s_date:this.value,data_type:"id"}).then(r =>{ + me.frm.call( + 'get_pending_data', + { + item_name: field.value, + s_date: this.value, + data_type: "id" + }).then(r =>{ schedule_id = r.message; }) } @@ -117,7 +118,6 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ item_name: values.item_name, s_id: schedule_id, source_name: me.frm.doc.name, - }, callback: function (r) { if (!r.exc) { @@ -125,8 +125,6 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ frappe.set_route("Form", r.message.doctype, r.message.name); } } - - }); d.hide(); } diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index d11bf7e735..0bc2e87a6c 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -45,28 +45,27 @@ class MaintenanceSchedule(TransactionBase): "Half Yearly": 182, "Yearly": 365 } - for i in self.items: - - if i.periodicity and i.start_date: - if not i.end_date: - if i.no_of_visits: - i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + for item in self.items: + if item.periodicity and item.start_date: + if not item.end_date: + if item.no_of_visits: + item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) else: - i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) + item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) - diff = date_diff(i.end_date, i.start_date) + 1 - no_of_visits = cint(diff / days_in_period[i.periodicity]) + diff = date_diff(item.end_date, item.start_date) + 1 + no_of_visits = cint(diff / days_in_period[item.periodicity]) - if not i.no_of_visits or i.no_of_visits == 0: - i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) - diff = date_diff(i.end_date, i.start_date ) + 1 - i.no_of_visits = cint(diff / days_in_period[i.periodicity]) + if not item.no_of_visits or item.no_of_visits == 0: + item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) + diff = date_diff(item.end_date, item.start_date ) + 1 + item.no_of_visits = cint(diff / days_in_period[item.periodicity]) - elif i.no_of_visits > no_of_visits: - i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + elif item.no_of_visits > no_of_visits: + item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) - elif i.no_of_visits < no_of_visits: - i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + elif item.no_of_visits < no_of_visits: + item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) def on_submit(self): @@ -92,9 +91,10 @@ class MaintenanceSchedule(TransactionBase): if no_email_sp: frappe.msgprint( - frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( - self.owner, "
    " + "
    ".join(no_email_sp) - )) + _("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( + self.owner, "
    " + "
    ".join(no_email_sp) + ) + ) scheduled_date = frappe.db.sql("""select scheduled_date from `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and @@ -103,12 +103,12 @@ class MaintenanceSchedule(TransactionBase): for key in scheduled_date: description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer) event = frappe.get_doc({ - "doctype": "Event", - "owner": email_map.get(d.sales_person, self.owner), - "subject": description, - "description": description, - "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", - "event_type": "Private", + "doctype": "Event", + "owner": email_map.get(d.sales_person, self.owner), + "subject": description, + "description": description, + "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", + "event_type": "Private", }) event.add_participant(self.doctype, self.name) event.insert(ignore_permissions=1) @@ -126,7 +126,7 @@ class MaintenanceSchedule(TransactionBase): start_date_copy = add_days(start_date_copy, add_by) if len(schedule_list) < no_of_visit: schedule_date = self.validate_schedule_date_for_holiday_list(getdate(start_date_copy), - sales_person) + sales_person) if schedule_date > getdate(end_date): schedule_date = getdate(end_date) schedule_list.append(schedule_date) @@ -280,30 +280,27 @@ class MaintenanceSchedule(TransactionBase): def on_trash(self): delete_events(self.doctype, self.name) - @frappe.whitelist() def get_pending_data(self,data_type,s_date = None, item_name = None): if data_type == "date": dates = "" - for i in self.schedules: - if i.item_name == item_name and i.completion_status == "Pending": - dates = dates + "\n" + formatdate(i.scheduled_date, "dd-MM-yyyy") + for schedule in self.schedules: + if schedule.item_name == item_name and schedule.completion_status == "Pending": + dates = dates + "\n" + formatdate(schedule.scheduled_date, "dd-MM-yyyy") return dates elif data_type == "items": items = "" - for i in self.items: - for s in self.schedules: - if i.item_name == s.item_name and s.completion_status == "Pending": - items = items + "\n" + i.item_name + for item in self.items: + for schedule in self.schedules: + if item.item_name == schedule.item_name and schedule.completion_status == "Pending": + items = items + "\n" + item.item_name break return items elif data_type == "id": - for s in self.schedules: - if s.item_name == item_name and s_date == formatdate(s.scheduled_date,"dd-mm-yyyy"): - return s.name + for schedule in self.schedules: + if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date,"dd-mm-yyyy"): + return schedule.name - - @frappe.whitelist() def update_serial_nos(s_id): serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') @@ -314,7 +311,7 @@ def update_serial_nos(s_id): return False @frappe.whitelist() -def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None): +def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): from frappe.model.mapper import get_mapped_doc def update_status(source, target, parent): diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 403d1ab4cc..d6105c657e 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -15,16 +15,13 @@ frappe.ui.form.on('Maintenance Visit', { 'name': ["in", serial_nos] } }; - } else { - return { filters: { 'item_code': item.item_code } }; } - }); }, setup: function (frm) { @@ -35,18 +32,16 @@ frappe.ui.form.on('Maintenance Visit', { onload: function (frm, cdt, cdn) { let item = locals[cdt][cdn]; if (frm.maintenance_type == 'Scheduled') { - - let s_id = item.purposes[0].prevdoc_detail_docname; + let schedule_id = item.purposes[0].prevdoc_detail_docname; frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", args: { - s_id: s_id + s_id: schedule_id }, callback: function (r) { serial_nos = r.message; } }); - } if (!frm.doc.status) { From aeb88385bbae9470956aefec57d69eb3a6fbc0fa Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:11:36 +0530 Subject: [PATCH 0773/1264] patch: timesheet changes --- erpnext/patches.txt | 2 +- ...me_billable_to_is_billable_in_timesheet.py | 7 ------ .../patches/v13_0/update_timesheet_changes.py | 24 +++++++++++++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) delete mode 100644 erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py create mode 100644 erpnext/patches/v13_0/update_timesheet_changes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0caad586e5..d4655e19b9 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -779,4 +779,4 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed -erpnext.patches.v13_0.rename_billable_to_is_billable_in_timesheet +erpnext.patches.v13_0.update_timesheet_changes diff --git a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py deleted file mode 100644 index 6860a37559..0000000000 --- a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - if "billable" in frappe.db.get_table_columns("Timesheet Detail"): - rename_field("Timesheet Detail", "billable", "is_billable") \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py new file mode 100644 index 0000000000..87178b2f84 --- /dev/null +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if "billable" in frappe.db.get_table_columns("Timesheet Detail"): + rename_field("Timesheet Detail", "billable", "is_billable") + + base_currency = frappe.defaults.get_global_default('currency') + frappe.reload_doc("projects", "doctype", "timesheet") + frappe.reload_doc("projects", "doctype", "timesheet_detail") + + frappe.db.sql("""UPDATE `tabTimesheet Detail` + SET base_billing_rate = billing_rate, + base_billing_amount = billing_amount, + base_costing_rate = costing_rate, + base_costing_amount = costing_amount""") + + frappe.db.sql("""UPDATE `tabTimesheet` + SET currency = '{0}', + exchange_rate = 1.0, + base_total_billable_amount = total_billable_amount, + base_total_billed_amount = total_billed_amount, + base_total_costing_amount = total_costing_amount""".format(base_currency)) \ No newline at end of file From aa516e5d178769a07fe1c400f60e96ca54ac36a7 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:12:18 +0530 Subject: [PATCH 0774/1264] fix: review changes --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 4512244027..9bb9c38532 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -116,7 +116,7 @@ frappe.ui.form.on("Timesheet", { currency: function(frm) { let base_currency = frappe.defaults.get_global_default('currency'); - if (base_currency != frm.doc.company) { + if (base_currency != frm.doc.currency) { frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { From 1270febfffc16fc6c548487727629b56f57fa354 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:15:58 +0530 Subject: [PATCH 0775/1264] fix: sider issues --- erpnext/patches/v13_0/update_timesheet_changes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index 87178b2f84..3acce18c63 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -11,13 +11,13 @@ def execute(): frappe.reload_doc("projects", "doctype", "timesheet_detail") frappe.db.sql("""UPDATE `tabTimesheet Detail` - SET base_billing_rate = billing_rate, + SET base_billing_rate = billing_rate, base_billing_amount = billing_amount, base_costing_rate = costing_rate, base_costing_amount = costing_amount""") frappe.db.sql("""UPDATE `tabTimesheet` - SET currency = '{0}', + SET currency = '{0}', exchange_rate = 1.0, base_total_billable_amount = total_billable_amount, base_total_billed_amount = total_billed_amount, From 0169cd845ac9d6d00eba92b61195650a88fa05d0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 20 May 2021 14:20:50 +0530 Subject: [PATCH 0776/1264] fix: sider --- .../maintenance_schedule.js | 122 +++++++++--------- .../maintenance_schedule.py | 4 +- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 075bd40660..1e5773c8bc 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -71,65 +71,69 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let options = ""; me.frm.call('get_pending_data', {data_type: "items"}).then(r =>{ - options = r.message + options = r.message; - let schedule_id = ""; - let d = new frappe.ui.Dialog({ - title: __("Enter Visit Details"), - fields: [{ - fieldtype: "Select", - fieldname: "item_name", - label: __("Item Name"), - options: options, - reqd: 1, - onchange: function () { - let field = d.get_field("scheduled_date"); - me.frm.call('get_pending_data',{item_name:this.value,data_type:"date"}).then(r =>{ - field.df.options = r.message; - field.refresh(); - }) - } - }, - { - label: __('Scheduled Date'), - fieldname: 'scheduled_date', - fieldtype: 'Select', - options: "", - reqd: 1, - onchange: function () { - let field = d.get_field('item_name'); - me.frm.call( - 'get_pending_data', - { - item_name: field.value, - s_date: this.value, - data_type: "id" - }).then(r =>{ - schedule_id = r.message; - }) - } - }, - ], - primary_action_label: 'Create Visit', - primary_action(values) { - frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", - args: { - item_name: values.item_name, - s_id: schedule_id, - source_name: me.frm.doc.name, - }, - callback: function (r) { - if (!r.exc) { - frappe.model.sync(r.message); - frappe.set_route("Form", r.message.doctype, r.message.name); - } + let schedule_id = ""; + let d = new frappe.ui.Dialog({ + title: __("Enter Visit Details"), + fields: [{ + fieldtype: "Select", + fieldname: "item_name", + label: __("Item Name"), + options: options, + reqd: 1, + onchange: function () { + let field = d.get_field("scheduled_date"); + me.frm.call('get_pending_data', + { + item_name: this.value, + data_type: "date" + }).then(r => { + field.df.options = r.message; + field.refresh(); + }); } - }); - d.hide(); - } - }); - d.show(); + }, + { + label: __('Scheduled Date'), + fieldname: 'scheduled_date', + fieldtype: 'Select', + options: "", + reqd: 1, + onchange: function () { + let field = d.get_field('item_name'); + me.frm.call( + 'get_pending_data', + { + item_name: field.value, + s_date: this.value, + data_type: "id" + }).then(r =>{ + schedule_id = r.message; + }); + } + }, + ], + primary_action_label: 'Create Visit', + primary_action(values) { + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", + args: { + item_name: values.item_name, + s_id: schedule_id, + source_name: me.frm.doc.name, + }, + callback: function (r) { + if (!r.exc) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + }); + d.hide(); + } + }); + d.show(); }); }, __('Create')); } @@ -154,9 +158,9 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ set_no_of_visits: function (doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); - + let me = this; if (item.start_date && item.periodicity) { - me.frm.call('validate_end_date_visits') + me.frm.call('validate_end_date_visits'); } }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 0bc2e87a6c..5d573c5524 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -58,7 +58,7 @@ class MaintenanceSchedule(TransactionBase): if not item.no_of_visits or item.no_of_visits == 0: item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) - diff = date_diff(item.end_date, item.start_date ) + 1 + diff = date_diff(item.end_date, item.start_date) + 1 item.no_of_visits = cint(diff / days_in_period[item.periodicity]) elif item.no_of_visits > no_of_visits: @@ -93,8 +93,8 @@ class MaintenanceSchedule(TransactionBase): frappe.msgprint( _("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( self.owner, "
    " + "
    ".join(no_email_sp) - ) ) + ) scheduled_date = frappe.db.sql("""select scheduled_date from `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and From 4427390ab386c54b7de9e78d551bfa8ed1d6c502 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 May 2021 17:19:24 +0530 Subject: [PATCH 0777/1264] fix: Do not throw error in migrate --- erpnext/regional/india/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index ca679e43d2..fc227defbf 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -695,7 +695,7 @@ def get_gst_accounts(company=None, account_wise=False, only_reverse_charge=0, on filters=filters, fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) - if not gst_settings_accounts and not frappe.flags.in_test: + if not gst_settings_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: frappe.throw(_("Please set GST Accounts in GST Settings")) for d in gst_settings_accounts: From 21e662f67870ed37a887c5ffbffb18622d84bb6c Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 20 May 2021 17:20:05 +0530 Subject: [PATCH 0778/1264] fix: apply permission while selecting projects (#25765) --- erpnext/projects/doctype/task/task.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 6a9d2d1424..3cd92ee719 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -5,12 +5,6 @@ frappe.provide("erpnext.projects"); frappe.ui.form.on("Task", { setup: function (frm) { - frm.set_query("project", function () { - return { - query: "erpnext.projects.doctype.task.task.get_project" - } - }); - frm.make_methods = { 'Timesheet': () => frappe.model.open_mapped_doc({ method: 'erpnext.projects.doctype.task.task.make_timesheet', From f0e6a169101fe92994d93e1e6f91bee40f8baf9c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 20 May 2021 19:29:12 +0530 Subject: [PATCH 0779/1264] test: updated test for generated schedule dates --- .../test_maintenance_schedule.py | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 834c05476e..58ee964fb5 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -2,7 +2,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals -from frappe.utils.data import add_days, today +from frappe.utils.data import add_days, today, formatdate from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import make_maintenance_visit import frappe @@ -27,6 +27,7 @@ class TestMaintenanceSchedule(unittest.TestCase): ms = make_maintenance_schedule() ms.save() i = ms.items[0] + expected_dates = [] expected_end_date = add_days(i.start_date, i.no_of_visits * 7) self.assertEqual(i.end_date, expected_end_date) @@ -39,36 +40,39 @@ class TestMaintenanceSchedule(unittest.TestCase): items = items.split('\n') items.pop(0) expected_items = ['_Test Item'] - self.assertTrue(items,expected_items) + self.assertTrue(items, expected_items) - dates = ms.get_pending_data(data_type = "date",item_name = i.item_name) + # "dates" contains all generated schedule dates + dates = ms.get_pending_data(data_type = "date", item_name = i.item_name) dates = dates.split('\n') dates.pop(0) - expected_dates = ['07-05-2021','14-05-2021'] - self.assertEqual(dates,expected_dates) + expected_dates.append(formatdate(add_days(i.start_date, 7), "dd-MM-yyyy")) + expected_dates.append(formatdate(add_days(i.start_date, 14), "dd-MM-yyyy")) + + # test for generated schedule dates + self.assertEqual(dates, expected_dates) - ms.submit() - s_id = ms.get_pending_data(data_type = "id",item_name = i.item_name, s_date = "14-05-2021") + s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1]) test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) visit = frappe.new_doc('Maintenance Visit') visit = test visit.completion_status = "Partially Completed" - visit.set('purposes',[{ - 'item_code':i.item_code, - 'description':"test", - 'work_done':"test", - 'prevdoc_docname':ms.name, - 'prevdoc_doctype':ms.doctype, - 'prevdoc_detail_docname':s_id + visit.set('purposes', [{ + 'item_code': i.item_code, + 'description': "test", + 'work_done': "test", + 'prevdoc_docname' :ms.name, + 'prevdoc_doctype': ms.doctype, + 'prevdoc_detail_docname': s_id }]) visit.submit() - ms = frappe.get_doc('Maintenance Schedule',ms.name) - self.assertTrue(ms.schedules[1].completion_status,"Partially Completed") + ms = frappe.get_doc('Maintenance Schedule', ms.name) + + #checks if visit status is back updated in schedule + self.assertTrue(ms.schedules[1].completion_status, "Partially Completed") - - def get_events(ms): return frappe.get_all("Event Participants", filters={ "reference_doctype": ms.doctype, @@ -76,7 +80,6 @@ def get_events(ms): "parenttype": "Event" }) - def make_maintenance_schedule(): ms = frappe.new_doc("Maintenance Schedule") ms.company = "_Test Company" From 0d8b9a9d0a110d7379ebb9eba9e96d61bb1ca02b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 19:53:30 +0530 Subject: [PATCH 0780/1264] fix(patch): billable field not renamed --- erpnext/patches/v13_0/update_timesheet_changes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index 3acce18c63..93b7f8e59a 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -3,19 +3,20 @@ import frappe from frappe.model.utils.rename_field import rename_field def execute(): - if "billable" in frappe.db.get_table_columns("Timesheet Detail"): - rename_field("Timesheet Detail", "billable", "is_billable") - - base_currency = frappe.defaults.get_global_default('currency') frappe.reload_doc("projects", "doctype", "timesheet") frappe.reload_doc("projects", "doctype", "timesheet_detail") + if frappe.db.has_column("Timesheet Detail", "billable"): + rename_field("Timesheet Detail", "billable", "is_billable") + + base_currency = frappe.defaults.get_global_default('currency') + frappe.db.sql("""UPDATE `tabTimesheet Detail` SET base_billing_rate = billing_rate, base_billing_amount = billing_amount, base_costing_rate = costing_rate, base_costing_amount = costing_amount""") - + frappe.db.sql("""UPDATE `tabTimesheet` SET currency = '{0}', exchange_rate = 1.0, From be247ec3ded28cec31228a61f9ea8e8ceb29cc88 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 22:17:20 +0530 Subject: [PATCH 0781/1264] fix: error message placeholders and sider issues --- erpnext/projects/doctype/timesheet/timesheet.js | 4 ++-- erpnext/projects/doctype/timesheet/timesheet.py | 16 ++++++++-------- erpnext/public/js/utils.js | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 9bb9c38532..63078ea7bd 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -146,10 +146,10 @@ frappe.ui.form.on("Timesheet", { frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); - frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], + frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], frm.doc.currency != base_currency) - if (frm.doc?.time_logs.length > 0) { + if (frm.doc.time_logs.length > 0) { frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 1ee59aef8b..d3c21a3728 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -150,7 +150,7 @@ class Timesheet(Document): def validate_project(self, data): if self.parent_project and self.parent_project != data.project: - frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.")).format(data.idx, self.parent_project) + frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.").format(data.idx, self.parent_project)) def validate_overlap_for(self, fieldname, args, value, ignore_validation=False): if not value or ignore_validation: @@ -221,14 +221,14 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to if from_time and to_time: condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" - return frappe.db.sql("""SELECT tsd.name as name, - tsd.parent as parent, tsd.billing_hours as billing_hours, - tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, + return frappe.db.sql("""SELECT tsd.name as name, + tsd.parent as parent, tsd.billing_hours as billing_hours, + tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, tsd.description as description, ts.currency as currency - FROM `tabTimesheet Detail` tsd - INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent - WHERE tsd.parenttype = 'Timesheet' - and tsd.docstatus=1 {0} + FROM `tabTimesheet Detail` tsd + INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent + WHERE tsd.parenttype = 'Timesheet' + and tsd.docstatus=1 {0} and tsd.is_billable = 1 and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 39004503a0..ce40ced11f 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -734,7 +734,7 @@ frappe.form.link_formatters['Project'] = function(value, doc) { // if value is blank in report view or project name and name are the same, return as is return value; } -} +}; // add description on posting time $(document).on('app_ready', function() { From 8a407f1ec308ef4a4c142746cebf39e18c72ed53 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 22:23:44 +0530 Subject: [PATCH 0782/1264] fix: sider issues --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- erpnext/projects/doctype/timesheet/timesheet.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 63078ea7bd..84c7b8118b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -147,7 +147,7 @@ frappe.ui.form.on("Timesheet", { frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], - frm.doc.currency != base_currency) + frm.doc.currency != base_currency); if (frm.doc.time_logs.length > 0) { frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d3c21a3728..d42c6ab175 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -137,7 +137,7 @@ class Timesheet(Document): def validate_time_logs(self): for data in self.get('time_logs'): self.validate_overlap(data) - self.validate_task_project(data) + self.set_project(data) self.validate_project(data) def validate_overlap(self, data): @@ -145,7 +145,7 @@ class Timesheet(Document): self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap) self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap) - def validate_task_project(self, data): + def set_project(self, data): data.project = data.project or frappe.db.get_value("Task", data.task, "project") def validate_project(self, data): From a7d0dbb085f23f3cedc1b88b546bcc64cb029d56 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 23:02:11 +0530 Subject: [PATCH 0783/1264] fix: calculate total billing amount on fetching timesheets - show timesheet billing amounts in doc currency --- .../doctype/sales_invoice/sales_invoice.js | 57 +++++++++---------- .../doctype/sales_invoice/sales_invoice.json | 5 +- .../sales_invoice_timesheet.json | 3 +- .../projects/doctype/timesheet/timesheet.py | 6 +- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 747d0a931a..1808005f62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -820,7 +820,7 @@ frappe.ui.form.on('Sales Invoice', { }, add_timesheet_row: function(frm, row, exchange_rate) { - frm.add_child('timesheets',{ + frm.add_child('timesheets', { 'activity_type': row.activity_type, 'description': row.description, 'time_sheet': row.parent, @@ -828,7 +828,8 @@ frappe.ui.form.on('Sales Invoice', { 'billing_amount': flt(row.billing_amount) * flt(exchange_rate), 'timesheet_detail': row.name }); - frm.refresh_field('timesheets') + frm.refresh_field('timesheets'); + calculate_total_billing_amount(frm); }, refresh: function(frm) { @@ -871,36 +872,32 @@ frappe.ui.form.on('Sales Invoice', { project: data.project }, callback: function(r) { - if(!r.exc) { - if(r.message.length > 0) { - frm.clear_table('timesheets') - r.message.forEach((d) => { - let exchange_rate = 1.0; - if (frm.doc.currency != d.currency) { - frappe.call({ - method: "erpnext.setup.utils.get_exchange_rate", - args: { - from_currency: d.currency, - to_currency: frm.doc.currency - }, - callback: function(r) { - if (r.message) { - exchange_rate = r.message; - frm.events.add_timesheet_row(frm, d, exchange_rate); - } + if (!r.exc && r.message.length > 0) { + frm.clear_table('timesheets') + r.message.forEach((d) => { + let exchange_rate = 1.0; + if (frm.doc.currency != d.currency) { + frappe.call({ + method: 'erpnext.setup.utils.get_exchange_rate', + args: { + from_currency: d.currency, + to_currency: frm.doc.currency + }, + callback: function(r) { + if (r.message) { + exchange_rate = r.message; + frm.events.add_timesheet_row(frm, d, exchange_rate); } - }); - } - else { - frm.events.add_timesheet_row(frm, d, exchange_rate); - } - }); - } - else { - frappe.msgprint(__('No Timesheet Found.')) - } - d.hide(); + } + }); + } else { + frm.events.add_timesheet_row(frm, d, exchange_rate); + } + }); + } else { + frappe.msgprint(__('No Timesheets found with the selected filters.')) } + d.hide(); } }); }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 48d644cb43..e7dd6b8a60 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -772,6 +772,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Total Billing Amount", + "options": "currency", "print_hide": 1, "read_only": 1 }, @@ -1960,7 +1961,7 @@ "label": "Is Debit Note" }, { - "default": 0, + "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", @@ -1977,7 +1978,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-05-13 17:53:26.185370", + "modified": "2021-05-20 22:48:33.988881", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json index 9321630829..f069e8dd0b 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -34,6 +34,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Billing Amount", + "options": "currency", "read_only": 1 }, { @@ -64,7 +65,7 @@ ], "istable": 1, "links": [], - "modified": "2021-05-13 16:52:32.995266", + "modified": "2021-05-20 22:33:57.234846", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Timesheet", diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d42c6ab175..a3e4577f90 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -342,9 +342,9 @@ def get_activity_cost(employee=None, activity_type=None, currency=None): rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) if rate and currency and currency!=base_currency: - exchnage_rate = get_exchange_rate(base_currency, currency) - rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchnage_rate - rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchnage_rate + exchange_rate = get_exchange_rate(base_currency, currency) + rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchange_rate + rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchange_rate return rate[0] if rate else {} From 605ea044f37510cf8f12daf2a5f82a5ea44d3002 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 21 May 2021 11:24:33 +0530 Subject: [PATCH 0784/1264] fix: update process SOA format --- .../process_statement_of_accounts.html | 118 ++++++++++-------- .../process_statement_of_accounts.json | 19 ++- .../process_statement_of_accounts.py | 8 +- 3 files changed, 92 insertions(+), 53 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index f61aacbce2..7328f168e3 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -1,24 +1,42 @@ -

    {{ filters.party[0] }}

    -

    {{ _("Statement of Accounts") }}

    +
    +
    + {% if letter_head %} +
    {{ letter_head.content }}
    +
    + {% endif %} +
    + +

    {{ _("STATEMENTS OF ACCOUNTS") }}

    +
    +
    {{ _("Customer: ") }} {{filters.party[0] }}
    +
    + {{ _("Date: ") }} + {{ frappe.format(filters.from_date, 'Date')}} + {{ _("to") }} + {{ frappe.format(filters.to_date, 'Date')}} +
    +
    +
    -
    - {{ frappe.format(filters.from_date, 'Date')}} - {{ _("to") }} - {{ frappe.format(filters.to_date, 'Date')}} -
    - - - - - - - - - - - - - +
    {{ _("Date") }}{{ _("Ref") }}{{ _("Party") }}{{ _("Debit") }}{{ _("Credit") }}{{ _("Balance (Dr - Cr)") }}
    + + + + + + + + + + + {% for row in data %} {% if(row.posting_date) %} @@ -58,32 +76,34 @@ {% endfor %} -
    {{ _("Date") }}{{ _("Reference") }}{{ _("Remarks") }}{{ _("Debit") }}{{ _("Credit") }}{{ _("Balance (Dr - Cr)") }}
    -

    -{% if ageing %} -

    {{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

    -
    - {{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}} -
    -
    - - - - - - - - - - - - - - - - - - -
    30 Days60 Days90 Days120 Days
    {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}
    -{% endif %} -

    Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}

    \ No newline at end of file + +
    + {% if ageing %} +

    {{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }} + {{ _("up to " ) }} {{ frappe.format(filters.to_date, 'Date')}} +

    + + + + + + + + + + + + + + + + + +
    30 Days60 Days90 Days120 Days
    {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}
    + {% endif %} + {% if terms_and_conditions %} +
    + {{ terms_and_conditions }} +
    + {% endif %} +
    \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index 4be0e2ec06..27a5f50ce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_workflow": 1, "autoname": "Prompt", "creation": "2020-05-22 16:46:18.712954", "doctype": "DocType", @@ -28,9 +27,11 @@ "customers", "preferences", "orientation", - "section_break_14", "include_ageing", "ageing_based_on", + "section_break_14", + "letter_head", + "terms_and_conditions", "section_break_1", "enable_auto_email", "section_break_18", @@ -270,10 +271,22 @@ "fieldname": "body", "fieldtype": "Text Editor", "label": "Body" + }, + { + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head" + }, + { + "fieldname": "terms_and_conditions", + "fieldtype": "Link", + "label": "Terms and Conditions", + "options": "Terms and Conditions" } ], "links": [], - "modified": "2020-08-08 08:47:09.185728", + "modified": "2021-05-21 10:14:22.426672", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index a0dbff3db4..2ad455c48f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -64,6 +64,9 @@ def get_report_pdf(doc, consolidated=True): tax_id = frappe.get_doc('Customer', entry.customer).tax_id presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ or doc.currency or get_company_currency(doc.company) + if doc.letter_head: + from frappe.www.printview import get_letter_head + letter_head = get_letter_head(doc, 0) filters= frappe._dict({ 'from_date': doc.from_date, @@ -91,7 +94,10 @@ def get_report_pdf(doc, consolidated=True): continue html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None}) + {"filters": filters, "data": res, "ageing": ageing[0] if doc.include_ageing else None, + "letter_head": letter_head if doc.letter_head else None, + "terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms') + if doc.terms_and_conditions else None}) html = frappe.render_template(base_template_path, {"body": html, \ "css": get_print_style(), "title": "Statement For " + entry.customer}) From 795909fdcd0b682c54129b7226a93b99f1240db1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 May 2021 11:23:20 +0530 Subject: [PATCH 0785/1264] fix: warehouse not found in stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.js | 4 ++-- erpnext/stock/doctype/stock_entry/stock_entry.json | 4 +++- .../doctype/stock_entry_type/stock_entry_type.json | 12 ++++++++++-- .../doctype/stock_entry_type/stock_entry_type.py | 4 +++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index daa1e51182..a40ef001ee 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -600,7 +600,6 @@ frappe.ui.form.on('Stock Entry', { add_to_transit: function(frm) { if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') { frm.set_value('to_warehouse', ''); - frm.set_value('stock_entry_type', 'Material Transfer'); frm.fields_dict.to_warehouse.get_query = function() { return { filters:{ @@ -615,7 +614,8 @@ frappe.ui.form.on('Stock Entry', { }, set_tansit_warehouse: function(frm) { - if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse) { + if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse + && frm.doc.from_warehouse) { let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company'; let dn = frm.doc.from_warehouse ? frm.doc.from_warehouse : frm.doc.company; frappe.db.get_value(dt, dn, 'default_in_transit_warehouse', (r) => { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 98c047a09e..7f94591005 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -637,6 +637,8 @@ { "default": "0", "depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry", + "fetch_from": "stock_entry_type.add_to_transit", + "fetch_if_empty": 1, "fieldname": "add_to_transit", "fieldtype": "Check", "label": "Add to Transit", @@ -655,7 +657,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-09 14:58:13.267321", + "modified": "2021-05-21 11:29:11.917161", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json index 0f2b55ec34..eee38be027 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json @@ -6,7 +6,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "purpose" + "purpose", + "add_to_transit" ], "fields": [ { @@ -18,10 +19,17 @@ "options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor", "reqd": 1, "set_only_once": 1 + }, + { + "default": "0", + "depends_on": "eval: doc.purpose == 'Material Transfer'", + "fieldname": "add_to_transit", + "fieldtype": "Check", + "label": "Add to Transit" } ], "links": [], - "modified": "2020-08-10 23:24:37.160817", + "modified": "2021-05-21 11:27:01.144110", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Type", diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py index a4116aba2c..1069ec8713 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py @@ -7,4 +7,6 @@ from __future__ import unicode_literals from frappe.model.document import Document class StockEntryType(Document): - pass + def validate(self): + if self.add_to_transit and self.purpose != 'Material Transfer': + self.add_to_transit = 0 From 9979cf5fcc20cf6f267b94bd23a63ce6c4d761c6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 21 May 2021 12:12:42 +0530 Subject: [PATCH 0786/1264] fix: wrong quantity after transaction for parallel stock transactions When two transactions are inserted parallelly then previous SLE could be incorrect for some of them. Locking SLE table would prevent reading from it till transaction is complete. --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9729987d2d..790318a625 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -232,7 +232,8 @@ class update_entries_after(object): and is_cancelled = 0 and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) order by timestamp(posting_date, posting_time) desc, creation desc - limit 1""", args, as_dict=1) + limit 1 + for update""", args, as_dict=1) return sle[0] if sle else frappe._dict() From 4dcac4ae8198ec6e594e1bdfe2408fc33f627940 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 21 May 2021 13:12:30 +0530 Subject: [PATCH 0787/1264] refactor(minor): Use identity instead of equality Ignore false positive. --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 790318a625..b2825fc26f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -624,7 +624,7 @@ class update_entries_after(object): break # If no entry found with outgoing rate, collapse stack - if index == None: + if index is None: # nosemgrep new_stock_value = sum((d[0]*d[1] for d in self.wh_data.stock_queue)) - qty_to_pop*outgoing_rate new_stock_qty = sum((d[0] for d in self.wh_data.stock_queue)) - qty_to_pop self.wh_data.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]] From 1097dc89c599435ff78d786d24be64c9b383b858 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 21 May 2021 14:04:03 +0530 Subject: [PATCH 0788/1264] fix: show allow zero valuation only when auto checked (#25778) --- .../stock_reconciliation_item.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 85c7ebe263..6bbba051f9 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2015-02-17 01:06:05.072764", "doctype": "DocType", "document_type": "Other", @@ -170,6 +171,7 @@ }, { "default": "0", + "depends_on": "allow_zero_valuation_rate", "fieldname": "allow_zero_valuation_rate", "fieldtype": "Check", "label": "Allow Zero Valuation Rate", @@ -179,7 +181,7 @@ ], "istable": 1, "links": [], - "modified": "2021-03-23 11:09:44.407157", + "modified": "2021-05-21 12:13:33.041266", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", @@ -189,4 +191,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file From e930456be2e3bea0e7dfbf514470d26cf7e4e4b7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 21 May 2021 16:31:12 +0530 Subject: [PATCH 0789/1264] fix(e-invoicing): 'NoneType' object is not iterable (#25781) --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 70e6d07eab..7f25812b47 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -43,7 +43,7 @@ def validate_eligibility(doc): invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - has_non_gst_item = any(d for d in doc.get('items') if d.get('is_non_gst')) + has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst')) if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: return False From 0b5e340b6e7f68a41a09cad0ab8dca8105ec905d Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 21 May 2021 16:32:56 +0530 Subject: [PATCH 0790/1264] fix(pos): return case for item with available qty equal to one (#25760) --- .../pos_closing_entry_detail.json | 3 ++- .../accounts/doctype/pos_invoice/pos_invoice.py | 16 +++++----------- .../pos_invoice_merge_log.py | 6 +++--- 3 files changed, 10 insertions(+), 15 deletions(-) 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 index 6e7768dc54..bbf1ba0020 100644 --- 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 @@ -46,6 +46,7 @@ "reqd": 1 }, { + "default": "0", "fieldname": "closing_amount", "fieldtype": "Currency", "in_list_view": 1, @@ -57,7 +58,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-23 16:45:43.662034", + "modified": "2021-05-19 20:08:44.523861", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry Detail", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 473db565fa..f55fdab21c 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -455,32 +455,26 @@ class POSInvoice(SalesInvoice): @frappe.whitelist() def get_stock_availability(item_code, warehouse): - latest_sle = frappe.db.sql("""select qty_after_transaction - from `tabStock Ledger Entry` + bin_qty = frappe.db.sql("""select actual_qty from `tabBin` 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 = get_pos_reserved_qty(item_code, warehouse) - sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 + bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0 - if sle_qty and pos_sales_qty: - return sle_qty - pos_sales_qty - else: - return sle_qty + return bin_qty - pos_sales_qty def get_pos_reserved_qty(item_code, warehouse): reserved_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 ifnull(p.consolidated_invoice, '') = '' and p_item.docstatus = 1 and p_item.item_code = %s and p_item.warehouse = %s """, (item_code, warehouse), as_dict=1) - + return reserved_qty[0].qty or 0 if reserved_qty else 0 @frappe.whitelist() 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 index bc7874305c..b0ddea304c 100644 --- 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 @@ -56,12 +56,12 @@ class POSInvoiceMergeLog(Document): sales = [d for d in pos_invoice_docs if d.get('is_return') == 0] sales_invoice, credit_note = "", "" - if sales: - sales_invoice = self.process_merging_into_sales_invoice(sales) - if returns: credit_note = self.process_merging_into_credit_note(returns) + if sales: + sales_invoice = self.process_merging_into_sales_invoice(sales) + self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) From b0019bcf5be87ddb88846af76baf018ae532d7aa Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 21 May 2021 21:27:55 +0530 Subject: [PATCH 0791/1264] fix: Item Variant Details Report Co-authored-by: gavindsouza - Handling of variants with special characters - Set data in appropriately named columns - Code cleanup --- .../item_variant_details.py | 262 +++++++++++------- 1 file changed, 157 insertions(+), 105 deletions(-) diff --git a/erpnext/stock/report/item_variant_details/item_variant_details.py b/erpnext/stock/report/item_variant_details/item_variant_details.py index e8449cc33e..d8563d7927 100644 --- a/erpnext/stock/report/item_variant_details/item_variant_details.py +++ b/erpnext/stock/report/item_variant_details/item_variant_details.py @@ -14,47 +14,58 @@ def get_data(item): if not item: return [] item_dicts = [] - variants = None - variant_results = frappe.db.sql("""select name from `tabItem` - where variant_of = %s""", item, as_dict=1) + variant_results = frappe.db.get_all( + "Item", + fields=["name"], + filters={ + "variant_of": ["=", item], + "disabled": 0 + } + ) + if not variant_results: - frappe.msgprint(_("There isn't any item variant for the selected item")) + frappe.msgprint(_("There aren't any item variants for the selected item")) return [] else: - variants = ", ".join([frappe.db.escape(variant['name']) for variant in variant_results]) + variant_list = [variant['name'] for variant in variant_results] - order_count_map = get_open_sales_orders_map(variants) - stock_details_map = get_stock_details_map(variants) - buying_price_map = get_buying_price_map(variants) - selling_price_map = get_selling_price_map(variants) - attr_val_map = get_attribute_values_map(variants) + order_count_map = get_open_sales_orders_count(variant_list) + stock_details_map = get_stock_details_map(variant_list) + buying_price_map = get_buying_price_map(variant_list) + selling_price_map = get_selling_price_map(variant_list) + attr_val_map = get_attribute_values_map(variant_list) - attribute_list = [d[0] for d in frappe.db.sql("""select attribute - from `tabItem Variant Attribute` - where parent in ({variants}) group by attribute""".format(variants=variants))] + attributes = frappe.db.get_all( + "Item Variant Attribute", + fields=["attribute"], + filters={ + "parent": ["in", variant_list] + }, + group_by="attribute" + ) + attribute_list = [row.get("attribute") for row in attributes] # Prepare dicts variant_dicts = [{"variant_name": d['name']} for d in variant_results] for item_dict in variant_dicts: - name = item_dict["variant_name"] + name = item_dict.get("variant_name") - for d in attribute_list: - attr_dict = attr_val_map[name] - if attr_dict and attr_dict.get(d): - item_dict[d] = attr_val_map[name][d] + for attribute in attribute_list: + attr_dict = attr_val_map.get(name) + if attr_dict and attr_dict.get(attribute): + item_dict[frappe.scrub(attribute)] = attr_val_map.get(name).get(attribute) - item_dict["Open Orders"] = order_count_map.get(name) or 0 + item_dict["open_orders"] = order_count_map.get(name) or 0 if stock_details_map.get(name): - item_dict["Inventory"] = stock_details_map.get(name)["Inventory"] or 0 - item_dict["In Production"] = stock_details_map.get(name)["In Production"] or 0 - item_dict["Available Selling"] = stock_details_map.get(name)["Available Selling"] or 0 + item_dict["current_stock"] = stock_details_map.get(name)["Inventory"] or 0 + item_dict["in_production"] = stock_details_map.get(name)["In Production"] or 0 else: - item_dict["Inventory"] = item_dict["In Production"] = item_dict["Available Selling"] = 0 + item_dict["current_stock"] = item_dict["in_production"] = 0 - item_dict["Avg. Buying Price List Rate"] = buying_price_map.get(name) or 0 - item_dict["Avg. Selling Price List Rate"] = selling_price_map.get(name) or 0 + item_dict["avg_buying_price_list_rate"] = buying_price_map.get(name) or 0 + item_dict["avg_selling_price_list_rate"] = selling_price_map.get(name) or 0 item_dicts.append(item_dict) @@ -71,117 +82,158 @@ def get_columns(item): item_doc = frappe.get_doc("Item", item) - for d in item_doc.attributes: - columns.append(d.attribute + ":Data:100") + for entry in item_doc.attributes: + columns.append({ + "fieldname": frappe.scrub(entry.attribute), + "label": entry.attribute, + "fieldtype": "Data", + "width": 100 + }) - columns += [_("Avg. Buying Price List Rate") + ":Currency:110", _("Avg. Selling Price List Rate") + ":Currency:110", - _("Inventory") + ":Float:100", _("In Production") + ":Float:100", - _("Open Orders") + ":Float:100", _("Available Selling") + ":Float:100" + additional_columns = [ + { + "fieldname": "avg_buying_price_list_rate", + "label": _("Avg. Buying Price List Rate"), + "fieldtype": "Currency", + "width": 150 + }, + { + "fieldname": "avg_selling_price_list_rate", + "label": _("Avg. Selling Price List Rate"), + "fieldtype": "Currency", + "width": 150 + }, + { + "fieldname": "current_stock", + "label": _("Current Stock"), + "fieldtype": "Float", + "width": 120 + }, + { + "fieldname": "in_production", + "label": _("In Production"), + "fieldtype": "Float", + "width": 150 + }, + { + "fieldname": "open_orders", + "label": _("Open Sales Orders"), + "fieldtype": "Float", + "width": 150 + } ] + columns.extend(additional_columns) return columns -def get_open_sales_orders_map(variants): - open_sales_orders = frappe.db.sql(""" - select - count(*) as count, - item_code - from - `tabSales Order Item` - where - docstatus = 1 and - qty > ifnull(delivered_qty, 0) and - item_code in ({variants}) - group by - item_code - """.format(variants=variants), as_dict=1) +def get_open_sales_orders_count(variants_list): + open_sales_orders = frappe.db.get_list( + "Sales Order", + fields=[ + "name", + "`tabSales Order Item`.item_code" + ], + filters=[ + ["Sales Order", "docstatus", "=", 1], + ["Sales Order Item", "item_code", "in", variants_list] + ], + distinct=1 + ) order_count_map = {} - for d in open_sales_orders: - order_count_map[d["item_code"]] = d["count"] + for row in open_sales_orders: + item_code = row.get("item_code") + if order_count_map.get(item_code) is None: + order_count_map[item_code] = 1 + else: + order_count_map[item_code] += 1 return order_count_map -def get_stock_details_map(variants): - stock_details = frappe.db.sql(""" - select - sum(planned_qty) as planned_qty, - sum(actual_qty) as actual_qty, - sum(projected_qty) as projected_qty, - item_code - from - `tabBin` - where - item_code in ({variants}) - group by - item_code - """.format(variants=variants), as_dict=1) +def get_stock_details_map(variant_list): + stock_details = frappe.db.get_all( + "Bin", + fields=[ + "sum(planned_qty) as planned_qty", + "sum(actual_qty) as actual_qty", + "sum(projected_qty) as projected_qty", + "item_code", + ], + filters={ + "item_code": ["in", variant_list] + }, + group_by="item_code" + ) stock_details_map = {} - for d in stock_details: - name = d["item_code"] + for row in stock_details: + name = row.get("item_code") stock_details_map[name] = { - "Inventory" :d["actual_qty"], - "In Production" :d["planned_qty"], - "Available Selling" :d["projected_qty"] + "Inventory": row.get("actual_qty"), + "In Production": row.get("planned_qty") } return stock_details_map -def get_buying_price_map(variants): - buying = frappe.db.sql(""" - select - avg(price_list_rate) as avg_rate, - item_code - from - `tabItem Price` - where - item_code in ({variants}) and buying=1 - group by - item_code - """.format(variants=variants), as_dict=1) +def get_buying_price_map(variant_list): + buying = frappe.db.get_all( + "Item Price", + fields=[ + "avg(price_list_rate) as avg_rate", + "item_code", + ], + filters={ + "item_code": ["in", variant_list], + "buying": 1 + }, + group_by="item_code" + ) buying_price_map = {} - for d in buying: - buying_price_map[d["item_code"]] = d["avg_rate"] + for row in buying: + buying_price_map[row.get("item_code")] = row.get("avg_rate") return buying_price_map -def get_selling_price_map(variants): - selling = frappe.db.sql(""" - select - avg(price_list_rate) as avg_rate, - item_code - from - `tabItem Price` - where - item_code in ({variants}) and selling=1 - group by - item_code - """.format(variants=variants), as_dict=1) +def get_selling_price_map(variant_list): + selling = frappe.db.get_all( + "Item Price", + fields=[ + "avg(price_list_rate) as avg_rate", + "item_code", + ], + filters={ + "item_code": ["in", variant_list], + "selling": 1 + }, + group_by="item_code" + ) selling_price_map = {} - for d in selling: - selling_price_map[d["item_code"]] = d["avg_rate"] + for row in selling: + selling_price_map[row.get("item_code")] = row.get("avg_rate") return selling_price_map -def get_attribute_values_map(variants): - list_attr = frappe.db.sql(""" - select - attribute, attribute_value, parent - from - `tabItem Variant Attribute` - where - parent in ({variants}) - """.format(variants=variants), as_dict=1) +def get_attribute_values_map(variant_list): + attribute_list = frappe.db.get_all( + "Item Variant Attribute", + fields=[ + "attribute", + "attribute_value", + "parent" + ], + filters={ + "parent": ["in", variant_list] + } + ) attr_val_map = {} - for d in list_attr: - name = d["parent"] + for row in attribute_list: + name = row.get("parent") if not attr_val_map.get(name): attr_val_map[name] = {} - attr_val_map[name][d["attribute"]] = d["attribute_value"] + attr_val_map[name][row.get("attribute")] = row.get("attribute_value") return attr_val_map From e1ab290911cbdfb62b9150e3c85da8567868d8e5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 11:25:35 +0530 Subject: [PATCH 0792/1264] chore: remove woocommerce package (#25736) This is not used anywhere. It was added in this commit https://github.com/frappe/erpnext/commit/df83148d7ccb15ba18b59c7d9b761a3651bb1dec even there it isn't being used. --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f1ffeb8f48..32da48e9d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,3 @@ python-youtube~=0.8.0 taxjar~=1.9.2 tweepy~=3.10.0 Unidecode~=1.2.0 -WooCommerce~=3.0.0 From 330353a5ced3d40dc2e699a4481234de205b1fd7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:07:45 +0530 Subject: [PATCH 0793/1264] refactor: use frappe.throw instread of recreating _msgprint was basically duplicating behvaiour of frappe.throw --- erpnext/stock/doctype/item/item.py | 25 ++++++------------- .../stock_reconciliation.py | 6 ++--- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index dbac79465e..174c87b48d 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1068,42 +1068,31 @@ def get_timeline_data(doctype, name): return out -def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1): +def validate_end_of_life(item_code, end_of_life=None, disabled=None): if (not end_of_life) or (disabled is None): end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"]) if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date(): - msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life)) - _msgprint(msg, verbose) + frappe.throw(_("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))) if disabled: - _msgprint(_("Item {0} is disabled").format(item_code), verbose) + frappe.throw(_("Item {0} is disabled").format(item_code)) -def validate_is_stock_item(item_code, is_stock_item=None, verbose=1): +def validate_is_stock_item(item_code, is_stock_item=None): if not is_stock_item: is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item") if is_stock_item != 1: - msg = _("Item {0} is not a stock Item").format(item_code) - - _msgprint(msg, verbose) + frappe.throw(_("Item {0} is not a stock Item").format(item_code)) -def validate_cancelled_item(item_code, docstatus=None, verbose=1): +def validate_cancelled_item(item_code, docstatus=None): if docstatus is None: docstatus = frappe.db.get_value("Item", item_code, "docstatus") if docstatus == 2: - msg = _("Item {0} is cancelled").format(item_code) - _msgprint(msg, verbose) - -def _msgprint(msg, verbose): - if verbose: - msgprint(msg, raise_exception=True) - else: - raise frappe.ValidationError(msg) - + frappe.throw(_("Item {0} is cancelled").format(item_code)) def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): """returns last purchase details in stock uom""" diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 7e216d6181..96b1cadaaf 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -167,8 +167,8 @@ class StockReconciliation(StockController): item = frappe.get_doc("Item", item_code) # end of life and stock item - validate_end_of_life(item_code, item.end_of_life, item.disabled, verbose=0) - validate_is_stock_item(item_code, item.is_stock_item, verbose=0) + validate_end_of_life(item_code, item.end_of_life, item.disabled) + validate_is_stock_item(item_code, item.is_stock_item) # item should not be serialized if item.has_serial_no and not row.serial_no and not item.serial_no_series: @@ -179,7 +179,7 @@ class StockReconciliation(StockController): raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code)) # docstatus should be < 2 - validate_cancelled_item(item_code, item.docstatus, verbose=0) + validate_cancelled_item(item_code, item.docstatus) except Exception as e: self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e)) From 4a2dbd4885777e435282df7afe8631f157f7a0a8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:11:30 +0530 Subject: [PATCH 0794/1264] refactor: cleanup get_timeline_data, remove py2 --- erpnext/stock/doctype/item/item.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 174c87b48d..61d7e56d13 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -12,7 +12,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError, copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes) 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, +from frappe.utils import (cint, cstr, flt, formatdate, getdate, now_datetime, random_string, strip, get_link_to_form, nowtime) from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ @@ -21,8 +21,6 @@ from frappe.website.doctype.website_slideshow.website_slideshow import \ from frappe.website.render import clear_cache from frappe.website.website_generator import WebsiteGenerator -from six import iteritems - class DuplicateReorderRows(frappe.ValidationError): pass @@ -1054,18 +1052,15 @@ def make_item_price(item, price_list_name, item_price): }).insert() def get_timeline_data(doctype, name): - '''returns timeline data based on stock ledger entry''' - out = {} - items = dict(frappe.db.sql('''select posting_date, count(*) - from `tabStock Ledger Entry` where item_code=%s - and posting_date > date_sub(curdate(), interval 1 year) - group by posting_date''', name)) + """get timeline data based on Stock Ledger Entry. This is displayed as heatmap on the item page.""" - for date, count in iteritems(items): - timestamp = get_timestamp(date) - out.update({timestamp: count}) + items = frappe.db.sql("""select unix_timestamp(posting_date), count(*) + from `tabStock Ledger Entry` + where item_code=%s and posting_date > date_sub(curdate(), interval 1 year) + group by posting_date""", name) + + return dict(items) - return out def validate_end_of_life(item_code, end_of_life=None, disabled=None): From ad58a8164aeabfe0c87c54052ec5ba3db4c1ca56 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:58:22 +0530 Subject: [PATCH 0795/1264] refactor: code cleanup minor fixes for improving code quality --- erpnext/stock/doctype/item/item.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 61d7e56d13..0b92e27152 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -201,7 +201,7 @@ class Item(WebsiteGenerator): def make_route(self): if not self.route: return cstr(frappe.db.get_value('Item Group', self.item_group, - 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5)) + 'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5)) def validate_website_image(self): if frappe.flags.in_import: @@ -256,7 +256,6 @@ class Item(WebsiteGenerator): "attached_to_name": self.name }) except frappe.DoesNotExistError: - pass # cleanup frappe.local.message_log.pop() From 0b4858d8e5d84723d82544721784d60e8541e3c2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:59:52 +0530 Subject: [PATCH 0796/1264] refactor: eliminate unnecessary loop, container casts --- erpnext/stock/doctype/item/item.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 0b92e27152..c41dd67727 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -539,10 +539,7 @@ class Item(WebsiteGenerator): def fill_customer_code(self): """ Append all the customer codes and insert into "customer_code" field of item table """ - cust_code = [] - for d in self.get('customer_items'): - cust_code.append(d.ref_code) - self.customer_code = ','.join(cust_code) + self.customer_code = ','.join(d.ref_code for d in self.get("customer_items", [])) def check_item_tax(self): """Check whether Tax Rate is not entered twice for same Tax Type""" @@ -755,7 +752,7 @@ class Item(WebsiteGenerator): template_item.save() def validate_item_defaults(self): - companies = list(set([row.company for row in self.item_defaults])) + companies = {row.company for row in self.item_defaults} if len(companies) != len(self.item_defaults): frappe.throw(_("Cannot set multiple Item Defaults for a company.")) From 83e6e2e68aec4ef9b6095652a83d19e44bf90e31 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:01:50 +0530 Subject: [PATCH 0797/1264] refactor: add guard clause for readability --- erpnext/stock/doctype/item/item.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index c41dd67727..b665eb8e46 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -953,20 +953,22 @@ class Item(WebsiteGenerator): d.variant_of = self.variant_of def cant_change(self): - if not self.get("__islocal"): - fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no") + if self.get("__islocal"): + return - values = frappe.db.get_value("Item", self.name, fields, as_dict=True) - if not values.get('valuation_method') and self.get('valuation_method'): - values['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO" + fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no") - if values: - for field in fields: - if cstr(self.get(field)) != cstr(values.get(field)): - if not self.check_if_linked_document_exists(field): - break # no linked document, allowed - else: - frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) + values = frappe.db.get_value("Item", self.name, fields, as_dict=True) + if not values.get('valuation_method') and self.get('valuation_method'): + values['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO" + + if values: + for field in fields: + if cstr(self.get(field)) != cstr(values.get(field)): + if not self.check_if_linked_document_exists(field): + break # no linked document, allowed + else: + frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) def check_if_linked_document_exists(self, field): linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item", From 931c886f92c34453f87b54e315b8f3614a10df48 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:06:16 +0530 Subject: [PATCH 0798/1264] fix: not checking all fields `break` will break out of the loop without checking remaining fields. --- erpnext/stock/doctype/item/item.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index b665eb8e46..d09a4aa0dc 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -965,9 +965,7 @@ class Item(WebsiteGenerator): if values: for field in fields: if cstr(self.get(field)) != cstr(values.get(field)): - if not self.check_if_linked_document_exists(field): - break # no linked document, allowed - else: + if self.check_if_linked_document_exists(field): frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) def check_if_linked_document_exists(self, field): From 4b484d741d81834ad9749e9395b2510397b7ae09 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:10:54 +0530 Subject: [PATCH 0799/1264] refactor: use is_new() instead of __islocal Interface over implementation. --- erpnext/stock/doctype/item/item.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index d09a4aa0dc..7906923e6f 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -127,7 +127,7 @@ class Item(WebsiteGenerator): self.cant_change() self.update_show_in_website() - if not self.get("__islocal"): + if not self.is_new(): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") self.old_website_item_groups = frappe.db.sql_list("""select item_group from `tabWebsite Item Group` @@ -807,7 +807,7 @@ class Item(WebsiteGenerator): frappe.throw(_("Item has variants.")) def validate_attributes_in_variants(self): - if not self.has_variants or self.get("__islocal"): + if not self.has_variants or self.is_new(): return old_doc = self.get_doc_before_save() @@ -895,7 +895,7 @@ class Item(WebsiteGenerator): frappe.throw(_("Variant Based On cannot be changed")) def validate_uom(self): - if not self.get("__islocal"): + if not self.is_new(): check_stock_uom_with_bin(self.name, self.stock_uom) if self.has_variants: for d in frappe.db.get_all("Item", filters={"variant_of": self.name}): @@ -953,7 +953,7 @@ class Item(WebsiteGenerator): d.variant_of = self.variant_of def cant_change(self): - if self.get("__islocal"): + if self.is_new(): return fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no") From c229ac932288189366ec6dd57f74db6f34248b1f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:15:32 +0530 Subject: [PATCH 0800/1264] refactor: add guard clause for readability Both functions only execute based on a condition. In such cases condition should immediately exit the function, this is called "guard clause" and helps in readability (less indent, and easy to "exit" when reading the code. --- erpnext/stock/doctype/item/item.py | 94 ++++++++++++++++-------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 7906923e6f..f7856be4ae 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -359,47 +359,49 @@ class Item(WebsiteGenerator): context.update(get_slideshow(self)) def set_attribute_context(self, context): - if self.has_variants: - attribute_values_available = {} - context.attribute_values = {} - context.selected_attributes = {} + if not self.has_variants: + return - # load attributes - for v in context.variants: - v.attributes = frappe.get_all("Item Variant Attribute", - fields=["attribute", "attribute_value"], - filters={"parent": v.name}) - # make a map for easier access in templates - v.attribute_map = frappe._dict({}) - for attr in v.attributes: - v.attribute_map[attr.attribute] = attr.attribute_value + attribute_values_available = {} + context.attribute_values = {} + context.selected_attributes = {} - for attr in v.attributes: - values = attribute_values_available.setdefault(attr.attribute, []) - if attr.attribute_value not in values: - values.append(attr.attribute_value) + # load attributes + for v in context.variants: + v.attributes = frappe.get_all("Item Variant Attribute", + fields=["attribute", "attribute_value"], + filters={"parent": v.name}) + # make a map for easier access in templates + v.attribute_map = frappe._dict({}) + for attr in v.attributes: + v.attribute_map[attr.attribute] = attr.attribute_value - if v.name == context.variant.name: - context.selected_attributes[attr.attribute] = attr.attribute_value + for attr in v.attributes: + values = attribute_values_available.setdefault(attr.attribute, []) + if attr.attribute_value not in values: + values.append(attr.attribute_value) - # filter attributes, order based on attribute table - for attr in self.attributes: - values = context.attribute_values.setdefault(attr.attribute, []) + if v.name == context.variant.name: + context.selected_attributes[attr.attribute] = attr.attribute_value - if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")): - for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt): - values.append(val) + # filter attributes, order based on attribute table + for attr in self.attributes: + values = context.attribute_values.setdefault(attr.attribute, []) - else: - # get list of values defined (for sequence) - for attr_value in frappe.db.get_all("Item Attribute Value", - fields=["attribute_value"], - filters={"parent": attr.attribute}, order_by="idx asc"): + if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")): + for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt): + values.append(val) - if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): - values.append(attr_value.attribute_value) + else: + # get list of values defined (for sequence) + for attr_value in frappe.db.get_all("Item Attribute Value", + fields=["attribute_value"], + filters={"parent": attr.attribute}, order_by="idx asc"): - context.variant_info = json.dumps(context.variants) + if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): + values.append(attr_value.attribute_value) + + context.variant_info = json.dumps(context.variants) def set_disabled_attributes(self, context): """Disable selection options of attribute combinations that do not result in a variant""" @@ -736,20 +738,22 @@ class Item(WebsiteGenerator): def update_template_item(self): """Set Show in Website for Template Item if True for its Variant""" - if self.variant_of: - if self.show_in_website: - self.show_variant_in_website = 1 - self.show_in_website = 0 + if not self.variant_of: + return - if self.show_variant_in_website: - # show template - template_item = frappe.get_doc("Item", self.variant_of) + if self.show_in_website: + self.show_variant_in_website = 1 + self.show_in_website = 0 - if not template_item.show_in_website: - template_item.show_in_website = 1 - template_item.flags.dont_update_variants = True - template_item.flags.ignore_permissions = True - template_item.save() + if self.show_variant_in_website: + # show template + template_item = frappe.get_doc("Item", self.variant_of) + + if not template_item.show_in_website: + template_item.show_in_website = 1 + template_item.flags.dont_update_variants = True + template_item.flags.ignore_permissions = True + template_item.save() def validate_item_defaults(self): companies = {row.company for row in self.item_defaults} From a2d6cf3125fff2ba04e0478021212ffb99777a4f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 18:38:14 +0530 Subject: [PATCH 0801/1264] chore: remove pytlint config (#25796) - We have flake8 config and it runs in Sider. Flake8 and pylint have huge overlap, no point in using both tools. - The config is not valid pylint config. So it's useless anyway. --- .pylintrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 4b2ea0a564..0000000000 --- a/.pylintrc +++ /dev/null @@ -1 +0,0 @@ -disable=access-member-before-definition \ No newline at end of file From 7b4a38c71e850223d982c30be2ed448764897c81 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 18:49:26 +0530 Subject: [PATCH 0802/1264] chore: remove print from account controller (#25807) This is polluting test output and it's not useful for debugging without context. --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 996c4ed11b..544e624725 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1011,7 +1011,7 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - print(grand_total, base_grand_total) + if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) From 5457db06013792defa1b1ccb58260245eeb932e9 Mon Sep 17 00:00:00 2001 From: krishnagirishp <80564074+krishnagirishp@users.noreply.github.com> Date: Sun, 23 May 2021 21:13:44 +0530 Subject: [PATCH 0803/1264] chore: remove uses of six.PY2 in codebase (#25062) * remove uses of six.py2 in codebase * changes based on pr feedback * Update amazon_mws_api.py Co-authored-by: Ankush Menat --- .../doctype/amazon_mws_settings/amazon_mws_api.py | 7 ++++--- erpnext/regional/germany/utils/datev/datev_csv.py | 3 +-- erpnext/regional/india/e_invoice/utils.py | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py index f713684d37..7fd3b34fd5 100755 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import urllib +from urllib.parse import quote import hashlib import hmac import base64 @@ -68,8 +69,9 @@ def calc_md5(string): """ md = hashlib.md5() md.update(string) - return base64.encodestring(md.digest()).strip('\n') if six.PY2 \ - else base64.encodebytes(md.digest()).decode().strip() + return base64.encodebytes(md.digest()).decode().strip() + + def remove_empty(d): """ @@ -177,7 +179,6 @@ class MWS(object): 'SignatureMethod': 'HmacSHA256', } params.update(extra_data) - quote = urllib.quote if six.PY2 else urllib.parse.quote request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)]) signature = self.calc_signature(method, request_description) url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature)) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index 826d51f712..122c15fd81 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -55,8 +55,7 @@ def get_datev_csv(data, filters, csv_class): quoting=QUOTE_NONNUMERIC ) - if not six.PY2: - data = data.encode('latin_1', errors='replace') + data = data.encode('latin_1', errors='replace') header = get_header(filters, csv_class) header = ';'.join(header).encode('latin_1', errors='replace') diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 7f25812b47..843fb012b9 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -534,11 +534,9 @@ def santize_einvoice_fields(einvoice): return einvoice def safe_json_load(json_string): - JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError - try: return json.loads(json_string) - except JSONDecodeError as e: + except json.JSONDecodeError as e: # print a snippet of 40 characters around the location where error occured pos = e.pos start, end = max(0, pos-20), min(len(json_string)-1, pos+20) From ce88c945cd420c001bf786476a62f6e17ebf7692 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 24 May 2021 11:53:27 +0530 Subject: [PATCH 0804/1264] Update stock_entry.js --- erpnext/stock/doctype/stock_entry/stock_entry.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index a40ef001ee..de23e769f8 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -609,11 +609,11 @@ frappe.ui.form.on('Stock Entry', { } }; }; - frm.trigger('set_tansit_warehouse'); + frm.trigger('set_transit_warehouse'); } }, - set_tansit_warehouse: function(frm) { + set_transit_warehouse: function(frm) { if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse && frm.doc.from_warehouse) { let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company'; @@ -985,7 +985,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, from_warehouse: function(doc) { - this.frm.trigger('set_tansit_warehouse'); + this.frm.trigger('set_transit_warehouse'); this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse); }, From c12264f6bcded0c914885f9d238fa5c63d665282 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 24 May 2021 13:20:22 +0530 Subject: [PATCH 0805/1264] chore: Cleanup Customer and Supplier Details section in Stock Entry - Changed depends on value to "Send to Subcontractor" for supplier fields - Removed Customer fields as they are not relevant to any Stock Entry purpose - Renamed section to "Supplier Details" visibe on subcontracting transfer --- .../doctype/stock_entry/stock_entry.json | 51 +++---------------- 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 98c047a09e..567b2ac3b0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -59,10 +59,6 @@ "supplier_name", "supplier_address", "address_display", - "column_break_39", - "customer", - "customer_name", - "customer_address", "accounting_dimensions_section", "project", "dimension_col_break", @@ -435,13 +431,13 @@ }, { "collapsible": 1, - "depends_on": "eval: in_list([\"Sales Return\", \"Purchase Return\", \"Send to Subcontractor\"], doc.purpose)", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "contact_section", "fieldtype": "Section Break", - "label": "Customer or Supplier Details" + "label": "Supplier Details" }, { - "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "supplier", "fieldtype": "Link", "label": "Supplier", @@ -453,7 +449,7 @@ }, { "bold": 1, - "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "supplier_name", "fieldtype": "Data", "label": "Supplier Name", @@ -463,7 +459,7 @@ "read_only": 1 }, { - "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "supplier_address", "fieldtype": "Link", "label": "Supplier Address", @@ -477,41 +473,6 @@ "fieldtype": "Small Text", "label": "Address" }, - { - "fieldname": "column_break_39", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.purpose==\"Sales Return\"", - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "no_copy": 1, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "print_hide": 1 - }, - { - "bold": 1, - "depends_on": "eval:doc.purpose==\"Sales Return\"", - "fieldname": "customer_name", - "fieldtype": "Data", - "label": "Customer Name", - "no_copy": 1, - "oldfieldname": "customer_name", - "oldfieldtype": "Data", - "read_only": 1 - }, - { - "depends_on": "eval:doc.purpose==\"Sales Return\"", - "fieldname": "customer_address", - "fieldtype": "Small Text", - "label": "Customer Address", - "no_copy": 1, - "oldfieldname": "customer_address", - "oldfieldtype": "Small Text" - }, { "collapsible": 1, "fieldname": "printing_settings", @@ -655,7 +616,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-09 14:58:13.267321", + "modified": "2021-05-24 11:32:23.904307", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", From bf1b3b89d1cb07481006a94d78112c110be74f70 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 24 May 2021 17:03:15 +0530 Subject: [PATCH 0806/1264] refactor: updated conditional visibility of check box --- .../manufacturing/doctype/production_plan/production_plan.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 3041507caf..1c0dde227c 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -322,6 +322,7 @@ }, { "default": "0", + "depends_on": "eval:doc.get_items_from == 'Sales Order'", "fieldname": "combine_items", "fieldtype": "Check", "label": "Consolidate Items" @@ -342,7 +343,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-07 16:56:00.255001", + "modified": "2021-05-24 16:59:03.643211", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", From dc7afa743c20505883df39f3aa7a151a58ef498f Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 24 May 2021 17:33:48 +0530 Subject: [PATCH 0807/1264] fix: profitability test (#25812) * fix: profitability test * fix: replaced class method * fix: removed print statement --- .../project_profitability/test_project_profitability.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index f2b9c2509a..ea6bdb54ca 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -8,7 +8,7 @@ from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_ from erpnext.projects.report.project_profitability.project_profitability import execute class TestProjectProfitability(unittest.TestCase): - @classmethod + def setUp(self): emp = make_employee('test_employee_9@salary.com', company='_Test Company') if not frappe.db.exists('Salary Component', 'Timesheet Component'): @@ -21,7 +21,7 @@ class TestProjectProfitability(unittest.TestCase): self.sales_invoice.due_date = nowdate() self.sales_invoice.submit() - frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 8) + frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8) def test_project_profitability(self): filters = { @@ -55,4 +55,4 @@ class TestProjectProfitability(unittest.TestCase): def tearDown(self): frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() - frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file + frappe.get_doc("Timesheet", self.timesheet.name).cancel() From 44c489223b855f837a279c65c989f30500ea70e8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:43:28 +0530 Subject: [PATCH 0808/1264] chore: remove py2 compat code --- erpnext/stock/doctype/item/item.py | 4 +--- erpnext/stock/doctype/item/test_item.py | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f7856be4ae..a6f5160b5c 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1,8 +1,6 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import itertools import json import erpnext diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e0b89d8e45..c300132ad0 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -14,7 +14,6 @@ from erpnext.stock.doctype.item.item import get_uom_conv_factor from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details -from six import iteritems test_ignore = ["BOM"] test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand"] @@ -98,7 +97,7 @@ class TestItem(unittest.TestCase): "ignore_pricing_rule": 1 }) - for key, value in iteritems(to_check): + for key, value in to_check.items(): self.assertEqual(value, details.get(key)) def test_item_tax_template(self): @@ -194,7 +193,7 @@ class TestItem(unittest.TestCase): "plc_conversion_rate": 1, "customer": "_Test Customer", }) - for key, value in iteritems(sales_item_check): + for key, value in sales_item_check.items(): self.assertEqual(value, sales_item_details.get(key)) purchase_item_check = { @@ -215,7 +214,7 @@ class TestItem(unittest.TestCase): "plc_conversion_rate": 1, "supplier": "_Test Supplier", }) - for key, value in iteritems(purchase_item_check): + for key, value in purchase_item_check.items(): self.assertEqual(value, purchase_item_details.get(key)) def test_item_attribute_change_after_variant(self): @@ -464,7 +463,7 @@ class TestItem(unittest.TestCase): self.assertEqual(len(matching_barcodes), 1) details = matching_barcodes[0] - for key, value in iteritems(barcode_properties): + for key, value in barcode_properties.items(): self.assertEqual(value, details.get(key)) # Add barcode again - should cause DuplicateEntryError From ccbde0efa07306710676d144fb7faf29635639db Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:52:00 +0530 Subject: [PATCH 0809/1264] refactor: use enumerate instead of trackign index also removed dead code --- erpnext/stock/doctype/item/item.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a6f5160b5c..9a52fb4fc1 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -72,8 +72,6 @@ class Item(WebsiteGenerator): if not self.description: self.description = self.item_name - # if self.is_sales_item and not self.get('is_item_from_hub'): - # self.publish_in_hub = 1 def after_insert(self): '''set opening stock and item price''' @@ -1277,14 +1275,13 @@ def get_item_attribute(parent, attribute_value=''): filters = {'parent': parent, 'attribute_value': ("like", "%%%s%%" % attribute_value)}) def update_variants(variants, template, publish_progress=True): - count=0 - for d in variants: + total = len(variants) + for count, d in enumerate(variants, start=1): variant = frappe.get_doc("Item", d) copy_attributes_to_variant(template, variant) variant.save() - count+=1 if publish_progress: - frappe.publish_progress(count*100/len(variants), title = _("Updating Variants...")) + frappe.publish_progress(count / total * 100, title=_("Updating Variants...")) def on_doctype_update(): # since route is a Text column, it needs a length for indexing From 958d485ba49d47af46324395519d9683cbde4674 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:58:38 +0530 Subject: [PATCH 0810/1264] refactor: msgprint(raise_exception)->frappe.throw --- erpnext/stock/doctype/item/item.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 9a52fb4fc1..e865bda5c1 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -516,7 +516,7 @@ class Item(WebsiteGenerator): def validate_item_type(self): if self.has_serial_no == 1 and self.is_stock_item == 0 and not self.is_fixed_asset: - msgprint(_("'Has Serial No' can not be 'Yes' for non-stock item"), raise_exception=1) + frappe.throw(_("'Has Serial No' can not be 'Yes' for non-stock item")) if self.has_serial_no == 0 and self.serial_no_series: self.serial_no_series = None @@ -1269,7 +1269,7 @@ def get_uom_conv_factor(uom, stock_uom): @frappe.whitelist() def get_item_attribute(parent, attribute_value=''): if not frappe.has_permission("Item"): - frappe.msgprint(_("No Permission"), raise_exception=1) + frappe.throw(_("No Permission")) return frappe.get_all("Item Attribute Value", fields = ["attribute_value"], filters = {'parent': parent, 'attribute_value': ("like", "%%%s%%" % attribute_value)}) From 297dc5e345b494c6f9cdba12653cdf45721a2af3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 00:17:08 +0530 Subject: [PATCH 0811/1264] perf: add basic optimisation for uom conversion --- erpnext/stock/doctype/item/item.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index e865bda5c1..2c862dcfb7 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1244,6 +1244,9 @@ def get_item_details(item_code, company=None): @frappe.whitelist() def get_uom_conv_factor(uom, stock_uom): + if uom == stock_uom: + return 1.0 + uoms = [uom, stock_uom] value = "" uom_details = frappe.db.sql("""select to_uom, from_uom, value from `tabUOM Conversion Factor`\ From 0d7f54c6c22578797f1e55eb4c29fbc452c591ce Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 00:30:27 +0530 Subject: [PATCH 0812/1264] refactor: simplify UOM conversion logic - Remove unnecessary sql query - Remove convoluted matching logic and be explcitiy while querying. - better variable names for understanding matching cases --- erpnext/stock/doctype/item/item.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 2c862dcfb7..ef855c7db5 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1247,27 +1247,22 @@ def get_uom_conv_factor(uom, stock_uom): if uom == stock_uom: return 1.0 - uoms = [uom, stock_uom] - value = "" - uom_details = frappe.db.sql("""select to_uom, from_uom, value from `tabUOM Conversion Factor`\ - where to_uom in ({0}) - """.format(', '.join([frappe.db.escape(i, percent=False) for i in uoms])), as_dict=True) + exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom, "from_uom": uom}, ["value"], as_dict=1) + if exact_match: + return exact_match.value - for d in uom_details: - if d.from_uom == stock_uom and d.to_uom == uom: - value = 1/flt(d.value) - elif d.from_uom == uom and d.to_uom == stock_uom: - value = d.value + inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom, "from_uom": stock_uom}, ["value"], as_dict=1) + if inverse_match: + return 1 / inverse_match.value - if not value: - uom_stock = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom}, ["from_uom", "value"], as_dict=1) - uom_row = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom}, ["from_uom", "value"], as_dict=1) + # This attempts to try and get conversion from intermediate UOM. E.g. mg <=> g <=> kg + uom_stock = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom}, ["from_uom", "value"], as_dict=1) + uom_row = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom}, ["from_uom", "value"], as_dict=1) - if uom_stock and uom_row: - if uom_stock.from_uom == uom_row.from_uom: - value = flt(uom_stock.value) * 1/flt(uom_row.value) + if uom_stock and uom_row: + if uom_stock.from_uom == uom_row.from_uom: + return flt(uom_stock.value) * 1/flt(uom_row.value) - return value @frappe.whitelist() def get_item_attribute(parent, attribute_value=''): From 019be66b7b1d137e686ca9b8189f638abdd5f47d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 01:12:47 +0530 Subject: [PATCH 0813/1264] fix: consider all UOMs for intermediate conversion - Using `get_value` will restrict intermediate UOM to first UOM that is found. - A self join is required to truly capture the required behaviour. - Add explanation and examples. --- erpnext/stock/doctype/item/item.py | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index ef855c7db5..a5bc492422 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1244,24 +1244,40 @@ def get_item_details(item_code, company=None): @frappe.whitelist() def get_uom_conv_factor(uom, stock_uom): + """ Get UOM conversion factor from uom to stock_uom + e.g. uom = "Kg", stock_uom = "Gram" then returns 1000.0 + """ if uom == stock_uom: return 1.0 - exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom, "from_uom": uom}, ["value"], as_dict=1) + from_uom, to_uom = uom, stock_uom # renaming for readability + + exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1) if exact_match: return exact_match.value - inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom, "from_uom": stock_uom}, ["value"], as_dict=1) + inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": from_uom, "from_uom": to_uom}, ["value"], as_dict=1) if inverse_match: return 1 / inverse_match.value - # This attempts to try and get conversion from intermediate UOM. E.g. mg <=> g <=> kg - uom_stock = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom}, ["from_uom", "value"], as_dict=1) - uom_row = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom}, ["from_uom", "value"], as_dict=1) + # This attempts to try and get conversion from intermediate UOM. + # case: + # g -> mg = 1000 + # g -> kg = 0.001 + # therefore kg -> mg = 1000 / 0.001 = 1,000,000 + intermediate_match = frappe.db.sql(""" + select (first.value / second.value) as value + from `tabUOM Conversion Factor` first + join `tabUOM Conversion Factor` second + on first.from_uom = second.from_uom + where + first.to_uom = %(to_uom)s + and second.to_uom = %(from_uom)s + limit 1 + """, {"to_uom": to_uom, "from_uom": from_uom}, as_dict=1) - if uom_stock and uom_row: - if uom_stock.from_uom == uom_row.from_uom: - return flt(uom_stock.value) * 1/flt(uom_row.value) + if intermediate_match: + return intermediate_match[0].value @frappe.whitelist() From b9fa12d5721dddd00291fca1e9ef157527fb5905 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 12:26:01 +0530 Subject: [PATCH 0814/1264] test: add tests for uom conversion function --- erpnext/stock/doctype/item/test_item.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index c300132ad0..2366f06f6d 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -374,6 +374,14 @@ class TestItem(unittest.TestCase): self.assertEqual(item_doc.uoms[1].uom, "Kg") self.assertEqual(item_doc.uoms[1].conversion_factor, 1000) + def test_uom_conv_intermediate(self): + factor = get_uom_conv_factor("Pound", "Gram") + self.assertAlmostEqual(factor, 453.592, 3) + + def test_uom_conv_base_case(self): + factor = get_uom_conv_factor("m", "m") + self.assertEqual(factor, 1.0) + def test_item_variant_by_manufacturer(self): fields = [{'field_name': 'description'}, {'field_name': 'variant_based_on'}] set_item_variant_settings(fields) From f5a937bc45e2fe8dd4a18a3b804a86e3caa84cad Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 12:38:15 +0530 Subject: [PATCH 0815/1264] test: check index creation on item table --- erpnext/stock/doctype/item/test_item.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 2366f06f6d..d9d1e5a44d 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -487,6 +487,20 @@ class TestItem(unittest.TestCase): new_barcode.barcode_type = 'EAN' self.assertRaises(InvalidBarcode, item_doc.save) + def test_index_creation(self): + "check if index is getting created in db" + from erpnext.stock.doctype.item.item import on_doctype_update + on_doctype_update() + + indices = frappe.db.sql("show index from tabItem", as_dict=1) + expected_columns = {"item_code", "item_name", "item_group", "route"} + for index in indices: + expected_columns.discard(index.get("Column_name")) + + if expected_columns: + self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") + + def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') doc.set('fields', fields) From eb177328767c940857f46ca2345e972e9915eda2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 13:12:16 +0530 Subject: [PATCH 0816/1264] test: add test for item attribute completion --- erpnext/stock/doctype/item/item.py | 5 +++-- erpnext/stock/doctype/item/test_item.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a5bc492422..ec46f60f2b 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1281,12 +1281,13 @@ def get_uom_conv_factor(uom, stock_uom): @frappe.whitelist() -def get_item_attribute(parent, attribute_value=''): +def get_item_attribute(parent, attribute_value=""): + """Used for providing auto-completions in child table.""" if not frappe.has_permission("Item"): frappe.throw(_("No Permission")) return frappe.get_all("Item Attribute Value", fields = ["attribute_value"], - filters = {'parent': parent, 'attribute_value': ("like", "%%%s%%" % attribute_value)}) + filters = {'parent': parent, 'attribute_value': ("like", f"%{attribute_value}%")}) def update_variants(variants, template, publish_progress=True): total = len(variants) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index d9d1e5a44d..7cd6050c02 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -10,13 +10,13 @@ from frappe.test_runner import make_test_objects from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError, InvalidItemAttributeValueError, get_variant) from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode -from erpnext.stock.doctype.item.item import get_uom_conv_factor +from erpnext.stock.doctype.item.item import get_uom_conv_factor, get_item_attribute from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details test_ignore = ["BOM"] -test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand"] +test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] def make_item(item_code, properties=None): if frappe.db.exists("Item", item_code): @@ -500,6 +500,21 @@ class TestItem(unittest.TestCase): if expected_columns: self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") + def test_attribute_completions(self): + expected_attrs = [{'attribute_value': 'Small'}, + {'attribute_value': 'Extra Small'}, + {'attribute_value': 'Extra Large'}, + {'attribute_value': 'Large'}, + {'attribute_value': '2XL'}, + {'attribute_value': 'Medium'}] + + attrs = get_item_attribute("Test Size") + self.assertEqual(attrs, expected_attrs) + + attrs = get_item_attribute("Test Size", attribute_value="extra") + self.assertEqual(attrs, [{'attribute_value': 'Extra Small'}, {'attribute_value': 'Extra Large'}]) + + def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') From a11a8e8ab2e30eaa9f3b1cd80c27dc9ad8f13aeb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 13:19:22 +0530 Subject: [PATCH 0817/1264] chore: add blame ignore file --- .git-blame-ignore-revs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..be425ec2d9 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,12 @@ +# Since version 2.23 (released in August 2019), git-blame has a feature +# to ignore or bypass certain commits. +# +# This file contains a list of commits that are not likely what you +# are looking for in a blame, such as mass reformatting or renaming. +# You can set this file as a default ignore file for blame by running +# the following command. +# +# $ git config blame.ignoreRevsFile .git-blame-ignore-revs + +# This commit just changes spaces to tabs for indentation in some files +5f473611bd6ed57703716244a054d3fb5ba9cd23 From 4e360f805f5cb4f7ed500316aa97ca7e52b2f9bf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 14:48:03 +0530 Subject: [PATCH 0818/1264] test: hoist defaults to function signature --- erpnext/stock/doctype/item/test_item.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 7cd6050c02..9694927914 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -530,23 +530,24 @@ def make_item_variant(): test_records = frappe.get_test_records('Item') -def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, - customer=None, is_purchase_item=None, opening_stock=None, company=None): +def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC", + is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0, + company="_Test Company"): if not frappe.db.exists("Item", item_code): item = frappe.new_doc("Item") item.item_code = item_code item.item_name = item_code item.description = item_code item.item_group = "All Item Groups" - item.is_stock_item = is_stock_item or 1 - item.opening_stock = opening_stock or 0 - item.valuation_rate = valuation_rate or 0.0 + item.is_stock_item = is_stock_item + item.opening_stock = opening_stock + item.valuation_rate = valuation_rate item.is_purchase_item = is_purchase_item item.is_customer_provided_item = is_customer_provided_item item.customer = customer or '' item.append("item_defaults", { - "default_warehouse": warehouse or '_Test Warehouse - _TC', - "company": company or "_Test Company" + "default_warehouse": warehouse, + "company": company }) item.save() else: From fc54cf68ac33b213dd44821c10e01f84a6c4727a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 15:04:41 +0530 Subject: [PATCH 0819/1264] test: add tests for checking stock_uom with bin --- erpnext/stock/doctype/item/test_item.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 9694927914..8df12a3f16 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -514,6 +514,35 @@ class TestItem(unittest.TestCase): attrs = get_item_attribute("Test Size", attribute_value="extra") self.assertEqual(attrs, [{'attribute_value': 'Extra Small'}, {'attribute_value': 'Extra Large'}]) + def test_check_stock_uom_with_bin(self): + # this item has opening stock and stock_uom set in test_records. + item = frappe.get_doc("Item", "_Test Item") + item.stock_uom = "Gram" + self.assertRaises(frappe.ValidationError, item.save) + + def test_check_stock_uom_with_bin_no_sle(self): + from erpnext.stock.stock_balance import update_bin_qty + item = create_item("_Item with bin qty") + item.stock_uom = "Gram" + item.save() + + update_bin_qty(item.item_code, "_Test Warehouse - _TC", { + "reserved_qty": 10 + }) + + item.stock_uom = "Kilometer" + self.assertRaises(frappe.ValidationError, item.save) + + update_bin_qty(item.item_code, "_Test Warehouse - _TC", { + "reserved_qty": 0 + }) + + item.load_from_db() + item.stock_uom = "Kilometer" + try: + item.save() + except frappe.ValidationError as e: + self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}") def set_item_variant_settings(fields): From 57266a7343edd1fb963d20db28593bed3f80ae50 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 14:21:24 +0530 Subject: [PATCH 0820/1264] refactor: check_stock_uom_with_bin --- erpnext/stock/doctype/item/item.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index ec46f60f2b..a0bd49543e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1183,27 +1183,25 @@ def check_stock_uom_with_bin(item, stock_uom): if stock_uom == frappe.db.get_value("Item", item, "stock_uom"): return - matched = True ref_uom = frappe.db.get_value("Stock Ledger Entry", {"item_code": item}, "stock_uom") if ref_uom: if cstr(ref_uom) != cstr(stock_uom): - matched = False - else: - bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1) - for bin in bin_list: - if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 - or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom): - matched = False - break + frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item)) - if matched and bin_list: - frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item)) + bin_list = frappe.db.sql(""" + select * from tabBin where item_code = %s + and (reserved_qty > 0 or ordered_qty > 0 or indented_qty > 0 or planned_qty > 0) + and stock_uom != %s + """, (item, stock_uom), as_dict=1) + + if bin_list: + frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You need to either cancel the linked documents or create a new Item.").format(item)) + + # No SLE or documents against item. Bin UOM can be changed safely. + frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item)) - if not matched: - frappe.throw( - _("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item)) def get_item_defaults(item_code, company): item = frappe.get_cached_doc('Item', item_code) From e971b4592e3bb1894294f561a522f2b06336908b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 15:33:06 +0530 Subject: [PATCH 0821/1264] test: add test for is_stock_item --- erpnext/stock/doctype/item/test_item.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 8df12a3f16..d9c77efc24 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -10,7 +10,8 @@ from frappe.test_runner import make_test_objects from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError, InvalidItemAttributeValueError, get_variant) from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode -from erpnext.stock.doctype.item.item import get_uom_conv_factor, get_item_attribute +from erpnext.stock.doctype.item.item import (get_uom_conv_factor, get_item_attribute, + validate_is_stock_item) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details @@ -544,6 +545,13 @@ class TestItem(unittest.TestCase): except frappe.ValidationError as e: self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}") + def test_validate_stock_item(self): + self.assertRaises(frappe.ValidationError, validate_is_stock_item, "_Test Non Stock Item") + + try: + validate_is_stock_item("_Test Item") + except frappe.ValidationError as e: + self.fail(f"stock item considered non-stock item: {e}") def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') From 42e057d079c1807393d376d347762e97100b6883 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 15:45:25 +0530 Subject: [PATCH 0822/1264] test: add test for get_timeline_data in item --- erpnext/stock/doctype/item/test_item.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index d9c77efc24..234a9132c2 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -11,7 +11,7 @@ from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsE InvalidItemAttributeValueError, get_variant) from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode from erpnext.stock.doctype.item.item import (get_uom_conv_factor, get_item_attribute, - validate_is_stock_item) + validate_is_stock_item, get_timeline_data) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details @@ -488,6 +488,20 @@ class TestItem(unittest.TestCase): new_barcode.barcode_type = 'EAN' self.assertRaises(InvalidBarcode, item_doc.save) + def test_heatmap_data(self): + import time + data = get_timeline_data("Item", "_Test Item") + self.assertTrue(isinstance(data, dict)) + + now = time.time() + one_year_ago = now - 366 * 24 * 60 * 60 + + for timestamp, count in data.items(): + self.assertIsInstance(timestamp, int) + self.assertTrue(one_year_ago <= timestamp <= now) + self.assertIsInstance(count, int) + self.assertTrue(count >= 0) + def test_index_creation(self): "check if index is getting created in db" from erpnext.stock.doctype.item.item import on_doctype_update From 76dd6e904682a1bec1ff21d66c45e164fd26a47b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 16:19:48 +0530 Subject: [PATCH 0823/1264] test: contextmanager to change settings --- erpnext/tests/utils.py | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 16ecd5180b..11eb6afc1f 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -1,7 +1,8 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +import copy +from contextlib import contextmanager import frappe @@ -41,3 +42,38 @@ def create_test_contact_and_address(): contact.add_email("test_contact_customer@example.com", is_primary=True) contact.add_phone("+91 0000000000", is_primary_phone=True) contact.insert() + + +@contextmanager +def change_settings(doctype, settings_dict): + """ A context manager to ensure that settings are changed before running + function and restored after running it regardless of exceptions occured. + This is useful in tests where you want to make changes in a function but + don't retain those changes. + import and use as decorator to cover full function or using `with` statement. + + example: + @change_settings("Stock Settings", {"item_naming_by": "Naming Series"}) + def test_case(self): + ... + """ + + try: + settings = frappe.get_doc(doctype) + # remember setting + previous_settings = copy.deepcopy(settings_dict) + for key in previous_settings: + previous_settings[key] = getattr(settings, key) + + # change setting + for key, value in settings_dict.items(): + setattr(settings, key, value) + settings.save() + yield # yield control to calling function + + finally: + # restore settings + settings = frappe.get_doc(doctype) + for key, value in previous_settings.items(): + setattr(settings, key, value) + settings.save() From c15fef571fda7fa6bf2ae7f43380b29098775d87 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 16:26:37 +0530 Subject: [PATCH 0824/1264] test: item naming series behaviour --- erpnext/stock/doctype/item/test_item.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 234a9132c2..9adacdfb78 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -14,6 +14,7 @@ from erpnext.stock.doctype.item.item import (get_uom_conv_factor, get_item_attri validate_is_stock_item, get_timeline_data) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details +from erpnext.tests.utils import change_settings test_ignore = ["BOM"] @@ -567,6 +568,13 @@ class TestItem(unittest.TestCase): except frappe.ValidationError as e: self.fail(f"stock item considered non-stock item: {e}") + @change_settings("Stock Settings", {"item_naming_by": "Naming Series"}) + def test_autoname_series(self): + item = frappe.new_doc("Item") + item.item_group = "All Item Groups" + item.save() # if item code saved without item_code then series worked + + def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') doc.set('fields', fields) From 3aed662f4690ad6fb5fda680aad4246103eded81 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 16:41:03 +0530 Subject: [PATCH 0825/1264] chore: translation / semgrep / sider fixes --- erpnext/stock/doctype/item/item.py | 6 +++--- erpnext/stock/doctype/item/test_item.py | 10 +++++----- .../stock_reconciliation/stock_reconciliation.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a0bd49543e..dd815404fa 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -365,8 +365,8 @@ class Item(WebsiteGenerator): # load attributes for v in context.variants: v.attributes = frappe.get_all("Item Variant Attribute", - fields=["attribute", "attribute_value"], - filters={"parent": v.name}) + fields=["attribute", "attribute_value"], + filters={"parent": v.name}) # make a map for easier access in templates v.attribute_map = frappe._dict({}) for attr in v.attributes: @@ -1256,7 +1256,7 @@ def get_uom_conv_factor(uom, stock_uom): inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": from_uom, "from_uom": to_uom}, ["value"], as_dict=1) if inverse_match: - return 1 / inverse_match.value + return 1 / inverse_match.value # This attempts to try and get conversion from intermediate UOM. # case: diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 9adacdfb78..406039dc58 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -518,11 +518,11 @@ class TestItem(unittest.TestCase): def test_attribute_completions(self): expected_attrs = [{'attribute_value': 'Small'}, - {'attribute_value': 'Extra Small'}, - {'attribute_value': 'Extra Large'}, - {'attribute_value': 'Large'}, - {'attribute_value': '2XL'}, - {'attribute_value': 'Medium'}] + {'attribute_value': 'Extra Small'}, + {'attribute_value': 'Extra Large'}, + {'attribute_value': 'Large'}, + {'attribute_value': '2XL'}, + {'attribute_value': 'Medium'}] attrs = get_item_attribute("Test Size") self.assertEqual(attrs, expected_attrs) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 96b1cadaaf..b9f91906c6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -96,7 +96,7 @@ class StockReconciliation(StockController): def validate_data(self): def _get_msg(row_num, msg): - return _("Row # {0}: ").format(row_num+1) + msg + return _("Row # {0}:").format(row_num+1) + " " + msg self.validation_messages = [] item_warehouse_combinations = [] @@ -182,7 +182,7 @@ class StockReconciliation(StockController): validate_cancelled_item(item_code, item.docstatus) except Exception as e: - self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e)) + self.validation_messages.append(_("Row #") + " " + ("%d: " % (row.idx)) + cstr(e)) def update_stock_ledger(self): """ find difference between current and expected entries From 15f8a0fb22addd730b1ebfb43635cfb29f1ddb90 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 18:10:21 +0530 Subject: [PATCH 0826/1264] test: fix flaky test --- erpnext/stock/doctype/item/test_item.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 406039dc58..c7467a5a0f 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -517,18 +517,15 @@ class TestItem(unittest.TestCase): self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") def test_attribute_completions(self): - expected_attrs = [{'attribute_value': 'Small'}, - {'attribute_value': 'Extra Small'}, - {'attribute_value': 'Extra Large'}, - {'attribute_value': 'Large'}, - {'attribute_value': '2XL'}, - {'attribute_value': 'Medium'}] + expected_attrs = {"Small", "Extra Small", "Extra Large", "Large", "2XL", "Medium"} attrs = get_item_attribute("Test Size") - self.assertEqual(attrs, expected_attrs) + received_attrs = {attr.attribute_value for attr in attrs} + self.assertEqual(received_attrs, expected_attrs) attrs = get_item_attribute("Test Size", attribute_value="extra") - self.assertEqual(attrs, [{'attribute_value': 'Extra Small'}, {'attribute_value': 'Extra Large'}]) + received_attrs = {attr.attribute_value for attr in attrs} + self.assertEqual(received_attrs, {"Extra Small", "Extra Large"}) def test_check_stock_uom_with_bin(self): # this item has opening stock and stock_uom set in test_records. From b6d061fa8c90c678cfd2095577fbe7e3eab1f218 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 24 May 2021 18:22:03 +0530 Subject: [PATCH 0827/1264] fix(pos): handle db lock timeout error while pos closing (#25813) --- .../pos_invoice_merge_log.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 index b0ddea304c..08e072e204 100644 --- 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 @@ -42,8 +42,9 @@ class POSInvoiceMergeLog(Document): if return_against_status != "Consolidated": # if return entry is not getting merged in the current pos closing and if it is not consolidated bold_unconsolidated = frappe.bold("not Consolidated") - msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ") + msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}.") .format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated)) + msg += " " msg += _("Original invoice should be consolidated before or along with the return invoice.") msg += "

    " msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against) @@ -274,9 +275,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): closing_entry.db_set('error_message', '') closing_entry.update_opening_entry() - except Exception: + except Exception as e: frappe.db.rollback() - message_log = frappe.message_log.pop() + message_log = frappe.message_log.pop() if frappe.message_log else str(e) error_message = safe_load_json(message_log) if closing_entry: @@ -300,9 +301,9 @@ def cancel_merge_logs(merge_logs, closing_entry=None): closing_entry.db_set('error_message', '') closing_entry.update_opening_entry(for_cancel=True) - except Exception: + except Exception as e: frappe.db.rollback() - message_log = frappe.message_log.pop() + message_log = frappe.message_log.pop() if frappe.message_log else str(e) error_message = safe_load_json(message_log) if closing_entry: @@ -348,11 +349,9 @@ def job_already_enqueued(job_name): return True def safe_load_json(message): - JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError - try: json_message = json.loads(message).get('message') - except JSONDecodeError: + except Exception: json_message = message return json_message \ No newline at end of file From dd5b31f8c45ac78b12f07b6cff31bedffbb68762 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 24 May 2021 18:23:03 +0530 Subject: [PATCH 0828/1264] fix: set disable rounded total if it is globally enabled (#25789) --- erpnext/setup/doctype/global_defaults/global_defaults.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index e587217181..a0ba1efb5b 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -59,13 +59,15 @@ class GlobalDefaults(Document): # Make property setters to hide rounded total fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", - "Supplier Quotation", "Purchase Order", "Purchase Invoice"): + "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"): make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "disable_rounded_total", "default", cint(self.disable_rounded_total), "Text", validate_fields_for_doctype=False) + def toggle_in_words(self): self.disable_in_words = cint(self.disable_in_words) From c348215f61abb478fb0ddb64f4ed9b6e251897ff Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 18 May 2021 08:38:49 +0530 Subject: [PATCH 0829/1264] fix: Cashlfow mapper not showing data --- erpnext/accounts/report/cash_flow/custom_cash_flow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py index fe2bc725e0..ff87276a87 100644 --- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py +++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py @@ -165,7 +165,7 @@ def add_data_for_operating_activities( if profit_data: profit_data.update({ "indent": 1, - "parent_account": get_mapper_for(light_mappers, position=0)['section_header'] + "parent_account": get_mapper_for(light_mappers, position=1)['section_header'] }) data.append(profit_data) section_data.append(profit_data) @@ -312,10 +312,10 @@ def add_data_for_other_activities( def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper): data = [] - operating_activities_mapper = get_mapper_for(light_mappers, position=0) + operating_activities_mapper = get_mapper_for(light_mappers, position=1) other_mappers = [ - get_mapper_for(light_mappers, position=1), - get_mapper_for(light_mappers, position=2) + get_mapper_for(light_mappers, position=2), + get_mapper_for(light_mappers, position=3) ] if operating_activities_mapper: From 25112244ed5c3a4dc62a14bacd5362d3febff356 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 14 May 2021 21:31:22 +0530 Subject: [PATCH 0830/1264] fix: Ignore rounding diff while importig JV using data import --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index fefab82efc..ed1bd28223 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -39,7 +39,11 @@ class JournalEntry(AccountsController): self.validate_multi_currency() self.set_amounts_in_company_currency() self.validate_debit_credit_amount() - self.validate_total_debit_and_credit() + + # Do not validate while importing via data import + if not frappe.flags.in_import: + self.validate_total_debit_and_credit() + self.validate_against_jv() self.validate_reference_doc() self.set_against_account() From 5670cf43862d4de29e905e185a25fe9d50b5038c Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 24 May 2021 20:24:20 +0530 Subject: [PATCH 0831/1264] feat: enhancements in Training Event (#25782) * feat: Some Minor fixes * fix: patch file * fix: patch Co-authored-by: Rucha Mahabal --- .../doctype/training_event/training_event.js | 32 +- .../training_event_employee.json | 301 ++++-------------- .../training_scheduled.json | 8 +- .../training_scheduled/training_scheduled.md | 3 + erpnext/patches.txt | 1 + .../v13_0/set_training_event_attendance.py | 9 + 6 files changed, 113 insertions(+), 241 deletions(-) create mode 100644 erpnext/patches/v13_0/set_training_event_attendance.py diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js index 12bc920b18..b7d34b178a 100644 --- a/erpnext/hr/doctype/training_event/training_event.js +++ b/erpnext/hr/doctype/training_event/training_event.js @@ -2,23 +2,41 @@ // For license information, please see license.txt frappe.ui.form.on('Training Event', { - onload_post_render: function(frm) { + onload_post_render: function (frm) { frm.get_field("employees").grid.set_multiple_add("employee"); }, - refresh: function(frm) { - if(!frm.doc.__islocal) { - frm.add_custom_button(__("Training Result"), function() { + refresh: function (frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("Training Result"), function () { frappe.route_options = { training_event: frm.doc.name - } + }; frappe.set_route("List", "Training Result"); }); - frm.add_custom_button(__("Training Feedback"), function() { + frm.add_custom_button(__("Training Feedback"), function () { frappe.route_options = { training_event: frm.doc.name - } + }; frappe.set_route("List", "Training Feedback"); }); } } }); + +frappe.ui.form.on("Training Event Employee", { + employee: function (frm) { + let emp = []; + for (let d in frm.doc.employees) { + if (frm.doc.employees[d].employee) { + emp.push(frm.doc.employees[d].employee); + } + } + frm.set_query("employee", "employees", function () { + return { + filters: { + name: ["NOT IN", emp] + } + }; + }); + } +}); diff --git a/erpnext/hr/doctype/training_event_employee/training_event_employee.json b/erpnext/hr/doctype/training_event_employee/training_event_employee.json index e3a40649b4..2d313e9fac 100644 --- a/erpnext/hr/doctype/training_event_employee/training_event_employee.json +++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.json @@ -1,241 +1,80 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-08-08 05:33:39.965305", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-08-08 05:33:39.965305", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "column_break_3", + "status", + "attendance", + "is_mandatory" + ], "fields": [ { - "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": 1, - "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": 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", + "in_list_view": 1, + "label": "Employee", + "options": "Employee" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "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": "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Read Only", + "label": "Employee Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "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 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "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 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Open", - "fieldname": "status", - "fieldtype": "Select", - "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": "Status", - "length": 0, - "no_copy": 1, - "options": "Open\nInvited\nCompleted\nFeedback Submitted", - "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": "Open", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "no_copy": 1, + "options": "Open\nInvited\nCompleted\nFeedback Submitted" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "attendance", - "fieldtype": "Select", - "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": "Attendance", - "length": 0, - "no_copy": 0, - "options": "Mandatory\nOptional", - "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": "attendance", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Attendance", + "options": "Present\nAbsent" + }, + { + "columns": 2, + "default": "1", + "fieldname": "is_mandatory", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Mandatory" } - ], - "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-01-30 11:28:16.170333", - "modified_by": "Administrator", - "module": "HR", - "name": "Training Event Employee", - "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 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-21 12:41:59.336237", + "modified_by": "Administrator", + "module": "HR", + "name": "Training Event Employee", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json index 966b887572..e49541e321 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.json +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json @@ -11,16 +11,18 @@ "event": "Submit", "idx": 0, "is_standard": 1, - "message": "\n \n \n \n \n \n \n \n
    \n
    \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n
    \n
    \n\n\n \n \n \n \n \n \n \n
    \n
    \n
      \n
    • {{ doc.introduction }}
    • \n
    • {{_(\"Event Location\")}}: {{ doc.location }}
    • \n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n
    • {{_(\"Date\")}}: {{ start.strftime(\"%A, %d %b %Y\") }}
    • \n
    • \n {{_(\"Timing\")}}: {{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}\n
    • \n {% else %}\n
    • {{_(\"Start Time\")}}: {{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n
    • {{_(\"End Time\")}}: {{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n {% endif %}\n
    \n {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n
    \n
    ", - "modified": "2019-11-29 15:38:31.805409", + "message": "\n \n \n \n \n \n \n \n
    \n
    \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n
    \n
    \n\n\n \n \n \n \n \n \n \n
    \n
    \n {{ doc.introduction }}\n
      \n
    • {{_(\"Event Location\")}}: {{ doc.location }}
    • \n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n
    • {{_(\"Date\")}}: {{ start.strftime(\"%A, %d %b %Y\") }}
    • \n
    • \n {{_(\"Timing\")}}: {{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}\n
    • \n {% else %}\n
    • {{_(\"Start Time\")}}: {{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n
    • {{_(\"End Time\")}}: {{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n {% endif %}\n
    • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
    • \n {% if doc.is_mandatory %}\n
    • Note: This Training Event is mandatory
    • \n {% endif %}\n
    \n
    \n
    ", + "modified": "2021-05-24 16:29:13.165930", "modified_by": "Administrator", "module": "HR", "name": "Training Scheduled", "owner": "Administrator", "recipients": [ { - "email_by_document_field": "employee_emails" + "receiver_by_document_field": "employee_emails" } ], + "send_system_notification": 0, + "send_to_all_assignees": 0, "subject": "Training Scheduled: {{ doc.name }}" } \ No newline at end of file diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md index 374038ac20..418fd4990e 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.md +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md @@ -35,6 +35,9 @@ {% endif %}
  • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
  • + {% if doc.is_mandatory %} +
  • Note: This Training Event is mandatory
  • + {% endif %}
    diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d4655e19b9..1e8ce3c658 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -780,3 +780,4 @@ erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed erpnext.patches.v13_0.update_timesheet_changes +erpnext.patches.v13_0.set_training_event_attendance diff --git a/erpnext/patches/v13_0/set_training_event_attendance.py b/erpnext/patches/v13_0/set_training_event_attendance.py new file mode 100644 index 0000000000..18cad8d86c --- /dev/null +++ b/erpnext/patches/v13_0/set_training_event_attendance.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('hr', 'doctype', 'training_event') + frappe.reload_doc('hr', 'doctype', 'training_event_employee') + + frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'") + frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'") \ No newline at end of file From b4f0347c02ded2df18156b5a999ca50b82a5ce38 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 May 2021 20:12:24 +0530 Subject: [PATCH 0832/1264] fix: removed serial no validation for sales invoice --- .../doctype/sales_invoice/sales_invoice.py | 21 ------------------- .../sales_invoice/test_sales_invoice.py | 6 ------ 2 files changed, 27 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bb74a02606..1a1f889657 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1121,7 +1121,6 @@ class SalesInvoice(SellingController): """ self.set_serial_no_against_delivery_note() self.validate_serial_against_delivery_note() - self.validate_serial_against_sales_invoice() def set_serial_no_against_delivery_note(self): for item in self.items: @@ -1152,26 +1151,6 @@ class SalesInvoice(SellingController): frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format( item.idx, item.qty, item.item_code, len(si_serial_nos))) - def validate_serial_against_sales_invoice(self): - """ check if serial number is already used in other sales invoice """ - for item in self.items: - if not item.serial_no: - continue - - for serial_no in item.serial_no.split("\n"): - serial_no_details = frappe.db.get_value("Serial No", serial_no, - ["sales_invoice", "item_code"], as_dict=1) - - if not serial_no_details: - continue - - if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \ - and self.name != serial_no_details.sales_invoice: - sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company") - if sales_invoice_company == self.company: - frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}") - .format(serial_no, serial_no_details.sales_invoice)) - def update_project(self): if self.project: project = frappe.get_doc("Project", self.project) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 9059d0b040..df6d483904 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -933,12 +933,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse")) self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no"), si.name) - self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"), - si.name) - - # check if the serial number is already linked with any other Sales Invoice - _si = frappe.copy_doc(si.as_dict()) - self.assertRaises(frappe.ValidationError, _si.insert) return si From 073dcf7e4265539ee521c489202aceb0c38cf84a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 May 2021 14:06:10 +0530 Subject: [PATCH 0833/1264] ci(semgrep): fix false positives (#25823) --- .github/helper/semgrep_rules/translate.py | 8 ++++++++ .github/helper/semgrep_rules/translate.yml | 4 ++-- .github/helper/semgrep_rules/ux.js | 9 +++++++++ .github/helper/semgrep_rules/ux.py | 18 ++++++++--------- .github/helper/semgrep_rules/ux.yml | 23 ++++++++++++++++++---- 5 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 .github/helper/semgrep_rules/ux.js diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py index bd6cd9126c..9de6aa94f0 100644 --- a/.github/helper/semgrep_rules/translate.py +++ b/.github/helper/semgrep_rules/translate.py @@ -51,3 +51,11 @@ _(f"what" + f"this is also not cool") _("") # ruleid: frappe-translation-empty-string _('') + + +class Test: + # ok: frappe-translation-python-splitting + def __init__( + args + ): + pass diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml index fa4ec9e15d..5f03fb9fd0 100644 --- a/.github/helper/semgrep_rules/translate.yml +++ b/.github/helper/semgrep_rules/translate.yml @@ -44,8 +44,8 @@ rules: pattern-either: - pattern: _(...) + _(...) - pattern: _("..." + "...") - - pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\` - - pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( ) + - pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\` + - pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( ) message: | Do not split strings inside translate function. Do not concatenate using translate functions. Please refer: https://frappeframework.com/docs/user/en/translations diff --git a/.github/helper/semgrep_rules/ux.js b/.github/helper/semgrep_rules/ux.js new file mode 100644 index 0000000000..ae73f9cc60 --- /dev/null +++ b/.github/helper/semgrep_rules/ux.js @@ -0,0 +1,9 @@ + +// ok: frappe-missing-translate-function-js +frappe.msgprint('{{ _("Both login and password required") }}'); + +// ruleid: frappe-missing-translate-function-js +frappe.msgprint('What'); + +// ok: frappe-missing-translate-function-js +frappe.throw(' {{ _("Both login and password required") }}. '); diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py index 4a74457435..a00d3cd8ae 100644 --- a/.github/helper/semgrep_rules/ux.py +++ b/.github/helper/semgrep_rules/ux.py @@ -2,30 +2,30 @@ import frappe from frappe import msgprint, throw, _ -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python throw("Error Occured") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python frappe.throw("Error Occured") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python frappe.msgprint("Useful message") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python msgprint("Useful message") -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python translatedmessage = _("Hello") -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python throw(translatedmessage) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python msgprint(translatedmessage) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python msgprint(_("Helpful message")) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python frappe.throw(_("Error occured")) diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml index ed06a6a80c..dd667f36c0 100644 --- a/.github/helper/semgrep_rules/ux.yml +++ b/.github/helper/semgrep_rules/ux.yml @@ -1,15 +1,30 @@ rules: -- id: frappe-missing-translate-function +- id: frappe-missing-translate-function-python pattern-either: - patterns: - pattern: frappe.msgprint("...", ...) - pattern-not: frappe.msgprint(_("..."), ...) - - pattern-not: frappe.msgprint(__("..."), ...) - patterns: - pattern: frappe.throw("...", ...) - pattern-not: frappe.throw(_("..."), ...) - - pattern-not: frappe.throw(__("..."), ...) message: | All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations - languages: [python, javascript, json] + languages: [python] + severity: ERROR + +- id: frappe-missing-translate-function-js + pattern-either: + - patterns: + - pattern: frappe.msgprint("...", ...) + - pattern-not: frappe.msgprint(__("..."), ...) + # ignore microtemplating e.g. msgprint("{{ _("server side translation") }}") + - pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...) + - patterns: + - pattern: frappe.throw("...", ...) + - pattern-not: frappe.throw(__("..."), ...) + # ignore microtemplating + - pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...) + message: | + All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations + languages: [javascript] severity: ERROR From 3efd411ddb6ddb9b4f655e48d50cd7d8efa62fd6 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Tue, 25 May 2021 16:24:01 +0530 Subject: [PATCH 0834/1264] fix: Hold status is added in the report --- .../report/issue_summary/issue_summary.js | 1 + .../report/issue_summary/issue_summary.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js index eb0e06cd08..a5e1de627e 100644 --- a/erpnext/support/report/issue_summary/issue_summary.js +++ b/erpnext/support/report/issue_summary/issue_summary.js @@ -42,6 +42,7 @@ frappe.query_reports["Issue Summary"] = { "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, + {label: __('Hold'), value: 'Hold'}, {label: __('Resolved'), value: 'Resolved'}, {label: __('Closed'), value: 'Closed'} ] diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py index 7861e30d25..d93790d593 100644 --- a/erpnext/support/report/issue_summary/issue_summary.py +++ b/erpnext/support/report/issue_summary/issue_summary.py @@ -62,7 +62,7 @@ class IssueSummary(object): 'width': 200 }) - self.statuses = ['Open', 'Replied', 'Resolved', 'Closed'] + self.statuses = ['Open', 'Replied', 'Hold', 'Resolved', 'Closed'] for status in self.statuses: self.columns.append({ 'label': _(status), @@ -265,6 +265,7 @@ class IssueSummary(object): labels = [] open_issues = [] replied_issues = [] + hold_issues = [] resolved_issues = [] closed_issues = [] @@ -277,6 +278,7 @@ class IssueSummary(object): labels.append(entry.get(entity_field)) open_issues.append(entry.get('open')) replied_issues.append(entry.get('replied')) + hold_issues.append(entry.get('hold')) resolved_issues.append(entry.get('resolved')) closed_issues.append(entry.get('closed')) @@ -292,6 +294,10 @@ class IssueSummary(object): 'name': 'Replied', 'values': replied_issues[:30] }, + { + 'name': 'Hold', + 'values': hold_issues[:30] + }, { 'name': 'Resolved', 'values': resolved_issues[:30] @@ -313,12 +319,14 @@ class IssueSummary(object): open_issues = 0 replied = 0 + hold = 0 resolved = 0 closed = 0 for entry in self.data: open_issues += entry.get('open') replied += entry.get('replied') + hold += entry.get('hold') resolved += entry.get('resolved') closed += entry.get('closed') @@ -335,6 +343,12 @@ class IssueSummary(object): 'label': _('Replied'), 'datatype': 'Int', }, + { + 'value': hold, + 'indicator': 'Grey', + 'label': _('Hold'), + 'datatype': 'Int', + }, { 'value': resolved, 'indicator': 'Green', From c9aa7268187748c0cbb5189768d463b71b9880f5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Sep 2020 20:30:24 +0530 Subject: [PATCH 0835/1264] feat: Tax deduction against advance payments --- .../advance_taxes_and_charges/__init__.py | 0 .../advance_taxes_and_charges.json | 201 ++++++++++++++++++ .../advance_taxes_and_charges.py | 10 + 3 files changed, 211 insertions(+) create mode 100644 erpnext/accounts/doctype/advance_taxes_and_charges/__init__.py create mode 100644 erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json create mode 100644 erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/__init__.py b/erpnext/accounts/doctype/advance_taxes_and_charges/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json new file mode 100644 index 0000000000..79eda8c5fe --- /dev/null +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -0,0 +1,201 @@ +{ + "actions": [], + "creation": "2020-09-12 22:26:19.594367", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "add_deduct_tax", + "charge_type", + "row_id", + "account_head", + "col_break_1", + "description", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "section_break_8", + "rate", + "section_break_9", + "tax_amount", + "total", + "column_break_13", + "base_tax_amount", + "base_total" + ], + "fields": [ + { + "columns": 2, + "fieldname": "charge_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "oldfieldname": "charge_type", + "oldfieldtype": "Select", + "options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1", + "fieldname": "row_id", + "fieldtype": "Data", + "label": "Reference Row #", + "oldfieldname": "row_id", + "oldfieldtype": "Data", + "show_days": 1, + "show_seconds": 1 + }, + { + "columns": 2, + "fieldname": "account_head", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account Head", + "oldfieldname": "account_head", + "oldfieldtype": "Link", + "options": "Account", + "reqd": 1, + "search_index": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "col_break_1", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, + "width": "50%" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Small Text", + "print_width": "300px", + "reqd": 1, + "show_days": 1, + "show_seconds": 1, + "width": "300px" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": ":Company", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "oldfieldname": "cost_center_other_charges", + "oldfieldtype": "Link", + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "columns": 2, + "fieldname": "rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Rate", + "oldfieldname": "rate", + "oldfieldtype": "Currency", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "columns": 2, + "fieldname": "tax_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "show_days": 1, + "show_seconds": 1 + }, + { + "columns": 2, + "fieldname": "total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total", + "options": "currency", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "base_tax_amount", + "fieldtype": "Currency", + "label": "Amount (Company Currency)", + "oldfieldname": "tax_amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "base_total", + "fieldtype": "Currency", + "label": "Total (Company Currency)", + "oldfieldname": "total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "add_deduct_tax", + "fieldtype": "Select", + "label": "Add Or Deduct", + "options": "Add\nDeduct", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-09-12 22:30:36.150935", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Advance Taxes and Charges", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "ASC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py new file mode 100644 index 0000000000..597d2ccc62 --- /dev/null +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.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 AdvanceTaxesandCharges(Document): + pass From 61c5e478afada33b5d8ffe43fc174d345f581e03 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Oct 2020 13:56:55 +0530 Subject: [PATCH 0836/1264] feat: Tax deduction against advance payments --- .../doctype/payment_entry/payment_entry.js | 137 ++++++- .../doctype/payment_entry/payment_entry.json | 358 ++++++++++++++---- .../doctype/payment_entry/payment_entry.py | 114 +++++- .../payment_entry_deduction.json | 196 +++------- erpnext/controllers/accounts_controller.py | 10 + erpnext/public/js/controllers/accounts.js | 2 +- 6 files changed, 607 insertions(+), 210 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index b80e8ada38..29e4a31dad 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -3,6 +3,8 @@ {% include "erpnext/public/js/controllers/accounts.js" %} frappe.provide("erpnext.accounts.dimensions"); +cur_frm.cscript.tax_table = "Advance Taxes and Charges"; + frappe.ui.form.on('Payment Entry', { onload: function(frm) { if(frm.doc.__islocal) { @@ -182,6 +184,8 @@ frappe.ui.form.on('Payment Entry', { frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)); frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency); + frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges && + (frm.doc.paid_from_account_currency != company_currency)); frm.toggle_display("base_received_amount", ( frm.doc.paid_to_account_currency != company_currency @@ -843,12 +847,12 @@ frappe.ui.form.on('Payment Entry', { if(frm.doc.payment_type == "Receive" && frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions && frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) { - unallocated_amount = (frm.doc.base_received_amount + total_deductions - - frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate; + unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges + + frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate; } else if (frm.doc.payment_type == "Pay" && frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions && frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) { - unallocated_amount = (frm.doc.base_paid_amount - (total_deductions + unallocated_amount = (frm.doc.base_paid_amount + frm.doc.base_total_taxes_and_charges - (total_deductions + frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate; } } @@ -874,7 +878,8 @@ frappe.ui.form.on('Payment Entry', { var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [], function(d) { return flt(d.amount) })); - frm.set_value("difference_amount", difference_amount - total_deductions); + frm.set_value("difference_amount", difference_amount - total_deductions + + frm.doc.base_total_taxes_and_charges); frm.events.hide_unhide_fields(frm); }, @@ -1002,7 +1007,107 @@ frappe.ui.form.on('Payment Entry', { } }); } - } + }, + + sales_taxes_and_charges_template: function(frm) { + frm.trigger('fetch_taxes_from_template'); + }, + + purchase_taxes_and_charges_template: function(frm) { + frm.trigger('fetch_taxes_from_template'); + }, + + fetch_taxes_from_template: function(frm) { + let master_doctype = ''; + let taxes_and_charges = ''; + + if (frm.doc.party_type == 'Supplier') { + master_doctype = 'Purchase Taxes and Charges Template'; + taxes_and_charges = frm.doc.purchase_taxes_and_charges_template; + } else if (frm.doc.party_type == 'Customer') { + master_doctype = 'Sales Taxes and Charges Template'; + taxes_and_charges = frm.doc.sales_taxes_and_charges_template; + } + + if (!taxes_and_charges) { + return; + } + + frappe.call({ + method: "erpnext.controllers.accounts_controller.get_taxes_and_charges", + args: { + "master_doctype": master_doctype, + "master_name": taxes_and_charges + }, + callback: function(r) { + if(!r.exc && r.message) { + // set taxes table + if(r.message) { + for (let tax of r.message) { + if (tax.charge_type === 'On Net Total') { + tax.charge_type = 'On Paid Amount'; + } + me.frm.add_child("taxes", tax); + } + frm.trigger('calculate_taxes'); + frm.events.set_unallocated_amount(frm); + } + } + } + }); + }, + + calculate_taxes: function(frm) { + frm.doc.total_taxes_and_charges = 0.0; + frm.doc.base_total_taxes_and_charges = 0.0; + + $.each(me.frm.doc["taxes"] || [], function(i, tax) { + let tax_rate = tax.rate; + let current_tax_amount = 0.0; + + // To set row_id by default as previous row. + if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) { + if (tax.idx === 1) { + frappe.throw(__("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")); + } + if (!tax.row_id) { + tax.row_id = tax.idx - 1; + } + } + + if(tax.charge_type == "Actual") { + current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax)); + } else if(tax.charge_type == "On Paid Amount") { + current_tax_amount = (tax_rate / 100.0) * frm.doc.paid_amount; + } else if(tax.charge_type == "On Previous Row Amount") { + current_tax_amount = (tax_rate / 100.0) * + frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount; + + } else if(tax.charge_type == "On Previous Row Total") { + current_tax_amount = (tax_rate / 100.0) * + frm.doc["taxes"][cint(tax.row_id) - 1].total; + } + + tax.tax_amount = current_tax_amount; + tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate; + + current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; + + if(i==0) { + tax.total = flt(frm.doc.paid_amount + current_tax_amount, precision("total", tax)); + } else { + tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax)); + } + + tax.base_total = tax.total * frm.doc.source_exchange_rate; + frm.doc.total_taxes_and_charges += current_tax_amount; + frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate; + + frm.refresh_field('taxes'); + frm.refresh_field('total_taxes_and_charges'); + frm.refresh_field('base_total_taxes_and_charges'); + }); + }, }); @@ -1049,6 +1154,28 @@ frappe.ui.form.on('Payment Entry Reference', { } }) +frappe.ui.form.on('Advance Taxes and Charges', { + rate: function(frm) { + frm.events.calculate_taxes(frm); + frm.events.set_unallocated_amount(frm); + }, + + tax_amount : function(frm) { + frm.events.calculate_taxes(frm); + frm.events.set_unallocated_amount(frm); + }, + + row_id: function(frm) { + frm.events.calculate_taxes(frm); + frm.events.set_unallocated_amount(frm); + }, + + taxes_remove: function(frm) { + frm.events.calculate_taxes(frm); + frm.events.set_unallocated_amount(frm); + } +}) + frappe.ui.form.on('Payment Entry Deduction', { amount: function(frm) { frm.events.set_unallocated_amount(frm); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 328584a61a..11ae17093f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -24,6 +24,10 @@ "party_bank_account", "contact_person", "contact_email", + "tds_details_section", + "tax_withholding_category", + "column_break_20", + "apply_tax_withholding_amount", "payment_accounts_section", "party_balance", "paid_from", @@ -52,6 +56,12 @@ "unallocated_amount", "difference_amount", "write_off_difference_amount", + "taxes_and_charges_section", + "purchase_taxes_and_charges_template", + "sales_taxes_and_charges_template", + "taxes", + "base_total_taxes_and_charges", + "total_taxes_and_charges", "deductions_or_loss_section", "deductions", "transaction_references", @@ -82,7 +92,9 @@ { "fieldname": "type_of_payment", "fieldtype": "Section Break", - "label": "Type of Payment" + "label": "Type of Payment", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -92,7 +104,9 @@ "options": "ACC-PAY-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -103,11 +117,15 @@ "label": "Payment Type", "options": "Receive\nPay\nInternal Transfer", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_5", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -116,7 +134,9 @@ "fieldtype": "Date", "in_list_view": 1, "label": "Posting Date", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", @@ -125,26 +145,34 @@ "options": "Company", "print_hide": 1, "remember_last_selected_value": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "in_list_view": 1, "label": "Mode of Payment", - "options": "Mode of Payment" + "options": "Mode of Payment", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:in_list([\"Receive\", \"Pay\"], doc.payment_type)", "fieldname": "party_section", "fieldtype": "Section Break", - "label": "Payment From / To" + "label": "Payment From / To", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:in_list([\"Receive\", \"Pay\"], doc.payment_type) && doc.docstatus==0", @@ -154,7 +182,9 @@ "label": "Party Type", "options": "DocType", "print_hide": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -163,7 +193,9 @@ "fieldtype": "Dynamic Link", "in_standard_filter": 1, "label": "Party", - "options": "party_type" + "options": "party_type", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -171,18 +203,24 @@ "fieldname": "party_name", "fieldtype": "Data", "in_global_search": 1, - "label": "Party Name" + "label": "Party Name", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "party", "fieldname": "contact_person", "fieldtype": "Link", "label": "Contact", - "options": "Contact" + "options": "Contact", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "contact_person", @@ -190,13 +228,17 @@ "fieldtype": "Data", "label": "Email", "options": "Email", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "payment_accounts_section", "fieldtype": "Section Break", - "label": "Accounts" + "label": "Accounts", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "party", @@ -204,7 +246,9 @@ "fieldtype": "Currency", "label": "Party Balance", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -215,7 +259,9 @@ "label": "Account Paid From", "options": "Account", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "paid_from", @@ -225,7 +271,9 @@ "options": "Currency", "print_hide": 1, "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "paid_from", @@ -234,11 +282,15 @@ "label": "Account Balance", "options": "paid_from_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_18", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:(in_list([\"Internal Transfer\", \"Receive\"], doc.payment_type) || doc.party)", @@ -248,7 +300,9 @@ "label": "Account Paid To", "options": "Account", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "paid_to", @@ -258,7 +312,9 @@ "options": "Currency", "print_hide": 1, "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "paid_to", @@ -267,13 +323,17 @@ "label": "Account Balance", "options": "paid_to_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:(doc.paid_to && doc.paid_from)", "fieldname": "payment_amounts_section", "fieldtype": "Section Break", - "label": "Amount" + "label": "Amount", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -281,14 +341,18 @@ "fieldtype": "Currency", "label": "Paid Amount", "options": "paid_from_account_currency", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "source_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_paid_amount", @@ -297,11 +361,15 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_21", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -310,14 +378,18 @@ "label": "Received Amount", "options": "paid_to_account_currency", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "target_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_received_amount", @@ -326,30 +398,40 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:(doc.party && doc.paid_from && doc.paid_to && doc.paid_amount && doc.received_amount)", "fieldname": "section_break_14", "fieldtype": "Section Break", - "label": "Reference" + "label": "Reference", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.docstatus==0", "fieldname": "get_outstanding_invoice", "fieldtype": "Button", - "label": "Get Outstanding Invoice" + "label": "Get Outstanding Invoice", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "references", "fieldtype": "Table", "label": "Payment References", - "options": "Payment Entry Reference" + "options": "Payment Entry Reference", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_34", "fieldtype": "Section Break", - "label": "Writeoff" + "label": "Writeoff", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -358,7 +440,9 @@ "fieldtype": "Currency", "label": "Total Allocated Amount", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total_allocated_amount", @@ -366,23 +450,31 @@ "label": "Total Allocated Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "set_exchange_gain_loss", "fieldtype": "Button", - "label": "Set Exchange Gain / Loss" + "label": "Set Exchange Gain / Loss", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_36", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:(doc.paid_amount && doc.received_amount && doc.references)", "fieldname": "unallocated_amount", "fieldtype": "Currency", "label": "Unallocated Amount", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -392,13 +484,17 @@ "label": "Difference Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "difference_amount", "fieldname": "write_off_difference_amount", "fieldtype": "Button", - "label": "Write Off Difference Amount" + "label": "Write Off Difference Amount", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -406,29 +502,39 @@ "depends_on": "eval:(doc.paid_amount && doc.received_amount)", "fieldname": "deductions_or_loss_section", "fieldtype": "Section Break", - "label": "Deductions or Loss" + "label": "Deductions or Loss", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "deductions", "fieldtype": "Table", "label": "Payment Deductions or Loss", - "options": "Payment Entry Deduction" + "options": "Payment Entry Deduction", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "transaction_references", "fieldtype": "Section Break", - "label": "Transaction ID" + "label": "Transaction ID", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, "depends_on": "eval:(doc.paid_from && doc.paid_to)", "fieldname": "reference_no", "fieldtype": "Data", - "label": "Cheque/Reference No" + "label": "Cheque/Reference No", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_23", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -436,7 +542,9 @@ "fieldname": "reference_date", "fieldtype": "Date", "label": "Cheque/Reference Date", - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.docstatus==1", @@ -445,21 +553,27 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "depends_on": "eval:(doc.paid_from && doc.paid_to && doc.paid_amount && doc.received_amount)", "fieldname": "section_break_12", "fieldtype": "Section Break", - "label": "More Information" + "label": "More Information", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "project", "fieldtype": "Link", "label": "Project", "options": "Project", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "remarks", @@ -470,33 +584,43 @@ }, { "fieldname": "column_break_16", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "letter_head", "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "print_heading", "fieldtype": "Link", "label": "Print Heading", "options": "Print Heading", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "bank_account.bank", "fieldname": "bank", "fieldtype": "Read Only", - "label": "Bank" + "label": "Bank", + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "bank_account.bank_account_no", "fieldname": "bank_account_no", "fieldtype": "Read Only", - "label": "Bank Account No" + "label": "Bank Account No", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_order", @@ -505,12 +629,16 @@ "no_copy": 1, "options": "Payment Order", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "subscription_section", "fieldtype": "Section Break", - "label": "Subscription Section" + "label": "Subscription Section", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -520,7 +648,9 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amended_from", @@ -529,7 +659,9 @@ "no_copy": 1, "options": "Payment Entry", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "title", @@ -544,14 +676,18 @@ "fieldname": "bank_account", "fieldtype": "Link", "label": "Company Bank Account", - "options": "Bank Account" + "options": "Bank Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "party", "fieldname": "party_bank_account", "fieldtype": "Link", "label": "Party Bank Account", - "options": "Bank Account" + "options": "Bank Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_order_status", @@ -559,17 +695,23 @@ "hidden": 1, "label": "Payment Order Status", "options": "Initiated\nPayment Ordered", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions" + "label": "Accounting Dimensions", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "Draft", @@ -584,12 +726,94 @@ "fieldname": "custom_remarks", "fieldtype": "Check", "label": "Custom Remarks" + }, + { + "depends_on": "eval: doc.payment_type == 'Pay' && doc.party_type == 'Supplier'", + "fieldname": "tds_details_section", + "fieldtype": "Section Break", + "label": "TDS Details", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "fieldname": "apply_tax_withholding_amount", + "fieldtype": "Check", + "label": "Apply Tax Withholding Amount", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "collapsible": 1, + "fieldname": "taxes_and_charges_section", + "fieldtype": "Section Break", + "label": "Taxes and Charges", + "show_days": 1, + "show_seconds": 1 + }, + { + "depends_on": "eval:doc.party_type == 'Supplier'", + "fieldname": "purchase_taxes_and_charges_template", + "fieldtype": "Link", + "label": "Taxes and Charges Template", + "options": "Purchase Taxes and Charges Template", + "show_days": 1, + "show_seconds": 1 + }, + { + "depends_on": "eval: doc.party_type == 'Customer'", + "fieldname": "sales_taxes_and_charges_template", + "fieldtype": "Link", + "label": "Taxes and Charges Template", + "options": "Sales Taxes and Charges Template", + "show_days": 1, + "show_seconds": 1 + }, + { + "depends_on": "eval: doc.party_type == 'Supplier' || doc.party_type == 'Customer'", + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Advance Taxes and Charges", + "options": "Advance Taxes and Charges", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "base_total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-03-08 13:05:16.958866", + "modified": "2020-09-13 22:33:59.860146", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 62ab76c323..a41b4512a1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -5,7 +5,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 frappe.utils import flt, comma_or, nowdate, getdate, cint 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 @@ -15,6 +15,7 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from six import string_types, iteritems @@ -52,6 +53,8 @@ class PaymentEntry(AccountsController): self.set_exchange_rate() self.validate_mandatory() self.validate_reference_documents() + self.set_tax_withholding() + self.calculate_taxes() self.set_amounts() self.clear_unallocated_reference_document_rows() self.validate_payment_against_negative_invoice() @@ -386,6 +389,43 @@ class PaymentEntry(AccountsController): else: self.status = 'Draft' + def set_tax_withholding(self): + if not self.party_type == 'Supplier': + return + + if not self.apply_tax_withholding_amount: + return + + if self.references: + return + + args = frappe._dict({ + 'company': self.company, + 'supplier': self.party, + 'posting_date': self.posting_date, + 'net_total': self.paid_amount + }) + + tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category) + + if not tax_withholding_details: + return + + accounts = [] + for d in self.taxes: + if d.account_head == tax_withholding_details.get("account_head"): + d.update(tax_withholding_details) + accounts.append(d.account_head) + + if not accounts or tax_withholding_details.get("account_head") not in accounts: + self.append("taxes", tax_withholding_details) + + to_remove = [d for d in self.taxes + if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")] + + for d in to_remove: + self.remove(d) + def set_amounts(self): self.set_amounts_in_company_currency() self.set_total_allocated_amount() @@ -423,12 +463,12 @@ class PaymentEntry(AccountsController): if self.payment_type == "Receive" \ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): - self.unallocated_amount = (self.base_received_amount + total_deductions - + self.unallocated_amount = (self.base_received_amount + self.base_total_taxes_and_charges + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): - self.unallocated_amount = (self.base_paid_amount - (total_deductions + + self.unallocated_amount = (self.base_paid_amount + self.base_total_taxes_and_charges - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate def set_difference_amount(self): @@ -446,7 +486,7 @@ class PaymentEntry(AccountsController): total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) - self.difference_amount = flt(self.difference_amount - total_deductions, + self.difference_amount = flt(self.difference_amount - total_deductions + self.base_total_taxes_and_charges, self.precision("difference_amount")) # Paid amount is auto allocated in the reference document by default. @@ -532,6 +572,7 @@ class PaymentEntry(AccountsController): self.add_party_gl_entries(gl_entries) self.add_bank_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries) + self.add_tax_gl_entries(gl_entries) make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) @@ -607,6 +648,26 @@ class PaymentEntry(AccountsController): }, item=self) ) + def add_tax_gl_entries(self, gl_entries): + for d in self.get('taxes'): + account_currency = get_account_currency(d.account_head) + if account_currency != self.company_currency: + frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency)) + + dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" + + gl_entries.append( + self.get_gl_dict({ + "account": d.account_head, + "against": self.party if self.payment_type=="Receive" else self.paid_from, + dr_or_cr: d.base_tax_amount, + dr_or_cr + "_in_account_currency": d.base_tax_amount \ + if account_currency==self.company_currency \ + else d.tax_amount, + "cost_center": d.cost_center + }, account_currency, item=d) + ) + def add_deductions_gl_entries(self, gl_entries): for d in self.get("deductions"): if d.amount: @@ -671,6 +732,51 @@ class PaymentEntry(AccountsController): self.append('deductions', row) self.set_unallocated_amount() + def calculate_taxes(self): + self.total_taxes_and_charges = 0.0 + self.base_total_taxes_and_charges = 0.0 + + for i, tax in enumerate(self.taxes): + tax_rate = tax.rate + current_tax_rate = 0.0 + + # To set row_id by default as previous row. + if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]: + if tax.idx == 1: + frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) + + if not tax.row_id: + tax.row_id = tax.idx - 1 + + if tax.charge_type == "Actual": + current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax)) + elif tax.charge_type == "On Paid Amount": + current_tax_amount = (tax_rate / 100.0) * self.paid_amount + elif tax.charge_type == "On Previous Row Amount": + current_tax_amount = (tax_rate / 100.0) * \ + self.taxes[cint(tax.row_id) - 1].tax_amount + + elif tax.charge_type == "On Previous Row Total": + current_tax_amount = (tax_rate / 100.0) * \ + self.taxes[cint(tax.row_id) - 1].total + + tax.tax_amount = current_tax_amount + tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate + + if tax.add_deduct_tax == "Deduct": + current_tax_amount *= -1.0 + else: + current_tax_amount *= 1.0 + + if i == 0: + tax.total = flt(self.paid_amount + current_tax_amount, self.precision("total", tax)) + else: + tax.total = flt(self.taxes[i-1].total + current_tax_amount, self.precision("total", tax)) + + tax.base_total = tax.total * self.source_exchange_rate + self.total_taxes_and_charges += current_tax_amount + self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate + @frappe.whitelist() def get_outstanding_reference_documents(args): diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json index 7060d11691..61a1462dd7 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json @@ -1,140 +1,70 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-06-15 15:56:30.815503", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-06-15 15:56:30.815503", + "doctype": "DocType", + "editable_grid": 1, + "field_order": [ + "account", + "cost_center", + "amount", + "column_break_2", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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": 1, - "in_standard_filter": 0, - "label": "Account", - "length": 0, - "no_copy": 0, - "options": "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "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": "cost_center", - "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": "Cost Center", - "length": 0, - "no_copy": 0, - "options": "Cost Center", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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": "cost_center", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Cost Center", + "options": "Cost Center", + "print_hide": 1, + "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": "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": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "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": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-01-07 16:52:07.040146", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Payment Entry Deduction", - "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 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-09-12 20:38:08.110674", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Entry Deduction", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 544e624725..65e5823b91 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -617,6 +617,10 @@ class AccountsController(TransactionBase): payment_entries = get_advance_payment_entries(party_type, party, party_account, order_doctype, order_list, include_unallocated) + payment_entry_list = [d.reference_name for d in payment_entries] + + payment_entry_taxes = get_payment_entry_taxes(payment_entry_list) + res = journal_entries + payment_entries return res @@ -1241,6 +1245,12 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, return list(payment_entries_against_order) + list(unallocated_payment_entries) +def get_payment_entry_taxes(payment_entry_list): + taxes = frappe.db.sql(""" + SELECT t.parent, t.add_deduct_tax, t.charge_type, t.account_head, t.cost_center, + t.tax_amount FROM `tabAdvance Taxes and Charges` where t.parent in %s""" + ,(payment_entry_list, ), as_dict=1) + def update_invoice_status(): # Daily update the status of the invoices diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index ceeecb28a2..23d636061c 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -165,7 +165,7 @@ cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) { msg = __("Please select Charge Type first"); d.row_id = ""; d.rate = d.tax_amount = 0.0; - } else if((d.charge_type == 'Actual' || d.charge_type == 'On Net Total') && d.row_id) { + } else if((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) { msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"); d.row_id = ""; } else if((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) { From c26de286138426f74f4752c6139ce243c5bf854e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 2 Nov 2020 19:57:27 +0530 Subject: [PATCH 0837/1264] fix: TDS against Purhase Orders --- .../advance_taxes_and_charges.json | 4 +- .../doctype/payment_entry/payment_entry.js | 12 +- .../doctype/payment_entry/payment_entry.json | 31 +- .../doctype/payment_entry/payment_entry.py | 25 +- .../purchase_invoice/purchase_invoice.json | 597 +++++++++++++----- .../purchase_invoice/purchase_invoice.py | 5 +- .../purchase_taxes_and_charges.json | 101 ++- erpnext/controllers/accounts_controller.py | 11 - erpnext/controllers/taxes_and_totals.py | 40 ++ erpnext/public/js/payment/payments.js | 18 +- 10 files changed, 635 insertions(+), 209 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index 79eda8c5fe..5b3299e656 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -33,7 +33,7 @@ "label": "Type", "oldfieldname": "charge_type", "oldfieldtype": "Select", - "options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total", + "options": "\nActual\nOn Paid Amount\nIncluded In Paid Amount\nOn Previous Row Amount\nOn Previous Row Total", "reqd": 1, "show_days": 1, "show_seconds": 1 @@ -190,7 +190,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-12 22:30:36.150935", + "modified": "2020-10-27 20:02:34.734260", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 29e4a31dad..3e11bc0e59 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -220,7 +220,7 @@ frappe.ui.form.on('Payment Entry', { var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: ""; frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount", - "difference_amount"], company_currency); + "difference_amount", "base_paid_amount_after_tax"], company_currency); frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency); frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency); @@ -368,6 +368,16 @@ frappe.ui.form.on('Payment Entry', { } }, + apply_tax_withholding_amount: function(frm) { + if (!frm.doc.apply_tax_withholding_amount) { + frm.set_value("tax_withholding_category", ''); + } else { + frappe.db.get_value('Supplier', frm.doc.party, 'tax_withholding_category', (values) => { + frm.set_value("tax_withholding_category", values.tax_withholding_category); + }); + } + }, + paid_from: function(frm) { if(frm.set_party_account_based_on_party) return; diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 11ae17093f..f158345af6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -25,9 +25,9 @@ "contact_person", "contact_email", "tds_details_section", - "tax_withholding_category", - "column_break_20", "apply_tax_withholding_amount", + "column_break_20", + "tax_withholding_category", "payment_accounts_section", "party_balance", "paid_from", @@ -39,8 +39,10 @@ "paid_to_account_balance", "payment_amounts_section", "paid_amount", + "paid_amount_after_tax", "source_exchange_rate", "base_paid_amount", + "base_paid_amount_after_tax", "column_break_21", "received_amount", "target_exchange_rate", @@ -580,7 +582,9 @@ "fieldtype": "Small Text", "label": "Remarks", "no_copy": 1, - "read_only_depends_on": "eval:doc.custom_remarks == 0" + "read_only_depends_on": "eval:doc.custom_remarks == 0", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_16", @@ -736,6 +740,7 @@ "show_seconds": 1 }, { + "depends_on": "eval:doc.apply_tax_withholding_amount", "fieldname": "tax_withholding_category", "fieldtype": "Link", "label": "Tax Withholding Category", @@ -808,12 +813,30 @@ "read_only": 1, "show_days": 1, "show_seconds": 1 + }, + { + "fieldname": "paid_amount_after_tax", + "fieldtype": "Currency", + "hidden": 1, + "label": "Paid Amount After Tax", + "options": "paid_from_account_currency", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "base_paid_amount_after_tax", + "fieldtype": "Currency", + "label": "Paid Amount After Tax (Company Currency)", + "options": "Company:company:default_currency", + "show_days": 1, + "show_seconds": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-13 22:33:59.860146", + "modified": "2020-10-25 20:50:14.896628", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a41b4512a1..a21deb5431 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -396,9 +396,6 @@ class PaymentEntry(AccountsController): if not self.apply_tax_withholding_amount: return - if self.references: - return - args = frappe._dict({ 'company': self.company, 'supplier': self.party, @@ -427,11 +424,18 @@ class PaymentEntry(AccountsController): self.remove(d) def set_amounts(self): + self.set_amounts_after_tax() self.set_amounts_in_company_currency() self.set_total_allocated_amount() self.set_unallocated_amount() self.set_difference_amount() + def set_amounts_after_tax(self): + self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(self.total_taxes_and_charges), + self.precision("paid_amount_after_tax")) + self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate), + self.precision("base_paid_amount_after_tax")) + def set_amounts_in_company_currency(self): self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0 if self.paid_amount: @@ -463,12 +467,12 @@ class PaymentEntry(AccountsController): if self.payment_type == "Receive" \ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): - self.unallocated_amount = (self.base_received_amount + self.base_total_taxes_and_charges + total_deductions - + self.unallocated_amount = (self.base_received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): - self.unallocated_amount = (self.base_paid_amount + self.base_total_taxes_and_charges - (total_deductions + + self.unallocated_amount = (self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate def set_difference_amount(self): @@ -486,7 +490,7 @@ class PaymentEntry(AccountsController): total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) - self.difference_amount = flt(self.difference_amount - total_deductions + self.base_total_taxes_and_charges, + self.difference_amount = flt(self.difference_amount - total_deductions, self.precision("difference_amount")) # Paid amount is auto allocated in the reference document by default. @@ -631,8 +635,8 @@ class PaymentEntry(AccountsController): "account": self.paid_from, "account_currency": self.paid_from_account_currency, "against": self.party if self.payment_type=="Pay" else self.paid_to, - "credit_in_account_currency": self.paid_amount, - "credit": self.base_paid_amount, + "credit_in_account_currency": self.paid_amount_after_tax, + "credit": self.base_paid_amount_after_tax, "cost_center": self.cost_center }, item=self) ) @@ -654,7 +658,10 @@ class PaymentEntry(AccountsController): if account_currency != self.company_currency: frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency)) - dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" + if self.payment_type == 'Pay': + dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" + elif self.payment_type == 'Receive': + dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" gl_entries.append( self.get_gl_dict({ diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d3d3ffa17f..36450a9255 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -127,6 +127,7 @@ "write_off_cost_center", "advances_section", "allocate_advances_automatically", + "adjust_advance_taxes", "get_advances", "advances", "payment_schedule_section", @@ -175,7 +176,9 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "naming_series", @@ -187,7 +190,9 @@ "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier", @@ -199,7 +204,9 @@ "options": "Supplier", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -211,7 +218,9 @@ "label": "Supplier Name", "oldfieldname": "supplier_name", "oldfieldtype": "Data", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "supplier.tax_id", @@ -219,21 +228,27 @@ "fieldtype": "Read Only", "label": "Tax Id", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "due_date", "fieldtype": "Date", "label": "Due Date", "oldfieldname": "due_date", - "oldfieldtype": "Date" + "oldfieldtype": "Date", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "is_paid", "fieldtype": "Check", "label": "Is Paid", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -241,19 +256,25 @@ "fieldtype": "Check", "label": "Is Return (Debit Note)", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply Tax Withholding Amount", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -263,13 +284,17 @@ "label": "Company", "options": "Company", "print_hide": 1, - "remember_last_selected_value": 1 + "remember_last_selected_value": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "default": "Today", @@ -281,7 +306,9 @@ "oldfieldtype": "Date", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "posting_time", @@ -290,6 +317,8 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", + "show_days": 1, + "show_seconds": 1, "width": "100px" }, { @@ -298,7 +327,9 @@ "fieldname": "set_posting_time", "fieldtype": "Check", "label": "Edit Posting Date and Time", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amended_from", @@ -310,44 +341,58 @@ "oldfieldtype": "Link", "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.on_hold", "fieldname": "sb_14", "fieldtype": "Section Break", - "label": "Hold Invoice" + "label": "Hold Invoice", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "on_hold", "fieldtype": "Check", - "label": "Hold Invoice" + "label": "Hold Invoice", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.on_hold", "description": "Once set, this invoice will be on hold till the set date", "fieldname": "release_date", "fieldtype": "Date", - "label": "Release Date" + "label": "Release Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cb_17", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.on_hold", "fieldname": "hold_comment", "fieldtype": "Small Text", - "label": "Reason For Putting On Hold" + "label": "Reason For Putting On Hold", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "bill_no", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", - "label": "Supplier Invoice Details" + "label": "Supplier Invoice Details", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "bill_no", @@ -355,11 +400,15 @@ "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_15", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "bill_date", @@ -368,13 +417,17 @@ "no_copy": 1, "oldfieldname": "bill_date", "oldfieldtype": "Date", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "return_against", "fieldname": "returns", "fieldtype": "Section Break", - "label": "Returns" + "label": "Returns", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "return_against", @@ -384,26 +437,34 @@ "no_copy": 1, "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact" + "label": "Address and Contact", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier_address", "fieldtype": "Link", "label": "Select Supplier Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "address_display", "fieldtype": "Small Text", "label": "Address", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_person", @@ -411,51 +472,67 @@ "in_global_search": 1, "label": "Contact Person", "options": "Contact", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "label": "Contact", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_email", "fieldtype": "Small Text", "label": "Contact Email", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_address", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address", "fieldtype": "Link", "label": "Select Shipping Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", "label": "Shipping Address", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "currency_and_price_list", "fieldtype": "Section Break", "label": "Currency and Price List", - "options": "fa fa-tag" + "options": "fa fa-tag", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "currency", @@ -464,7 +541,9 @@ "oldfieldname": "currency", "oldfieldtype": "Select", "options": "Currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "conversion_rate", @@ -473,18 +552,24 @@ "oldfieldname": "conversion_rate", "oldfieldtype": "Currency", "precision": "9", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break2", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "buying_price_list", "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "price_list_currency", @@ -492,14 +577,18 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "plc_conversion_rate", "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -508,11 +597,15 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -521,7 +614,9 @@ "fieldtype": "Link", "label": "Set Accepted Warehouse", "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -531,11 +626,15 @@ "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_warehouse", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -543,25 +642,33 @@ "fieldtype": "Select", "label": "Raw Materials Supplied", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "items_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart" + "options": "fa fa-shopping-cart", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "update_stock", "fieldtype": "Check", "label": "Update Stock", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "show_days": 1, + "show_seconds": 1 }, { "allow_bulk_edit": 1, @@ -571,42 +678,56 @@ "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Purchase Invoice Item", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rule_details", "fieldtype": "Section Break", - "label": "Pricing Rules" + "label": "Pricing Rules", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rules", "fieldtype": "Table", "label": "Pricing Rule Detail", "options": "Pricing Rule Detail", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible_depends_on": "supplied_items", "fieldname": "raw_materials_supplied", "fieldtype": "Section Break", - "label": "Raw Materials Supplied" + "label": "Raw Materials Supplied", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplied_items", "fieldtype": "Table", "label": "Supplied Items", "options": "Purchase Receipt Item Supplied", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_26", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_qty", "fieldtype": "Float", "label": "Total Quantity", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total", @@ -614,7 +735,9 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_net_total", @@ -624,18 +747,24 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_28", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total", "fieldtype": "Currency", "label": "Total", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "net_total", @@ -645,42 +774,56 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", "options": "Tax Category", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_49", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", "options": "Shipping Rule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_51", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges", @@ -689,7 +832,9 @@ "oldfieldname": "purchase_other_charges", "oldfieldtype": "Link", "options": "Purchase Taxes and Charges Template", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes", @@ -697,13 +842,17 @@ "label": "Purchase Taxes and Charges", "oldfieldname": "purchase_tax_details", "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges" + "options": "Purchase Taxes and Charges", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "sec_tax_breakup", "fieldtype": "Section Break", - "label": "Tax Breakup" + "label": "Tax Breakup", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "other_charges_calculation", @@ -712,13 +861,17 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "totals", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_taxes_and_charges_added", @@ -728,7 +881,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_taxes_and_charges_deducted", @@ -738,7 +893,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total_taxes_and_charges", @@ -748,11 +905,15 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_40", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges_added", @@ -762,7 +923,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges_deducted", @@ -772,7 +935,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_taxes_and_charges", @@ -780,14 +945,18 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "discount_amount", "fieldname": "section_break_44", "fieldtype": "Section Break", - "label": "Additional Discount" + "label": "Additional Discount", + "show_days": 1, + "show_seconds": 1 }, { "default": "Grand Total", @@ -795,7 +964,9 @@ "fieldtype": "Select", "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_discount_amount", @@ -803,28 +974,38 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_46", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "additional_discount_percentage", "fieldtype": "Float", "label": "Additional Discount Percentage", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_49", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_grand_total", @@ -834,7 +1015,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_rounding_adjustment", @@ -843,7 +1026,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -853,7 +1038,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_in_words", @@ -863,13 +1050,17 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break8", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_hide": 1, + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -880,7 +1071,9 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rounding_adjustment", @@ -889,7 +1082,9 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -899,7 +1094,9 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "in_words", @@ -909,7 +1106,9 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_advance", @@ -920,7 +1119,9 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "outstanding_amount", @@ -931,14 +1132,18 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total" + "label": "Disable Rounded Total", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -946,20 +1151,26 @@ "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", - "label": "Payments" + "label": "Payments", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", "options": "Mode of Payment", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "clearance_date", @@ -967,11 +1178,15 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_br_payments", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_paid", @@ -980,7 +1195,9 @@ "label": "Paid Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_paid_amount", @@ -989,7 +1206,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -997,7 +1216,9 @@ "depends_on": "grand_total", "fieldname": "write_off", "fieldtype": "Section Break", - "label": "Write Off" + "label": "Write Off", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "write_off_amount", @@ -1005,7 +1226,9 @@ "label": "Write Off Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_write_off_amount", @@ -1014,11 +1237,15 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_61", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1026,7 +1253,9 @@ "fieldtype": "Link", "label": "Write Off Account", "options": "Account", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1034,7 +1263,9 @@ "fieldtype": "Link", "label": "Write Off Cost Center", "options": "Cost Center", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1044,13 +1275,17 @@ "label": "Advance Payments", "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "allocate_advances_automatically", "fieldtype": "Check", - "label": "Set Advances and Allocate (FIFO)" + "label": "Set Advances and Allocate (FIFO)", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.allocate_advances_automatically", @@ -1058,7 +1293,9 @@ "fieldtype": "Button", "label": "Get Advances Paid", "oldfieldtype": "Button", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "advances", @@ -1068,20 +1305,26 @@ "oldfieldname": "advance_allocation_details", "oldfieldtype": "Table", "options": "Purchase Invoice Advance", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:(!doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", - "label": "Payment Terms" + "label": "Payment Terms", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_terms_template", "fieldtype": "Link", "label": "Payment Terms Template", - "options": "Payment Terms Template" + "options": "Payment Terms Template", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_schedule", @@ -1089,7 +1332,9 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1097,25 +1342,33 @@ "fieldname": "terms_section_break", "fieldtype": "Section Break", "label": "Terms and Conditions", - "options": "fa fa-legal" + "options": "fa fa-legal", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tc_name", "fieldtype": "Link", "label": "Terms", "options": "Terms and Conditions", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", - "label": "Terms and Conditions1" + "label": "Terms and Conditions1", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings" + "label": "Printing Settings", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1123,7 +1376,9 @@ "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1131,11 +1386,15 @@ "fieldname": "group_same_items", "fieldtype": "Check", "label": "Group same items", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_112", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1147,14 +1406,18 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1 + "report_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "language", "fieldtype": "Data", "label": "Print Language", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1163,7 +1426,9 @@ "label": "More Information", "oldfieldtype": "Section Break", "options": "fa fa-file-text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "credit_to", @@ -1174,7 +1439,9 @@ "options": "Account", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "party_account_currency", @@ -1184,7 +1451,9 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -1194,7 +1463,9 @@ "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "against_expense_account", @@ -1204,11 +1475,15 @@ "no_copy": 1, "oldfieldname": "against_expense_account", "oldfieldtype": "Small Text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_63", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "Draft", @@ -1235,14 +1510,18 @@ "no_copy": 1, "oldfieldname": "remarks", "oldfieldtype": "Text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1251,7 +1530,9 @@ "fieldtype": "Date", "label": "From Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1260,11 +1541,15 @@ "fieldtype": "Date", "label": "To Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_114", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "auto_repeat", @@ -1273,24 +1558,32 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "depends_on": "eval: doc.auto_repeat", "fieldname": "update_auto_repeat_reference", "fieldtype": "Button", - "label": "Update Auto Repeat Reference" + "label": "Update Auto Repeat Reference", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions " + "label": "Accounting Dimensions ", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1298,7 +1591,9 @@ "fieldname": "is_internal_supplier", "fieldtype": "Check", "label": "Is Internal Supplier", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_withholding_category", @@ -1306,19 +1601,25 @@ "hidden": 1, "label": "Tax Withholding Category", "options": "Tax Withholding Category", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "billing_address", "fieldtype": "Link", "label": "Select Billing Address", - "options": "Address" + "options": "Address", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "billing_address_display", "fieldtype": "Small Text", "label": "Billing Address", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "project", @@ -1380,7 +1681,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-04-30 22:45:58.334107", + "modified": "2020-10-27 21:21:22.647349", "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 83e9f7583e..84dd491a56 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1088,9 +1088,10 @@ class PurchaseInvoice(BuyingController): accounts = [] for d in self.taxes: - if d.account_head == tax_withholding_details.get("account_head"): + if d.account_head == tax_withholding_details.get("account_head") and not d.is_advance_tax: d.update(tax_withholding_details) - accounts.append(d.account_head) + if not d.is_advance_tax: + accounts.append(d.account_head) if not accounts or tax_withholding_details.get("account_head") not in accounts: self.append("taxes", tax_withholding_details) diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index f9fdc4b605..de7ef2ac96 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -28,7 +28,8 @@ "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", - "item_wise_tax_detail" + "item_wise_tax_detail", + "is_advance_tax" ], "fields": [ { @@ -39,7 +40,9 @@ "oldfieldname": "category", "oldfieldtype": "Select", "options": "Valuation and Total\nValuation\nTotal", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "Add", @@ -49,7 +52,9 @@ "oldfieldname": "add_deduct_tax", "oldfieldtype": "Select", "options": "Add\nDeduct", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "columns": 2, @@ -61,7 +66,9 @@ "oldfieldname": "charge_type", "oldfieldtype": "Select", "options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total\nOn Item Quantity", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1", @@ -69,7 +76,9 @@ "fieldtype": "Data", "label": "Reference Row #", "oldfieldname": "row_id", - "oldfieldtype": "Data" + "oldfieldtype": "Data", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -77,11 +86,15 @@ "fieldname": "included_in_print_rate", "fieldtype": "Check", "label": "Is this Tax included in Basic Rate?", - "report_hide": 1 + "report_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break1", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "columns": 2, @@ -92,7 +105,9 @@ "oldfieldname": "account_head", "oldfieldtype": "Link", "options": "Account", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": ":Company", @@ -101,7 +116,9 @@ "label": "Cost Center", "oldfieldname": "cost_center", "oldfieldtype": "Link", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "description", @@ -111,11 +128,15 @@ "oldfieldtype": "Small Text", "print_width": "300px", "reqd": 1, + "show_days": 1, + "show_seconds": 1, "width": "300px" }, { "fieldname": "section_break_10", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "columns": 2, @@ -124,11 +145,15 @@ "in_list_view": 1, "label": "Rate", "oldfieldname": "rate", - "oldfieldtype": "Currency" + "oldfieldtype": "Currency", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_9", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "columns": 2, @@ -138,7 +163,9 @@ "label": "Amount", "oldfieldname": "tax_amount", "oldfieldtype": "Currency", - "options": "currency" + "options": "currency", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_amount_after_discount_amount", @@ -146,7 +173,9 @@ "label": "Tax Amount After Discount Amount", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "columns": 2, @@ -157,11 +186,15 @@ "oldfieldname": "total", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_14", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_tax_amount", @@ -169,7 +202,9 @@ "label": "Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total", @@ -177,7 +212,9 @@ "hidden": 1, "label": "Total (Company Currency)", "options": "Company:company:default_currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_tax_amount_after_discount_amount", @@ -185,7 +222,9 @@ "label": "Tax Amount After Discount Amount", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "item_wise_tax_detail", @@ -195,22 +234,38 @@ "oldfieldname": "item_wise_tax_detail", "oldfieldtype": "Small Text", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions" + "label": "Accounting Dimensions", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "fieldname": "is_advance_tax", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Advance Tax", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2020-10-25 18:24:43.487567", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 65e5823b91..a805dc4e91 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -617,10 +617,6 @@ class AccountsController(TransactionBase): payment_entries = get_advance_payment_entries(party_type, party, party_account, order_doctype, order_list, include_unallocated) - payment_entry_list = [d.reference_name for d in payment_entries] - - payment_entry_taxes = get_payment_entry_taxes(payment_entry_list) - res = journal_entries + payment_entries return res @@ -1245,13 +1241,6 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, return list(payment_entries_against_order) + list(unallocated_payment_entries) -def get_payment_entry_taxes(payment_entry_list): - taxes = frappe.db.sql(""" - SELECT t.parent, t.add_deduct_tax, t.charge_type, t.account_head, t.cost_center, - t.tax_amount FROM `tabAdvance Taxes and Charges` where t.parent in %s""" - ,(payment_entry_list, ), as_dict=1) - - def update_invoice_status(): # Daily update the status of the invoices diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 9fae49482d..f3d7b2635e 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -40,6 +40,7 @@ class calculate_taxes_and_totals(object): self.validate_conversion_rate() self.calculate_item_values() self.validate_item_tax_template() + self.apply_advance_taxes() self.initialize_taxes() self.determine_exclusive_rate() self.calculate_net_total() @@ -683,6 +684,32 @@ class calculate_taxes_and_totals(object): self.calculate_paid_amount() + def apply_advance_taxes(self): + if cint(self.doc.get('adjust_advance_taxes')): + if self.doc.get('advances'): + payment_entry_list = [d.reference_name for d in self.doc.get('advances')] + advance_taxes = get_advance_taxes(payment_entry_list) + accounts = [] + + # Remove already added advance taxes if any + for tax in self.doc.get('taxes'): + if tax.is_advance_tax: + self.doc.remove(tax) + else: + accounts.append(tax.account_head) + + for tax in advance_taxes: + # Reverse add deduct from payment entry in invoice + if tax.account_head in accounts: + add_deduct_tax = 'Deduct' if tax.add_deduct_tax == 'Add' else 'Add' + tax.update({ + 'add_deduct_tax': add_deduct_tax, + 'category': tax.get('category') or 'Total', + 'is_advance_tax': 1, + 'charge_type': 'On Net Total' if tax.charge_type == 'On Paid Amount' else tax.charge_type + }) + + self.doc.append('taxes', tax) def get_itemised_tax_breakup_html(doc): if not doc.taxes: @@ -820,3 +847,16 @@ class init_landed_taxes_and_totals(object): for d in self.doc.get(self.tax_field): d.amount = flt(d.amount, d.precision("amount")) d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) + +def get_advance_taxes(payment_entry_list): + taxes = [] + if payment_entry_list: + taxes = frappe.db.sql( + """ + SELECT t.parent, t.add_deduct_tax, t.charge_type, t.rate, + t.account_head, t.cost_center, t.tax_amount, t.description + FROM `tabAdvance Taxes and Charges` t, `tabPayment Entry` p + WHERE t.parent = p.name AND t.parent in %s + """, (payment_entry_list, ), as_dict=1) + + return taxes diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index 0d656bc1fb..b168952e40 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -8,7 +8,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.dialog = new frappe.ui.Dialog({ title: 'Payment' }); - + this.dialog.show(); this.$body = this.dialog.body; this.set_payment_primary_action(); @@ -25,7 +25,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ set_payment_primary_action: function(){ var me = this; - + this.dialog.set_primary_action(__("Submit"), function() { // Allow no ZERO payment $.each(me.frm.doc.payments, function (index, data) { @@ -100,7 +100,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.selected_mode.select() this.bind_amount_change_event(); }, - + bind_keyboard_event: function(){ var me = this; this.payment_val = ''; @@ -114,17 +114,17 @@ erpnext.payments = erpnext.stock.StockController.extend({ me.idx = $(this).attr("idx"); me.set_outstanding_amount() }) - + $(this.$body).find('.form-control').click(function(){ me.idx = $(this).attr("idx"); me.set_outstanding_amount(); me.update_paid_amount(true); }) - + $(this.$body).find('.write_off_amount').change(function(){ me.write_off_amount(flt($(this).val()), precision("write_off_amount")); }) - + $(this.$body).find('.change_amount').change(function(){ me.change_amount(flt($(this).val()), precision("change_amount")); }) @@ -138,7 +138,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ $(this.$body).find('.amount').attr('disabled', true); this.selected_mode.attr('disabled', false); }, - + bind_numeric_keys_event: function(){ var me = this; $(this.$body).find('.pos-keyboard-key').click(function(){ @@ -147,7 +147,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ me.idx = me.selected_mode.attr("idx") me.update_paid_amount() }) - + $(this.$body).find('.delete-btn').click(function(){ me.payment_val = cstr(flt(me.selected_mode.val())).slice(0, -1); me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)); @@ -156,7 +156,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ }) }, - + bind_amount_change_event: function(){ var me = this; this.selected_mode.change(function(){ From d18dde77577cb041ce06e4cf7971087157ceb933 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 29 Nov 2020 21:40:04 +0530 Subject: [PATCH 0838/1264] fix: Add tds in PO and code cleanup --- .../advance_taxes_and_charges.json | 31 +- .../doctype/payment_entry/payment_entry.json | 66 ++- .../doctype/payment_entry/payment_entry.py | 89 +++- .../purchase_invoice/purchase_invoice.py | 48 +- .../purchase_taxes_and_charges.json | 15 +- .../doctype/sales_invoice/sales_invoice.py | 42 ++ .../doctype/purchase_order/purchase_order.js | 8 + .../purchase_order/purchase_order.json | 481 +++++++++++++----- .../doctype/purchase_order/purchase_order.py | 35 ++ erpnext/controllers/accounts_controller.py | 30 ++ erpnext/controllers/taxes_and_totals.py | 28 - 11 files changed, 676 insertions(+), 197 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index 5b3299e656..aac7eb6bcb 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -12,6 +12,7 @@ "account_head", "col_break_1", "description", + "included_in_paid_amount", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -20,9 +21,11 @@ "section_break_9", "tax_amount", "total", + "allocated_amount", "column_break_13", "base_tax_amount", - "base_total" + "base_total", + "base_allocated_amount" ], "fields": [ { @@ -185,12 +188,36 @@ "reqd": 1, "show_days": 1, "show_seconds": 1 + }, + { + "default": "0", + "fieldname": "included_in_paid_amount", + "fieldtype": "Check", + "label": "Included In Paid Amount", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "label": "Allocated Amount", + "options": "currency", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "base_allocated_amount", + "fieldtype": "Currency", + "label": "Allocated Amount (Company Currency)", + "options": "Company:company:default_currency", + "show_days": 1, + "show_seconds": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-27 20:02:34.734260", + "modified": "2020-11-29 19:06:14.666460", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index f158345af6..ec92db0708 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -24,15 +24,12 @@ "party_bank_account", "contact_person", "contact_email", - "tds_details_section", - "apply_tax_withholding_amount", - "column_break_20", - "tax_withholding_category", "payment_accounts_section", "party_balance", "paid_from", "paid_from_account_currency", "paid_from_account_balance", + "advance_tax_account", "column_break_18", "paid_to", "paid_to_account_currency", @@ -45,8 +42,10 @@ "base_paid_amount_after_tax", "column_break_21", "received_amount", + "received_amount_after_tax", "target_exchange_rate", "base_received_amount", + "base_received_amount_after_tax", "section_break_14", "get_outstanding_invoice", "references", @@ -61,6 +60,10 @@ "taxes_and_charges_section", "purchase_taxes_and_charges_template", "sales_taxes_and_charges_template", + "column_break_55", + "apply_tax_withholding_amount", + "tax_withholding_category", + "section_break_56", "taxes", "base_total_taxes_and_charges", "total_taxes_and_charges", @@ -731,14 +734,6 @@ "fieldtype": "Check", "label": "Custom Remarks" }, - { - "depends_on": "eval: doc.payment_type == 'Pay' && doc.party_type == 'Supplier'", - "fieldname": "tds_details_section", - "fieldtype": "Section Break", - "label": "TDS Details", - "show_days": 1, - "show_seconds": 1 - }, { "depends_on": "eval:doc.apply_tax_withholding_amount", "fieldname": "tax_withholding_category", @@ -750,18 +745,13 @@ }, { "default": "0", + "depends_on": "eval:doc.party_type == 'Supplier'", "fieldname": "apply_tax_withholding_amount", "fieldtype": "Check", "label": "Apply Tax Withholding Amount", "show_days": 1, "show_seconds": 1 }, - { - "fieldname": "column_break_20", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 - }, { "collapsible": 1, "fieldname": "taxes_and_charges_section", @@ -829,6 +819,44 @@ "fieldtype": "Currency", "label": "Paid Amount After Tax (Company Currency)", "options": "Company:company:default_currency", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_55", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "section_break_56", + "fieldtype": "Section Break", + "hide_border": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "advance_tax_account", + "fieldtype": "Link", + "label": "Advance Tax Account", + "options": "Account", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "received_amount_after_tax", + "fieldtype": "Currency", + "label": "Received Amount After Tax", + "options": "paid_to_account_currency", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "base_received_amount_after_tax", + "fieldtype": "Currency", + "label": "Received Amount After Tax (Company Currency)", + "options": "Company:company:default_currency", "show_days": 1, "show_seconds": 1 } @@ -836,7 +864,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-25 20:50:14.896628", + "modified": "2020-11-29 20:03:57.772062", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a21deb5431..251b581723 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -65,6 +65,7 @@ class PaymentEntry(AccountsController): self.validate_allocated_amount() self.validate_paid_invoices() self.ensure_supplier_is_not_blocked() + self.validate_advance_tax_account() self.set_status() def on_submit(self): @@ -310,6 +311,9 @@ class PaymentEntry(AccountsController): + "

    " + _("If this is undesirable please cancel the corresponding Payment Entry."), title=_("Warning"), indicator="orange") + def validate_advance_tax_account(self): + if self.get('taxes') and not self.advance_tax_account: + frappe.throw(_('Please select advance tax account')) def validate_journal_entry(self): for d in self.get("references"): @@ -396,11 +400,29 @@ class PaymentEntry(AccountsController): if not self.apply_tax_withholding_amount: return + reference_doclist = [] + net_total = self.paid_amount + included_in_paid_amount = 0 + + if self.get('references'): + for doc in self.get('references'): + if doc.reference_doctype == 'Purchase Order': + reference_doclist.append(doc.reference_name) + + if reference_doclist: + order_amount = frappe.db.get_all('Purchase Order', fields=['sum(net_total)'], + filters = {'name': ('in', reference_doclist), 'docstatus': 1, + 'apply_tds': 1}, as_list=1) + + if order_amount: + net_total = order_amount[0][0] + included_in_paid_amount = 1 + args = frappe._dict({ 'company': self.company, 'supplier': self.party, 'posting_date': self.posting_date, - 'net_total': self.paid_amount + 'net_total': net_total }) tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category) @@ -408,6 +430,8 @@ class PaymentEntry(AccountsController): if not tax_withholding_details: return + tax_withholding_details.update({'included_in_paid_amount': included_in_paid_amount}) + accounts = [] for d in self.taxes: if d.account_head == tax_withholding_details.get("account_head"): @@ -424,18 +448,37 @@ class PaymentEntry(AccountsController): self.remove(d) def set_amounts(self): - self.set_amounts_after_tax() + self.set_received_amount() self.set_amounts_in_company_currency() + self.set_amounts_after_tax() self.set_total_allocated_amount() self.set_unallocated_amount() self.set_difference_amount() + def set_received_amount(self): + self.base_received_amount = self.base_paid_amount + def set_amounts_after_tax(self): - self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(self.total_taxes_and_charges), + applicable_tax = 0 + base_applicable_tax = 0 + for tax in self.get('taxes'): + if not tax.included_in_paid_amount: + amount = -1 * tax.tax_amount if tax.add_deduct_tax == 'Deduct' else tax.tax_amount + base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == 'Deduct' else tax.base_tax_amount + + applicable_tax += amount + base_applicable_tax += base_amount + + self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(applicable_tax), self.precision("paid_amount_after_tax")) self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate), self.precision("base_paid_amount_after_tax")) + self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax), + self.precision("paid_amount_after_tax")) + self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.source_exchange_rate), + self.precision("base_paid_amount_after_tax")) + def set_amounts_in_company_currency(self): self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0 if self.paid_amount: @@ -465,14 +508,14 @@ class PaymentEntry(AccountsController): if self.party: total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) if self.payment_type == "Receive" \ - and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ - and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): - self.unallocated_amount = (self.base_received_amount + total_deductions - + and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \ + and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate): + self.unallocated_amount = (self.received_amount_after_tax + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate elif self.payment_type == "Pay" \ - and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ - and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): - self.unallocated_amount = (self.base_paid_amount - (total_deductions + + and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \ + and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate): + self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate def set_difference_amount(self): @@ -482,11 +525,11 @@ class PaymentEntry(AccountsController): base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount) if self.payment_type == "Receive": - self.difference_amount = base_party_amount - self.base_received_amount + self.difference_amount = base_party_amount - self.base_received_amount_after_tax elif self.payment_type == "Pay": - self.difference_amount = self.base_paid_amount - base_party_amount + self.difference_amount = self.base_paid_amount_after_tax - base_party_amount else: - self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) + self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax) total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) @@ -616,7 +659,7 @@ class PaymentEntry(AccountsController): gl_entries.append(gle) if self.unallocated_amount: - base_unallocated_amount = base_unallocated_amount = self.unallocated_amount * \ + base_unallocated_amount = self.unallocated_amount * \ (self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate) gle = party_gl_dict.copy() @@ -646,8 +689,8 @@ class PaymentEntry(AccountsController): "account": self.paid_to, "account_currency": self.paid_to_account_currency, "against": self.party if self.payment_type=="Receive" else self.paid_from, - "debit_in_account_currency": self.received_amount, - "debit": self.base_received_amount, + "debit_in_account_currency": self.received_amount_after_tax, + "debit": self.base_received_amount_after_tax, "cost_center": self.cost_center }, item=self) ) @@ -660,8 +703,10 @@ class PaymentEntry(AccountsController): if self.payment_type == 'Pay': dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" + rev_dr_cr = "credit" if d.add_deduct_tax == "Add" else "debit" elif self.payment_type == 'Receive': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" + rev_dr_cr = "debit" if d.add_deduct_tax == "Add" else "credit" gl_entries.append( self.get_gl_dict({ @@ -672,8 +717,18 @@ class PaymentEntry(AccountsController): if account_currency==self.company_currency \ else d.tax_amount, "cost_center": d.cost_center - }, account_currency, item=d) - ) + }, account_currency, item=d)) + + gl_entries.append( + self.get_gl_dict({ + "account": self.advance_tax_account, + "against": self.party if self.payment_type=="Receive" else self.paid_from, + rev_dr_cr: d.base_tax_amount, + rev_dr_cr + "_in_account_currency": d.base_tax_amount \ + if account_currency==self.company_currency \ + else d.tax_amount, + "cost_center": d.cost_center + }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): for d in self.get("deductions"): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 84dd491a56..8f7248f1e6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -456,6 +456,8 @@ class PurchaseInvoice(BuyingController): self.make_tax_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) + self.allocate_advance_taxes(gl_entries) + gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = merge_similar_entries(gl_entries) @@ -899,6 +901,46 @@ class PurchaseInvoice(BuyingController): "cost_center": self.cost_center }, account_currency, item=self)) + def allocate_advance_taxes(self, gl_entries): + tax_map = self.get_tax_map() + for pe in self.get('advances'): + pe = frappe.get_doc('Payment Entry', pe.reference_name) + for tax in pe.get('taxes'): + account_currency = get_account_currency(tax.account_head) + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + + unallocated_amount = tax.tax_amount - tax.allocated_amount + if tax_map.get(tax.account_head): + amount = tax_map.get(tax.account_head) + if amount < unallocated_amount: + unallocated_amount = amount + + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "against": self.supplier, + dr_or_cr: unallocated_amount, + dr_or_cr + "_in_account_currency": unallocated_amount \ + if account_currency==self.company_currency \ + else unallocated_amount, + 'cost_center': tax.cost_center + }, account_currency, item=tax)) + + gl_entries.append( + self.get_gl_dict({ + "account": pe.advance_tax_account, + "against": self.supplier, + rev_dr_cr: unallocated_amount, + rev_dr_cr + "_in_account_currency": unallocated_amount \ + if account_currency==self.company_currency \ + else unallocated_amount, + 'cost_center': tax.cost_center + }, account_currency, item=tax)) + + frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) + tax_map[tax.account_head] -= unallocated_amount + def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries if cint(self.is_paid) and self.cash_bank_account and self.paid_amount: @@ -1088,10 +1130,10 @@ class PurchaseInvoice(BuyingController): accounts = [] for d in self.taxes: - if d.account_head == tax_withholding_details.get("account_head") and not d.is_advance_tax: + if d.account_head == tax_withholding_details.get("account_head"): d.update(tax_withholding_details) - if not d.is_advance_tax: - accounts.append(d.account_head) + + accounts.append(d.account_head) if not accounts or tax_withholding_details.get("account_head") not in accounts: self.append("taxes", tax_withholding_details) diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index de7ef2ac96..0db0b6823d 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -28,8 +28,7 @@ "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", - "item_wise_tax_detail", - "is_advance_tax" + "item_wise_tax_detail" ], "fields": [ { @@ -250,22 +249,12 @@ "fieldtype": "Column Break", "show_days": 1, "show_seconds": 1 - }, - { - "default": "0", - "fieldname": "is_advance_tax", - "fieldtype": "Check", - "hidden": 1, - "label": "Is Advance Tax", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-10-25 18:24:43.487567", + "modified": "2020-11-29 19:11:58.826078", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index a008742390..9af94fd780 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -842,6 +842,8 @@ class SalesInvoice(SellingController): self.make_tax_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) + self.allocate_advance_taxes(gl_entries) + self.make_item_gl_entries(gl_entries) # merge gl entries before adding pos entries @@ -911,6 +913,46 @@ class SalesInvoice(SellingController): "cost_center": self.cost_center }, account_currency, item=self)) + def allocate_advance_taxes(self, gl_entries): + tax_map = self.get_tax_map() + for pe in self.get('advances'): + pe = frappe.get_doc('Payment Entry', pe.reference_name) + for tax in pe.get('taxes'): + account_currency = get_account_currency(tax.account_head) + dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + + unallocated_amount = tax.tax_amount - tax.allocated_amount + if tax_map.get(tax.account_head): + amount = tax_map.get(tax.account_head) + if amount < unallocated_amount: + unallocated_amount = amount + + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "against": self.customer, + dr_or_cr: unallocated_amount, + dr_or_cr + "_in_account_currency": unallocated_amount \ + if account_currency==self.company_currency \ + else unallocated_amount, + 'cost_center': tax.cost_center + }, account_currency, item=tax)) + + gl_entries.append( + self.get_gl_dict({ + "account": pe.advance_tax_account, + "against": self.customer, + rev_dr_cr: unallocated_amount, + rev_dr_cr + "_in_account_currency": unallocated_amount \ + if account_currency==self.company_currency \ + else unallocated_amount, + 'cost_center': tax.cost_center + }, account_currency, item=tax)) + + frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) + tax_map[tax.account_head] -= unallocated_amount + def make_item_gl_entries(self, gl_entries): # income account gl entries for item in self.get("items"): diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index dd0f065848..139aa72c08 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -45,6 +45,14 @@ frappe.ui.form.on("Purchase Order", { }); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); + }, + + apply_tds: function(frm) { + if (!frm.doc.apply_tds) { + frm.set_value("tax_withholding_category", ''); + } else { + frm.set_value("tax_withholding_category", frm.supplier_tds); + } } }); diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ee2beea67f..64eda5f280 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -14,6 +14,8 @@ "supplier", "get_items_from_open_material_requests", "supplier_name", + "apply_tds", + "tax_withholding_category", "column_break1", "company", "transaction_date", @@ -142,7 +144,9 @@ { "fieldname": "supplier_section", "fieldtype": "Section Break", - "options": "fa fa-user" + "options": "fa fa-user", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -152,7 +156,9 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "naming_series", @@ -164,7 +170,9 @@ "options": "PUR-ORD-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -178,14 +186,18 @@ "options": "Supplier", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))", "description": "Fetch items based on Default Supplier.", "fieldname": "get_items_from_open_material_requests", "fieldtype": "Button", - "label": "Get Items from Open Material Requests" + "label": "Get Items from Open Material Requests", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -194,7 +206,9 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Supplier Name", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", @@ -206,13 +220,17 @@ "options": "Company", "print_hide": 1, "remember_last_selected_value": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_width": "50%", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -224,27 +242,35 @@ "oldfieldname": "transaction_date", "oldfieldtype": "Date", "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "schedule_date", "fieldtype": "Date", - "label": "Required By" + "label": "Required By", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "depends_on": "eval:doc.docstatus===1", "fieldname": "order_confirmation_no", "fieldtype": "Data", - "label": "Order Confirmation No" + "label": "Order Confirmation No", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "depends_on": "eval:doc.order_confirmation_no", "fieldname": "order_confirmation_date", "fieldtype": "Date", - "label": "Order Confirmation Date" + "label": "Order Confirmation Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amended_from", @@ -256,19 +282,25 @@ "oldfieldtype": "Data", "options": "Purchase Order", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "drop_ship", "fieldtype": "Section Break", - "label": "Drop Ship" + "label": "Drop Ship", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "customer", "fieldtype": "Link", "label": "Customer", "options": "Customer", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -276,31 +308,41 @@ "fieldtype": "Data", "label": "Customer Name", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_19", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "customer_contact_person", "fieldtype": "Link", "label": "Customer Contact", - "options": "Contact" + "options": "Contact", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "customer_contact_display", "fieldtype": "Small Text", "hidden": 1, "label": "Customer Contact", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "customer_contact_mobile", "fieldtype": "Small Text", "hidden": 1, "label": "Customer Mobile No", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "customer_contact_email", @@ -308,27 +350,35 @@ "hidden": 1, "label": "Customer Contact Email", "options": "Email", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact" + "label": "Address and Contact", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier_address", "fieldtype": "Link", "label": "Supplier Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_person", "fieldtype": "Link", "label": "Supplier Contact", "options": "Contact", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "address_display", @@ -355,32 +405,42 @@ "label": "Contact Email", "options": "Email", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_address", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address", "fieldtype": "Link", "label": "Company Shipping Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", "label": "Shipping Address Details", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "currency_and_price_list", "fieldtype": "Section Break", "label": "Currency and Price List", - "options": "fa fa-tag" + "options": "fa fa-tag", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "currency", @@ -390,7 +450,9 @@ "oldfieldtype": "Select", "options": "Currency", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "conversion_rate", @@ -400,18 +462,24 @@ "oldfieldtype": "Currency", "precision": "9", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cb_price_list", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "buying_price_list", "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "price_list_currency", @@ -419,14 +487,18 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "plc_conversion_rate", "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -435,7 +507,9 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sec_warehouse", @@ -448,11 +522,15 @@ "fieldtype": "Link", "label": "Set Target Warehouse", "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_warehouse", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -461,26 +539,34 @@ "in_standard_filter": 1, "label": "Supply Raw Materials", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_subcontracted==\"Yes\"", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "items_section", "fieldtype": "Section Break", "hide_border": 1, "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart" + "options": "fa fa-shopping-cart", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "show_days": 1, + "show_seconds": 1 }, { "allow_bulk_edit": 1, @@ -490,26 +576,34 @@ "oldfieldname": "po_details", "oldfieldtype": "Table", "options": "Purchase Order Item", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "section_break_48", "fieldtype": "Section Break", - "label": "Pricing Rules" + "label": "Pricing Rules", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rules", "fieldtype": "Table", "label": "Purchase Order Pricing Rule", "options": "Pricing Rule Detail", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible_depends_on": "supplied_items", "fieldname": "raw_material_details", "fieldtype": "Section Break", - "label": "Raw Materials Supplied" + "label": "Raw Materials Supplied", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplied_items", @@ -519,17 +613,23 @@ "oldfieldtype": "Table", "options": "Purchase Order Item Supplied", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sb_last_purchase", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_qty", "fieldtype": "Float", "label": "Total Quantity", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total", @@ -537,7 +637,9 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_net_total", @@ -548,18 +650,24 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_26", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total", "fieldtype": "Currency", "label": "Total", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "net_total", @@ -569,20 +677,26 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges", @@ -591,18 +705,24 @@ "oldfieldname": "purchase_other_charges", "oldfieldtype": "Link", "options": "Purchase Taxes and Charges Template", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_50", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", "options": "Shipping Rule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_52", @@ -615,13 +735,17 @@ "label": "Purchase Taxes and Charges", "oldfieldname": "purchase_tax_details", "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges" + "options": "Purchase Taxes and Charges", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "sec_tax_breakup", "fieldtype": "Section Break", - "label": "Tax Breakup" + "label": "Tax Breakup", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "other_charges_calculation", @@ -630,14 +754,18 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "totals", "fieldtype": "Section Break", "label": "Taxes and Charges", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "base_taxes_and_charges_added", @@ -648,7 +776,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "base_taxes_and_charges_deducted", @@ -659,7 +789,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "base_total_taxes_and_charges", @@ -671,11 +803,15 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_39", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "taxes_and_charges_added", @@ -686,7 +822,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "taxes_and_charges_deducted", @@ -697,7 +835,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "total_taxes_and_charges", @@ -706,14 +846,18 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "apply_discount_on", "fieldname": "discount_section", "fieldtype": "Section Break", - "label": "Additional Discount" + "label": "Additional Discount", + "show_days": 1, + "show_seconds": 1 }, { "default": "Grand Total", @@ -721,7 +865,9 @@ "fieldtype": "Select", "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_discount_amount", @@ -729,24 +875,32 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_45", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "additional_discount_percentage", "fieldtype": "Float", "label": "Additional Discount Percentage", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "totals_section", @@ -762,7 +916,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_rounding_adjustment", @@ -771,7 +927,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "description": "In Words will be visible once you save the Purchase Order.", @@ -782,7 +940,9 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_rounded_total", @@ -792,12 +952,16 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break4", "fieldtype": "Column Break", - "oldfieldtype": "Column Break" + "oldfieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "grand_total", @@ -807,7 +971,9 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rounding_adjustment", @@ -816,20 +982,26 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rounded_total", "fieldtype": "Currency", "label": "Rounded Total", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total" + "label": "Disable Rounded Total", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "in_words", @@ -839,7 +1011,9 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "advance_paid", @@ -848,19 +1022,25 @@ "no_copy": 1, "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "payment_schedule_section", "fieldtype": "Section Break", - "label": "Payment Terms" + "label": "Payment Terms", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_terms_template", "fieldtype": "Link", "label": "Payment Terms Template", - "options": "Payment Terms Template" + "options": "Payment Terms Template", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_schedule", @@ -868,7 +1048,9 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -877,7 +1059,9 @@ "fieldtype": "Section Break", "label": "Terms and Conditions", "oldfieldtype": "Section Break", - "options": "fa fa-legal" + "options": "fa fa-legal", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tc_name", @@ -886,21 +1070,27 @@ "oldfieldname": "tc_name", "oldfieldtype": "Link", "options": "Terms and Conditions", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", "label": "Terms and Conditions", "oldfieldname": "terms", - "oldfieldtype": "Text Editor" + "oldfieldtype": "Text Editor", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "more_info", "fieldtype": "Section Break", "label": "More Information", - "oldfieldtype": "Section Break" + "oldfieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "Draft", @@ -915,7 +1105,9 @@ "print_hide": 1, "read_only": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "ref_sq", @@ -926,7 +1118,9 @@ "oldfieldtype": "Data", "options": "Supplier Quotation", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "party_account_currency", @@ -936,18 +1130,24 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "inter_company_order_reference", "fieldtype": "Link", "label": "Inter Company Order Reference", "options": "Sales Order", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_74", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -957,7 +1157,9 @@ "label": "% Received", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.__islocal", @@ -967,7 +1169,9 @@ "label": "% Billed", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -977,6 +1181,8 @@ "oldfieldtype": "Column Break", "print_hide": 1, "print_width": "50%", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -987,7 +1193,9 @@ "oldfieldname": "letter_head", "oldfieldtype": "Select", "options": "Letter Head", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -999,11 +1207,15 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1 + "report_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_86", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1011,19 +1223,25 @@ "fieldname": "group_same_items", "fieldtype": "Check", "label": "Group same items", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "language", "fieldtype": "Data", "label": "Print Language", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", - "label": "Subscription Section" + "label": "Subscription Section", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1031,7 +1249,9 @@ "fieldtype": "Date", "label": "From Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1039,11 +1259,15 @@ "fieldtype": "Date", "label": "To Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_97", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "auto_repeat", @@ -1052,27 +1276,35 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "depends_on": "eval: doc.auto_repeat", "fieldname": "update_auto_repeat_reference", "fieldtype": "Button", - "label": "Update Auto Repeat Reference" + "label": "Update Auto Repeat Reference", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", - "options": "Tax Category" + "options": "Tax Category", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "supplied_items", "fieldname": "set_reserve_warehouse", "fieldtype": "Link", "label": "Set Reserve Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1082,7 +1314,9 @@ }, { "fieldname": "column_break_75", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "billing_address", @@ -1118,13 +1352,30 @@ "label": "Represents Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "fieldname": "apply_tds", + "fieldtype": "Check", + "label": "Apply Tax Withholding Amount", + "show_days": 1, + "show_seconds": 1 + }, + { + "depends_on": "eval: doc.apply_tds", + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-01-20 22:07:23.487138", + "modified": "2020-11-28 17:42:25.177827", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index ef9372eeb6..782593a5c5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -17,6 +17,7 @@ from erpnext.accounts.party import get_party_account_currency from six import string_types from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ unlink_inter_company_doc @@ -39,11 +40,18 @@ class PurchaseOrder(BuyingController): 'percent_join_field': 'material_request' }] + def onload(self): + supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category") + self.set_onload("supplier_tds", supplier_tds) + def validate(self): super(PurchaseOrder, self).validate() self.set_status() + # apply tax withholding only if checked and applicable + self.set_tax_withholding() + self.validate_supplier() self.validate_schedule_date() validate_for_items(self) @@ -87,6 +95,33 @@ class PurchaseOrder(BuyingController): if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')): self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]]) + def set_tax_withholding(self): + if not self.apply_tds: + return + + tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category) + + if not tax_withholding_details: + return + + accounts = [] + for d in self.taxes: + if d.account_head == tax_withholding_details.get("account_head"): + d.update(tax_withholding_details) + accounts.append(d.account_head) + + if not accounts or tax_withholding_details.get("account_head") not in accounts: + self.append("taxes", tax_withholding_details) + + to_remove = [d for d in self.taxes + if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")] + + for d in to_remove: + self.remove(d) + + # calculate totals again after applying TDS + self.calculate_taxes_and_totals() + def validate_supplier(self): prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos') if prevent_po: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a805dc4e91..da88853a96 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -700,6 +700,7 @@ class AccountsController(TransactionBase): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries if self.doctype in ["Sales Invoice", "Purchase Invoice"]: + self.update_allocated_advance_taxes() if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): unlink_ref_doc_from_payment_entries(self) @@ -707,6 +708,35 @@ class AccountsController(TransactionBase): if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'): unlink_ref_doc_from_payment_entries(self) + def get_tax_map(self): + tax_map = {} + for tax in self.get('taxes'): + tax_map.setdefault(tax.account_head, 0.0) + tax_map[tax.account_head] += tax.tax_amount + + return tax_map + + def update_allocated_advance_taxes(self): + if self.get('advances'): + tax_accounts = [d.account_head for d in self.get('taxes')] + allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'], + filters={'voucher_no': self.name, 'account': ('in', tax_accounts)}, + group_by='account', as_list=1)) + + tax_map = self.get_tax_map() + + for pe in self.get('advances'): + pe = frappe.get_doc('Payment Entry', pe.reference_name) + for tax in pe.get('taxes'): + allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head) + if allocated_amount > tax.tax_amount: + allocated_amount = tax.tax_amount + + if allocated_amount: + frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount - allocated_amount) + tax_map[tax.account_head] -= allocated_amount + allocated_tax_map[tax.account_head] -= allocated_amount + def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for item_allowance = {} diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index f3d7b2635e..a6b54eb590 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -40,7 +40,6 @@ class calculate_taxes_and_totals(object): self.validate_conversion_rate() self.calculate_item_values() self.validate_item_tax_template() - self.apply_advance_taxes() self.initialize_taxes() self.determine_exclusive_rate() self.calculate_net_total() @@ -684,33 +683,6 @@ class calculate_taxes_and_totals(object): self.calculate_paid_amount() - def apply_advance_taxes(self): - if cint(self.doc.get('adjust_advance_taxes')): - if self.doc.get('advances'): - payment_entry_list = [d.reference_name for d in self.doc.get('advances')] - advance_taxes = get_advance_taxes(payment_entry_list) - accounts = [] - - # Remove already added advance taxes if any - for tax in self.doc.get('taxes'): - if tax.is_advance_tax: - self.doc.remove(tax) - else: - accounts.append(tax.account_head) - - for tax in advance_taxes: - # Reverse add deduct from payment entry in invoice - if tax.account_head in accounts: - add_deduct_tax = 'Deduct' if tax.add_deduct_tax == 'Add' else 'Add' - tax.update({ - 'add_deduct_tax': add_deduct_tax, - 'category': tax.get('category') or 'Total', - 'is_advance_tax': 1, - 'charge_type': 'On Net Total' if tax.charge_type == 'On Paid Amount' else tax.charge_type - }) - - self.doc.append('taxes', tax) - def get_itemised_tax_breakup_html(doc): if not doc.taxes: return From 79b422c0a96f425b4098516b331b5a96d17c5aec Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 28 Feb 2021 18:07:21 +0530 Subject: [PATCH 0839/1264] fix: Advance TDS in TDS payable monthly report --- .../doctype/payment_entry/payment_entry.js | 19 ++- .../doctype/payment_entry/payment_entry.json | 2 +- .../doctype/payment_entry/payment_entry.py | 26 ++-- .../purchase_invoice/purchase_invoice.py | 2 +- .../tax_withholding_category.py | 6 +- .../tds_payable_monthly.js | 41 +++++- .../tds_payable_monthly.py | 127 ++++++++++++++---- .../purchase_order/purchase_order.json | 2 +- erpnext/setup/doctype/company/company.js | 2 +- 9 files changed, 175 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 3e11bc0e59..19d73b1ad4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -93,6 +93,15 @@ frappe.ui.form.on('Payment Entry', { } }); + frm.set_query("advance_tax_account", function() { + return { + filters: { + "company": frm.doc.company, + "root_type": ["in", ["Asset", "Liability"]] + } + } + }); + frm.set_query("reference_doctype", "references", function() { if (frm.doc.party_type == "Customer") { var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"]; @@ -1103,10 +1112,12 @@ frappe.ui.form.on('Payment Entry', { current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; - if(i==0) { - tax.total = flt(frm.doc.paid_amount + current_tax_amount, precision("total", tax)); - } else { - tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax)); + if (!tax.included_in_paid_amount) { + if(i==0) { + tax.total = flt(frm.doc.paid_amount + current_tax_amount, precision("total", tax)); + } else { + tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax)); + } } tax.base_total = tax.total * frm.doc.source_exchange_rate; diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index ec92db0708..d239c41fd8 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -864,7 +864,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-29 20:03:57.772062", + "modified": "2021-02-27 13:56:20.007336", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 251b581723..33eb6f2f26 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -65,7 +65,7 @@ class PaymentEntry(AccountsController): self.validate_allocated_amount() self.validate_paid_invoices() self.ensure_supplier_is_not_blocked() - self.validate_advance_tax_account() + self.set_advance_tax_account() self.set_status() def on_submit(self): @@ -311,9 +311,13 @@ class PaymentEntry(AccountsController): + "

    " + _("If this is undesirable please cancel the corresponding Payment Entry."), title=_("Warning"), indicator="orange") - def validate_advance_tax_account(self): + def set_advance_tax_account(self): if self.get('taxes') and not self.advance_tax_account: - frappe.throw(_('Please select advance tax account')) + unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account') + if not unrealized_profit_loss_account: + frappe.throw(_("Please select advance tax account or add Unrealized Profit / Loss Account in company master")) + + self.advance_tax_account = unrealized_profit_loss_account def validate_journal_entry(self): for d in self.get("references"): @@ -418,8 +422,10 @@ class PaymentEntry(AccountsController): net_total = order_amount[0][0] included_in_paid_amount = 1 + # Adding args as purchase invoice to get TDS amount args = frappe._dict({ 'company': self.company, + 'doctype': 'Purchase Invoice', 'supplier': self.party, 'posting_date': self.posting_date, 'net_total': net_total @@ -727,7 +733,7 @@ class PaymentEntry(AccountsController): rev_dr_cr + "_in_account_currency": d.base_tax_amount \ if account_currency==self.company_currency \ else d.tax_amount, - "cost_center": d.cost_center + "cost_center": d.cost_center or self.cost_center }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): @@ -830,12 +836,14 @@ class PaymentEntry(AccountsController): else: current_tax_amount *= 1.0 - if i == 0: - tax.total = flt(self.paid_amount + current_tax_amount, self.precision("total", tax)) - else: - tax.total = flt(self.taxes[i-1].total + current_tax_amount, self.precision("total", tax)) + if not tax.included_in_paid_amount: + if i == 0: + tax.total = flt(self.paid_amount + current_tax_amount, self.precision("total", tax)) + else: + tax.total = flt(self.taxes[i-1].total + current_tax_amount, self.precision("total", tax)) + + tax.base_total = tax.total * self.source_exchange_rate - tax.base_total = tax.total * self.source_exchange_rate self.total_taxes_and_charges += current_tax_amount self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 8f7248f1e6..5d4796c916 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -935,7 +935,7 @@ class PurchaseInvoice(BuyingController): rev_dr_cr + "_in_account_currency": unallocated_amount \ if account_currency==self.company_currency \ else unallocated_amount, - 'cost_center': tax.cost_center + 'cost_center': tax.cost_center or self.cost_center }, account_currency, item=tax)) frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 5c1cbaa4aa..b9ee4a0963 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -49,7 +49,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): if not parties: parties.append(party) - fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company) + fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company) tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company) if not tax_details: @@ -154,7 +154,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details) tax_amount = 0 - posting_date = inv.posting_date + posting_date = inv.get('posting_date') or inv.get('transaction_date') if party_type == 'Supplier': ldc = get_lower_deduction_certificate(fiscal_year, pan_no) if tax_deducted: @@ -257,7 +257,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): if ldc and is_valid_certificate( ldc.valid_from, ldc.valid_upto, - inv.posting_date, tax_deducted, + inv.get('posting_date') or inv.get('transaction_date'), tax_deducted, inv.net_total, ldc.certificate_limit ): tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js index 344539eef6..011c29621a 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js @@ -54,6 +54,32 @@ frappe.query_reports["TDS Payable Monthly"] = { frappe.query_report.refresh(); } }, + { + "fieldname":"purchase_order", + "label": __("Purchase Order"), + "fieldtype": "Link", + "options": "Purchase Order", + "get_query": function() { + return { + "filters": { + "name": ["in", frappe.query_report.invoices] + } + } + }, + on_change: function() { + let supplier = frappe.query_report.get_filter_value('supplier'); + if(!supplier) return; // return if no supplier selected + + // filter order based on selected supplier + let orders = []; + frappe.query_report.order_data.map(d => { + if(d.supplier==supplier) + orders.push(d.name) + }); + frappe.query_report.orders = orders; + frappe.query_report.refresh(); + } + }, { "fieldname":"from_date", "label": __("From Date"), @@ -75,15 +101,24 @@ frappe.query_reports["TDS Payable Monthly"] = { onload: function(report) { // fetch all tds applied invoices frappe.call({ - "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices", + "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders", callback: function(r) { let invoices = []; - r.message.map(d => { + let orders = []; + + r.message.invoices.map(d => { invoices.push(d.name); }); - report["invoice_data"] = r.message; + r.message.orders.map(d => { + orders.push(d.name); + }); + + report["invoice_data"] = r.message.invoices; report["invoices"] = invoices; + + report["order_data"] = r.message.orders; + report["invoices"] = orders; } }); } diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index a9fb237a04..7ce61f4a28 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -8,14 +8,18 @@ from frappe.utils import getdate def execute(filters=None): filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user) + filters["orders"] = frappe.cache().hget("orders", frappe.session.user) validate_filters(filters) set_filters(filters) + # TDS payment entries + payment_entries = get_payment_entires(filters) + columns = get_columns(filters) - if not filters["invoices"]: + if not (filters.get("invoices") and filters.get('orders')): return columns, [] - res = get_result(filters) + res = get_result(filters, payment_entries) return columns, res @@ -26,9 +30,14 @@ def validate_filters(filters): def set_filters(filters): invoices = [] + orders = [] + + if not filters.get("invoices"): + filters["invoices"] = get_tds_invoices_and_orders()['invoices'] + + if not filters.get("orders"): + filters["orders"] = get_tds_invoices_and_orders()['orders'] - if not filters["invoices"]: - filters["invoices"] = get_tds_invoices() if filters.supplier and filters.purchase_invoice: for d in filters["invoices"]: if d.name == filters.purchase_invoice and d.supplier == filters.supplier: @@ -42,12 +51,28 @@ def set_filters(filters): if d.name == filters.purchase_invoice: invoices.append(d) + if filters.supplier and filters.purchase_order: + for d in filters.get("orders"): + if d.name == filters.purchase_order and d.supplier == filters.supplier: + orders.append(d) + elif filters.supplier and not filters.purchase_order: + for d in filters.get("orders"): + if d.supplier == filters.supplier: + orders.append(d) + elif filters.purchase_order and not filters.supplier: + for d in filters.get("invoices"): + if d.name == filters.purchase_order: + orders.append(d) + filters["invoices"] = invoices if invoices else filters["invoices"] + filters["orders"] = orders if orders else filters["orders"] filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') -def get_result(filters): - supplier_map, tds_docs = get_supplier_map(filters) - gle_map = get_gle_map(filters) +def get_result(filters, payment_entries): + supplier_map, tds_docs = get_supplier_map(filters, payment_entries) + documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries] + + gle_map = get_gle_map(filters, documents) out = [] for d in gle_map: @@ -62,10 +87,11 @@ def get_result(filters): for k in gle_map[d]: if k.party == supplier_map[d] and k.credit > 0: - total_amount_credited += k.credit - elif account_list and k.account == account and k.credit > 0: - tds_deducted = k.credit - total_amount_credited += k.credit + total_amount_credited += (k.credit - k.debit) + elif account_list and k.account == account and (k.credit - k.debit) > 0: + tds_deducted = (k.credit - k.debit) + total_amount_credited += (k.credit - k.debit) + voucher_type = k.voucher_type rate = [i.tax_withholding_rate for i in tds_doc.rates if i.fiscal_year == gle_map[d][0].fiscal_year] @@ -73,32 +99,40 @@ def get_result(filters): if rate and len(rate) > 0 and tds_deducted: rate = rate[0] - if getdate(filters.from_date) <= gle_map[d][0].posting_date \ - and getdate(filters.to_date) >= gle_map[d][0].posting_date: - row = [supplier.pan, supplier.name] + row = [supplier.pan, supplier.name] - if filters.naming_series == 'Naming Series': - row.append(supplier.supplier_name) + if filters.naming_series == 'Naming Series': + row.append(supplier.supplier_name) - row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited, - tds_deducted, gle_map[d][0].posting_date, "Purchase Invoice", d]) - out.append(row) + row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited, + tds_deducted, gle_map[d][0].posting_date, voucher_type, d]) + out.append(row) return out -def get_supplier_map(filters): +def get_supplier_map(filters, payment_entries): # create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}} # pre-fetch all distinct applicable tds docs supplier_map, tds_docs = {}, {} pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" + supplier_list = [d.supplier for d in filters["invoices"]] + [d.supplier for d in filters["orders"]] + supplier_detail = frappe.db.get_all('Supplier', - {"name": ["in", [d.supplier for d in filters["invoices"]]]}, + {"name": ["in", supplier_list]}, ["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"]) for d in filters["invoices"]: supplier_map[d.get("name")] = [k for k in supplier_detail if k.name == d.get("supplier")][0] + for d in filters["orders"]: + supplier_map[d.get("name")] = [k for k in supplier_detail + if k.name == d.get("supplier")][0] + + for d in payment_entries: + supplier_map[d.get("name")] = [k for k in supplier_detail + if k.name == d.get("supplier")][0] + for d in supplier_detail: if d.get("tax_withholding_category") not in tds_docs: tds_docs[d.get("tax_withholding_category")] = \ @@ -106,13 +140,19 @@ def get_supplier_map(filters): return supplier_map, tds_docs -def get_gle_map(filters): +def get_gle_map(filters, documents): # create gle_map of the form # {"purchase_invoice": list of dict of all gle created for this invoice} gle_map = {} - gle = frappe.db.get_all('GL Entry',\ - {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0}, - ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"]) + filter_obj = {} + gle = frappe.db.get_all('GL Entry', + { + "voucher_no": ["in", documents], + 'is_cancelled': 0, + 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), + }, + ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"], + ) for d in gle: if not d.voucher_no in gle_map: @@ -201,8 +241,28 @@ def get_columns(filters): return columns +def get_payment_entires(filters): + filter_dict = { + 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), + 'party_type': 'Supplier', + 'apply_tax_withholding_amount': 1 + } + + if filters.get('purchase_order') or filters.get('purchase_invoice'): + parent = frappe.db.get_all('Payment Entry Reference', + { + 'reference_name': ('in', [d.get('name') for d in filters.get('orders')] + + [d.get('name') for d in filters.get('invoices')]) + }, ['parent']) + filter_dict.update({'name': ('in', [d.get('parent') for d in parent])}) + + payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'], + filters=filter_dict) + + return payment_entries + @frappe.whitelist() -def get_tds_invoices(): +def get_tds_invoices_and_orders(): # fetch tds applicable supplier and fetch invoices for these suppliers suppliers = [d.name for d in frappe.db.get_list("Supplier", {"tax_withholding_category": ["!=", ""]}, ["name"])] @@ -210,7 +270,16 @@ def get_tds_invoices(): invoices = frappe.db.get_list("Purchase Invoice", {"supplier": ["in", suppliers]}, ["name", "supplier"]) - invoices = [d for d in invoices if d.supplier] - frappe.cache().hset("invoices", frappe.session.user, invoices) + orders = frappe.db.get_list("Purchase Order", + {"supplier": ["in", suppliers]}, ["name", "supplier"]) - return invoices + invoices = [d for d in invoices if d.supplier] + orders = [d for d in orders if d.supplier] + + frappe.cache().hset("invoices", frappe.session.user, invoices) + frappe.cache().hset("orders", frappe.session.user, invoices) + + return { + 'invoices': invoices, + 'orders': orders + } diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 64eda5f280..47ec212b12 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1375,7 +1375,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2020-11-28 17:42:25.177827", + "modified": "2021-02-27 22:07:23.487138", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 9957aad019..9dc7fc42ef 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -276,7 +276,7 @@ erpnext.company.setup_queries = function(frm) { ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], - ["unrealized_profit_loss_account", {"root_type": "Liability"},] + ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}] ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); From a87e3fcb7c5ddd476fa492b5c22f18f2ba79556e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 28 Feb 2021 23:28:25 +0530 Subject: [PATCH 0840/1264] fix: TDS report cleanup --- .../doctype/payment_entry/payment_entry.py | 5 +- .../purchase_invoice/purchase_invoice.json | 21 +++++--- .../tds_payable_monthly.js | 19 +++----- .../tds_payable_monthly.py | 48 +++++++------------ 4 files changed, 42 insertions(+), 51 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 33eb6f2f26..c0feb44a7f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -967,7 +967,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): outstanding_invoices.pop(idx - 1) outstanding_invoices += invoice_ref_based_on_payment_terms[idx] - + return outstanding_invoices def get_orders_to_be_billed(posting_date, party_type, party, @@ -1411,6 +1411,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= }) pe.set_difference_amount() + if doc.doctype == 'Purchase Order' and doc.apply_tds: + pe.apply_tax_withholding_amount = 1 + return pe def get_bank_cash_account(doc, bank_account): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 36450a9255..7fef60cde1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -127,7 +127,6 @@ "write_off_cost_center", "advances_section", "allocate_advances_automatically", - "adjust_advance_taxes", "get_advances", "advances", "payment_schedule_section", @@ -1492,7 +1491,9 @@ "in_standard_filter": 1, "label": "Status", "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "inter_company_invoice_reference", @@ -1501,7 +1502,9 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "remarks", @@ -1633,7 +1636,9 @@ "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1642,7 +1647,9 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company" + "options": "Company", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", @@ -1654,6 +1661,8 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", + "show_days": 1, + "show_seconds": 1, "width": "50px" }, { @@ -1681,7 +1690,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2020-10-27 21:21:22.647349", + "modified": "2021-02-28 22:33:15.728392", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js index 011c29621a..72de318a48 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js @@ -70,13 +70,13 @@ frappe.query_reports["TDS Payable Monthly"] = { let supplier = frappe.query_report.get_filter_value('supplier'); if(!supplier) return; // return if no supplier selected - // filter order based on selected supplier - let orders = []; - frappe.query_report.order_data.map(d => { + // filter invoices based on selected supplier + let invoices = []; + frappe.query_report.invoice_data.map(d => { if(d.supplier==supplier) - orders.push(d.name) + invoices.push(d.name) }); - frappe.query_report.orders = orders; + frappe.query_report.invoices = invoices; frappe.query_report.refresh(); } }, @@ -104,21 +104,14 @@ frappe.query_reports["TDS Payable Monthly"] = { "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders", callback: function(r) { let invoices = []; - let orders = []; - r.message.invoices.map(d => { + r.message.map(d => { invoices.push(d.name); }); - r.message.orders.map(d => { - orders.push(d.name); - }); - report["invoice_data"] = r.message.invoices; report["invoices"] = invoices; - report["order_data"] = r.message.orders; - report["invoices"] = orders; } }); } diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 7ce61f4a28..5101e3d752 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -8,7 +8,6 @@ from frappe.utils import getdate def execute(filters=None): filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user) - filters["orders"] = frappe.cache().hget("orders", frappe.session.user) validate_filters(filters) set_filters(filters) @@ -16,7 +15,7 @@ def execute(filters=None): payment_entries = get_payment_entires(filters) columns = get_columns(filters) - if not (filters.get("invoices") and filters.get('orders')): + if not filters.get("invoices"): return columns, [] res = get_result(filters, payment_entries) @@ -30,13 +29,9 @@ def validate_filters(filters): def set_filters(filters): invoices = [] - orders = [] if not filters.get("invoices"): - filters["invoices"] = get_tds_invoices_and_orders()['invoices'] - - if not filters.get("orders"): - filters["orders"] = get_tds_invoices_and_orders()['orders'] + filters["invoices"] = get_tds_invoices_and_orders() if filters.supplier and filters.purchase_invoice: for d in filters["invoices"]: @@ -50,24 +45,25 @@ def set_filters(filters): for d in filters["invoices"]: if d.name == filters.purchase_invoice: invoices.append(d) - - if filters.supplier and filters.purchase_order: - for d in filters.get("orders"): + elif filters.supplier and filters.purchase_order: + for d in filters.get("invoices"): if d.name == filters.purchase_order and d.supplier == filters.supplier: - orders.append(d) + invoices.append(d) elif filters.supplier and not filters.purchase_order: - for d in filters.get("orders"): + for d in filters.get("invoices"): if d.supplier == filters.supplier: - orders.append(d) + invoices.append(d) elif filters.purchase_order and not filters.supplier: for d in filters.get("invoices"): if d.name == filters.purchase_order: - orders.append(d) + print("$#$#$$#") + invoices.append(d) filters["invoices"] = invoices if invoices else filters["invoices"] - filters["orders"] = orders if orders else filters["orders"] filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') + #print(filters.get('invoices')) + def get_result(filters, payment_entries): supplier_map, tds_docs = get_supplier_map(filters, payment_entries) documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries] @@ -115,7 +111,7 @@ def get_supplier_map(filters, payment_entries): # pre-fetch all distinct applicable tds docs supplier_map, tds_docs = {}, {} pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - supplier_list = [d.supplier for d in filters["invoices"]] + [d.supplier for d in filters["orders"]] + supplier_list = [d.supplier for d in filters["invoices"]] supplier_detail = frappe.db.get_all('Supplier', {"name": ["in", supplier_list]}, @@ -125,10 +121,6 @@ def get_supplier_map(filters, payment_entries): supplier_map[d.get("name")] = [k for k in supplier_detail if k.name == d.get("supplier")][0] - for d in filters["orders"]: - supplier_map[d.get("name")] = [k for k in supplier_detail - if k.name == d.get("supplier")][0] - for d in payment_entries: supplier_map[d.get("name")] = [k for k in supplier_detail if k.name == d.get("supplier")][0] @@ -248,12 +240,10 @@ def get_payment_entires(filters): 'apply_tax_withholding_amount': 1 } - if filters.get('purchase_order') or filters.get('purchase_invoice'): + if filters.get('purchase_invoice') or filters.get('purchase_order'): parent = frappe.db.get_all('Payment Entry Reference', - { - 'reference_name': ('in', [d.get('name') for d in filters.get('orders')] + - [d.get('name') for d in filters.get('invoices')]) - }, ['parent']) + { 'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent']) + filter_dict.update({'name': ('in', [d.get('parent') for d in parent])}) payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'], @@ -273,13 +263,9 @@ def get_tds_invoices_and_orders(): orders = frappe.db.get_list("Purchase Order", {"supplier": ["in", suppliers]}, ["name", "supplier"]) + invoices = invoices + orders invoices = [d for d in invoices if d.supplier] - orders = [d for d in orders if d.supplier] frappe.cache().hset("invoices", frappe.session.user, invoices) - frappe.cache().hset("orders", frappe.session.user, invoices) - return { - 'invoices': invoices, - 'orders': orders - } + return invoices From fd380d34f99b70616cfa06fb6ce30cdccce931fe Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 May 2021 14:04:51 +0530 Subject: [PATCH 0841/1264] fix: Linting errors --- .../doctype/payment_entry/payment_entry.py | 17 +++--- .../purchase_invoice/purchase_invoice.py | 8 +-- .../doctype/sales_invoice/sales_invoice.py | 8 +-- .../tds_payable_monthly.py | 4 +- erpnext/public/js/controllers/accounts.js | 12 ++-- erpnext/public/js/payment/payments.js | 56 +++++++++---------- 6 files changed, 52 insertions(+), 53 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c0feb44a7f..736048fe3e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -516,13 +516,13 @@ class PaymentEntry(AccountsController): if self.payment_type == "Receive" \ and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \ and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate): - self.unallocated_amount = (self.received_amount_after_tax + total_deductions - - self.base_total_allocated_amount) / self.source_exchange_rate + self.unallocated_amount = (self.received_amount_after_tax + total_deductions - + self.base_total_allocated_amount) / self.source_exchange_rate elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \ and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate): - self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions + - self.base_total_allocated_amount)) / self.target_exchange_rate + self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions + + self.base_total_allocated_amount)) / self.target_exchange_rate def set_difference_amount(self): base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) @@ -719,8 +719,8 @@ class PaymentEntry(AccountsController): "account": d.account_head, "against": self.party if self.payment_type=="Receive" else self.paid_from, dr_or_cr: d.base_tax_amount, - dr_or_cr + "_in_account_currency": d.base_tax_amount \ - if account_currency==self.company_currency \ + dr_or_cr + "_in_account_currency": d.base_tax_amount + if account_currency==self.company_currency else d.tax_amount, "cost_center": d.cost_center }, account_currency, item=d)) @@ -730,8 +730,8 @@ class PaymentEntry(AccountsController): "account": self.advance_tax_account, "against": self.party if self.payment_type=="Receive" else self.paid_from, rev_dr_cr: d.base_tax_amount, - rev_dr_cr + "_in_account_currency": d.base_tax_amount \ - if account_currency==self.company_currency \ + rev_dr_cr + "_in_account_currency": d.base_tax_amount + if account_currency==self.company_currency else d.tax_amount, "cost_center": d.cost_center or self.cost_center }, account_currency, item=d)) @@ -806,7 +806,6 @@ class PaymentEntry(AccountsController): for i, tax in enumerate(self.taxes): tax_rate = tax.rate - current_tax_rate = 0.0 # To set row_id by default as previous row. if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5d4796c916..83c55cc653 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -921,8 +921,8 @@ class PurchaseInvoice(BuyingController): "account": tax.account_head, "against": self.supplier, dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount \ - if account_currency==self.company_currency \ + dr_or_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency else unallocated_amount, 'cost_center': tax.cost_center }, account_currency, item=tax)) @@ -932,8 +932,8 @@ class PurchaseInvoice(BuyingController): "account": pe.advance_tax_account, "against": self.supplier, rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount \ - if account_currency==self.company_currency \ + rev_dr_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency else unallocated_amount, 'cost_center': tax.cost_center or self.cost_center }, account_currency, item=tax)) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9af94fd780..44b8325c62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -933,8 +933,8 @@ class SalesInvoice(SellingController): "account": tax.account_head, "against": self.customer, dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount \ - if account_currency==self.company_currency \ + dr_or_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency else unallocated_amount, 'cost_center': tax.cost_center }, account_currency, item=tax)) @@ -944,8 +944,8 @@ class SalesInvoice(SellingController): "account": pe.advance_tax_account, "against": self.customer, rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount \ - if account_currency==self.company_currency \ + rev_dr_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency else unallocated_amount, 'cost_center': tax.cost_center }, account_currency, item=tax)) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 5101e3d752..619d799d06 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -136,7 +136,7 @@ def get_gle_map(filters, documents): # create gle_map of the form # {"purchase_invoice": list of dict of all gle created for this invoice} gle_map = {} - filter_obj = {} + gle = frappe.db.get_all('GL Entry', { "voucher_no": ["in", documents], @@ -242,7 +242,7 @@ def get_payment_entires(filters): if filters.get('purchase_invoice') or filters.get('purchase_order'): parent = frappe.db.get_all('Payment Entry Reference', - { 'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent']) + {'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent']) filter_dict.update({'name': ('in', [d.get('parent') for d in parent])}) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 23d636061c..7b997a1153 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -156,31 +156,31 @@ cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) { var d = locals[cdt][cdn]; var msg = ""; - if(d.account_head && !d.description) { + if (d.account_head && !d.description) { // set description from account head d.description = d.account_head.split(' - ').slice(0, -1).join(' - '); } - if(!d.charge_type && (d.row_id || d.rate || d.tax_amount)) { + if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) { msg = __("Please select Charge Type first"); d.row_id = ""; d.rate = d.tax_amount = 0.0; - } else if((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) { + } else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) { msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"); d.row_id = ""; - } else if((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) { + } else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) { if (d.idx == 1) { msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"); d.charge_type = ''; } else if (!d.row_id) { msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]); d.row_id = ""; - } else if(d.row_id && d.row_id >= d.idx) { + } else if (d.row_id && d.row_id >= d.idx) { msg = __("Cannot refer row number greater than or equal to current row number for this Charge type"); d.row_id = ""; } } - if(msg) { + if (msg) { frappe.validated = false; refresh_field("taxes"); frappe.throw(msg); diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index b168952e40..e2bd602ba7 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -16,14 +16,14 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.select_text() }, - select_text: function(){ + select_text: function() { var me = this; - $(this.$body).find('.form-control').click(function(){ + $(this.$body).find('.form-control').click(function() { $(this).select(); }) }, - set_payment_primary_action: function(){ + set_payment_primary_action: function() { var me = this; this.dialog.set_primary_action(__("Submit"), function() { @@ -38,7 +38,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ }) }, - make_keyboard: function(){ + make_keyboard: function() { var me = this; $(this.$body).empty(); $(this.$body).html(frappe.render_template('pos_payment', this.frm.doc)) @@ -47,10 +47,10 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.clear_amount() }, - make_multimode_payment: function(){ + make_multimode_payment: function() { var me = this; - if(this.frm.doc.change_amount > 0){ + if(this.frm.doc.change_amount > 0) { me.payment_val = me.doc.outstanding_amount } @@ -59,7 +59,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.payments.amount = flt(this.payment_val); }, - show_payment_details: function(){ + show_payment_details: function() { var me = this; var multimode_payments = $(this.$body).find('.multimode-payments').empty(); if(this.frm.doc.payments.length){ @@ -84,7 +84,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ } }, - set_outstanding_amount: function(){ + set_outstanding_amount: function() { this.selected_mode = $(this.$body).find(repl("input[idx='%(idx)s']",{'idx': this.idx})); this.highlight_selected_row() this.payment_val = 0.0 @@ -101,45 +101,45 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.bind_amount_change_event(); }, - bind_keyboard_event: function(){ + bind_keyboard_event: function() { var me = this; this.payment_val = ''; this.bind_form_control_event(); this.bind_numeric_keys_event(); }, - bind_form_control_event: function(){ + bind_form_control_event: function() { var me = this; - $(this.$body).find('.pos-payment-row').click(function(){ + $(this.$body).find('.pos-payment-row').click(function() { me.idx = $(this).attr("idx"); me.set_outstanding_amount() - }) + }); - $(this.$body).find('.form-control').click(function(){ + $(this.$body).find('.form-control').click(function() { me.idx = $(this).attr("idx"); me.set_outstanding_amount(); me.update_paid_amount(true); - }) + }); - $(this.$body).find('.write_off_amount').change(function(){ + $(this.$body).find('.write_off_amount').change(function() { me.write_off_amount(flt($(this).val()), precision("write_off_amount")); - }) + }); - $(this.$body).find('.change_amount').change(function(){ + $(this.$body).find('.change_amount').change(function() { me.change_amount(flt($(this).val()), precision("change_amount")); - }) + }); }, - highlight_selected_row: function(){ + highlight_selected_row: function() { var me = this; - var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']",{'idx': this.idx})); + var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']", {'idx': this.idx})); $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode') selected_row.addClass('selected-payment-mode') $(this.$body).find('.amount').attr('disabled', true); this.selected_mode.attr('disabled', false); }, - bind_numeric_keys_event: function(){ + bind_numeric_keys_event: function() { var me = this; $(this.$body).find('.pos-keyboard-key').click(function(){ me.payment_val += $(this).text(); @@ -148,7 +148,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ me.update_paid_amount() }) - $(this.$body).find('.delete-btn').click(function(){ + $(this.$body).find('.delete-btn').click(function() { me.payment_val = cstr(flt(me.selected_mode.val())).slice(0, -1); me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)); me.idx = me.selected_mode.attr("idx") @@ -157,9 +157,9 @@ erpnext.payments = erpnext.stock.StockController.extend({ }, - bind_amount_change_event: function(){ + bind_amount_change_event: function() { var me = this; - this.selected_mode.change(function(){ + this.selected_mode.change(function() { me.payment_val = flt($(this).val()) || 0.0; me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)) me.idx = me.selected_mode.attr("idx") @@ -169,7 +169,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ clear_amount: function() { var me = this; - $(this.$body).find('.clr').click(function(e){ + $(this.$body).find('.clr').click(function(e) { e.stopPropagation(); me.idx = $(this).attr("idx"); me.selected_mode = $(me.$body).find(repl("input[idx='%(idx)s']",{'idx': me.idx})); @@ -215,10 +215,10 @@ erpnext.payments = erpnext.stock.StockController.extend({ } }, - update_payment_amount: function(){ + update_payment_amount: function() { var me = this; - $.each(this.frm.doc.payments, function(index, data){ + $.each(this.frm.doc.payments, function(index, data) { if(cint(me.idx) == cint(data.idx)){ data.amount = flt(me.selected_mode.val(), 2) } @@ -228,7 +228,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.show_amounts(); }, - show_amounts: function(){ + show_amounts: function() { var me = this; $(this.$body).find(".write_off_amount").val(format_currency(this.frm.doc.write_off_amount, this.frm.doc.currency)); $(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency)); From 77dcee9d677c3f1833c213c50a9369c373991589 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 16 May 2021 17:45:45 +0530 Subject: [PATCH 0842/1264] fix: Test Cases --- .../doctype/payment_entry/payment_entry.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 736048fe3e..ac8e7070f5 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -482,7 +482,7 @@ class PaymentEntry(AccountsController): self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax), self.precision("paid_amount_after_tax")) - self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.source_exchange_rate), + self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.target_exchange_rate), self.precision("base_paid_amount_after_tax")) def set_amounts_in_company_currency(self): @@ -720,8 +720,8 @@ class PaymentEntry(AccountsController): "against": self.party if self.payment_type=="Receive" else self.paid_from, dr_or_cr: d.base_tax_amount, dr_or_cr + "_in_account_currency": d.base_tax_amount - if account_currency==self.company_currency - else d.tax_amount, + if account_currency==self.company_currency + else d.tax_amount, "cost_center": d.cost_center }, account_currency, item=d)) @@ -731,8 +731,8 @@ class PaymentEntry(AccountsController): "against": self.party if self.payment_type=="Receive" else self.paid_from, rev_dr_cr: d.base_tax_amount, rev_dr_cr + "_in_account_currency": d.base_tax_amount - if account_currency==self.company_currency - else d.tax_amount, + if account_currency==self.company_currency + else d.tax_amount, "cost_center": d.cost_center or self.cost_center }, account_currency, item=d)) @@ -804,7 +804,7 @@ class PaymentEntry(AccountsController): self.total_taxes_and_charges = 0.0 self.base_total_taxes_and_charges = 0.0 - for i, tax in enumerate(self.taxes): + for i, tax in enumerate(self.get('taxes')): tax_rate = tax.rate # To set row_id by default as previous row. @@ -821,11 +821,11 @@ class PaymentEntry(AccountsController): current_tax_amount = (tax_rate / 100.0) * self.paid_amount elif tax.charge_type == "On Previous Row Amount": current_tax_amount = (tax_rate / 100.0) * \ - self.taxes[cint(tax.row_id) - 1].tax_amount + self.get('taxes')[cint(tax.row_id) - 1].tax_amount elif tax.charge_type == "On Previous Row Total": current_tax_amount = (tax_rate / 100.0) * \ - self.taxes[cint(tax.row_id) - 1].total + self.get('taxes')[cint(tax.row_id) - 1].total tax.tax_amount = current_tax_amount tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate @@ -839,7 +839,7 @@ class PaymentEntry(AccountsController): if i == 0: tax.total = flt(self.paid_amount + current_tax_amount, self.precision("total", tax)) else: - tax.total = flt(self.taxes[i-1].total + current_tax_amount, self.precision("total", tax)) + tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax)) tax.base_total = tax.total * self.source_exchange_rate From e2f83ffaa4c5770210fedb8ec22d433fb21378b3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 16 May 2021 17:46:07 +0530 Subject: [PATCH 0843/1264] fix: Linting fixes and other checks --- .../doctype/payment_entry/payment_entry.json | 2 +- .../purchase_invoice/purchase_invoice.py | 31 +++++----- .../doctype/sales_invoice/sales_invoice.py | 8 +-- .../doctype/purchase_order/purchase_order.js | 4 +- erpnext/public/js/payment/payments.js | 62 +++++++++---------- 5 files changed, 53 insertions(+), 54 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index d239c41fd8..afc72045e6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -864,7 +864,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-02-27 13:56:20.007336", + "modified": "2021-05-15 13:05:16.958866", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 83c55cc653..a4bac3550d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -251,11 +251,9 @@ class PurchaseInvoice(BuyingController): if self.update_stock and (not item.from_warehouse): if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]: - msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"])) - msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse)) - msg += _("or it is not the default inventory account") + msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format( + item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse)) frappe.msgprint(msg, title=_("Expense Head Changed")) - item.expense_account = warehouse_account[item.warehouse]["account"] else: # check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not @@ -266,8 +264,8 @@ class PurchaseInvoice(BuyingController): if negative_expense_booked_in_pr: if for_validate and item.expense_account and item.expense_account != stock_not_billed_account: - msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account)) - msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt)) + msg = _("Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}").format( + item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt)) frappe.msgprint(msg, title=_("Expense Head Changed")) item.expense_account = stock_not_billed_account @@ -275,8 +273,9 @@ class PurchaseInvoice(BuyingController): # If no purchase receipt present then book expense in 'Stock Received But Not Billed' # This is done in cases when Purchase Invoice is created before Purchase Receipt if for_validate and item.expense_account and item.expense_account != stock_not_billed_account: - msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account)) - msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code)) + msg = _("Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.").format( + item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code)) + msg += "
    " msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice") frappe.msgprint(msg, title=_("Expense Head Changed")) @@ -308,8 +307,8 @@ class PurchaseInvoice(BuyingController): if not d.purchase_order: msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code)) msg += "

    " - msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required'))) - msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')) + msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format( + frappe.bold(_('Purchase Order Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')) throw(msg, title=_("Mandatory Purchase Order")) def pr_required(self): @@ -323,8 +322,8 @@ class PurchaseInvoice(BuyingController): if not d.purchase_receipt and d.item_code in stock_items: msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code)) msg += "

    " - msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required'))) - msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')) + msg += _("To submit the invoice without purchase receipt please set {0} as {1} in {2}").format( + frappe.bold(_('Purchase Receipt Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')) throw(msg, title=_("Mandatory Purchase Receipt")) def validate_write_off_account(self): @@ -922,8 +921,8 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, dr_or_cr: unallocated_amount, dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, + if account_currency==self.company_currency + else unallocated_amount, 'cost_center': tax.cost_center }, account_currency, item=tax)) @@ -933,8 +932,8 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, rev_dr_cr: unallocated_amount, rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, + if account_currency==self.company_currency + else unallocated_amount, 'cost_center': tax.cost_center or self.cost_center }, account_currency, item=tax)) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 44b8325c62..9a596f2b32 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -934,8 +934,8 @@ class SalesInvoice(SellingController): "against": self.customer, dr_or_cr: unallocated_amount, dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, + if account_currency==self.company_currency + else unallocated_amount, 'cost_center': tax.cost_center }, account_currency, item=tax)) @@ -945,8 +945,8 @@ class SalesInvoice(SellingController): "against": self.customer, rev_dr_cr: unallocated_amount, rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, + if account_currency==self.company_currency + else unallocated_amount, 'cost_center': tax.cost_center }, account_currency, item=tax)) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 139aa72c08..ccb3ce33ec 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -321,7 +321,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( if(me.values) { me.values.sub_con_rm_items.map((row,i) => { if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { - frappe.throw(__("Item Code, warehouse, quantity are required on row {0}", [i+1])); + frappe.throw(__("Item Code and warehouse and quantity are required on row {0}", [i+1])); } }) me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()) @@ -517,7 +517,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( args: { reference_doctype: me.frm.doctype, reference_name: me.frm.docname, - content: __('Reason for hold: ')+data.reason_for_hold, + content: __('Reason for hold:') + " " +data.reason_for_hold, comment_email: frappe.session.user, comment_by: frappe.session.user_fullname }, diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index e2bd602ba7..9fb2f3ee7f 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -13,7 +13,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.$body = this.dialog.body; this.set_payment_primary_action(); this.make_keyboard(); - this.select_text() + this.select_text(); }, select_text: function() { @@ -43,15 +43,15 @@ erpnext.payments = erpnext.stock.StockController.extend({ $(this.$body).empty(); $(this.$body).html(frappe.render_template('pos_payment', this.frm.doc)) this.show_payment_details(); - this.bind_keyboard_event() - this.clear_amount() + this.bind_keyboard_event(); + this.clear_amount(); }, make_multimode_payment: function() { var me = this; - if(this.frm.doc.change_amount > 0) { - me.payment_val = me.doc.outstanding_amount + if (this.frm.doc.change_amount > 0) { + me.payment_val = me.doc.outstanding_amount; } this.payments = frappe.model.add_child(this.frm.doc, 'Multi Mode Payment', "payments"); @@ -112,7 +112,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ var me = this; $(this.$body).find('.pos-payment-row').click(function() { me.idx = $(this).attr("idx"); - me.set_outstanding_amount() + me.set_outstanding_amount(); }); $(this.$body).find('.form-control').click(function() { @@ -133,8 +133,8 @@ erpnext.payments = erpnext.stock.StockController.extend({ highlight_selected_row: function() { var me = this; var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']", {'idx': this.idx})); - $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode') - selected_row.addClass('selected-payment-mode') + $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode'); + selected_row.addClass('selected-payment-mode'); $(this.$body).find('.amount').attr('disabled', true); this.selected_mode.attr('disabled', false); }, @@ -143,15 +143,15 @@ erpnext.payments = erpnext.stock.StockController.extend({ var me = this; $(this.$body).find('.pos-keyboard-key').click(function(){ me.payment_val += $(this).text(); - me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)) - me.idx = me.selected_mode.attr("idx") - me.update_paid_amount() - }) + me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)); + me.idx = me.selected_mode.attr("idx"); + me.update_paid_amount(); + }); $(this.$body).find('.delete-btn').click(function() { me.payment_val = cstr(flt(me.selected_mode.val())).slice(0, -1); me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)); - me.idx = me.selected_mode.attr("idx") + me.idx = me.selected_mode.attr("idx"); me.update_paid_amount(); }) @@ -161,10 +161,10 @@ erpnext.payments = erpnext.stock.StockController.extend({ var me = this; this.selected_mode.change(function() { me.payment_val = flt($(this).val()) || 0.0; - me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)) - me.idx = me.selected_mode.attr("idx") - me.update_payment_amount() - }) + me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)); + me.idx = me.selected_mode.attr("idx"); + me.update_payment_amount(); + }); }, clear_amount: function() { @@ -177,7 +177,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ me.selected_mode.val(0.0); me.highlight_selected_row(); me.update_payment_amount(); - }) + }); }, write_off_amount: function(write_off_amount) { @@ -186,32 +186,32 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.frm.doc.write_off_amount = flt(write_off_amount, precision("write_off_amount")); this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate, precision("base_write_off_amount")); - this.calculate_outstanding_amount(false) - this.show_amounts() + this.calculate_outstanding_amount(false); + this.show_amounts(); }, change_amount: function(change_amount) { var me = this; this.frm.doc.change_amount = flt(change_amount, precision("change_amount")); - this.calculate_write_off_amount() - this.show_amounts() + this.calculate_write_off_amount(); + this.show_amounts(); }, update_paid_amount: function(update_write_off) { var me = this; - if(in_list(['change_amount', 'write_off_amount'], this.idx)){ + if (in_list(['change_amount', 'write_off_amount'], this.idx)) { var value = me.selected_mode.val(); - if(me.idx == 'change_amount'){ - me.change_amount(value) + if (me.idx == 'change_amount') { + me.change_amount(value); } else{ if(flt(value) == 0 && update_write_off && me.frm.doc.outstanding_amount > 0) { value = flt(me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate, precision(me.idx)); } - me.write_off_amount(value) + me.write_off_amount(value); } - }else{ - this.update_payment_amount() + } else { + this.update_payment_amount(); } }, @@ -220,7 +220,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ $.each(this.frm.doc.payments, function(index, data) { if(cint(me.idx) == cint(data.idx)){ - data.amount = flt(me.selected_mode.val(), 2) + data.amount = flt(me.selected_mode.val(), 2); } }) @@ -232,8 +232,8 @@ erpnext.payments = erpnext.stock.StockController.extend({ var me = this; $(this.$body).find(".write_off_amount").val(format_currency(this.frm.doc.write_off_amount, this.frm.doc.currency)); $(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency)); - $(this.$body).find('.change_amount').val(format_currency(this.frm.doc.change_amount, this.frm.doc.currency)) - $(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency)) + $(this.$body).find('.change_amount').val(format_currency(this.frm.doc.change_amount, this.frm.doc.currency)); + $(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency)); this.update_invoice(); } }) \ No newline at end of file From a23aaf43f47d515a8ec2a848a9341b560aa92706 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 May 2021 20:58:50 +0530 Subject: [PATCH 0844/1264] fix: Allocate advance taxes only for payment entry --- .../purchase_invoice/purchase_invoice.py | 65 ++++++++++--------- .../doctype/sales_invoice/sales_invoice.py | 65 ++++++++++--------- erpnext/controllers/accounts_controller.py | 19 +++--- 3 files changed, 76 insertions(+), 73 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a4bac3550d..f925bcf5ba 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -903,42 +903,43 @@ class PurchaseInvoice(BuyingController): def allocate_advance_taxes(self, gl_entries): tax_map = self.get_tax_map() for pe in self.get('advances'): - pe = frappe.get_doc('Payment Entry', pe.reference_name) - for tax in pe.get('taxes'): - account_currency = get_account_currency(tax.account_head) - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + if pe.reference_type == 'Payment Entry': + pe = frappe.get_doc('Payment Entry', pe.reference_name) + for tax in pe.get('taxes'): + account_currency = get_account_currency(tax.account_head) + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - unallocated_amount = tax.tax_amount - tax.allocated_amount - if tax_map.get(tax.account_head): - amount = tax_map.get(tax.account_head) - if amount < unallocated_amount: - unallocated_amount = amount + unallocated_amount = tax.tax_amount - tax.allocated_amount + if tax_map.get(tax.account_head): + amount = tax_map.get(tax.account_head) + if amount < unallocated_amount: + unallocated_amount = amount - gl_entries.append( - self.get_gl_dict({ - "account": tax.account_head, - "against": self.supplier, - dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center - }, account_currency, item=tax)) + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "against": self.supplier, + dr_or_cr: unallocated_amount, + dr_or_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency + else unallocated_amount, + 'cost_center': tax.cost_center + }, account_currency, item=tax)) - gl_entries.append( - self.get_gl_dict({ - "account": pe.advance_tax_account, - "against": self.supplier, - rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center or self.cost_center - }, account_currency, item=tax)) + gl_entries.append( + self.get_gl_dict({ + "account": pe.advance_tax_account, + "against": self.supplier, + rev_dr_cr: unallocated_amount, + rev_dr_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency + else unallocated_amount, + 'cost_center': tax.cost_center or self.cost_center + }, account_currency, item=tax)) - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) - tax_map[tax.account_head] -= unallocated_amount + frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) + tax_map[tax.account_head] -= unallocated_amount def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9a596f2b32..664b3ab0f2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -916,42 +916,43 @@ class SalesInvoice(SellingController): def allocate_advance_taxes(self, gl_entries): tax_map = self.get_tax_map() for pe in self.get('advances'): - pe = frappe.get_doc('Payment Entry', pe.reference_name) - for tax in pe.get('taxes'): - account_currency = get_account_currency(tax.account_head) - dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + if pe.reference_type == 'Payment Entry': + pe = frappe.get_doc('Payment Entry', pe.reference_name) + for tax in pe.get('taxes'): + account_currency = get_account_currency(tax.account_head) + dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - unallocated_amount = tax.tax_amount - tax.allocated_amount - if tax_map.get(tax.account_head): - amount = tax_map.get(tax.account_head) - if amount < unallocated_amount: - unallocated_amount = amount + unallocated_amount = tax.tax_amount - tax.allocated_amount + if tax_map.get(tax.account_head): + amount = tax_map.get(tax.account_head) + if amount < unallocated_amount: + unallocated_amount = amount - gl_entries.append( - self.get_gl_dict({ - "account": tax.account_head, - "against": self.customer, - dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center - }, account_currency, item=tax)) + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "against": self.customer, + dr_or_cr: unallocated_amount, + dr_or_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency + else unallocated_amount, + 'cost_center': tax.cost_center + }, account_currency, item=tax)) - gl_entries.append( - self.get_gl_dict({ - "account": pe.advance_tax_account, - "against": self.customer, - rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center - }, account_currency, item=tax)) + gl_entries.append( + self.get_gl_dict({ + "account": pe.advance_tax_account, + "against": self.customer, + rev_dr_cr: unallocated_amount, + rev_dr_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency + else unallocated_amount, + 'cost_center': tax.cost_center + }, account_currency, item=tax)) - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) - tax_map[tax.account_head] -= unallocated_amount + frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) + tax_map[tax.account_head] -= unallocated_amount def make_item_gl_entries(self, gl_entries): # income account gl entries diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index da88853a96..11a9d90a1f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -726,16 +726,17 @@ class AccountsController(TransactionBase): tax_map = self.get_tax_map() for pe in self.get('advances'): - pe = frappe.get_doc('Payment Entry', pe.reference_name) - for tax in pe.get('taxes'): - allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head) - if allocated_amount > tax.tax_amount: - allocated_amount = tax.tax_amount + if pe.reference_type == 'Payment Entry': + pe = frappe.get_doc('Payment Entry', pe.reference_name) + for tax in pe.get('taxes'): + allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head) + if allocated_amount > tax.tax_amount: + allocated_amount = tax.tax_amount - if allocated_amount: - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount - allocated_amount) - tax_map[tax.account_head] -= allocated_amount - allocated_tax_map[tax.account_head] -= allocated_amount + if allocated_amount: + frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount - allocated_amount) + tax_map[tax.account_head] -= allocated_amount + allocated_tax_map[tax.account_head] -= allocated_amount def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for From c9da1fc568db4d6a163b3ba755ec69e776bcea69 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 May 2021 18:38:35 +0530 Subject: [PATCH 0845/1264] chore: Test case for adance TDS allocation --- .../doctype/payment_entry/payment_entry.json | 333 +++++------------- .../doctype/payment_entry/payment_entry.py | 6 +- .../purchase_invoice/purchase_invoice.py | 41 --- .../purchase_invoice/test_purchase_invoice.py | 90 +++++ .../doctype/sales_invoice/sales_invoice.py | 41 --- erpnext/controllers/accounts_controller.py | 56 ++- 6 files changed, 233 insertions(+), 334 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index afc72045e6..b4b92f926e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -97,9 +97,7 @@ { "fieldname": "type_of_payment", "fieldtype": "Section Break", - "label": "Type of Payment", - "show_days": 1, - "show_seconds": 1 + "label": "Type of Payment" }, { "bold": 1, @@ -109,9 +107,7 @@ "options": "ACC-PAY-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "bold": 1, @@ -122,15 +118,11 @@ "label": "Payment Type", "options": "Receive\nPay\nInternal Transfer", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "column_break_5", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "bold": 1, @@ -139,9 +131,7 @@ "fieldtype": "Date", "in_list_view": 1, "label": "Posting Date", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "company", @@ -150,34 +140,26 @@ "options": "Company", "print_hide": 1, "remember_last_selected_value": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "in_list_view": 1, "label": "Mode of Payment", - "options": "Mode of Payment", - "show_days": 1, - "show_seconds": 1 + "options": "Mode of Payment" }, { "depends_on": "eval:in_list([\"Receive\", \"Pay\"], doc.payment_type)", "fieldname": "party_section", "fieldtype": "Section Break", - "label": "Payment From / To", - "show_days": 1, - "show_seconds": 1 + "label": "Payment From / To" }, { "depends_on": "eval:in_list([\"Receive\", \"Pay\"], doc.payment_type) && doc.docstatus==0", @@ -187,9 +169,7 @@ "label": "Party Type", "options": "DocType", "print_hide": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "bold": 1, @@ -198,9 +178,7 @@ "fieldtype": "Dynamic Link", "in_standard_filter": 1, "label": "Party", - "options": "party_type", - "show_days": 1, - "show_seconds": 1 + "options": "party_type" }, { "allow_on_submit": 1, @@ -208,24 +186,18 @@ "fieldname": "party_name", "fieldtype": "Data", "in_global_search": 1, - "label": "Party Name", - "show_days": 1, - "show_seconds": 1 + "label": "Party Name" }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "party", "fieldname": "contact_person", "fieldtype": "Link", "label": "Contact", - "options": "Contact", - "show_days": 1, - "show_seconds": 1 + "options": "Contact" }, { "depends_on": "contact_person", @@ -233,17 +205,13 @@ "fieldtype": "Data", "label": "Email", "options": "Email", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "payment_accounts_section", "fieldtype": "Section Break", - "label": "Accounts", - "show_days": 1, - "show_seconds": 1 + "label": "Accounts" }, { "depends_on": "party", @@ -251,9 +219,7 @@ "fieldtype": "Currency", "label": "Party Balance", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "bold": 1, @@ -264,9 +230,7 @@ "label": "Account Paid From", "options": "Account", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "paid_from", @@ -276,9 +240,7 @@ "options": "Currency", "print_hide": 1, "read_only": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "paid_from", @@ -287,15 +249,11 @@ "label": "Account Balance", "options": "paid_from_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_18", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:(in_list([\"Internal Transfer\", \"Receive\"], doc.payment_type) || doc.party)", @@ -305,9 +263,7 @@ "label": "Account Paid To", "options": "Account", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "paid_to", @@ -317,9 +273,7 @@ "options": "Currency", "print_hide": 1, "read_only": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "paid_to", @@ -328,17 +282,13 @@ "label": "Account Balance", "options": "paid_to_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:(doc.paid_to && doc.paid_from)", "fieldname": "payment_amounts_section", "fieldtype": "Section Break", - "label": "Amount", - "show_days": 1, - "show_seconds": 1 + "label": "Amount" }, { "bold": 1, @@ -346,18 +296,14 @@ "fieldtype": "Currency", "label": "Paid Amount", "options": "paid_from_account_currency", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "source_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "base_paid_amount", @@ -366,15 +312,11 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "column_break_21", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "bold": 1, @@ -383,18 +325,14 @@ "label": "Received Amount", "options": "paid_to_account_currency", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "target_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", "print_hide": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "base_received_amount", @@ -403,40 +341,30 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "eval:(doc.party && doc.paid_from && doc.paid_to && doc.paid_amount && doc.received_amount)", "fieldname": "section_break_14", "fieldtype": "Section Break", - "label": "Reference", - "show_days": 1, - "show_seconds": 1 + "label": "Reference" }, { "depends_on": "eval:doc.docstatus==0", "fieldname": "get_outstanding_invoice", "fieldtype": "Button", - "label": "Get Outstanding Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Get Outstanding Invoice" }, { "fieldname": "references", "fieldtype": "Table", "label": "Payment References", - "options": "Payment Entry Reference", - "show_days": 1, - "show_seconds": 1 + "options": "Payment Entry Reference" }, { "fieldname": "section_break_34", "fieldtype": "Section Break", - "label": "Writeoff", - "show_days": 1, - "show_seconds": 1 + "label": "Writeoff" }, { "bold": 1, @@ -445,9 +373,7 @@ "fieldtype": "Currency", "label": "Total Allocated Amount", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total_allocated_amount", @@ -455,31 +381,23 @@ "label": "Total Allocated Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "set_exchange_gain_loss", "fieldtype": "Button", - "label": "Set Exchange Gain / Loss", - "show_days": 1, - "show_seconds": 1 + "label": "Set Exchange Gain / Loss" }, { "fieldname": "column_break_36", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:(doc.paid_amount && doc.received_amount && doc.references)", "fieldname": "unallocated_amount", "fieldtype": "Currency", "label": "Unallocated Amount", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "bold": 1, @@ -489,17 +407,13 @@ "label": "Difference Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "difference_amount", "fieldname": "write_off_difference_amount", "fieldtype": "Button", - "label": "Write Off Difference Amount", - "show_days": 1, - "show_seconds": 1 + "label": "Write Off Difference Amount" }, { "collapsible": 1, @@ -507,39 +421,29 @@ "depends_on": "eval:(doc.paid_amount && doc.received_amount)", "fieldname": "deductions_or_loss_section", "fieldtype": "Section Break", - "label": "Deductions or Loss", - "show_days": 1, - "show_seconds": 1 + "label": "Deductions or Loss" }, { "fieldname": "deductions", "fieldtype": "Table", "label": "Payment Deductions or Loss", - "options": "Payment Entry Deduction", - "show_days": 1, - "show_seconds": 1 + "options": "Payment Entry Deduction" }, { "fieldname": "transaction_references", "fieldtype": "Section Break", - "label": "Transaction ID", - "show_days": 1, - "show_seconds": 1 + "label": "Transaction ID" }, { "bold": 1, "depends_on": "eval:(doc.paid_from && doc.paid_to)", "fieldname": "reference_no", "fieldtype": "Data", - "label": "Cheque/Reference No", - "show_days": 1, - "show_seconds": 1 + "label": "Cheque/Reference No" }, { "fieldname": "column_break_23", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "bold": 1, @@ -547,9 +451,7 @@ "fieldname": "reference_date", "fieldtype": "Date", "label": "Cheque/Reference Date", - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "depends_on": "eval:doc.docstatus==1", @@ -558,76 +460,58 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "depends_on": "eval:(doc.paid_from && doc.paid_to && doc.paid_amount && doc.received_amount)", "fieldname": "section_break_12", "fieldtype": "Section Break", - "label": "More Information", - "show_days": 1, - "show_seconds": 1 + "label": "More Information" }, { "fieldname": "project", "fieldtype": "Link", "label": "Project", "options": "Project", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "remarks", "fieldtype": "Small Text", "label": "Remarks", "no_copy": 1, - "read_only_depends_on": "eval:doc.custom_remarks == 0", - "show_days": 1, - "show_seconds": 1 + "read_only_depends_on": "eval:doc.custom_remarks == 0" }, { "fieldname": "column_break_16", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "letter_head", "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "print_heading", "fieldtype": "Link", "label": "Print Heading", "options": "Print Heading", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fetch_from": "bank_account.bank", "fieldname": "bank", "fieldtype": "Read Only", - "label": "Bank", - "show_days": 1, - "show_seconds": 1 + "label": "Bank" }, { "fetch_from": "bank_account.bank_account_no", "fieldname": "bank_account_no", "fieldtype": "Read Only", - "label": "Bank Account No", - "show_days": 1, - "show_seconds": 1 + "label": "Bank Account No" }, { "fieldname": "payment_order", @@ -636,16 +520,12 @@ "no_copy": 1, "options": "Payment Order", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "subscription_section", "fieldtype": "Section Break", - "label": "Subscription Section", - "show_days": 1, - "show_seconds": 1 + "label": "Subscription Section" }, { "allow_on_submit": 1, @@ -655,9 +535,7 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "amended_from", @@ -666,9 +544,7 @@ "no_copy": 1, "options": "Payment Entry", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "title", @@ -683,18 +559,14 @@ "fieldname": "bank_account", "fieldtype": "Link", "label": "Company Bank Account", - "options": "Bank Account", - "show_days": 1, - "show_seconds": 1 + "options": "Bank Account" }, { "depends_on": "party", "fieldname": "party_bank_account", "fieldtype": "Link", "label": "Party Bank Account", - "options": "Bank Account", - "show_days": 1, - "show_seconds": 1 + "options": "Bank Account" }, { "fieldname": "payment_order_status", @@ -702,23 +574,17 @@ "hidden": 1, "label": "Payment Order Status", "options": "Initiated\nPayment Ordered", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions" }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "Draft", @@ -739,70 +605,55 @@ "fieldname": "tax_withholding_category", "fieldtype": "Link", "label": "Tax Withholding Category", - "options": "Tax Withholding Category", - "show_days": 1, - "show_seconds": 1 + "mandatory_depends_on": "eval:doc.apply_tax_withholding_amount", + "options": "Tax Withholding Category" }, { "default": "0", "depends_on": "eval:doc.party_type == 'Supplier'", "fieldname": "apply_tax_withholding_amount", "fieldtype": "Check", - "label": "Apply Tax Withholding Amount", - "show_days": 1, - "show_seconds": 1 + "label": "Apply Tax Withholding Amount" }, { "collapsible": 1, "fieldname": "taxes_and_charges_section", "fieldtype": "Section Break", - "label": "Taxes and Charges", - "show_days": 1, - "show_seconds": 1 + "label": "Taxes and Charges" }, { "depends_on": "eval:doc.party_type == 'Supplier'", "fieldname": "purchase_taxes_and_charges_template", "fieldtype": "Link", "label": "Taxes and Charges Template", - "options": "Purchase Taxes and Charges Template", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Taxes and Charges Template" }, { "depends_on": "eval: doc.party_type == 'Customer'", "fieldname": "sales_taxes_and_charges_template", "fieldtype": "Link", "label": "Taxes and Charges Template", - "options": "Sales Taxes and Charges Template", - "show_days": 1, - "show_seconds": 1 + "options": "Sales Taxes and Charges Template" }, { "depends_on": "eval: doc.party_type == 'Supplier' || doc.party_type == 'Customer'", "fieldname": "taxes", "fieldtype": "Table", "label": "Advance Taxes and Charges", - "options": "Advance Taxes and Charges", - "show_days": 1, - "show_seconds": 1 + "options": "Advance Taxes and Charges" }, { "fieldname": "base_total_taxes_and_charges", "fieldtype": "Currency", "label": "Total Taxes and Charges (Company Currency)", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_taxes_and_charges", "fieldtype": "Currency", "label": "Total Taxes and Charges", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "paid_amount_after_tax", @@ -810,61 +661,47 @@ "hidden": 1, "label": "Paid Amount After Tax", "options": "paid_from_account_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_paid_amount_after_tax", "fieldtype": "Currency", "label": "Paid Amount After Tax (Company Currency)", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_55", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "section_break_56", "fieldtype": "Section Break", - "hide_border": 1, - "show_days": 1, - "show_seconds": 1 + "hide_border": 1 }, { "fieldname": "advance_tax_account", "fieldtype": "Link", "label": "Advance Tax Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "received_amount_after_tax", "fieldtype": "Currency", "label": "Received Amount After Tax", - "options": "paid_to_account_currency", - "show_days": 1, - "show_seconds": 1 + "options": "paid_to_account_currency" }, { "fieldname": "base_received_amount_after_tax", "fieldtype": "Currency", "label": "Received Amount After Tax (Company Currency)", - "options": "Company:company:default_currency", - "show_days": 1, - "show_seconds": 1 + "options": "Company:company:default_currency" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-15 13:05:16.958866", + "modified": "2021-05-19 02:33:08.192932", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", @@ -908,4 +745,4 @@ "sort_order": "DESC", "title_field": "title", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ac8e7070f5..cbe8045fd1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -436,7 +436,10 @@ class PaymentEntry(AccountsController): if not tax_withholding_details: return - tax_withholding_details.update({'included_in_paid_amount': included_in_paid_amount}) + tax_withholding_details.update({ + 'included_in_paid_amount': included_in_paid_amount, + 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) + }) accounts = [] for d in self.taxes: @@ -1412,6 +1415,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= if doc.doctype == 'Purchase Order' and doc.apply_tds: pe.apply_tax_withholding_amount = 1 + pe.tax_withholding_category = doc.tax_withholding_category return pe diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f925bcf5ba..934c731cf1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -900,47 +900,6 @@ class PurchaseInvoice(BuyingController): "cost_center": self.cost_center }, account_currency, item=self)) - def allocate_advance_taxes(self, gl_entries): - tax_map = self.get_tax_map() - for pe in self.get('advances'): - if pe.reference_type == 'Payment Entry': - pe = frappe.get_doc('Payment Entry', pe.reference_name) - for tax in pe.get('taxes'): - account_currency = get_account_currency(tax.account_head) - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - - unallocated_amount = tax.tax_amount - tax.allocated_amount - if tax_map.get(tax.account_head): - amount = tax_map.get(tax.account_head) - if amount < unallocated_amount: - unallocated_amount = amount - - gl_entries.append( - self.get_gl_dict({ - "account": tax.account_head, - "against": self.supplier, - dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center - }, account_currency, item=tax)) - - gl_entries.append( - self.get_gl_dict({ - "account": pe.advance_tax_account, - "against": self.supplier, - rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center or self.cost_center - }, account_currency, item=tax)) - - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) - tax_map[tax.account_head] -= unallocated_amount - def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries if cint(self.is_paid) and self.cash_bank_account and self.paid_amount: diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 66be11ff23..0da46e9886 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -950,6 +950,96 @@ class TestPurchaseInvoice(unittest.TestCase): acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.save() + def test_purchase_invoice_advance_taxes(self): + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice + + # Update tax withholding category with current fiscal year and rate details + update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate()) + + # Create Purchase Order with TDS applied + po = create_purchase_order(do_not_save=1, rate=3000) + po.apply_tds = 1 + po.tax_withholding_category = 'TDS - 194 - Dividends - Individual' + po.save() + po.submit() + + # Update Unrealized Profit / Loss Account which is used as default advance tax account + frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC') + + # Create Payment Entry Against the order + payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name) + payment_entry.save() + payment_entry.submit() + + # Check GLE for Payment Entry + expected_gle = [ + ['_Test Account Excise Duty - _TC', 6000, 0], + ['Cash - _TC', 0, 24000], + ['Creditors - _TC', 24000, 0], + ['TDS Payable - _TC', 0, 6000], + ] + + gl_entries = frappe.db.sql("""select account, debit, credit + from `tabGL Entry` + where voucher_type='Payment Entry' and voucher_no=%s + order by account asc""", (payment_entry.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.debit) + self.assertEqual(expected_gle[i][2], gle.credit) + + # Create Purchase Invoice against Purchase Order + purchase_invoice = get_mapped_purchase_invoice(po.name) + purchase_invoice.allocate_advances_automatically = 1 + purchase_invoice.save() + purchase_invoice.submit() + + # Check GLE for Purchase Invoice + # Zero net effect on final TDS Payable on invoice + expected_gle = [ + ['_Test Account Excise Duty - _TC', 0, 6000], + ['Cost of Goods Sold - _TC', 30000, 0], + ['Creditors - _TC', 0, 24000], + ['TDS Payable - _TC', 6000, 6000], + ] + + gl_entries = frappe.db.sql("""select account, debit, credit + from `tabGL Entry` + where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", (purchase_invoice.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.debit) + self.assertEqual(expected_gle[i][2], gle.credit) + +def update_tax_witholding_category(company, account, date): + from erpnext.accounts.utils import get_fiscal_year + + fiscal_year = get_fiscal_year(date=date, company=company) + + if not frappe.db.get_value('Tax Withholding Rate', + {'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}): + tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') + tds_category.append('rates', { + 'fiscal_year': fiscal_year[0], + 'tax_withholding_rate': 20, + 'single_threshold': 2500, + 'cumulative_threshold': 0 + }) + tds_category.save() + + if not frappe.db.get_value('Tax Withholding Account', + {'parent': 'TDS - 194 - Dividends - Individual', 'account': account}): + tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') + tds_category.append('accounts', { + 'company': company, + 'account': account + }) + tds_category.save() def unlink_payment_on_cancel_of_invoice(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 664b3ab0f2..485ae18bf0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -913,47 +913,6 @@ class SalesInvoice(SellingController): "cost_center": self.cost_center }, account_currency, item=self)) - def allocate_advance_taxes(self, gl_entries): - tax_map = self.get_tax_map() - for pe in self.get('advances'): - if pe.reference_type == 'Payment Entry': - pe = frappe.get_doc('Payment Entry', pe.reference_name) - for tax in pe.get('taxes'): - account_currency = get_account_currency(tax.account_head) - dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - - unallocated_amount = tax.tax_amount - tax.allocated_amount - if tax_map.get(tax.account_head): - amount = tax_map.get(tax.account_head) - if amount < unallocated_amount: - unallocated_amount = amount - - gl_entries.append( - self.get_gl_dict({ - "account": tax.account_head, - "against": self.customer, - dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center - }, account_currency, item=tax)) - - gl_entries.append( - self.get_gl_dict({ - "account": pe.advance_tax_account, - "against": self.customer, - rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - 'cost_center': tax.cost_center - }, account_currency, item=tax)) - - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount + unallocated_amount) - tax_map[tax.account_head] -= unallocated_amount - def make_item_gl_entries(self, gl_entries): # income account gl entries for item in self.get("items"): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 11a9d90a1f..1e85433a60 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -700,7 +700,7 @@ class AccountsController(TransactionBase): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries if self.doctype in ["Sales Invoice", "Purchase Invoice"]: - self.update_allocated_advance_taxes() + self.update_allocated_advance_taxes_on_cancel() if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): unlink_ref_doc_from_payment_entries(self) @@ -716,7 +716,7 @@ class AccountsController(TransactionBase): return tax_map - def update_allocated_advance_taxes(self): + def update_allocated_advance_taxes_on_cancel(self): if self.get('advances'): tax_accounts = [d.account_head for d in self.get('taxes')] allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'], @@ -734,10 +734,60 @@ class AccountsController(TransactionBase): allocated_amount = tax.tax_amount if allocated_amount: - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', tax.allocated_amount - allocated_amount) + frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', + tax.allocated_amount - allocated_amount) tax_map[tax.account_head] -= allocated_amount allocated_tax_map[tax.account_head] -= allocated_amount + def allocate_advance_taxes(self, gl_entries): + tax_map = self.get_tax_map() + for pe in self.get("advances"): + if pe.reference_type == "Payment Entry": + pe = frappe.get_doc("Payment Entry", pe.reference_name) + for tax in pe.get("taxes"): + account_currency = get_account_currency(tax.account_head) + + if self.doctype == "Purchase Invoice": + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + else: + dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + + party = self.supplier if self.doctype == "Purchase Invoice" else self.customer + unallocated_amount = tax.tax_amount - tax.allocated_amount + if tax_map.get(tax.account_head): + amount = tax_map.get(tax.account_head) + if amount < unallocated_amount: + unallocated_amount = amount + + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "against": party, + dr_or_cr: unallocated_amount, + dr_or_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency + else unallocated_amount, + "cost_center": tax.cost_center + }, account_currency, item=tax)) + + gl_entries.append( + self.get_gl_dict({ + "account": pe.advance_tax_account, + "against": party, + rev_dr_cr: unallocated_amount, + rev_dr_cr + "_in_account_currency": unallocated_amount + if account_currency==self.company_currency + else unallocated_amount, + "cost_center": tax.cost_center + }, account_currency, item=tax)) + + frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount", + tax.allocated_amount + unallocated_amount) + + tax_map[tax.account_head] -= unallocated_amount + def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for item_allowance = {} From b07f7d1b70352a6a85549541fc4a635df6ff393a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 May 2021 17:08:57 +0530 Subject: [PATCH 0846/1264] fix: Linting and other fixes --- .../doctype/payment_entry/payment_entry.js | 19 ++++++--- .../doctype/payment_entry/payment_entry.json | 4 +- .../doctype/payment_entry/payment_entry.py | 19 ++++++--- .../purchase_invoice/test_purchase_invoice.py | 40 +++++++++---------- .../doctype/purchase_order/purchase_order.js | 3 +- erpnext/public/js/payment/payments.js | 33 +++++++-------- 6 files changed, 66 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 19d73b1ad4..a7fcbd7e3b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -1112,12 +1112,16 @@ frappe.ui.form.on('Payment Entry', { current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; + let applicable_tax_amount = 0 + if (!tax.included_in_paid_amount) { - if(i==0) { - tax.total = flt(frm.doc.paid_amount + current_tax_amount, precision("total", tax)); - } else { - tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax)); - } + applicable_tax_amount = current_tax_amount + } + + if(i==0) { + tax.total = flt(frm.doc.paid_amount + applicable_tax_amount, precision("total", tax)); + } else { + tax.total = flt(frm.doc["taxes"][i-1].total + applicable_tax_amount, precision("total", tax)); } tax.base_total = tax.total * frm.doc.source_exchange_rate; @@ -1194,6 +1198,11 @@ frappe.ui.form.on('Advance Taxes and Charges', { taxes_remove: function(frm) { frm.events.calculate_taxes(frm); frm.events.set_unallocated_amount(frm); + }, + + included_in_paid_amount: function(frm) { + frm.events.calculate_taxes(frm); + frm.events.set_unallocated_amount(frm); } }) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index b4b92f926e..1e2bd8194a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -29,7 +29,6 @@ "paid_from", "paid_from_account_currency", "paid_from_account_balance", - "advance_tax_account", "column_break_18", "paid_to", "paid_to_account_currency", @@ -60,6 +59,7 @@ "taxes_and_charges_section", "purchase_taxes_and_charges_template", "sales_taxes_and_charges_template", + "advance_tax_account", "column_break_55", "apply_tax_withholding_amount", "tax_withholding_category", @@ -701,7 +701,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-19 02:33:08.192932", + "modified": "2021-05-20 02:04:56.766124", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index cbe8045fd1..d960156670 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -444,6 +444,11 @@ class PaymentEntry(AccountsController): accounts = [] for d in self.taxes: if d.account_head == tax_withholding_details.get("account_head"): + + # Preserve user updated included in paid amount + if d.included_in_paid_amount: + tax_withholding_details.update({'included_in_paid_amount': d.included_in_paid_amount}) + d.update(tax_withholding_details) accounts.append(d.account_head) @@ -839,12 +844,16 @@ class PaymentEntry(AccountsController): current_tax_amount *= 1.0 if not tax.included_in_paid_amount: - if i == 0: - tax.total = flt(self.paid_amount + current_tax_amount, self.precision("total", tax)) - else: - tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax)) + applicable_tax = current_tax_amount + else: + applicable_tax = 0 - tax.base_total = tax.total * self.source_exchange_rate + if i == 0: + tax.total = flt(self.paid_amount + applicable_tax, self.precision("total", tax)) + else: + tax.total = flt(self.get('taxes')[i-1].total + applicable_tax, self.precision("total", tax)) + + tax.base_total = tax.total * self.source_exchange_rate self.total_taxes_and_charges += current_tax_amount self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0da46e9886..aa56ddfdf3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -992,29 +992,29 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gle[i][2], gle.credit) # Create Purchase Invoice against Purchase Order - purchase_invoice = get_mapped_purchase_invoice(po.name) - purchase_invoice.allocate_advances_automatically = 1 - purchase_invoice.save() - purchase_invoice.submit() + # purchase_invoice = get_mapped_purchase_invoice(po.name) + # purchase_invoice.allocate_advances_automatically = 1 + # purchase_invoice.save() + # purchase_invoice.submit() - # Check GLE for Purchase Invoice - # Zero net effect on final TDS Payable on invoice - expected_gle = [ - ['_Test Account Excise Duty - _TC', 0, 6000], - ['Cost of Goods Sold - _TC', 30000, 0], - ['Creditors - _TC', 0, 24000], - ['TDS Payable - _TC', 6000, 6000], - ] + # # Check GLE for Purchase Invoice + # # Zero net effect on final TDS Payable on invoice + # expected_gle = [ + # ['_Test Account Excise Duty - _TC', 0, 6000], + # ['Cost of Goods Sold - _TC', 30000, 0], + # ['Creditors - _TC', 0, 24000], + # ['TDS Payable - _TC', 6000, 6000], + # ] - gl_entries = frappe.db.sql("""select account, debit, credit - from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", (purchase_invoice.name), as_dict=1) + # gl_entries = frappe.db.sql("""select account, debit, credit + # from `tabGL Entry` + # where voucher_type='Purchase Invoice' and voucher_no=%s + # order by account asc""", (purchase_invoice.name), as_dict=1) - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.debit) - self.assertEqual(expected_gle[i][2], gle.credit) + # for i, gle in enumerate(gl_entries): + # self.assertEqual(expected_gle[i][0], gle.account) + # self.assertEqual(expected_gle[i][1], gle.debit) + # self.assertEqual(expected_gle[i][2], gle.credit) def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index ccb3ce33ec..0f6d927b36 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -321,7 +321,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( if(me.values) { me.values.sub_con_rm_items.map((row,i) => { if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { - frappe.throw(__("Item Code and warehouse and quantity are required on row {0}", [i+1])); + let row_id = i+1; + frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id])); } }) me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()) diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index 9fb2f3ee7f..b9e933a38e 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -16,12 +16,11 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.select_text(); }, - select_text: function() { - var me = this; + select_text() { $(this.$body).find('.form-control').click(function() { $(this).select(); - }) - }, + }); + } set_payment_primary_action: function() { var me = this; @@ -62,8 +61,8 @@ erpnext.payments = erpnext.stock.StockController.extend({ show_payment_details: function() { var me = this; var multimode_payments = $(this.$body).find('.multimode-payments').empty(); - if(this.frm.doc.payments.length){ - $.each(this.frm.doc.payments, function(index, data){ + if (this.frm.doc.payments.length) { + $.each(this.frm.doc.payments, function(index, data) { $(frappe.render_template('payment_details', { mode_of_payment: data.mode_of_payment, amount: data.amount, @@ -88,12 +87,12 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.selected_mode = $(this.$body).find(repl("input[idx='%(idx)s']",{'idx': this.idx})); this.highlight_selected_row() this.payment_val = 0.0 - if(this.frm.doc.outstanding_amount > 0 && flt(this.selected_mode.val()) == 0.0){ + if (this.frm.doc.outstanding_amount > 0 && flt(this.selected_mode.val()) == 0.0) { //When user first time click on row this.payment_val = flt(this.frm.doc.outstanding_amount / this.frm.doc.conversion_rate, precision("outstanding_amount")) this.selected_mode.val(format_currency(this.payment_val, this.frm.doc.currency)); - this.update_payment_amount() - }else if(flt(this.selected_mode.val()) > 0){ + this.update_payment_amount(); + } else if (flt(this.selected_mode.val()) > 0) { //If user click on existing row which has value this.payment_val = flt(this.selected_mode.val()); } @@ -101,8 +100,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ this.bind_amount_change_event(); }, - bind_keyboard_event: function() { - var me = this; + bind_keyboard_event() { this.payment_val = ''; this.bind_form_control_event(); this.bind_numeric_keys_event(); @@ -130,8 +128,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ }); }, - highlight_selected_row: function() { - var me = this; + highlight_selected_row() { var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']", {'idx': this.idx})); $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode'); selected_row.addClass('selected-payment-mode'); @@ -157,7 +154,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ }, - bind_amount_change_event: function() { + bind_amount_change_event() { var me = this; this.selected_mode.change(function() { me.payment_val = flt($(this).val()) || 0.0; @@ -180,9 +177,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ }); }, - write_off_amount: function(write_off_amount) { - var me = this; - + write_off_amount(write_off_amount) { this.frm.doc.write_off_amount = flt(write_off_amount, precision("write_off_amount")); this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate, precision("base_write_off_amount")); @@ -204,7 +199,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ var value = me.selected_mode.val(); if (me.idx == 'change_amount') { me.change_amount(value); - } else{ + } else { if(flt(value) == 0 && update_write_off && me.frm.doc.outstanding_amount > 0) { value = flt(me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate, precision(me.idx)); } @@ -219,7 +214,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ var me = this; $.each(this.frm.doc.payments, function(index, data) { - if(cint(me.idx) == cint(data.idx)){ + if (cint(me.idx) == cint(data.idx)) { data.amount = flt(me.selected_mode.val(), 2); } }) From 6f84cee6feb1f2160bd1b48a6e4f45f8ee9cf952 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 21 May 2021 11:15:18 +0530 Subject: [PATCH 0847/1264] fix: Uncommentt test --- .../purchase_invoice/test_purchase_invoice.py | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index aa56ddfdf3..e71a416f3d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -991,30 +991,30 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gle[i][1], gle.debit) self.assertEqual(expected_gle[i][2], gle.credit) - # Create Purchase Invoice against Purchase Order - # purchase_invoice = get_mapped_purchase_invoice(po.name) - # purchase_invoice.allocate_advances_automatically = 1 - # purchase_invoice.save() - # purchase_invoice.submit() + Create Purchase Invoice against Purchase Order + purchase_invoice = get_mapped_purchase_invoice(po.name) + purchase_invoice.allocate_advances_automatically = 1 + purchase_invoice.save() + purchase_invoice.submit() - # # Check GLE for Purchase Invoice - # # Zero net effect on final TDS Payable on invoice - # expected_gle = [ - # ['_Test Account Excise Duty - _TC', 0, 6000], - # ['Cost of Goods Sold - _TC', 30000, 0], - # ['Creditors - _TC', 0, 24000], - # ['TDS Payable - _TC', 6000, 6000], - # ] + # Check GLE for Purchase Invoice + # Zero net effect on final TDS Payable on invoice + expected_gle = [ + ['_Test Account Excise Duty - _TC', 0, 6000], + ['Cost of Goods Sold - _TC', 30000, 0], + ['Creditors - _TC', 0, 24000], + ['TDS Payable - _TC', 6000, 6000], + ] - # gl_entries = frappe.db.sql("""select account, debit, credit - # from `tabGL Entry` - # where voucher_type='Purchase Invoice' and voucher_no=%s - # order by account asc""", (purchase_invoice.name), as_dict=1) + gl_entries = frappe.db.sql("""select account, debit, credit + from `tabGL Entry` + where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", (purchase_invoice.name), as_dict=1) - # for i, gle in enumerate(gl_entries): - # self.assertEqual(expected_gle[i][0], gle.account) - # self.assertEqual(expected_gle[i][1], gle.debit) - # self.assertEqual(expected_gle[i][2], gle.credit) + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.debit) + self.assertEqual(expected_gle[i][2], gle.credit) def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year From 36d47f97f400630ffaa259e7dc50fe7b120e0c35 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 21 May 2021 11:16:26 +0530 Subject: [PATCH 0848/1264] fix: Linting issues --- erpnext/public/js/payment/payments.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index b9e933a38e..ddf8706809 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -20,7 +20,7 @@ erpnext.payments = erpnext.stock.StockController.extend({ $(this.$body).find('.form-control').click(function() { $(this).select(); }); - } + }, set_payment_primary_action: function() { var me = this; @@ -85,8 +85,8 @@ erpnext.payments = erpnext.stock.StockController.extend({ set_outstanding_amount: function() { this.selected_mode = $(this.$body).find(repl("input[idx='%(idx)s']",{'idx': this.idx})); - this.highlight_selected_row() - this.payment_val = 0.0 + this.highlight_selected_row(); + this.payment_val = 0.0; if (this.frm.doc.outstanding_amount > 0 && flt(this.selected_mode.val()) == 0.0) { //When user first time click on row this.payment_val = flt(this.frm.doc.outstanding_amount / this.frm.doc.conversion_rate, precision("outstanding_amount")) From 55a3fb57dd213993907114615bb3c406a191d9e7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 22 May 2021 20:30:18 +0530 Subject: [PATCH 0849/1264] fix: Linting Issues --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 2 +- .../accounts/report/tds_payable_monthly/tds_payable_monthly.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index e71a416f3d..0da46e9886 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -991,7 +991,7 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gle[i][1], gle.debit) self.assertEqual(expected_gle[i][2], gle.credit) - Create Purchase Invoice against Purchase Order + # Create Purchase Invoice against Purchase Order purchase_invoice = get_mapped_purchase_invoice(po.name) purchase_invoice.allocate_advances_automatically = 1 purchase_invoice.save() diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 619d799d06..ceefa31cfa 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -56,7 +56,6 @@ def set_filters(filters): elif filters.purchase_order and not filters.supplier: for d in filters.get("invoices"): if d.name == filters.purchase_order: - print("$#$#$$#") invoices.append(d) filters["invoices"] = invoices if invoices else filters["invoices"] From 73e41c0bd6e7984c468ca5c3feaac0d5920110d2 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Tue, 25 May 2021 18:01:47 +0530 Subject: [PATCH 0850/1264] refactor: suggested changes --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index a154464a8b..2600790a59 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -383,7 +383,7 @@ class WorkOrder(Document): work_order_qty = 0.0 if plan_reference.item_reference == item_reference: if self.docstatus == 1: - work_order_qty = cint(plan_reference.qty) / total_bundle_qty + work_order_qty = flt(plan_reference.qty) / total_bundle_qty frappe.db.set_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty', work_order_qty) From 84f270e73223313dc789bb5617911ded5efbc0a7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 25 May 2021 18:03:42 +0530 Subject: [PATCH 0851/1264] fix: Remove unwanted commits --- erpnext/controllers/taxes_and_totals.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index a6b54eb590..0b89750766 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -819,16 +819,3 @@ class init_landed_taxes_and_totals(object): for d in self.doc.get(self.tax_field): d.amount = flt(d.amount, d.precision("amount")) d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) - -def get_advance_taxes(payment_entry_list): - taxes = [] - if payment_entry_list: - taxes = frappe.db.sql( - """ - SELECT t.parent, t.add_deduct_tax, t.charge_type, t.rate, - t.account_head, t.cost_center, t.tax_amount, t.description - FROM `tabAdvance Taxes and Charges` t, `tabPayment Entry` p - WHERE t.parent = p.name AND t.parent in %s - """, (payment_entry_list, ), as_dict=1) - - return taxes From 507a211c81c13cf8ea2b2ed401f703d6675b207c Mon Sep 17 00:00:00 2001 From: Rakshith N <36509967+rakshithrddy@users.noreply.github.com> Date: Tue, 25 May 2021 19:03:29 +0530 Subject: [PATCH 0852/1264] fix: fetch email id from dialog box in pos past order summary' (#25808) --- erpnext/selling/page/point_of_sale/pos_past_order_summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index acf4eb371f..cec831d616 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -241,7 +241,7 @@ erpnext.PointOfSale.PastOrderSummary = class { send_email() { const frm = this.events.get_frm(); - const recipients = this.email_dialog.get_values().recipients; + const recipients = this.email_dialog.get_values().email_id; const doc = this.doc || frm.doc; const print_format = frm.pos_print_format; From 6a62ad325f8d5abb350326a14f81a1e6f91db836 Mon Sep 17 00:00:00 2001 From: Laurynas Sakalauskas Date: Tue, 25 May 2021 16:39:44 +0300 Subject: [PATCH 0853/1264] fix(plaid): withdrawals and deposits are recorded incorrectly (#25784) --- .../doctype/plaid_settings/plaid_settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 21f1db619e..ce15e47c5e 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -183,11 +183,11 @@ def new_bank_transaction(transaction): bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"])) if float(transaction["amount"]) >= 0: - debit = float(transaction["amount"]) - credit = 0 - else: debit = 0 - credit = abs(float(transaction["amount"])) + credit = float(transaction["amount"]) + else: + debit = abs(float(transaction["amount"])) + credit = 0 status = "Pending" if transaction["pending"] == "True" else "Settled" From 4d61fa249786f294110afa85e6278ffb5a282b85 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 25 May 2021 19:16:02 +0530 Subject: [PATCH 0854/1264] fix: incorrect cr/dr shown in general ledger for multi-currency transactions (#25654) --- erpnext/accounts/report/utils.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 9de8d19f2a..b020d0a506 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -81,8 +81,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, company): presentation_currency = currency_info['presentation_currency'] company_currency = currency_info['company_currency'] - pl_accounts = [d.name for d in frappe.get_list('Account', - filters={'report_type': 'Profit and Loss', 'company': company})] + account_currencies = list(set(entry['account_currency'] for entry in gl_entries)) for entry in gl_entries: account = entry['account'] @@ -92,10 +91,15 @@ def convert_to_presentation_currency(gl_entries, currency_info, company): credit_in_account_currency = flt(entry['credit_in_account_currency']) account_currency = entry['account_currency'] - if account_currency != presentation_currency: - value = debit or credit + if len(account_currencies) == 1 and account_currency == presentation_currency: + if entry.get('debit'): + entry['debit'] = debit_in_account_currency - date = entry['posting_date'] if account in pl_accounts else currency_info['report_date'] + if entry.get('credit'): + entry['credit'] = credit_in_account_currency + else: + value = debit or credit + date = currency_info['report_date'] converted_value = convert(value, presentation_currency, company_currency, date) if entry.get('debit'): @@ -104,13 +108,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company): if entry.get('credit'): entry['credit'] = converted_value - elif account_currency == presentation_currency: - if entry.get('debit'): - entry['debit'] = debit_in_account_currency - - if entry.get('credit'): - entry['credit'] = credit_in_account_currency - converted_gl_list.append(entry) return converted_gl_list From 99636c6aca8a3a0faf6e69f777d75f0d75c3c73b Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 25 May 2021 19:17:01 +0530 Subject: [PATCH 0855/1264] fix: rearrange buttons for company doctype (#25617) --- erpnext/setup/doctype/company/company.js | 27 ++++++++-------------- erpnext/setup/doctype/company/company.json | 8 +------ 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 9957aad019..b24048d1ce 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -90,12 +90,6 @@ frappe.ui.form.on("Company", { frm.toggle_enable("default_currency", (frm.doc.__onload && !frm.doc.__onload.transactions_exist)); - if (frm.has_perm('write')) { - frm.add_custom_button(__('Create Tax Template'), function() { - frm.trigger("make_default_tax_template"); - }); - } - if (frappe.perm.has_perm("Cost Center", 0, 'read')) { frm.add_custom_button(__('Cost Centers'), function() { frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); @@ -121,17 +115,21 @@ frappe.ui.form.on("Company", { } if (frm.has_perm('write')) { - frm.add_custom_button(__('Default Tax Template'), function() { + frm.add_custom_button(__('Create Tax Template'), function() { frm.trigger("make_default_tax_template"); - }, __('Create')); + }, __('Manage')); + } + + if (frappe.user.has_role('System Manager')) { + if (frm.has_perm('write')) { + frm.add_custom_button(__('Delete Transactions'), function() { + frm.trigger("delete_company_transactions"); + }, __('Manage')); + } } } erpnext.company.set_chart_of_accounts_options(frm.doc); - - if (!frappe.user.has_role('System Manager')) { - frm.get_field("delete_company_transactions").hide(); - } }, make_default_tax_template: function(frm) { @@ -145,11 +143,6 @@ frappe.ui.form.on("Company", { }) }, - onload_post_render: function(frm) { - if(frm.get_field("delete_company_transactions").$input) - frm.get_field("delete_company_transactions").$input.addClass("btn-danger"); - }, - country: function(frm) { erpnext.company.set_chart_of_accounts_options(frm.doc); }, diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 83cbf475ab..061986d92d 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -99,7 +99,6 @@ "company_description", "registration_info", "registration_details", - "delete_company_transactions", "lft", "rgt", "old_parent" @@ -666,11 +665,6 @@ "oldfieldname": "registration_details", "oldfieldtype": "Code" }, - { - "fieldname": "delete_company_transactions", - "fieldtype": "Button", - "label": "Delete Company Transactions" - }, { "fieldname": "lft", "fieldtype": "Int", @@ -747,7 +741,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2021-02-16 15:53:37.167589", + "modified": "2021-05-07 03:11:28.189740", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 18cfced0320f567bd985db050a4260bd3db31331 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 25 May 2021 19:54:07 +0530 Subject: [PATCH 0856/1264] fix(Material Request): Make status on list and form view the same (#24856) --- erpnext/controllers/status_updater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index ed3aee5c1a..83d4c33140 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -76,12 +76,12 @@ status_map = { ["Stopped", "eval:self.status == 'Stopped'"], ["Cancelled", "eval:self.docstatus == 2"], ["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"], - ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"], ["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"], ["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"], ["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], + ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"], ["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"] ], "Bank Transaction": [ From 81376ea44f8d3afc4db1aad34028ce80eeeed94d Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 25 May 2021 20:39:17 +0530 Subject: [PATCH 0857/1264] feat: Show net values in Party Accounts (#25714) --- .../report/general_ledger/general_ledger.js | 5 ++++ .../report/general_ledger/general_ledger.py | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index fb0d359926..84f786814d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -166,6 +166,11 @@ frappe.query_reports["General Ledger"] = { "fieldname": "show_cancelled_entries", "label": __("Show Cancelled Entries"), "fieldtype": "Check" + }, + { + "fieldname": "show_net_values_in_party_account", + "label": __("Show Net Values in Party Account"), + "fieldtype": "Check" } ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index b5d7992604..562df4f6f7 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -344,6 +344,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): consolidated_gle = OrderedDict() group_by = group_by_field(filters.get('group_by')) + if filters.get('show_net_values_in_party_account'): + account_type_map = get_account_type_map(filters.get('company')) + def update_value_in_dict(data, key, gle): data[key].debit += flt(gle.debit) data[key].credit += flt(gle.credit) @@ -351,6 +354,24 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) + if filters.get('show_net_values_in_party_account') and \ + account_type_map.get(data[key].account) in ('Receivable', 'Payable'): + net_value = flt(data[key].debit) - flt(data[key].credit) + net_value_in_account_currency = flt(data[key].debit_in_account_currency) \ + - flt(data[key].credit_in_account_currency) + + if net_value < 0: + dr_or_cr = 'credit' + rev_dr_or_cr = 'debit' + else: + dr_or_cr = 'debit' + rev_dr_or_cr = 'credit' + + data[key][dr_or_cr] = abs(net_value) + data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency) + data[key][rev_dr_or_cr] = 0 + data[key][rev_dr_or_cr+'_in_account_currency'] = 0 + if data[key].against_voucher and gle.against_voucher: data[key].against_voucher += ', ' + gle.against_voucher @@ -388,6 +409,12 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): return totals, entries +def get_account_type_map(company): + account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'], + filters={'company': company}, as_list=1)) + + return account_type_map + def get_result_as_list(data, filters): balance, balance_in_account_currency = 0, 0 inv_details = get_supplier_invoice_details() From c262705143bd8c8d52d7dfa58f8a82bd0e4bebf1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 25 May 2021 20:39:28 +0530 Subject: [PATCH 0858/1264] feat: Show net values in Party Accounts (#25714) From ff96bdf0c19525b1baec6d647a4ce7a389699958 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 May 2021 19:54:40 +0530 Subject: [PATCH 0859/1264] fix(ux): fix unstranslated text in msgprint/throw --- .../accounting_dimension/accounting_dimension.py | 2 +- .../doctype/accounts_settings/accounts_settings.py | 3 ++- .../chart_of_accounts_importer.py | 4 ++-- .../process_statement_of_accounts.js | 8 ++++---- erpnext/crm/doctype/appointment/appointment.py | 10 +++++----- .../course_scheduling_tool/course_scheduling_tool.py | 2 +- erpnext/loan_management/doctype/loan/loan.py | 2 +- erpnext/non_profit/doctype/member/member.py | 2 +- erpnext/public/js/controllers/transaction.js | 6 +++--- erpnext/setup/install.py | 2 +- erpnext/www/book_appointment/index.js | 4 ++-- 11 files changed, 23 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 0ebf0eb541..7cd1e7736c 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -27,7 +27,7 @@ class AccountingDimension(Document): exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name']) if exists and self.is_new(): - frappe.throw("Document Type already used as a dimension") + frappe.throw(_("Document Type already used as a dimension")) if not self.is_new(): self.validate_document_type_change() diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 4d3388090d..ac4a2d6f16 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.utils import cint from frappe.model.document import Document from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -24,7 +25,7 @@ class AccountsSettings(Document): def validate_stale_days(self): if not self.allow_stale and cint(self.stale_days) <= 0: frappe.msgprint( - "Stale Days should start from 1.", title='Error', indicator='red', + _("Stale Days should start from 1."), title='Error', indicator='red', raise_exception=1) def enable_payment_schedule_in_print(self): diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index f96f59169e..ef44626b37 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -22,7 +22,7 @@ def validate_company(company): 'allow_account_creation_against_child_company']) if parent_company and (not allow_account_creation_against_child_company): - msg = _("{} is a child company. ").format(frappe.bold(company)) + msg = _("{} is a child company.").format(frappe.bold(company)) + " " msg += _("Please import accounts against parent company or enable {} in company master.").format( frappe.bold('Allow Account Creation Against Child Company')) frappe.throw(msg, title=_('Wrong Company')) @@ -56,7 +56,7 @@ def get_file(file_name): extension = extension.lstrip(".") if extension not in ('csv', 'xlsx', 'xls'): - frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload") + frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")) return file_doc, extension diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js index 6dc46430e0..088c190f45 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js @@ -19,7 +19,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'}); } else{ - frappe.msgprint('No Records for these settings.') + frappe.msgprint(__('No Records for these settings.')) } } }); @@ -33,7 +33,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { type: 'GET', success: function(result) { if(jQuery.isEmptyObject(result)){ - frappe.msgprint('No Records for these settings.'); + frappe.msgprint(__('No Records for these settings.')); } else{ window.location = url; @@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { frm.refresh_field('customers'); } else{ - frappe.throw('No Customers found with selected options.'); + frappe.throw(__('No Customers found with selected options.')); } } } @@ -129,4 +129,4 @@ frappe.ui.form.on('Process Statement Of Accounts Customer', { } }) } -}); \ No newline at end of file +}); diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2009ebf7cb..df73f09c49 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -38,7 +38,7 @@ class Appointment(Document): number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') if not number_of_agents == 0: if (number_of_appointments_in_same_slot >= number_of_agents): - frappe.throw('Time slot is not available') + frappe.throw(_('Time slot is not available')) # Link lead if not self.party: lead = self.find_lead_by_email() @@ -75,10 +75,10 @@ class Appointment(Document): subject=_('Appointment Confirmation')) if frappe.session.user == "Guest": frappe.msgprint( - 'Please check your email to confirm the appointment') + _('Please check your email to confirm the appointment')) else : frappe.msgprint( - 'Appointment was created. But no lead was found. Please check the email to confirm') + _('Appointment was created. But no lead was found. Please check the email to confirm')) def on_change(self): # Sync Calendar @@ -91,7 +91,7 @@ class Appointment(Document): def set_verified(self, email): if not email == self.customer_email: - frappe.throw('Email verification failed.') + frappe.throw(_('Email verification failed.')) # Create new lead self.create_lead_and_link() # Remove unverified status @@ -184,7 +184,7 @@ class Appointment(Document): appointment_event.insert(ignore_permissions=True) self.calendar_event = appointment_event.name self.save(ignore_permissions=True) - + def _get_verify_url(self): verify_route = '/book_appointment/verify' params = { diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py index 6a0dcf460a..0f2ea96a58 100644 --- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py +++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py @@ -75,7 +75,7 @@ class CourseSchedulingTool(Document): """Validates if Course Start Date is greater than Course End Date""" if self.course_start_date > self.course_end_date: frappe.throw( - "Course Start Date cannot be greater than Course End Date.") + _("Course Start Date cannot be greater than Course End Date.")) def delete_course_schedule(self, rescheduled, reschedule_errors): """Delete all course schedule within the Date range and specified filters""" diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 230475f2d1..69d11a8653 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -264,7 +264,7 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict pending_amount = amounts['pending_principal_amount'] if amount and (amount > pending_amount): - frappe.throw('Write Off amount cannot be greater than pending loan amount') + frappe.throw(_('Write Off amount cannot be greater than pending loan amount')) if not amount: amount = pending_amount diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index efc072ee97..30be585e9a 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -28,7 +28,7 @@ class Member(Document): def setup_subscription(self): non_profit_settings = frappe.get_doc('Non Profit Settings') if not non_profit_settings.enable_razorpay_for_memberships: - frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format( + frappe.throw(_('Please check Enable Razorpay for Memberships in {0} to setup subscription')).format( get_link_to_form('Non Profit Settings', 'Non Profit Settings')) controller = get_payment_gateway_controller("Razorpay") diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index e153e6ccbc..ad1976d2d2 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -953,15 +953,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) { var message1 = ""; var message2 = ""; - var final_message = "Please clear the "; + var final_message = __("Please clear the") + " "; if (this.frm.doc.payment_terms_template) { - message1 = "selected Payment Terms Template"; + message1 = __("selected Payment Terms Template"); final_message = final_message + message1; } if ((this.frm.doc.payment_schedule || []).length) { - message2 = "Payment Schedule Table"; + message2 = __("Payment Schedule Table"); if (message1.length !== 0) message2 = " and " + message2; final_message = final_message + message2; } diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index c7220cbc07..bbee74cafb 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -39,7 +39,7 @@ def check_setup_wizard_not_completed(): if cint(frappe.db.get_single_value('System Settings', 'setup_complete') or 0): message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed. You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall""" - frappe.throw(message) + frappe.throw(message) # nosemgrep def set_single_defaults(): diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js index 377a3cc097..5562cbd471 100644 --- a/erpnext/www/book_appointment/index.js +++ b/erpnext/www/book_appointment/index.js @@ -48,7 +48,7 @@ function setup_date_picker() { function hide_next_button() { let next_button = document.getElementById('next-button'); next_button.disabled = true; - next_button.onclick = () => frappe.msgprint("Please select a date and time"); + next_button.onclick = () => frappe.msgprint(__("Please select a date and time")); } function show_next_button() { @@ -63,7 +63,7 @@ function on_date_or_timezone_select() { if (date_picker.value === '') { clear_time_slots(); hide_next_button(); - frappe.throw('Please select a date'); + frappe.throw(__('Please select a date')); } window.selected_date = date_picker.value; window.selected_timezone = timezone.value; From 8fddd0f0c1a4b4f51af8e7834e37d7f48a08576a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 25 May 2021 22:03:50 +0530 Subject: [PATCH 0860/1264] fix: Update TDS rate for test --- .../doctype/purchase_invoice/test_purchase_invoice.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0da46e9886..319808ac95 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -975,10 +975,10 @@ class TestPurchaseInvoice(unittest.TestCase): # Check GLE for Payment Entry expected_gle = [ - ['_Test Account Excise Duty - _TC', 6000, 0], + ['_Test Account Excise Duty - _TC', 3000, 0], ['Cash - _TC', 0, 24000], ['Creditors - _TC', 24000, 0], - ['TDS Payable - _TC', 0, 6000], + ['TDS Payable - _TC', 0, 3000], ] gl_entries = frappe.db.sql("""select account, debit, credit @@ -1000,10 +1000,10 @@ class TestPurchaseInvoice(unittest.TestCase): # Check GLE for Purchase Invoice # Zero net effect on final TDS Payable on invoice expected_gle = [ - ['_Test Account Excise Duty - _TC', 0, 6000], + ['_Test Account Excise Duty - _TC', 0, 3000], ['Cost of Goods Sold - _TC', 30000, 0], ['Creditors - _TC', 0, 24000], - ['TDS Payable - _TC', 6000, 6000], + ['TDS Payable - _TC', 6000, 3000], ] gl_entries = frappe.db.sql("""select account, debit, credit @@ -1026,7 +1026,7 @@ def update_tax_witholding_category(company, account, date): tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') tds_category.append('rates', { 'fiscal_year': fiscal_year[0], - 'tax_withholding_rate': 20, + 'tax_withholding_rate': 10, 'single_threshold': 2500, 'cumulative_threshold': 0 }) From 5e27c7dae21c317bde812547cc6ae1f6c69ba61d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 25 May 2021 22:34:58 +0530 Subject: [PATCH 0861/1264] fix: Add non group filter for account --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index a7fcbd7e3b..a4022ef40a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -97,7 +97,8 @@ frappe.ui.form.on('Payment Entry', { return { filters: { "company": frm.doc.company, - "root_type": ["in", ["Asset", "Liability"]] + "root_type": ["in", ["Asset", "Liability"]], + "is_group": 0 } } }); From 0e804e292a65edca819ecd8d7b77ca88ab970151 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 26 May 2021 10:54:16 +0530 Subject: [PATCH 0862/1264] fix(gstr-1): incorrect gstin fetched incase of branch company address --- erpnext/regional/report/gstr_1/gstr_1.py | 36 ++++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 1e28a40f81..b7c096248f 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -574,7 +574,7 @@ class Gstr1Report(object): def get_json(filters, report_name, data): filters = json.loads(filters) report_data = json.loads(data) - gstin = get_company_gstin_number(filters["company"]) + gstin = get_company_gstin_number(filters["company"], filters["company_address"]) fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) @@ -810,23 +810,29 @@ def get_rate_and_tax_details(row, gstin): return {"num": int(num), "itm_det": itm_det} -def get_company_gstin_number(company): - filters = [ - ["is_your_company_address", "=", 1], - ["Dynamic Link", "link_doctype", "=", "Company"], - ["Dynamic Link", "link_name", "=", company], - ["Dynamic Link", "parenttype", "=", "Address"], - ] +def get_company_gstin_number(company, address=None): + if address: + gstin = frappe.db.get_value("Address", address, "gstin") - gstin = frappe.get_all("Address", filters=filters, fields=["gstin"]) - - if gstin: - return gstin[0]["gstin"] - else: - frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}").format( - frappe.bold(company) + if not gstin: + filters = [ + ["is_your_company_address", "=", 1], + ["Dynamic Link", "link_doctype", "=", "Company"], + ["Dynamic Link", "link_name", "=", company], + ["Dynamic Link", "parenttype", "=", "Address"], + ] + gstin = frappe.get_all("Address", filters=filters, pluck="gstin") + if gstin: + gstin[0] + + if not gstin: + address = frappe.bold(address) if address else "" + frappe.throw(_("Please set valid GSTIN No. in Company Address {} for company {}").format( + address, frappe.bold(company) )) + return gstin + @frappe.whitelist() def download_json_file(): ''' download json content in a file ''' From db5217e48e23a5bfbfa6c8127a99b1fd4fb8feab Mon Sep 17 00:00:00 2001 From: Anuja P Date: Wed, 26 May 2021 11:09:48 +0530 Subject: [PATCH 0863/1264] fix: renaming hold to on hold --- erpnext/support/doctype/issue/issue.json | 4 ++-- .../report/issue_summary/issue_summary.js | 2 +- .../report/issue_summary/issue_summary.py | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index a43381c5c6..bc29821ee2 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -119,7 +119,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "Open\nReplied\nHold\nResolved\nClosed", + "options": "Open\nReplied\nOn Hold\nResolved\nClosed", "search_index": 1 }, { @@ -410,7 +410,7 @@ "icon": "fa fa-ticket", "idx": 7, "links": [], - "modified": "2020-08-11 18:49:07.574769", + "modified": "2021-05-26 10:49:07.574769", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js index a5e1de627e..a5122d03ad 100644 --- a/erpnext/support/report/issue_summary/issue_summary.js +++ b/erpnext/support/report/issue_summary/issue_summary.js @@ -42,7 +42,7 @@ frappe.query_reports["Issue Summary"] = { "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, - {label: __('Hold'), value: 'Hold'}, + {label: __('On Hold'), value: 'On Hold'}, {label: __('Resolved'), value: 'Resolved'}, {label: __('Closed'), value: 'Closed'} ] diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py index d93790d593..bba25b8bed 100644 --- a/erpnext/support/report/issue_summary/issue_summary.py +++ b/erpnext/support/report/issue_summary/issue_summary.py @@ -62,7 +62,7 @@ class IssueSummary(object): 'width': 200 }) - self.statuses = ['Open', 'Replied', 'Hold', 'Resolved', 'Closed'] + self.statuses = ['Open', 'Replied', 'On Hold', 'Resolved', 'Closed'] for status in self.statuses: self.columns.append({ 'label': _(status), @@ -265,7 +265,7 @@ class IssueSummary(object): labels = [] open_issues = [] replied_issues = [] - hold_issues = [] + on_hold_issues = [] resolved_issues = [] closed_issues = [] @@ -278,7 +278,7 @@ class IssueSummary(object): labels.append(entry.get(entity_field)) open_issues.append(entry.get('open')) replied_issues.append(entry.get('replied')) - hold_issues.append(entry.get('hold')) + on_hold_issues.append(entry.get('on_hold')) resolved_issues.append(entry.get('resolved')) closed_issues.append(entry.get('closed')) @@ -295,8 +295,8 @@ class IssueSummary(object): 'values': replied_issues[:30] }, { - 'name': 'Hold', - 'values': hold_issues[:30] + 'name': 'On Hold', + 'values': on_hold_issues[:30] }, { 'name': 'Resolved', @@ -319,14 +319,14 @@ class IssueSummary(object): open_issues = 0 replied = 0 - hold = 0 + on_hold = 0 resolved = 0 closed = 0 for entry in self.data: open_issues += entry.get('open') replied += entry.get('replied') - hold += entry.get('hold') + on_hold += entry.get('on_hold') resolved += entry.get('resolved') closed += entry.get('closed') @@ -344,9 +344,9 @@ class IssueSummary(object): 'datatype': 'Int', }, { - 'value': hold, + 'value': on_hold, 'indicator': 'Grey', - 'label': _('Hold'), + 'label': _('On Hold'), 'datatype': 'Int', }, { From 2e0e4a7861ddc800589a788198c0df32a6e9ccb7 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 26 May 2021 11:13:19 +0530 Subject: [PATCH 0864/1264] refactor: Additional Salary form clean up (#25785) * feat: additional salary clean up * fix: Label and description * fix: labels Co-authored-by: Rucha Mahabal --- .../additional_salary/additional_salary.json | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json index 5e17a5cbb7..d9efe458dc 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.json +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json @@ -7,25 +7,30 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "employee_details_section", "naming_series", "employee", "employee_name", - "salary_component", - "type", - "amount", - "ref_doctype", - "ref_docname", - "amended_from", "column_break_5", "company", "department", + "salary_details_section", + "salary_component", + "type", "currency", + "amount", + "column_break_13", + "is_recurring", + "payroll_date", "from_date", "to_date", - "payroll_date", - "is_recurring", + "properties_and_references_section", + "deduct_full_tax_on_selected_payroll_date", + "ref_doctype", + "ref_docname", + "column_break_22", "overwrite_salary_structure_amount", - "deduct_full_tax_on_selected_payroll_date" + "amended_from" ], "fields": [ { @@ -81,7 +86,7 @@ }, { "depends_on": "eval:(doc.is_recurring==0)", - "description": "Date on which this component is applied", + "description": "The date on which Salary Component with Amount will contribute for Earnings/Deduction in Salary Slip. ", "fieldname": "payroll_date", "fieldtype": "Date", "in_list_view": 1, @@ -159,6 +164,7 @@ "fieldname": "ref_docname", "fieldtype": "Dynamic Link", "label": "Reference Document", + "no_copy": 1, "options": "ref_doctype", "read_only": 1 }, @@ -171,11 +177,34 @@ "print_hide": 1, "read_only": 1, "reqd": 1 + }, + { + "fieldname": "employee_details_section", + "fieldtype": "Section Break", + "label": "Employee Details" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "salary_details_section", + "fieldtype": "Section Break", + "label": "Salary Details" + }, + { + "fieldname": "properties_and_references_section", + "fieldtype": "Section Break", + "label": "Properties and References" } ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:33:59.098532", + "modified": "2021-05-26 11:10:00.812698", "modified_by": "Administrator", "module": "Payroll", "name": "Additional Salary", From 349ef8274beee540626635ba46987c4a7b21d9d8 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 26 May 2021 12:14:02 +0530 Subject: [PATCH 0865/1264] fix: student invalid password reset link (#25826) --- erpnext/education/doctype/student/student.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 2dc0f634f0..6be9e7104b 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -74,7 +74,6 @@ class Student(Document): student_user.flags.ignore_permissions = True student_user.add_roles("Student") student_user.save() - update_password_link = student_user.reset_password() def update_applicant_status(self): """Updates Student Applicant status to Admitted""" From 1cdf5a0dbad7eea6111839bfc14e4b59f8970f59 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 14:42:15 +0530 Subject: [PATCH 0866/1264] fix: add requested changes --- erpnext/controllers/stock_controller.py | 8 +- erpnext/public/js/controllers/transaction.js | 15 +- .../stock/doctype/stock_entry/stock_entry.js | 130 ++++++++++++++++++ 3 files changed, 144 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 6f97743b60..abc966a477 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -508,7 +508,7 @@ def make_quality_inspections(doctype, docname, items): inspections = [] for item in items: - if item.get("sample_size") > item.get("qty"): + if flt(item.get("sample_size")) > flt(item.get("qty")): frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( item_name=item.get("item_name"), sample_size=item.get("sample_size"), @@ -523,14 +523,14 @@ def make_quality_inspections(doctype, docname, items): "reference_name": docname, "item_code": item.get("item_code"), "description": item.get("description"), - "sample_size": item.get("sample_size"), + "sample_size": flt(item.get("sample_size")), "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, "batch_no": item.get("batch_no") }).insert() quality_inspection.save() - inspections.append(quality_inspection) + inspections.append(quality_inspection.name) - return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + return inspections def is_reposting_pending(): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8738957166..95562baa8e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2039,11 +2039,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, freeze: true, callback: function (r) { - if (r.message) { - frappe.msgprint({ - message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), - indicator: "green" - }); + if (r.message.length > 0) { + if (r.message.length === 1) { + frappe.set_route("Form", "Quality Inspection", r.message[0]); + } else { + frappe.route_options = { + "reference_type": me.frm.doc.doctype, + "reference_name": me.frm.doc.name + }; + frappe.set_route("List", "Quality Inspection"); + } } dialog.hide(); } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index de23e769f8..3524c41720 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -115,6 +115,13 @@ frappe.ui.form.on('Stock Entry', { return; } + if (!frm.is_new() && frm.doc.docstatus === 0) { + frm.add_custom_button(__("Quality Inspection(s)"), () => { + frm.trigger("make_quality_inspection"); + }, __("Create")); + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + let quality_inspection_field = frm.get_docfield("items", "quality_inspection"); quality_inspection_field.get_route_options_for_new_doc = function(row) { if (frm.is_new()) return; @@ -142,6 +149,129 @@ frappe.ui.form.on('Stock Entry', { }); }, + make_quality_inspection: function (frm) { + let data = []; + const fields = [ + { + label: "Items", + fieldtype: "Table", + fieldname: "items", + cannot_add_rows: true, + in_place_edit: true, + data: data, + get_data: () => { + return data; + }, + fields: [ + { + fieldtype: "Data", + fieldname: "docname", + hidden: true + }, + { + fieldtype: "Read Only", + fieldname: "item_code", + label: __("Item Code"), + in_list_view: true + }, + { + fieldtype: "Read Only", + fieldname: "item_name", + label: __("Item Name"), + in_list_view: true + }, + { + fieldtype: "Float", + fieldname: "qty", + label: __("Accepted Quantity"), + in_list_view: true, + read_only: true + }, + { + fieldtype: "Float", + fieldname: "sample_size", + label: __("Sample Size"), + reqd: true, + in_list_view: true + }, + { + fieldtype: "Data", + fieldname: "description", + label: __("Description"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "serial_no", + label: __("Serial No"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "batch_no", + label: __("Batch No"), + hidden: true + } + ] + } + ]; + + const dialog = new frappe.ui.Dialog({ + title: __("Select Items for Quality Inspection"), + fields: fields, + primary_action: function () { + const data = dialog.get_values(); + frappe.call({ + method: "erpnext.controllers.stock_controller.make_quality_inspections", + args: { + doctype: frm.doc.doctype, + docname: frm.doc.name, + items: data + }, + freeze: true, + callback: function (r) { + if (r.message.length > 0) { + if (r.message.length === 1) { + frappe.set_route("Form", "Quality Inspection", r.message[0]); + } else { + frappe.route_options = { + "reference_type": frm.doc.doctype, + "reference_name": frm.doc.name + }; + frappe.set_route("List", "Quality Inspection"); + } + } + dialog.hide(); + } + }); + }, + primary_action_label: __("Create") + }); + + frm.doc.items.forEach(item => { + if (!item.quality_inspection) { + let dialog_items = dialog.fields_dict.items; + dialog_items.df.data.push({ + "docname": item.name, + "item_code": item.item_code, + "item_name": item.item_name, + "qty": item.qty, + "description": item.description, + "serial_no": item.serial_no, + "batch_no": item.batch_no + }); + dialog_items.grid.refresh(); + } + }); + + data = dialog.fields_dict.items.df.data; + if (!data.length) { + frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); + } else { + dialog.show(); + } + }, + outgoing_stock_entry: function(frm) { frappe.call({ doc: frm.doc, From 1e3a3b27c6e4f66aa38c9adaf86b5bde39293a00 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 15:18:10 +0530 Subject: [PATCH 0867/1264] style: semgrep issues --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2c3519b04a..c77ea55775 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -225,7 +225,7 @@ class AccountsController(TransactionBase): def validate_date_with_fiscal_year(self): if self.meta.get_field("fiscal_year"): - date_field = "" + date_field = None if self.meta.get_field("posting_date"): date_field = "posting_date" elif self.meta.get_field("transaction_date"): From 9857d63523d3bffc1377622cbca2ff1c4c0815d0 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 15:41:36 +0530 Subject: [PATCH 0868/1264] fix: add requested changes --- .../stock/doctype/stock_entry/stock_entry.js | 130 +----------------- 1 file changed, 4 insertions(+), 126 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 3524c41720..93a6fc0e0a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -117,7 +117,8 @@ frappe.ui.form.on('Stock Entry', { if (!frm.is_new() && frm.doc.docstatus === 0) { frm.add_custom_button(__("Quality Inspection(s)"), () => { - frm.trigger("make_quality_inspection"); + let transaction_controller = new erpnext.TransactionController({ frm: frm }); + transaction_controller.make_quality_inspection(); }, __("Create")); frm.page.set_inner_btn_group_as_primary(__('Create')); } @@ -149,129 +150,6 @@ frappe.ui.form.on('Stock Entry', { }); }, - make_quality_inspection: function (frm) { - let data = []; - const fields = [ - { - label: "Items", - fieldtype: "Table", - fieldname: "items", - cannot_add_rows: true, - in_place_edit: true, - data: data, - get_data: () => { - return data; - }, - fields: [ - { - fieldtype: "Data", - fieldname: "docname", - hidden: true - }, - { - fieldtype: "Read Only", - fieldname: "item_code", - label: __("Item Code"), - in_list_view: true - }, - { - fieldtype: "Read Only", - fieldname: "item_name", - label: __("Item Name"), - in_list_view: true - }, - { - fieldtype: "Float", - fieldname: "qty", - label: __("Accepted Quantity"), - in_list_view: true, - read_only: true - }, - { - fieldtype: "Float", - fieldname: "sample_size", - label: __("Sample Size"), - reqd: true, - in_list_view: true - }, - { - fieldtype: "Data", - fieldname: "description", - label: __("Description"), - hidden: true - }, - { - fieldtype: "Data", - fieldname: "serial_no", - label: __("Serial No"), - hidden: true - }, - { - fieldtype: "Data", - fieldname: "batch_no", - label: __("Batch No"), - hidden: true - } - ] - } - ]; - - const dialog = new frappe.ui.Dialog({ - title: __("Select Items for Quality Inspection"), - fields: fields, - primary_action: function () { - const data = dialog.get_values(); - frappe.call({ - method: "erpnext.controllers.stock_controller.make_quality_inspections", - args: { - doctype: frm.doc.doctype, - docname: frm.doc.name, - items: data - }, - freeze: true, - callback: function (r) { - if (r.message.length > 0) { - if (r.message.length === 1) { - frappe.set_route("Form", "Quality Inspection", r.message[0]); - } else { - frappe.route_options = { - "reference_type": frm.doc.doctype, - "reference_name": frm.doc.name - }; - frappe.set_route("List", "Quality Inspection"); - } - } - dialog.hide(); - } - }); - }, - primary_action_label: __("Create") - }); - - frm.doc.items.forEach(item => { - if (!item.quality_inspection) { - let dialog_items = dialog.fields_dict.items; - dialog_items.df.data.push({ - "docname": item.name, - "item_code": item.item_code, - "item_name": item.item_name, - "qty": item.qty, - "description": item.description, - "serial_no": item.serial_no, - "batch_no": item.batch_no - }); - dialog_items.grid.refresh(); - } - }); - - data = dialog.fields_dict.items.df.data; - if (!data.length) { - frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); - } else { - dialog.show(); - } - }, - outgoing_stock_entry: function(frm) { frappe.call({ doc: frm.doc, @@ -285,7 +163,7 @@ frappe.ui.form.on('Stock Entry', { refresh: function(frm) { if(!frm.doc.docstatus) { frm.trigger('validate_purpose_consumption'); - frm.add_custom_button(__('Create Material Request'), function() { + frm.add_custom_button(__('Material Request'), function() { frappe.model.with_doctype('Material Request', function() { var mr = frappe.model.get_new_doc('Material Request'); var items = frm.get_field('items').grid.get_selected_children(); @@ -308,7 +186,7 @@ frappe.ui.form.on('Stock Entry', { }); frappe.set_route('Form', 'Material Request', mr.name); }); - }); + }, __("Create")); } if(frm.doc.items) { From dc513886668aa595bf73ebecd7312935c82284b5 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 26 May 2021 18:28:24 +0530 Subject: [PATCH 0869/1264] style: fixed indentations and formatting --- .../maintenance_schedule.js | 4 +- .../maintenance_schedule.py | 70 +++++++++---------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 1e5773c8bc..e2de941963 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -70,7 +70,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ this.frm.add_custom_button(__('Create Maintenance Visit'), function () { let options = ""; - me.frm.call('get_pending_data', {data_type: "items"}).then(r =>{ + me.frm.call('get_pending_data', {data_type: "items"}).then(r => { options = r.message; let schedule_id = ""; @@ -108,7 +108,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ item_name: field.value, s_date: this.value, data_type: "id" - }).then(r =>{ + }).then(r => { schedule_id = r.message; }); } diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 5d573c5524..ea76e91b3f 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -140,7 +140,7 @@ class MaintenanceSchedule(TransactionBase): if employee: holiday_list = get_holiday_list_for_employee(employee) else: - holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list") + holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list") holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list) @@ -161,16 +161,16 @@ class MaintenanceSchedule(TransactionBase): if d.start_date and d.end_date and d.periodicity and d.periodicity!="Random": date_diff = (getdate(d.end_date) - getdate(d.start_date)).days + 1 days_in_period = { - "Weekly": 7, - "Monthly": 30, - "Quarterly": 90, - "Half Yearly": 180, - "Yearly": 365 + "Weekly": 7, + "Monthly": 30, + "Quarterly": 90, + "Half Yearly": 180, + "Yearly": 365 } if date_diff < days_in_period[d.periodicity]: throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}") - .format(d.idx, d.periodicity, days_in_period[d.periodicity])) + .format(d.idx, d.periodicity, days_in_period[d.periodicity])) def validate_maintenance_detail(self): if not self.get('items'): @@ -217,28 +217,28 @@ class MaintenanceSchedule(TransactionBase): 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", "item_code"], 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") + .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): + and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under warranty upto {1}") - .format(serial_no, sr_details.warranty_expiry_date)) + .format(serial_no, sr_details.warranty_expiry_date)) if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under maintenance contract upto {1}") - .format(serial_no, sr_details.amc_expiry_date)) + .format(serial_no, sr_details.amc_expiry_date)) if not sr_details.warehouse and sr_details.delivery_date and \ - getdate(sr_details.delivery_date) >= getdate(amc_start_date): + getdate(sr_details.delivery_date) >= getdate(amc_start_date): throw(_("Maintenance start date can not be before delivery date for Serial No {0}") - .format(serial_no)) + .format(serial_no)) def validate_schedule(self): item_lst1 =[] @@ -281,7 +281,7 @@ class MaintenanceSchedule(TransactionBase): delete_events(self.doctype, self.name) @frappe.whitelist() - def get_pending_data(self,data_type,s_date = None, item_name = None): + def get_pending_data(self, data_type, s_date=None, item_name=None): if data_type == "date": dates = "" for schedule in self.schedules: @@ -298,7 +298,7 @@ class MaintenanceSchedule(TransactionBase): return items elif data_type == "id": for schedule in self.schedules: - if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date,"dd-mm-yyyy"): + if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date, "dd-mm-yyyy"): return schedule.name @frappe.whitelist() @@ -324,27 +324,25 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No target.serial_no = '' doclist = get_mapped_doc("Maintenance Schedule", source_name, { - "Maintenance Schedule": { - "doctype": "Maintenance Visit", - "field_map": { - "name": "maintenance_schedule" - }, - "validation": { - "docstatus": ["=", 1] - }, - "postprocess": update_status + "Maintenance Schedule": { + "doctype": "Maintenance Visit", + "field_map": { + "name": "maintenance_schedule" }, - "Maintenance Schedule Item": { - "doctype": "Maintenance Visit Purpose", - "field_map": { - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", - }, - "condition": lambda doc: doc.item_name == item_name, - - "postprocess": update_sid - - } + "validation": { + "docstatus": ["=", 1] + }, + "postprocess": update_status + }, + "Maintenance Schedule Item": { + "doctype": "Maintenance Visit Purpose", + "field_map": { + "parent": "prevdoc_docname", + "parenttype": "prevdoc_doctype", + }, + "condition": lambda doc: doc.item_name == item_name, + "postprocess": update_sid + } }, target_doc) return doclist From 5e4128e70ca2d851b199a1fb1419c01c9d16a5df Mon Sep 17 00:00:00 2001 From: Anuja P Date: Wed, 26 May 2021 20:03:14 +0530 Subject: [PATCH 0870/1264] fix: patch for existing issues --- erpnext/patches.txt | 1 + .../rename_issue_status_hold_to_on_hold.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1e8ce3c658..3a7aa1bce3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -781,3 +781,4 @@ erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed erpnext.patches.v13_0.update_timesheet_changes erpnext.patches.v13_0.set_training_event_attendance +erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py new file mode 100644 index 0000000000..b466678095 --- /dev/null +++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py @@ -0,0 +1,20 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + if frappe.db.exists('DocType', 'Issue'): + frappe.reload_doc("support", "doctype", "issue") + rename_status() + +def rename_status(): + frappe.db.sql(""" + UPDATE + `tabIssue` + SET + status = 'On Hold' + WHERE + status = 'Hold' + """) \ No newline at end of file From e2059fb9f65a854d2b8ed9f52d51eb40a4c0ad32 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Wed, 26 May 2021 20:07:53 +0530 Subject: [PATCH 0871/1264] fix: spaces to tabs --- .../v13_0/rename_issue_status_hold_to_on_hold.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py index b466678095..48325fc2d4 100644 --- a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py +++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py @@ -6,15 +6,15 @@ import frappe def execute(): if frappe.db.exists('DocType', 'Issue'): - frappe.reload_doc("support", "doctype", "issue") - rename_status() + frappe.reload_doc("support", "doctype", "issue") + rename_status() def rename_status(): frappe.db.sql(""" UPDATE - `tabIssue` + `tabIssue` SET - status = 'On Hold' - WHERE - status = 'Hold' + status = 'On Hold' + WHERE + status = 'Hold' """) \ No newline at end of file From be3671fe7b3965be2f34fb902e68d25c3f5c174b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 27 May 2021 13:27:17 +0530 Subject: [PATCH 0872/1264] fix: Create new supplier for test case --- .../purchase_invoice/test_purchase_invoice.py | 15 ++++++++++----- .../doctype/purchase_order/test_purchase_order.py | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 319808ac95..2610a69471 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_tra from erpnext.projects.doctype.project.test_project import make_project from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account from erpnext.stock.doctype.item.test_item import create_item +from erpnext.buying.doctype.supplier.test_supplier import create_supplier test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_ignore = ["Serial No"] @@ -955,11 +956,15 @@ class TestPurchaseInvoice(unittest.TestCase): from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice + # create a new supplier to test + supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier', + tax_withholding_category = 'TDS - 194 - Dividends - Individual') + # Update tax withholding category with current fiscal year and rate details update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate()) # Create Purchase Order with TDS applied - po = create_purchase_order(do_not_save=1, rate=3000) + po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000) po.apply_tds = 1 po.tax_withholding_category = 'TDS - 194 - Dividends - Individual' po.save() @@ -976,8 +981,8 @@ class TestPurchaseInvoice(unittest.TestCase): # Check GLE for Payment Entry expected_gle = [ ['_Test Account Excise Duty - _TC', 3000, 0], - ['Cash - _TC', 0, 24000], - ['Creditors - _TC', 24000, 0], + ['Cash - _TC', 0, 27000], + ['Creditors - _TC', 27000, 0], ['TDS Payable - _TC', 0, 3000], ] @@ -1002,8 +1007,8 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gle = [ ['_Test Account Excise Duty - _TC', 0, 3000], ['Cost of Goods Sold - _TC', 30000, 0], - ['Creditors - _TC', 0, 24000], - ['TDS Payable - _TC', 6000, 3000], + ['Creditors - _TC', 0, 27000], + ['TDS Payable - _TC', 3000, 3000], ] gl_entries = frappe.db.sql("""select account, debit, credit diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index aaa98f2f1f..39171960d8 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1111,7 +1111,7 @@ def create_purchase_order(**args): po.schedule_date = add_days(nowdate(), 1) po.company = args.company or "_Test Company" - po.supplier = args.customer or "_Test Supplier" + po.supplier = args.supplier or "_Test Supplier" po.is_subcontracted = args.is_subcontracted or "No" po.currency = args.currency or frappe.get_cached_value('Company', po.company, "default_currency") po.conversion_factor = args.conversion_factor or 1 From 45a89a7c6747b7708fa9b7cc77c2691365d81c20 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 27 May 2021 13:49:45 +0530 Subject: [PATCH 0873/1264] fix(pos): cannot add same item with different rates --- .../page/point_of_sale/pos_controller.js | 34 ++++++++++++------- .../page/point_of_sale/pos_item_cart.js | 25 +++++++++----- .../page/point_of_sale/pos_item_details.js | 28 ++++++++++----- .../page/point_of_sale/pos_item_selector.js | 9 +++-- 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 4f4f1b2240..0921f010e7 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -241,10 +241,8 @@ erpnext.PointOfSale.Controller = class { events: { get_frm: () => this.frm, - cart_item_clicked: (item_code, batch_no, uom) => { - const search_field = batch_no ? 'batch_no' : 'item_code'; - const search_value = batch_no || item_code; - const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom); + cart_item_clicked: (item_code, batch_no, uom, rate) => { + const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate) this.item_details.toggle_item_details_section(item_row); }, @@ -275,18 +273,25 @@ erpnext.PointOfSale.Controller = class { this.cart.toggle_numpad(minimize); }, - form_updated: async (cdt, cdn, fieldname, value) => { + form_updated: (cdt, cdn, fieldname, value) => { const item_row = frappe.model.get_doc(cdt, cdn); if (item_row && item_row[fieldname] != value) { - const { item_code, batch_no, uom } = this.item_details.current_item; + const { item_code, batch_no, uom, rate } = this.item_details.current_item; const event = { field: fieldname, value, - item: { item_code, batch_no, uom } + item: { item_code, batch_no, uom, rate } } return this.on_cart_update(event) } + + return Promise.resolve(); + }, + + highlight_cart_item: (item) => { + const cart_item = this.cart.get_cart_item(item); + this.cart.toggle_item_highlight(cart_item); }, item_field_focused: (fieldname) => { @@ -501,8 +506,8 @@ erpnext.PointOfSale.Controller = class { let item_row = undefined; try { let { field, value, item } = args; - const { item_code, batch_no, serial_no, uom } = item; - item_row = this.get_item_from_frm(item_code, batch_no, uom); + const { item_code, batch_no, serial_no, uom, rate } = item; + item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); const item_selected_from_selector = field === 'qty' && value === "+1" @@ -535,7 +540,7 @@ erpnext.PointOfSale.Controller = class { item_selected_from_selector && (value = flt(value)) - const args = { item_code, batch_no, [field]: value }; + const args = { item_code, batch_no, rate, [field]: value }; if (serial_no) { await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no); @@ -550,9 +555,11 @@ erpnext.PointOfSale.Controller = class { 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); + + this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row); + this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row); } } catch (error) { @@ -563,12 +570,13 @@ erpnext.PointOfSale.Controller = class { } } - get_item_from_frm(item_code, batch_no, uom) { + get_item_from_frm(item_code, batch_no, uom, rate) { 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) + && (i.rate == rate) ); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 11a63b3d4a..f7db9d4349 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -184,7 +184,8 @@ erpnext.PointOfSale.ItemCart = class { 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); + const rate = unescape($cart_item.attr('data-rate')); + me.events.cart_item_clicked(item_code, batch_no, uom, rate); this.numpad_value = ''; }); @@ -520,28 +521,34 @@ erpnext.PointOfSale.ItemCart = class { } } - get_cart_item({ item_code, batch_no, uom }) { + get_cart_item({ item_code, batch_no, uom, rate }) { 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 rate_attr = `[data-rate="${escape(rate)}"]`; const item_selector = batch_no ? - `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`; + `.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`; return this.$cart_items_wrapper.find(item_selector); } + get_item_from_frm(item) { + const doc = this.events.get_frm().doc; + const { item_code, batch_no, uom, rate } = item; + const search_field = batch_no ? 'batch_no' : 'item_code'; + const search_value = batch_no || item_code; + + return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate); + } + update_item_html(item, remove_item) { const $item = this.get_cart_item(item); if (remove_item) { $item && $item.next().remove() && $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); - + const item_row = this.get_item_from_frm(item); this.render_cart_item(item_row, $item); } @@ -559,7 +566,7 @@ erpnext.PointOfSale.ItemCart = class { this.$cart_items_wrapper.append( `
    + data-batch-no="${escape(item_data.batch_no || '')}" data-rate="${escape(item_data.rate)}">
    ` ) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 32a4556766..1afce32018 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -54,13 +54,24 @@ erpnext.PointOfSale.ItemDetails = class { this.$dicount_section = this.$component.find('.discount-section'); } - toggle_item_details_section(item) { - const { item_code, batch_no, uom } = this.current_item; + has_item_has_changed(item) { + const { item_code, batch_no, uom, rate } = 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; + const rate_is_same = item && rate === item.rate; + + if (!item) + return false - this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true; + if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same) + return false + + return true; + } + + toggle_item_details_section(item) { + this.item_has_changed = this.has_item_has_changed(item) this.events.toggle_item_selector(this.item_has_changed); this.toggle_component(this.item_has_changed); @@ -72,11 +83,12 @@ erpnext.PointOfSale.ItemDetails = class { 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.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom, rate: item.rate }; this.render_dom(item); this.render_discount_dom(item); this.render_form(item); + this.events.highlight_cart_item(item); } else { this.validate_serial_batch_item(); this.current_item = {}; @@ -198,12 +210,14 @@ erpnext.PointOfSale.ItemDetails = class { if (this.allow_rate_change) { this.rate_control.df.onchange = function() { if (this.value || flt(this.value) === 0) { + me.events.set_value_in_current_cart_item('rate', 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); }); + me.current_item.rate = this.value; } }; } else { @@ -292,11 +306,7 @@ erpnext.PointOfSale.ItemDetails = class { frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { const field_control = this[`${fieldname}_control`]; - const { item_code, batch_no, uom } = this.current_item; - const item_code_is_same = item_code === item_row.item_code; - const batch_is_same = batch_no == item_row.batch_no; - const uom_is_same = uom === item_row.uom; - const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false; + const item_is_same = !this.has_item_has_changed(item_row); if (item_is_same && field_control && field_control.get_value() !== value) { field_control.set_value(value); diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index b8a82a9eda..55b07e71cb 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -78,7 +78,7 @@ erpnext.PointOfSale.ItemSelector = class { get_item_html(item) { const me = this; // eslint-disable-next-line no-unused-vars - const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; + const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; let qty_to_display = actual_qty; @@ -108,6 +108,7 @@ erpnext.PointOfSale.ItemSelector = class { `
    ${get_item_image_html()} @@ -116,7 +117,7 @@ erpnext.PointOfSale.ItemSelector = class {
    ${frappe.ellipsis(item.item_name, 18)}
    -
    ${format_currency(item.price_list_rate, item.currency, 0) || 0}
    +
    ${format_currency(price_list_rate, item.currency, 0) || 0}
    ` ); @@ -213,13 +214,15 @@ erpnext.PointOfSale.ItemSelector = class { 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')); + let rate = unescape($item.attr('data-rate')); // 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; + rate = rate === "undefined" ? undefined : rate; - me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }}); + me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }}); me.set_search_value(''); }); From 3382655b5eb7f0e6477c63ab91ef2792735ef601 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 27 May 2021 14:27:38 +0530 Subject: [PATCH 0874/1264] fix: sider issues --- erpnext/selling/page/point_of_sale/pos_controller.js | 2 +- erpnext/selling/page/point_of_sale/pos_item_details.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 0921f010e7..ae3f9e3c9d 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -242,7 +242,7 @@ erpnext.PointOfSale.Controller = class { get_frm: () => this.frm, cart_item_clicked: (item_code, batch_no, uom, rate) => { - const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate) + const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); this.item_details.toggle_item_details_section(item_row); }, diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 1afce32018..df62696c4b 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -62,16 +62,16 @@ erpnext.PointOfSale.ItemDetails = class { const rate_is_same = item && rate === item.rate; if (!item) - return false + return false; if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same) - return false + return false; return true; } toggle_item_details_section(item) { - this.item_has_changed = this.has_item_has_changed(item) + this.item_has_changed = this.has_item_has_changed(item); this.events.toggle_item_selector(this.item_has_changed); this.toggle_component(this.item_has_changed); From ea18230274725f2cf0a3fa62b8b6f756af4dea79 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 27 May 2021 16:40:37 +0530 Subject: [PATCH 0875/1264] fix: Update GL Entry ordering --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2610a69471..07f9b42fbf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1005,8 +1005,8 @@ class TestPurchaseInvoice(unittest.TestCase): # Check GLE for Purchase Invoice # Zero net effect on final TDS Payable on invoice expected_gle = [ + ['_Test Account Cost for Goods Sold - _TC', 30000, 0], ['_Test Account Excise Duty - _TC', 0, 3000], - ['Cost of Goods Sold - _TC', 30000, 0], ['Creditors - _TC', 0, 27000], ['TDS Payable - _TC', 3000, 3000], ] From bb0a40b99572b4a3b278cb7ef0d0f1beeef9648b Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 27 May 2021 17:15:47 +0530 Subject: [PATCH 0876/1264] fix: ageing error in PSOA --- .../process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 2ad455c48f..0b0ee904ff 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -94,7 +94,7 @@ def get_report_pdf(doc, consolidated=True): continue html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "ageing": ageing[0] if doc.include_ageing else None, + {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None, "letter_head": letter_head if doc.letter_head else None, "terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms') if doc.terms_and_conditions else None}) From 9e4c28852e10848feda7bad57235a35fad807792 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:25:50 +0530 Subject: [PATCH 0877/1264] refactor: added maintenance visit to maintenance schedule dashboard --- .../doctype/maintenance_schedule/maintenance_schedule.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json index 4df0c6c0f7..4f89a679c8 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json @@ -231,11 +231,12 @@ "is_submittable": 1, "links": [ { - "link_doctype": "Maintenance Visit Purpose", - "link_fieldname": "prevdoc_docname" + "group": "Visits", + "link_doctype": "Maintenance Visit", + "link_fieldname": "maintenance_schedule" } ], - "modified": "2021-04-21 11:27:05.744109", + "modified": "2021-05-27 16:05:10.746465", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule", From bd783e8072c8d4dcae4b9249b5a88363bb0a2d1b Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:34:16 +0530 Subject: [PATCH 0878/1264] refactor: renamed item_ref to item_reference --- .../maintenance_schedule_detail.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 76acefbf61..8ccef6a817 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -18,7 +18,7 @@ "completion_status", "section_break_10", "serial_no", - "item_ref" + "item_reference" ], "fields": [ { @@ -97,14 +97,6 @@ "options": "Pending\nPartially Completed\nFully Completed", "read_only": 1 }, - { - "fieldname": "item_ref", - "fieldtype": "Link", - "hidden": 1, - "label": "Item Reference", - "options": "Maintenance Schedule Item", - "read_only": 1 - }, { "fieldname": "column_break_3", "fieldtype": "Column Break" @@ -120,12 +112,20 @@ { "fieldname": "section_break_10", "fieldtype": "Section Break" + }, + { + "fieldname": "item_reference", + "fieldtype": "Link", + "hidden": 1, + "label": "Item Reference", + "options": "Maintenance Schedule Item", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-21 11:07:29.524071", + "modified": "2021-05-27 16:07:25.905015", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", From fbc86942911035f29a85fc9302e81e091b990657 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:37:57 +0530 Subject: [PATCH 0879/1264] refactor: added maintenance schedule links to parent form --- .../maintenance_visit/maintenance_visit.json | 1286 ++++------------- 1 file changed, 284 insertions(+), 1002 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index 32bfa0e324..ec32239518 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -1,1042 +1,324 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-01-10 16:34:31", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "actions": [], + "autoname": "naming_series:", + "creation": "2013-01-10 16:34:31", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "customer_details", + "column_break0", + "naming_series", + "customer", + "customer_name", + "address_display", + "contact_display", + "contact_mobile", + "contact_email", + "maintenance_schedule", + "maintenance_schedule_detail", + "column_break1", + "mntc_date", + "mntc_time", + "maintenance_details", + "completion_status", + "column_break_14", + "maintenance_type", + "section_break0", + "purposes", + "more_info", + "customer_feedback", + "col_break3", + "status", + "amended_from", + "company", + "contact_info_section", + "customer_address", + "contact_person", + "col_break4", + "territory", + "customer_group" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_details", - "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, - "oldfieldtype": "Section Break", - "options": "fa fa-user", - "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 - }, + "fieldname": "customer_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-user" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "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, - "oldfieldtype": "Column Break", - "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 - }, + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "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": "Series", - "length": 0, - "no_copy": 1, - "options": "MAT-MVS-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-MVS-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "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": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "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": "customer", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_name", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "bold": 1, + "fieldname": "customer_name", + "fieldtype": "Data", + "hidden": 1, + "in_global_search": 1, + "label": "Customer Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "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": "Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "address_display", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "contact_display", + "fieldtype": "Small Text", + "hidden": 1, + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_mobile", - "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": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "contact_mobile", + "fieldtype": "Data", + "hidden": 1, + "label": "Mobile No", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_email", - "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": "Contact Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "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, - "oldfieldtype": "Column Break", - "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, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "mntc_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": "Maintenance Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "mntc_date", - "oldfieldtype": "Date", - "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 - }, + "default": "Today", + "fieldname": "mntc_date", + "fieldtype": "Date", + "label": "Maintenance Date", + "no_copy": 1, + "oldfieldname": "mntc_date", + "oldfieldtype": "Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mntc_time", - "fieldtype": "Time", - "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": "Maintenance Time", - "length": 0, - "no_copy": 1, - "oldfieldname": "mntc_time", - "oldfieldtype": "Time", - "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 - }, + "fieldname": "mntc_time", + "fieldtype": "Time", + "label": "Maintenance Time", + "no_copy": 1, + "oldfieldname": "mntc_time", + "oldfieldtype": "Time" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintenance_details", - "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, - "oldfieldtype": "Section Break", - "options": "fa fa-wrench", - "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 - }, + "fieldname": "maintenance_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-wrench" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "completion_status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Completion Status", - "length": 0, - "no_copy": 0, - "oldfieldname": "completion_status", - "oldfieldtype": "Select", - "options": "\nPartially Completed\nFully Completed", - "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 - }, + "fieldname": "completion_status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Completion Status", + "oldfieldname": "completion_status", + "oldfieldtype": "Select", + "options": "\nPartially Completed\nFully Completed", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_14", - "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 - }, + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Unscheduled", - "fieldname": "maintenance_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Maintenance Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "maintenance_type", - "oldfieldtype": "Select", - "options": "\nScheduled\nUnscheduled\nBreakdown", - "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 - }, + "default": "Unscheduled", + "fieldname": "maintenance_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Maintenance Type", + "oldfieldname": "maintenance_type", + "oldfieldtype": "Select", + "options": "\nScheduled\nUnscheduled\nBreakdown", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break0", - "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, - "oldfieldtype": "Section Break", - "options": "fa fa-wrench", - "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 - }, + "fieldname": "section_break0", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-wrench" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purposes", - "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": "Purposes", - "length": 0, - "no_copy": 0, - "oldfieldname": "maintenance_visit_details", - "oldfieldtype": "Table", - "options": "Maintenance Visit Purpose", - "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 - }, + "fieldname": "purposes", + "fieldtype": "Table", + "label": "Purposes", + "oldfieldname": "maintenance_visit_details", + "oldfieldtype": "Table", + "options": "Maintenance Visit Purpose", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "more_info", - "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": "More Information", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "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 - }, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_feedback", - "fieldtype": "Small Text", - "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": "Customer Feedback", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer_feedback", - "oldfieldtype": "Small Text", - "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 - }, + "fieldname": "customer_feedback", + "fieldtype": "Small Text", + "label": "Customer Feedback", + "oldfieldname": "customer_feedback", + "oldfieldtype": "Small Text" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break3", - "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 - }, + "fieldname": "col_break3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "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": 1, - "oldfieldname": "status", - "oldfieldtype": "Data", - "options": "\nDraft\nCancelled\nSubmitted", - "permlevel": 0, - "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 - }, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Data", + "options": "\nDraft\nCancelled\nSubmitted", + "read_only": 1, + "reqd": 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": 1, - "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, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Maintenance Visit", - "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": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Maintenance Visit", + "print_hide": 1, + "read_only": 1, "width": "150px" - }, + }, { - "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, - "oldfieldname": "company", - "oldfieldtype": "Select", - "options": "Company", - "permlevel": 0, - "print_hide": 1, - "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 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Select", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_info_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": "Contact Info", - "length": 0, - "no_copy": 0, - "options": "fa fa-bullhorn", - "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 - }, + "depends_on": "customer", + "fieldname": "contact_info_section", + "fieldtype": "Section Break", + "label": "Contact Info", + "options": "fa fa-bullhorn" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_address", - "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": "Customer Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "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 - }, + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_person", - "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": "Contact Person", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "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 - }, + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break4", - "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 - }, + "fieldname": "col_break4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "territory", - "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": "Territory", - "length": 0, - "no_copy": 0, - "options": "Territory", - "permlevel": 0, - "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 - }, + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "customer_group", - "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": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "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 + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group", + "print_hide": 1 + }, + { + "fieldname": "maintenance_schedule", + "fieldtype": "Link", + "label": "Maintenance Schedule", + "options": "Maintenance Schedule", + "read_only": 1 + }, + { + "fieldname": "maintenance_schedule_detail", + "fieldtype": "Link", + "hidden": 1, + "label": "Maintenance Schedule Detail", + "options": "Maintenance Schedule Detail" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-file-text", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Visit", - "owner": "Administrator", + ], + "icon": "fa fa-file-text", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-05-27 16:06:17.352572", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Visit", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Maintenance User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance User", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "customer", - "title_field": "customer_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "customer", + "title_field": "customer_name" } \ No newline at end of file From 740c68d075c4583045052df5e3192039d2cd4576 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 27 May 2021 17:43:53 +0530 Subject: [PATCH 0880/1264] fix: Debug Test --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 07f9b42fbf..8bb9d7dacf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1016,6 +1016,9 @@ class TestPurchaseInvoice(unittest.TestCase): where voucher_type='Purchase Invoice' and voucher_no=%s order by account asc""", (purchase_invoice.name), as_dict=1) + for gle in gl_entries: + print(gle.account, gle.debit, gle.credit) + for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.debit) From 04dfaf3b2afceeabafdc62ebb9eb15b8927fff15 Mon Sep 17 00:00:00 2001 From: anushka19 Date: Mon, 24 May 2021 20:49:46 +0530 Subject: [PATCH 0881/1264] fix: Broken help links fixed --- erpnext/public/js/help_links.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index e78992302f..aa9bba17c7 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -644,14 +644,14 @@ frappe.help.help_links["List/Payment Request"] = [ frappe.help.help_links["List/Asset"] = [ { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", }, ]; frappe.help.help_links["List/Asset Category"] = [ { label: "Asset Category", - url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + url: docsUrl + "user/manual/en/asset/asset-category", }, ]; @@ -663,7 +663,7 @@ frappe.help.help_links["List/Item"] = [ { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, { label: "Item Price", - url: docsUrl + "user/manual/en/stock/item/item-price", + url: docsUrl + "user/manual/en/stock/item-price", }, { label: "Barcode", @@ -672,25 +672,25 @@ frappe.help.help_links["List/Item"] = [ }, { label: "Item Wise Taxation", - url: docsUrl + "user/manual/en/accounts/item-wise-taxation", + url: docsUrl + "user/manual/en/accounts/item-tax-template", }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", }, { label: "Item Codification", - url: docsUrl + "user/manual/en/stock/item/item-codification", + url: docsUrl + "user/manual/en/stock/articles/item-codification", }, { label: "Item Variants", - url: docsUrl + "user/manual/en/stock/item/item-variants", + url: docsUrl + "user/manual/en/stock/item-variants", }, { label: "Item Valuation", url: docsUrl + - "user/manual/en/stock/item/item-valuation-fifo-and-moving-average", + "user/manual/en/stock/articles/item-valuation-fifo-and-moving-average", }, ]; @@ -698,7 +698,7 @@ frappe.help.help_links["Form/Item"] = [ { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, { label: "Item Price", - url: docsUrl + "user/manual/en/stock/item/item-price", + url: docsUrl + "user/manual/en/stock/item-price", }, { label: "Barcode", @@ -707,19 +707,19 @@ frappe.help.help_links["Form/Item"] = [ }, { label: "Item Wise Taxation", - url: docsUrl + "user/manual/en/accounts/item-wise-taxation", + url: docsUrl + "user/manual/en/accounts/item-tax-template", }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", }, { label: "Item Codification", - url: docsUrl + "user/manual/en/stock/item/item-codification", + url: docsUrl + "user/manual/en/stock/articles/item-codification", }, { label: "Item Variants", - url: docsUrl + "user/manual/en/stock/item/item-variants", + url: docsUrl + "user/manual/en/stock/item-variants", }, { label: "Item Valuation", From 46d39d27aaea10eda484cf41dda0b3bb6392f67f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 May 2021 14:16:01 +0200 Subject: [PATCH 0882/1264] fix: validate company in taxes setup --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 429a558c58..dd0ebd1517 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -11,6 +11,9 @@ from frappe import _ def setup_taxes_and_charges(company_name: str, country: str): + if not frappe.db.exists('Company', company_name): + frappe.throw(_('Company {} does not exist yet. Taxes setup aborted.').format(company_name)) + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json') with open(file_path, 'r') as json_file: tax_data = json.load(json_file) From 86ee3ebb09ea11e971a48e1718822a2e998fba10 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 May 2021 14:16:26 +0200 Subject: [PATCH 0883/1264] feat: create tax category during taxes setup --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index dd0ebd1517..672caf2606 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -102,6 +102,9 @@ def make_taxes_and_charges_template(company_name, doctype, template): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return + if template.get('tax_category'): + ensure_tax_category_exists(template.get('tax_category')) + for tax_row in template.get('taxes'): account_data = tax_row.get('account_head') tax_row_defaults = { @@ -233,3 +236,10 @@ def get_or_create_tax_group(company_name, root_type): tax_group_name = tax_group_account.name return tax_group_name + + +def ensure_tax_category_exists(name): + if not frappe.db.exists('Tax Category', name): + doc = frappe.new_doc('Tax Category') + doc.title = name + doc.save() From 50794407b49469682a53b6f91dd3257ca7b25f9d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 May 2021 14:17:53 +0200 Subject: [PATCH 0884/1264] feat: Item Tax Templates for Germany --- .../setup_wizard/data/country_wise_tax.json | 307 +++++++++++++----- 1 file changed, 232 insertions(+), 75 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 5876488033..2e2a0ca726 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -485,33 +485,32 @@ "SKR04 mit Kontonummern": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "account_number": "3806", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Umsatzsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Umsatzsteuer 7%", "account_number": "3801", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } ], "purchase_tax_templates": [ { - "title": "Abziehbare Vorsteuer 19%", + "title": "Vorsteuer", + "tax_category": "Vorsteuer", "taxes": [ { "account_head": { @@ -519,25 +518,23 @@ "account_number": "1406", "root_type": "Asset", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Abziehbare Vorsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Abziehbare Vorsteuer 7%", "account_number": "1401", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] }, { "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", + "tax_category": "Innergemeinschaftlicher Erwerb 19%", "taxes": [ { "account_head": { @@ -564,33 +561,32 @@ "SKR03 mit Kontonummern": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "account_number": "1776", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Umsatzsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Umsatzsteuer 7%", "account_number": "1771", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } ], "purchase_tax_templates": [ { - "title": "Abziehbare Vorsteuer 19%", + "title": "Vorsteuer", + "tax_category": "Vorsteuer", "taxes": [ { "account_head": { @@ -598,20 +594,17 @@ "account_number": "1576", "root_type": "Asset", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Abziehbare Vorsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Abziehbare Vorsteuer 7%", "account_number": "1571", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } @@ -620,33 +613,32 @@ "Standard with Numbers": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "account_number": "2301", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Umsatzsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Umsatzsteuer 7%", "account_number": "2302", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } ], "purchase_tax_templates": [ { - "title": "Abziehbare Vorsteuer 19%", + "title": "Vorsteuer", + "tax_category": "Vorsteuer", "taxes": [ { "account_head": { @@ -654,20 +646,107 @@ "account_number": "1501", "root_type": "Asset", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Abziehbare Vorsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Abziehbare Vorsteuer 7%", "account_number": "1502", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "rate": 0.00 + } + ] + } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + }, + { + "title": "Vorsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 } ] } @@ -676,13 +755,67 @@ "*": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "tax_rate": 19.00 - } + }, + "rate": 0.00 + }, + { + "account_head": { + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + }, + "rate": 0.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Vorsteuer 19%", + "tax_category": "Vorsteuer", + "taxes": [ + { + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "tax_rate": 19.00, + "root_type": "Asset" + }, + "rate": 0.00 + }, + { + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "rate": 0.00 + } + ] + } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 } ] }, @@ -690,36 +823,60 @@ "title": "Umsatzsteuer 7%", "taxes": [ { - "account_head": { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { "account_name": "Umsatzsteuer 7%", "tax_rate": 7.00 - } - } - ] - } - ], - "purchase_tax_templates": [ - { - "title": "Abziehbare Vorsteuer 19%", - "taxes": [ - { - "account_head": { - "account_name": "Abziehbare Vorsteuer 19%", - "tax_rate": 19.00, - "root_type": "Asset" - } + }, + "tax_rate": 7.00 } ] }, { - "title": "Abziehbare Vorsteuer 7%", + "title": "Vorsteuer 19%", "taxes": [ { - "account_head": { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { "account_name": "Abziehbare Vorsteuer 7%", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 } ] } From 2e83cb77ca0147b8ab6fd5b0b999cfb5e6f213ea Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 27 May 2021 20:09:45 +0530 Subject: [PATCH 0885/1264] fix: Split GL Entry --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 8bb9d7dacf..1ec49d178f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1008,7 +1008,8 @@ class TestPurchaseInvoice(unittest.TestCase): ['_Test Account Cost for Goods Sold - _TC', 30000, 0], ['_Test Account Excise Duty - _TC', 0, 3000], ['Creditors - _TC', 0, 27000], - ['TDS Payable - _TC', 3000, 3000], + ['TDS Payable - _TC', 0, 3000], + ['TDS Payable - _TC', 3000, 0] ] gl_entries = frappe.db.sql("""select account, debit, credit From a150645b5712f4d7b595a1966c4f0b8600b917f2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 27 May 2021 20:10:50 +0530 Subject: [PATCH 0886/1264] fix: Remove GL Entry from print --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 1ec49d178f..1c4350f8dc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1017,9 +1017,6 @@ class TestPurchaseInvoice(unittest.TestCase): where voucher_type='Purchase Invoice' and voucher_no=%s order by account asc""", (purchase_invoice.name), as_dict=1) - for gle in gl_entries: - print(gle.account, gle.debit, gle.credit) - for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.debit) From 591f3f0bb9ec944757a482db40e34162b3eb2fbe Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 4 May 2021 18:36:45 +0530 Subject: [PATCH 0887/1264] ci: Try Parallel tests --- .github/helper/install.sh | 2 +- .github/workflows/ci-tests.yml | 55 ++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 7b0f944c66..fd32624c2d 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,7 +12,7 @@ sudo apt install npm pip install frappe-bench -git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 +git clone https://github.com/surajshetty3416/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 84ecfb1457..de8d7ac503 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -13,7 +13,10 @@ jobs: include: - TYPE: "server" JOB_NAME: "Server" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage + RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext + - TYPE: "server" + JOB_NAME: "Server" + RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "patch" JOB_NAME: "Patch" RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate @@ -80,29 +83,29 @@ jobs: env: TYPE: ${{ matrix.TYPE }} - - name: Coverage - Pull Request - if: matrix.TYPE == 'server' && github.event_name == 'pull_request' - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip install coveralls==2.2.0 - pip install coverage==4.5.4 - coveralls --service=github - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - COVERALLS_SERVICE_NAME: github - - - name: Coverage - Push - if: matrix.TYPE == 'server' && github.event_name == 'push' - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip install coveralls==2.2.0 - pip install coverage==4.5.4 - coveralls --service=github-actions - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - COVERALLS_SERVICE_NAME: github-actions + # - name: Coverage - Pull Request + # if: matrix.TYPE == 'server' && github.event_name == 'pull_request' + # run: | + # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + # cd ${GITHUB_WORKSPACE} + # pip install coveralls==2.2.0 + # pip install coverage==4.5.4 + # coveralls --service=github + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + # COVERALLS_SERVICE_NAME: github + + # - name: Coverage - Push + # if: matrix.TYPE == 'server' && github.event_name == 'push' + # run: | + # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + # cd ${GITHUB_WORKSPACE} + # pip install coveralls==2.2.0 + # pip install coverage==4.5.4 + # coveralls --service=github-actions + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + # COVERALLS_SERVICE_NAME: github-actions From dd1530492104759f28df37e5b0235ab19551cff5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 4 May 2021 18:58:20 +0530 Subject: [PATCH 0888/1264] fix: Frappe branch --- .github/helper/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index fd32624c2d..44659f22fa 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,7 +12,7 @@ sudo apt install npm pip install frappe-bench -git clone https://github.com/surajshetty3416/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 +git clone https://github.com/surajshetty3416/frappe --branch "python-distributed-testing" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site From 34e620fb5b50b6198544ae16b9480b345933ab70 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 13:04:12 +0530 Subject: [PATCH 0889/1264] test: Fix dependency --- erpnext/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py index e69de29bb2..dcc4f5336d 100644 --- a/erpnext/tests/__init__.py +++ b/erpnext/tests/__init__.py @@ -0,0 +1 @@ +global_test_dependencies = ['User', 'Company'] From 85dd5d2252d5d2539bec0cabb364c87df22d8660 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 13:05:03 +0530 Subject: [PATCH 0890/1264] chore: Debug --- .github/workflows/ci-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index de8d7ac503..7373c1efa6 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -1,6 +1,6 @@ name: CI -on: [pull_request, workflow_dispatch, push] +on: [pull_request, workflow_dispatch] jobs: test: From 0f3d862ba993e56b1312e67334ae138ee52b663c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 15:13:09 +0530 Subject: [PATCH 0891/1264] chore: Debug --- .github/workflows/ci-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 7373c1efa6..b03aad794b 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -17,6 +17,9 @@ jobs: - TYPE: "server" JOB_NAME: "Server" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext + - TYPE: "server" + JOB_NAME: "Server" + RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "patch" JOB_NAME: "Patch" RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate From 7b74985a548aaf80778555b84fe10104ee2b90e6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 17:44:26 +0530 Subject: [PATCH 0892/1264] test: Fix test_dependencies --- erpnext/accounts/doctype/budget/test_budget.py | 2 ++ erpnext/education/doctype/fees/test_fees.py | 3 +-- erpnext/tests/__init__.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index c5ec23c829..603e21ea24 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -11,6 +11,8 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +test_dependencies = ['Monthly Distribution'] + class TestBudget(unittest.TestCase): def test_monthly_budget_crossed_ignore(self): set_total_expense_zero(nowdate(), "cost_center") diff --git a/erpnext/education/doctype/fees/test_fees.py b/erpnext/education/doctype/fees/test_fees.py index eedc2ae730..c6bb704b41 100644 --- a/erpnext/education/doctype/fees/test_fees.py +++ b/erpnext/education/doctype/fees/test_fees.py @@ -9,8 +9,7 @@ from frappe.utils import nowdate from frappe.utils.make_random import get_random from erpnext.education.doctype.program.test_program import make_program_and_linked_courses -# test_records = frappe.get_test_records('Fees') - +test_dependencies = ['Company'] class TestFees(unittest.TestCase): def test_fees(self): diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py index dcc4f5336d..593bc7c71b 100644 --- a/erpnext/tests/__init__.py +++ b/erpnext/tests/__init__.py @@ -1 +1 @@ -global_test_dependencies = ['User', 'Company'] +global_test_dependencies = ['User', 'Company', 'Cost Center', 'Account', 'Warehouse', 'Item'] From 232cd28d67921096ce10fb206894790264ef9e54 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 18:37:14 +0530 Subject: [PATCH 0893/1264] test: Fix dependencies --- .../doctype/accounting_dimension/test_accounting_dimension.py | 3 ++- .../test_accounting_dimension_filter.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index fc1d7e344a..6fb661eb54 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -7,7 +7,8 @@ import frappe import unittest from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension + +test_dependencies = ['Location'] class TestAccountingDimension(unittest.TestCase): def setUp(self): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index 7877abd026..78a88eb48c 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -9,6 +9,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError +test_dependencies = ['Location'] + class TestAccountingDimensionFilter(unittest.TestCase): def setUp(self): create_dimension() From c26d41acf98216fbd31a3b97344e9865ec25d8f5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 18:37:45 +0530 Subject: [PATCH 0894/1264] chore: Remove unnecessary print statements --- erpnext/controllers/accounts_controller.py | 1 - erpnext/setup/doctype/email_digest/email_digest.py | 3 +-- erpnext/stock/stock_balance.py | 5 +---- erpnext/utilities/__init__.py | 1 - 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 544e624725..f88e8df728 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1011,7 +1011,6 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 8c97322a71..5db54eeee1 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -808,7 +808,6 @@ def get_incomes_expenses_for_period(account, from_date, to_date): val = balance_on_to_date - balance_before_from_date else: last_year_closing_balance = get_balance_on(account, date=fy_start_date - timedelta(days=1)) - print(fy_start_date - timedelta(days=1), last_year_closing_balance) val = balance_on_to_date + (last_year_closing_balance - balance_before_from_date) return val @@ -837,4 +836,4 @@ def get_future_date_for_calendaer_event(frequency): elif frequency == "Monthly": to_date = add_to_date(from_date, months=1) - return from_date, to_date \ No newline at end of file + return from_date, to_date diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 8ba1f1ca5c..8917bfeae4 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -194,9 +194,6 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin serial_nos = frappe.db.sql("""select count(name) from `tabSerial No` where item_code=%s and warehouse=%s and docstatus < 2""", (d[0], d[1])) - if serial_nos and flt(serial_nos[0][0]) != flt(d[2]): - print(d[0], d[1], d[2], serial_nos[0][0]) - sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry` where item_code = %s and warehouse = %s and is_cancelled = 0 order by posting_date desc limit 1""", (d[0], d[1])) @@ -230,7 +227,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin }) update_bin(args) - + create_repost_item_valuation_entry({ "item_code": d[0], "warehouse": d[1], diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py index 618cc985ae..0a5aa3c49b 100644 --- a/erpnext/utilities/__init__.py +++ b/erpnext/utilities/__init__.py @@ -12,7 +12,6 @@ def update_doctypes(): for f in dt.fields: if f.fieldname == d.fieldname and f.fieldtype in ("Text", "Small Text"): - print(f.parent, f.fieldname) f.fieldtype = "Text Editor" dt.save() break From 3b69aa80fcd0d4abf270076762bc451141af29ac Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 7 May 2021 00:32:00 +0530 Subject: [PATCH 0895/1264] ci: Enable coveralls --- .github/workflows/ci-tests.yml | 62 +++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index b03aad794b..4d955190be 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -12,13 +12,13 @@ jobs: matrix: include: - TYPE: "server" - JOB_NAME: "Server" + JOB_NAME: "Server.1" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "server" - JOB_NAME: "Server" + JOB_NAME: "Server.2" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "server" - JOB_NAME: "Server" + JOB_NAME: "Server.3" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "patch" JOB_NAME: "Patch" @@ -86,29 +86,37 @@ jobs: env: TYPE: ${{ matrix.TYPE }} - # - name: Coverage - Pull Request - # if: matrix.TYPE == 'server' && github.event_name == 'pull_request' - # run: | - # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - # cd ${GITHUB_WORKSPACE} - # pip install coveralls==2.2.0 - # pip install coverage==4.5.4 - # coveralls --service=github - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - # COVERALLS_SERVICE_NAME: github + - name: Upload Coverage Data + if: matrix.TYPE == 'server' + run: | + cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + cd ${GITHUB_WORKSPACE} + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls --service=github-actions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_FLAG_NAME: run-${{ matrix.container }} + COVERALLS_SERVICE_NAME: github-actions + COVERALLS_PARALLEL: true - # - name: Coverage - Push - # if: matrix.TYPE == 'server' && github.event_name == 'push' - # run: | - # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - # cd ${GITHUB_WORKSPACE} - # pip install coveralls==2.2.0 - # pip install coverage==4.5.4 - # coveralls --service=github-actions - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - # COVERALLS_SERVICE_NAME: github-actions + coveralls: + name: Coverage Wrap Up + needs: test + container: python:3-slim + runs-on: ubuntu-18.04 + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Coveralls Finished + run: | + cd ${GITHUB_WORKSPACE} + ls -al + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b4c958caf05c2605c615e07fb82ab1d9df0419c0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 00:04:34 +0530 Subject: [PATCH 0896/1264] chore: Debug --- .github/workflows/patch.yml | 69 +++++++++++++++++++ .../{ci-tests.yml => server-tests.yml} | 31 +++------ 2 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/patch.yml rename .github/workflows/{ci-tests.yml => server-tests.yml} (70%) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml new file mode 100644 index 0000000000..7c9e0272c9 --- /dev/null +++ b/.github/workflows/patch.yml @@ -0,0 +1,69 @@ +name: Patch + +on: [pull_request, workflow_dispatch] + +jobs: + test: + runs-on: ubuntu-18.04 + + name: Patch Test + + services: + mysql: + image: mariadb:10.3 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: YES + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.6 + + - name: Add to Hosts + run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install + run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + + - name: Run Patch Tests + run: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/server-tests.yml similarity index 70% rename from .github/workflows/ci-tests.yml rename to .github/workflows/server-tests.yml index 4d955190be..4042a407e7 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/server-tests.yml @@ -1,4 +1,4 @@ -name: CI +name: Server on: [pull_request, workflow_dispatch] @@ -7,24 +7,12 @@ jobs: runs-on: ubuntu-18.04 strategy: - fail-fast: false + fail-fast: true matrix: - include: - - TYPE: "server" - JOB_NAME: "Server.1" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - - TYPE: "server" - JOB_NAME: "Server.2" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - - TYPE: "server" - JOB_NAME: "Server.3" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - - TYPE: "patch" - JOB_NAME: "Patch" - RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate + container: [1, 2] - name: ${{ matrix.JOB_NAME }} + name: Server Tests services: mysql: @@ -42,7 +30,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts @@ -55,6 +43,7 @@ jobs: restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- + - name: Cache node modules uses: actions/cache@v2 env: @@ -66,6 +55,7 @@ jobs: ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- + - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" @@ -82,12 +72,11 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Tests - run: ${{ matrix.RUN_COMMAND }} + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 2 --with-coverage env: - TYPE: ${{ matrix.TYPE }} + TYPE: server - name: Upload Coverage Data - if: matrix.TYPE == 'server' run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} @@ -113,10 +102,8 @@ jobs: - name: Coveralls Finished run: | cd ${GITHUB_WORKSPACE} - ls -al pip3 install coverage==5.5 pip3 install coveralls==3.0.1 coveralls --finish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - From 5e88b14a01e790fd324c537f9f7a71928b4bffea Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 00:05:48 +0530 Subject: [PATCH 0897/1264] chore: Debug --- .github/workflows/server-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 4042a407e7..1e9196119e 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: true matrix: - container: [1, 2] + container: [1, 2, 3] name: Server Tests @@ -72,7 +72,7 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 2 --with-coverage + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 3 --with-coverage env: TYPE: server From 6ca8989013656aa749b89c8afadd173ce1406ec8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 13:51:34 +0530 Subject: [PATCH 0898/1264] ci: Disble failfast temporarily --- .github/workflows/server-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 1e9196119e..0d5a3ba61a 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-18.04 strategy: - fail-fast: true + fail-fast: false matrix: container: [1, 2, 3] From 8696580254f35c6145063a34de54b0a811068c04 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 16:02:40 +0530 Subject: [PATCH 0899/1264] ci: Fix coveralls --- .github/workflows/server-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 0d5a3ba61a..e5fb72538d 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -82,12 +82,12 @@ jobs: cd ${GITHUB_WORKSPACE} pip3 install coverage==5.5 pip3 install coveralls==3.0.1 - coveralls --service=github-actions + coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: github-actions + COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} COVERALLS_PARALLEL: true coveralls: From 175cb27bcbdd23ef41e256bbf09748229135feeb Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 19:03:53 +0530 Subject: [PATCH 0900/1264] test: Pass ConflictingTaxRule during tax rule test --- erpnext/shopping_cart/test_shopping_cart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py index d857bf5f5c..ac61aebc56 100644 --- a/erpnext/shopping_cart/test_shopping_cart.py +++ b/erpnext/shopping_cart/test_shopping_cart.py @@ -7,7 +7,7 @@ import frappe from frappe.utils import nowdate, add_months from erpnext.shopping_cart.cart import _get_cart_quotation, update_cart, get_party from erpnext.tests.utils import create_test_contact_and_address - +from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule # test_dependencies = ['Payment Terms Template'] @@ -125,7 +125,7 @@ class TestShoppingCart(unittest.TestCase): tax_rule = frappe.get_test_records("Tax Rule")[0] try: frappe.get_doc(tax_rule).insert() - except frappe.DuplicateEntryError: + except (frappe.DuplicateEntryError, ConflictingTaxRule): pass def create_quotation(self): From c79f5d7514e73fb64f4e868dc672d6fd33a27ad3 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 9 May 2021 11:29:00 +0530 Subject: [PATCH 0901/1264] ci: Try parallel testing with orchestrator --- .github/workflows/server-tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index e5fb72538d..0e638104c8 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -72,9 +72,10 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 3 --with-coverage + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage env: TYPE: server + CI_BUILD_ID: ${{ github.run_id }} - name: Upload Coverage Data run: | From ab8816e11da22887e413b53b1a11bb916f21127e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 9 May 2021 14:38:28 +0530 Subject: [PATCH 0902/1264] test: Fix test dependency --- .../doctype/accounting_dimension/test_accounting_dimension.py | 2 +- .../test_accounting_dimension_filter.py | 2 +- .../doctype/accounting_period/test_accounting_period.py | 4 +++- erpnext/tests/__init__.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index 6fb661eb54..e657a9ae34 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -8,7 +8,7 @@ import unittest from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry -test_dependencies = ['Location'] +test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department'] class TestAccountingDimension(unittest.TestCase): def setUp(self): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index 78a88eb48c..7f6254f99f 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -9,7 +9,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError -test_dependencies = ['Location'] +test_dependencies = ['Location', 'Cost Center', 'Department'] class TestAccountingDimensionFilter(unittest.TestCase): def setUp(self): diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 10cd939894..3b4f138495 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -10,6 +10,8 @@ from erpnext.accounts.general_ledger import ClosedAccountingPeriod from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +test_dependencies = ['Item'] + class TestAccountingPeriod(unittest.TestCase): def test_overlap(self): ap1 = create_accounting_period(start_date = "2018-04-01", @@ -38,7 +40,7 @@ def create_accounting_period(**args): accounting_period.start_date = args.start_date or nowdate() accounting_period.end_date = args.end_date or add_months(nowdate(), 1) accounting_period.company = args.company or "_Test Company" - accounting_period.period_name =args.period_name or "_Test_Period_Name_1" + accounting_period.period_name = args.period_name or "_Test_Period_Name_1" accounting_period.append("closed_documents", { "document_type": 'Sales Invoice', "closed": 1 }) diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py index 593bc7c71b..a504340d40 100644 --- a/erpnext/tests/__init__.py +++ b/erpnext/tests/__init__.py @@ -1 +1 @@ -global_test_dependencies = ['User', 'Company', 'Cost Center', 'Account', 'Warehouse', 'Item'] +global_test_dependencies = ['User', 'Company', 'Item'] From a70e11450e9e17cc81ff47ffba967a6b76c1fb61 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 9 May 2021 14:38:54 +0530 Subject: [PATCH 0903/1264] ci: Check limits --- .github/workflows/server-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 0e638104c8..e7830c07f6 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: - container: [1, 2, 3] + container: [1, 2, 3, 4] name: Server Tests @@ -30,7 +30,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.6 - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts From 19c5fd72d65e92da553c58c4712ab200bd3174aa Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 09:18:25 +0530 Subject: [PATCH 0904/1264] refactor: Rename assertEquals to assertEqual to avoid deprecation warnings --- erpnext/accounts/doctype/dunning/test_dunning.py | 2 +- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 2 +- .../doctype/mpesa_settings/test_mpesa_settings.py | 4 ++-- erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py | 2 +- erpnext/regional/india/utils.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index c5ce514cdd..e2d4d82e41 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -29,7 +29,7 @@ class TestDunning(unittest.TestCase): 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() diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index aedf1c6f1a..556f49d34c 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -152,7 +152,7 @@ class PricingRule(Document): frappe.throw(_("Valid from date must be less than valid upto date")) def validate_condition(self): - if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition): + if self.condition and ("=" in self.condition) and re.match(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+', self.condition): frappe.throw(_("Invalid condition expression")) #-------------------------------------------------------------------------------- diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 30a270c204..3cd4b802c1 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -566,7 +566,7 @@ class TestAsset(unittest.TestCase): doc = make_invoice(pr.name) self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) - + def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"]) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index d370fbcda7..3c2e59ab82 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -81,7 +81,7 @@ class TestMpesaSettings(unittest.TestCase): integration_request.reload() self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") self.assertEqual(integration_request.status, "Completed") - + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") integration_request.delete() pr.reload() @@ -139,7 +139,7 @@ class TestMpesaSettings(unittest.TestCase): pr.cancel() pr.delete() pos_invoice.delete() - + def test_processing_of_only_one_succes_callback_payload(self): create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index d079bedb42..113fa513f9 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -29,7 +29,7 @@ class TestTherapyPlan(unittest.TestCase): self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') patient, medical_department, practitioner = create_healthcare_docs() - appointment = create_appointment(patient, practitioner, nowdate()) + appointment = create_appointment(patient, practitioner, nowdate()) session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) session = frappe.get_doc(session) session.submit() diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index fc227defbf..075c698fea 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -500,7 +500,7 @@ def download_ewb_json(): if not isinstance(docname, list): # removes characters not allowed in a filename (https://stackoverflow.com/a/38766141/4767738) - filename_prefix = re.sub('[^\w_.)( -]', '', docname) + filename_prefix = re.sub(r'[^\w_.)( -]', '', docname) frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(filename_prefix, frappe.utils.random_string(5)) From b3e647fecace96eb3c817c5dcf9241a5ebc3a3a4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 16:13:43 +0530 Subject: [PATCH 0905/1264] fix: Pass ORCHESTRATOR_URL via secrets --- .github/workflows/server-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index e7830c07f6..0acafcab43 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -76,6 +76,7 @@ jobs: env: TYPE: server CI_BUILD_ID: ${{ github.run_id }} + ORCHESTRATOR_URL: ${{ secrets.ORCHESTRATOR_URL }} - name: Upload Coverage Data run: | From a891d6eed2894c776d91a9a3995d8a3d8de11fe5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 17:25:21 +0530 Subject: [PATCH 0906/1264] fix: allow_zero_valuation_rate for rejected qty --- .../doctype/purchase_invoice/test_purchase_invoice.py | 7 ++++--- .../doctype/stock_ledger_entry/test_stock_ledger_entry.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 66be11ff23..53db689c84 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -636,8 +636,8 @@ class TestPurchaseInvoice(unittest.TestCase): def test_rejected_serial_no(self): pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1, - rejected_qty=1, rate=500, update_stock=1, - rejected_warehouse = "_Test Rejected Warehouse - _TC") + rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC", + allow_zero_valuation_rate=1) self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"), pi.get("items")[0].warehouse) @@ -994,7 +994,8 @@ def make_purchase_invoice(**args): "project": args.project, "rejected_warehouse": args.rejected_warehouse or "", "rejected_serial_no": args.rejected_serial_no or "", - "asset_location": args.location or "" + "asset_location": args.location or "", + "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0 }) if args.get_taxes_and_charges: diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 3296f5ba4a..a1f1897041 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -15,10 +15,12 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import create_landed_cost_voucher from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import BackDatedStockTransaction +from frappe.core.page.permission_manager.permission_manager import reset class TestStockLedgerEntry(unittest.TestCase): def setUp(self): items = create_items() + reset('Stock Entry') # delete SLE and BINs for all items frappe.db.sql("delete from `tabStock Ledger Entry` where item_code in (%s)" % (', '.join(['%s']*len(items))), items) From d1a13ec0504e39edfb7de193539ba2c94f5b52b9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 17:37:44 +0530 Subject: [PATCH 0907/1264] test: Fix valuation rate for raw materials --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e5ef978ca3..5095a80214 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -297,6 +297,8 @@ class TestPurchaseReceipt(unittest.TestCase): item_code = "Test Extra Item 1", qty=10, basic_rate=100) se2 = make_stock_entry(target="_Test Warehouse - _TC", item_code = "_Test FG Item", qty=1, basic_rate=100) + se3 = make_stock_entry(target="_Test Warehouse - _TC", + item_code = "Test Extra Item 2", qty=1, basic_rate=100) rm_items = [ { "item_code": item_code, @@ -331,6 +333,7 @@ class TestPurchaseReceipt(unittest.TestCase): se.cancel() se1.cancel() se2.cancel() + se3.cancel() po.reload() po.cancel() From d48ea663d9c458a20b30f7a8f3dc6f639406c4b7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 18:01:53 +0530 Subject: [PATCH 0908/1264] test: Fix test name - Rename TestSubcontractedItemToBeReceived > TestSubcontractedItemToBeTransferred --- ...t_subcontracted_raw_materials_to_be_transferred.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index 6900938236..c1fc6fb82f 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -9,12 +9,12 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import execute import json, frappe, unittest -class TestSubcontractedItemToBeReceived(unittest.TestCase): +class TestSubcontractedItemToBeTransferred(unittest.TestCase): - def test_pending_and_received_qty(self): + def test_pending_and_transferred_qty(self): po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes') - make_stock_entry(item_code='_Test Item', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100) - make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100) + make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100) + make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100) transfer_subcontracted_raw_materials(po.name) col, data = execute(filters=frappe._dict({'supplier': po.supplier, 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)), @@ -38,7 +38,8 @@ def transfer_subcontracted_raw_materials(po): 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}] rm_item_string = json.dumps(rm_item) se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string)) + se.from_warehouse = '_Test Warehouse 1 - _TC' se.to_warehouse = '_Test Warehouse 1 - _TC' se.stock_entry_type = 'Send to Subcontractor' se.save() - se.submit() \ No newline at end of file + se.submit() From 54354a84e1c15c8fadf836865d8bc0b5405321c9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 18:39:09 +0530 Subject: [PATCH 0909/1264] test: Fix permission error --- .../doctype/stock_ledger_entry/test_stock_ledger_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index a1f1897041..ba31ad7b06 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -316,10 +316,11 @@ class TestStockLedgerEntry(unittest.TestCase): # Set User with Stock User role but not Stock Manager try: user = frappe.get_doc("User", "test@example.com") - frappe.set_user(user.name) user.add_roles("Stock User") user.remove_roles("Stock Manager") + frappe.set_user(user.name) + stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, posting_date=add_days(today(), -1), do_not_submit=True) From 273589e835342ea364c3d096a1fd327769df98c8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 20:33:47 +0530 Subject: [PATCH 0910/1264] test: Fix a case where test used to fail due to holiday list - fixes: "Please set a default Holiday List for Employee EMP-00009 or Company Wind Power LLC" error --- .../hr/doctype/upload_attendance/test_upload_attendance.py | 7 +++++++ .../payroll/doctype/payroll_entry/test_payroll_entry.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py index 6e151d0e3c..03b0cf3da2 100644 --- a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py @@ -5,11 +5,18 @@ from __future__ import unicode_literals import frappe import unittest +import erpnext from frappe.utils import getdate from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data from erpnext.hr.doctype.employee.test_employee import make_employee +test_dependencies = ['Holiday List'] + class TestUploadAttendance(unittest.TestCase): + @classmethod + def setUpClass(cls): + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List') + def test_date_range(self): employee = make_employee("test_employee@company.com") employee_doc = frappe.get_doc("Employee", employee) diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py index 7528bf7a7f..b80b32061f 100644 --- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -15,7 +15,13 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_ from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans +test_dependencies = ['Holiday List'] + class TestPayrollEntry(unittest.TestCase): + @classmethod + def setUpClass(cls): + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List') + def setUp(self): for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]: From 96542c3d043a2865af4f77ab6da754eb62bebdc4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 20:49:07 +0530 Subject: [PATCH 0911/1264] style: Fix sider issues --- .../doctype/accounting_period/test_accounting_period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 3b4f138495..dc472c7695 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -40,7 +40,7 @@ def create_accounting_period(**args): accounting_period.start_date = args.start_date or nowdate() accounting_period.end_date = args.end_date or add_months(nowdate(), 1) accounting_period.company = args.company or "_Test Company" - accounting_period.period_name = args.period_name or "_Test_Period_Name_1" + accounting_period.period_name = args.period_name or "_Test_Period_Name_1" accounting_period.append("closed_documents", { "document_type": 'Sales Invoice', "closed": 1 }) From b9a8afc234b2a1647bc66e647d136b87d684f22a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 23:48:37 +0530 Subject: [PATCH 0912/1264] ci: Add test orchestrator URL --- .github/workflows/server-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 0acafcab43..bd60081064 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -76,7 +76,7 @@ jobs: env: TYPE: server CI_BUILD_ID: ${{ github.run_id }} - ORCHESTRATOR_URL: ${{ secrets.ORCHESTRATOR_URL }} + ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - name: Upload Coverage Data run: | @@ -87,7 +87,6 @@ jobs: coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} COVERALLS_FLAG_NAME: run-${{ matrix.container }} COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} COVERALLS_PARALLEL: true From 77f2686142568b12dcbea50db03928f6b8ec5673 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 10:09:06 +0530 Subject: [PATCH 0913/1264] ci: Use only 3 containers for now --- .github/workflows/server-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index bd60081064..6597048bc7 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: - container: [1, 2, 3, 4] + container: [1, 2, 3] name: Server Tests From 113a6f3d80b95fd249dbe95224f0be197d533c86 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 10:59:16 +0530 Subject: [PATCH 0914/1264] fix: Use frappe.safe_eval instead of eval --- erpnext/setup/doctype/email_digest/email_digest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 5db54eeee1..340d89bdf8 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -249,7 +249,7 @@ class EmailDigest(Document): card = cache.get(cache_key) if card: - card = eval(card) + card = frappe.safe_eval(card) else: card = frappe._dict(getattr(self, "get_" + key)()) From 6da2212e2d4f1eba80a4a5d845d515f62a1eaf62 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 12 May 2021 18:14:22 +0530 Subject: [PATCH 0915/1264] ci: Update frappe branch --- .github/helper/install.sh | 2 +- .github/workflows/server-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 44659f22fa..7b0f944c66 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,7 +12,7 @@ sudo apt install npm pip install frappe-bench -git clone https://github.com/surajshetty3416/frappe --branch "python-distributed-testing" --depth 1 +git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 6597048bc7..6f831ac3d1 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -12,7 +12,7 @@ jobs: matrix: container: [1, 2, 3] - name: Server Tests + name: Python Unit Tests services: mysql: From 85aeca14432ecbec86c88549358eac338298eaa4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 12 May 2021 23:13:11 +0530 Subject: [PATCH 0916/1264] ci: Update Python version to 3.7 --- .github/workflows/server-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 6f831ac3d1..92685e2177 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts From bcf116422a2d7ed1a4ea33eda1a894b055bbf5b0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 28 May 2021 09:12:24 +0530 Subject: [PATCH 0917/1264] ci: Do not generate coverage report for hotfix branch --- .github/workflows/server-tests.yml | 31 ------------------------------ 1 file changed, 31 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 92685e2177..de5cc61d1e 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -77,34 +77,3 @@ jobs: TYPE: server CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - - - name: Upload Coverage Data - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} - COVERALLS_PARALLEL: true - - coveralls: - name: Coverage Wrap Up - needs: test - container: python:3-slim - runs-on: ubuntu-18.04 - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Coveralls Finished - run: | - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls --finish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c4d4be3265d216b48aaabeffa777984f47626dbc Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 18 May 2021 15:42:13 +0530 Subject: [PATCH 0918/1264] fix: ensure website theme is applied correctly --- erpnext/public/scss/shopping_cart.scss | 18 +++++++++--------- erpnext/public/scss/website.scss | 9 ++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 159a8a47cd..92e5d32219 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -1,4 +1,3 @@ -@import "frappe/public/scss/desk/variables"; @import "frappe/public/scss/common/mixins"; body.product-page { @@ -217,12 +216,12 @@ body.product-page { border-color: var(--table-border-color) !important; padding: 15px; - @include media-breakpoint-between(xs, md) { + @media (max-width: 840px) { height: 300px; width: 300px; } - @include media-breakpoint-up(lg) { + @media (min-width: 1090px) { height: 350px; width: 350px; } @@ -233,11 +232,12 @@ body.product-page { } .item-slideshow { - @include media-breakpoint-between(xs, md) { + + @media (max-width: 840px) { max-height: 320px; } - @include media-breakpoint-up(lg) { + @media (min-width: 1090px) { max-height: 430px; } @@ -254,7 +254,7 @@ body.product-page { cursor: pointer; &:hover, &.active { - border-color: $primary; + border-color: var(--primary); } } @@ -316,12 +316,12 @@ body.product-page { } .item-group-slideshow { - .item-group-description { + // .item-group-description { // max-width: 900px; - } + // } .carousel-inner.rounded-carousel { - border-radius: $card-border-radius; + border-radius: var(--card-border-radius); } } diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss index 56b717c424..f4325c03f5 100644 --- a/erpnext/public/scss/website.scss +++ b/erpnext/public/scss/website.scss @@ -1,4 +1,3 @@ -@import "frappe/public/scss/website/variables"; .filter-options { max-height: 300px; @@ -14,7 +13,7 @@ } &.active { - border-color: $primary; + border-color: var(--primary); .check { display: inline-flex; @@ -25,7 +24,7 @@ .check { display: inline-flex; padding: 0.25rem; - background: $primary; + background: var(--primary); color: white; border-radius: 50%; font-size: 12px; @@ -38,12 +37,12 @@ } .result { - border-bottom: 1px solid $border-color; + border-bottom: 1px solid var(--border-color); } .transaction-list-item { padding: 1rem 0; - border-top: 1px solid $border-color; + border-top: 1px solid var(--border-color); position: relative; a.transaction-item-link { From f4db3139b7fb0b1f00b3b55fe3ed5005f3ee2d14 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 28 May 2021 08:55:38 +0530 Subject: [PATCH 0919/1264] refactor: Use css variables for breakpoint value --- erpnext/public/scss/shopping_cart.scss | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 92e5d32219..9402cf9ea4 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -73,15 +73,6 @@ body.product-page { } } - // .card-body { - // text-align: center; - // } - - // .featured-item { - // .card-body { - // text-align: left; - // } - // } .card-img-container { height: 210px; width: 100%; @@ -216,12 +207,12 @@ body.product-page { border-color: var(--table-border-color) !important; padding: 15px; - @media (max-width: 840px) { + @media (max-width: var(--md-width)) { height: 300px; width: 300px; } - @media (min-width: 1090px) { + @media (min-width: var(--lg-width)) { height: 350px; width: 350px; } @@ -233,11 +224,11 @@ body.product-page { .item-slideshow { - @media (max-width: 840px) { + @media (max-width: var(--md-width)) { max-height: 320px; } - @media (min-width: 1090px) { + @media (min-width: var(--lg-width)) { max-height: 430px; } @@ -316,9 +307,6 @@ body.product-page { } .item-group-slideshow { - // .item-group-description { - // max-width: 900px; - // } .carousel-inner.rounded-carousel { border-radius: var(--card-border-radius); From 8a776a63cd8884bce7ea4f2dcb4173c630055157 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 28 May 2021 09:56:30 +0530 Subject: [PATCH 0920/1264] fix(POS): Add Product Bundles to POS item search (#25860) --- erpnext/selling/page/point_of_sale/point_of_sale.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 750a1a6071..09a3efc491 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -62,7 +62,6 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va `tabItem` item {bin_join_selection} WHERE item.disabled = 0 - AND item.is_stock_item = 1 AND item.has_variants = 0 AND item.is_sales_item = 1 AND item.is_fixed_asset = 0 @@ -84,6 +83,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va ), {'warehouse': warehouse}, as_dict=1) if items_data: + items_data = filter_service_items(items_data) items = [d.item_code for d in items_data] item_prices_data = frappe.get_all("Item Price", fields = ["item_code", "price_list_rate", "currency"], @@ -135,6 +135,14 @@ def search_serial_or_batch_or_barcode_number(search_value): return {} +def filter_service_items(items): + for item in items: + if not item['is_stock_item']: + if not frappe.db.exists('Product Bundle', item['item_code']): + items.remove(item) + + return items + def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: return "item.name = {0}".format(frappe.db.escape(item_code)) From 17736afab555307bb95d743667f22be2c4592535 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 28 May 2021 10:03:41 +0530 Subject: [PATCH 0921/1264] fix(POS): Fix stock availability calculation if negative_stock_allowed is checked (#25859) --- erpnext/selling/page/point_of_sale/point_of_sale.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 09a3efc491..cb811df8e8 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -15,7 +15,6 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va data = dict() result = [] - allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items']) if not frappe.db.exists('Item Group', item_group): @@ -96,10 +95,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va for item in items_data: item_code = item.item_code item_price = item_prices.get(item_code) or {} - if allow_negative_stock: - item_stock_qty = frappe.db.sql("""select ifnull(sum(actual_qty), 0) from `tabBin` where item_code = %s""", item_code)[0][0] - else: - item_stock_qty = get_stock_availability(item_code, warehouse) + item_stock_qty = get_stock_availability(item_code, warehouse) row = {} row.update(item) From 3bca90dbe6b6b9335765e41345b2902b512171f7 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:42:31 +0530 Subject: [PATCH 0922/1264] refactor: updated mapping for maintenance schedule links in maintenance visit --- .../maintenance_schedule.py | 17 +++++++---------- .../test_maintenance_schedule.py | 6 ++++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index ea76e91b3f..d6e42f3ee1 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -34,7 +34,7 @@ class MaintenanceSchedule(TransactionBase): count = count + 1 child.sales_person = d.sales_person child.completion_status = "Pending" - child.item_ref = d.name + child.item_reference = d.name @frappe.whitelist() def validate_end_date_visits(self): @@ -314,11 +314,12 @@ def update_serial_nos(s_id): def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): from frappe.model.mapper import get_mapped_doc - def update_status(source, target, parent): + def update_status_and_detail(source, target, parent): target.maintenance_type = "Scheduled" + target.maintenance_schedule = source.name + target.maintenance_schedule_detail = s_id - def update_sid(source, target, parent): - target.prevdoc_detail_docname = s_id + def update_sales(source, target, parent): sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') target.service_person = sales_person target.serial_no = '' @@ -332,16 +333,12 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No "validation": { "docstatus": ["=", 1] }, - "postprocess": update_status + "postprocess": update_status_and_detail }, "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", - "field_map": { - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", - }, "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sid + "postprocess": update_sales } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 58ee964fb5..08282b4c3d 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -57,16 +57,18 @@ class TestMaintenanceSchedule(unittest.TestCase): test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) visit = frappe.new_doc('Maintenance Visit') visit = test + visit.maintenance_schedule = ms.name + visit.maintenance_schedule_detail = s_id visit.completion_status = "Partially Completed" - visit.set('purposes', [{ 'item_code': i.item_code, 'description': "test", 'work_done': "test", + 'service_person': "Sales Team", 'prevdoc_docname' :ms.name, 'prevdoc_doctype': ms.doctype, - 'prevdoc_detail_docname': s_id }]) + visit.save() visit.submit() ms = frappe.get_doc('Maintenance Schedule', ms.name) From 7fa045c1c9142fa4a3d26790fe999e2f455dcef6 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:44:28 +0530 Subject: [PATCH 0923/1264] refactor: using parent form links of maintenance schedule --- .../maintenance_visit/maintenance_visit.py | 91 +++++++++---------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 8a3094cb36..79d65c921a 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -17,70 +17,61 @@ class MaintenanceVisit(TransactionBase): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): frappe.throw(_("Serial No {0} does not exist").format(d.serial_no)) - def validate_mntc_date(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - item_ref = frappe.db.get_value('Maintenance Schedule Detail', detail_ref , 'item_ref') + def validate_maintenance_date(self): + if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: + item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference') start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): - frappe.throw(_("Date must be between {0} and {1}").format(start_date,end_date)) + frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date)) def validate(self): self.validate_serial_no() - self.validate_mntc_date() + self.validate_maintenance_date() - def get_schedule_datail_ref(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - return detail_ref - def update_completion_status(self): - detail_ref = self.get_schedule_datail_ref() - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) + if self.maintenance_schedule_detail: + frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', self.completion_status) def update_actual_date(self): - detail_ref = self.get_schedule_datail_ref() - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) + if self.maintenance_schedule_detail: + frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', self.mntc_date) def update_customer_issue(self, flag): - for d in self.get('purposes'): - if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' : - if flag==1: - mntc_date = self.mntc_date - service_person = d.service_person - work_done = d.work_done - status = "Open" - if self.completion_status == 'Fully Completed': - status = 'Closed' - elif self.completion_status == 'Partially Completed': - status = 'Work In Progress' - else: - nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name)) - - if nm: - status = 'Work In Progress' - mntc_date = nm and nm[0][1] or '' - service_person = nm and nm[0][2] or '' - work_done = nm and nm[0][3] or '' + if not self.maintenance_schedule: + for d in self.get('purposes'): + if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' : + if flag==1: + mntc_date = self.mntc_date + service_person = d.service_person + work_done = d.work_done + status = "Open" + if self.completion_status == 'Fully Completed': + status = 'Closed' + elif self.completion_status == 'Partially Completed': + status = 'Work In Progress' else: - status = 'Open' - mntc_date = None - service_person = None - work_done = None + nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name)) - wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname) - wc_doc.update({ - 'resolution_date': mntc_date, - 'resolved_by': service_person, - 'resolution_details': work_done, - 'status': status - }) + if nm: + status = 'Work In Progress' + mntc_date = nm and nm[0][1] or '' + service_person = nm and nm[0][2] or '' + work_done = nm and nm[0][3] or '' + else: + status = 'Open' + mntc_date = None + service_person = None + work_done = None - wc_doc.db_update() + wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname) + wc_doc.update({ + 'resolution_date': mntc_date, + 'resolved_by': service_person, + 'resolution_details': work_done, + 'status': status + }) + + wc_doc.db_update() def check_if_last_visit(self): """check if last maintenance visit against same sales order/ Warranty Claim""" From d071586589bb24162e268ec0fb4e295334ac4067 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:48:43 +0530 Subject: [PATCH 0924/1264] refactor: removed maintenance schedule detail link --- .../maintenance_visit_purpose.json | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 0d19d708d9..158f143ae8 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -17,8 +17,7 @@ "work_details", "work_done", "prevdoc_doctype", - "prevdoc_docname", - "prevdoc_detail_docname" + "prevdoc_docname" ], "fields": [ { @@ -90,44 +89,14 @@ "fieldtype": "Link", "hidden": 1, "label": "Document Type", - "no_copy": 1, - "oldfieldname": "prevdoc_doctype", - "oldfieldtype": "Data", - "options": "DocType", - "print_hide": 1, - "print_width": "150px", - "read_only": 1, - "report_hide": 1, - "width": "150px" + "options": "DocType" }, { "fieldname": "prevdoc_docname", "fieldtype": "Dynamic Link", - "label": "Against Document No", - "no_copy": 1, - "oldfieldname": "prevdoc_docname", - "oldfieldtype": "Data", - "options": "prevdoc_doctype", - "print_hide": 1, - "print_width": "160px", - "read_only": 1, - "report_hide": 1, - "width": "160px" - }, - { - "fieldname": "prevdoc_detail_docname", - "fieldtype": "Link", "hidden": 1, - "label": "Against Document Detail No", - "no_copy": 1, - "oldfieldname": "prevdoc_detail_docname", - "oldfieldtype": "Data", - "options": "Maintenance Schedule Detail", - "print_hide": 1, - "print_width": "160px", - "read_only": 1, - "report_hide": 1, - "width": "160px" + "label": "Against Document No", + "options": "prevdoc_doctype" }, { "fieldname": "column_break_3", @@ -141,7 +110,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-21 11:16:52.025914", + "modified": "2021-05-27 17:47:21.474282", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", From 0b02f1335f136a00066dd62b4d06f29cfe157d25 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 28 May 2021 11:04:34 +0530 Subject: [PATCH 0925/1264] refactor: removed redundant links in test case --- .../doctype/maintenance_schedule/test_maintenance_schedule.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 08282b4c3d..09981bad05 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -65,8 +65,6 @@ class TestMaintenanceSchedule(unittest.TestCase): 'description': "test", 'work_done': "test", 'service_person': "Sales Team", - 'prevdoc_docname' :ms.name, - 'prevdoc_doctype': ms.doctype, }]) visit.save() visit.submit() From faa25166a91022dbb969f0753fef8f07a0d01eef Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 28 May 2021 11:23:18 +0530 Subject: [PATCH 0926/1264] revert: "ci: Do not generate coverage report for hotfix branch" Reverts: https://github.com/frappe/erpnext/commit/bcf116422a2d7ed1a4ea33eda1a894b055bbf5b0 --- .github/workflows/server-tests.yml | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index de5cc61d1e..92685e2177 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -77,3 +77,34 @@ jobs: TYPE: server CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io + + - name: Upload Coverage Data + run: | + cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + cd ${GITHUB_WORKSPACE} + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: run-${{ matrix.container }} + COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} + COVERALLS_PARALLEL: true + + coveralls: + name: Coverage Wrap Up + needs: test + container: python:3-slim + runs-on: ubuntu-18.04 + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Coveralls Finished + run: | + cd ${GITHUB_WORKSPACE} + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d24eccd623a60d99bc6981f484fc5b9439cd8dd0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 11:36:24 +0530 Subject: [PATCH 0927/1264] fix(plaid): cannot reset plaid link for a bank account --- erpnext/accounts/doctype/bank/bank.js | 102 ++++++++++++++++++ .../doctype/plaid_settings/plaid_connector.py | 2 + .../doctype/plaid_settings/plaid_settings.py | 42 ++++++-- 3 files changed, 140 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 059e1d3158..337c93ef61 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -25,6 +25,10 @@ frappe.ui.form.on('Bank', { frm.add_custom_button(__('Refresh Plaid Link'), () => { new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token); }); + + frm.add_custom_button(__('Reset Plaid Link'), () => { + new erpnext.integrations.plaidLink(frm); + }); } } }); @@ -121,3 +125,101 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' }); } }; + +erpnext.integrations.plaidLink = class plaidLink { + constructor(parent) { + this.frm = parent; + this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; + this.init_config(); + } + + async init_config() { + this.product = ["auth", "transactions"]; + this.plaid_env = this.frm.doc.plaid_env; + this.client_name = frappe.boot.sitename; + this.token = await this.get_link_token(); + this.init_plaid(); + } + + async get_link_token() { + const token = await this.frm.call("get_link_token").then(resp => resp.message); + if (!token) { + frappe.throw(__('Cannot retrieve link token. Check Error Log for more information')); + } + return token; + } + + init_plaid() { + const me = this; + me.loadScript(me.plaidUrl) + .then(() => { + me.onScriptLoaded(me); + }) + .then(() => { + if (me.linkHandler) { + me.linkHandler.open(); + } + }) + .catch((error) => { + me.onScriptError(error); + }); + } + + loadScript(src) { + return new Promise(function (resolve, reject) { + if (document.querySelector('script[src="' + src + '"]')) { + resolve(); + return; + } + const el = document.createElement('script'); + el.type = 'text/javascript'; + el.async = true; + el.src = src; + el.addEventListener('load', resolve); + el.addEventListener('error', reject); + el.addEventListener('abort', reject); + document.head.appendChild(el); + }); + } + + onScriptLoaded(me) { + me.linkHandler = Plaid.create({ + clientName: me.client_name, + product: me.product, + env: me.plaid_env, + token: me.token, + onSuccess: me.plaid_success + }); + } + + onScriptError(error) { + frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information")); + console.log(error); + } + + plaid_success(token, response) { + const me = this; + + frappe.prompt({ + fieldtype: "Link", + options: "Company", + label: __("Company"), + fieldname: "company", + reqd: 1 + }, (data) => { + me.company = data.company; + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', { + token: token, + response: response + }).then((result) => { + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', { + response: response, + bank: result, + company: me.company + }); + }).then(() => { + frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' }); + }); + }, __("Select a company"), __("Continue")); + } +}; diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index 5f990cdd03..42d4b9b2b4 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -99,5 +99,7 @@ class PlaidConnector(): response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions)) transactions.extend(response["transactions"]) return transactions + except ItemError as e: + raise e except Exception: frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index ce15e47c5e..3ef069b5e2 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -12,6 +12,7 @@ from frappe.desk.doctype.tag.tag import add_tag from frappe.model.document import Document from frappe.utils import add_months, formatdate, getdate, today +from plaid.errors import ItemError class PlaidSettings(Document): @staticmethod @@ -51,7 +52,7 @@ def add_institution(token, response): }) bank.insert() except Exception: - frappe.throw(frappe.get_traceback()) + frappe.log_error(frappe.get_traceback(), title=_('Plaid Link Error')) else: bank = frappe.get_doc("Bank", response["institution"]["name"]) bank.plaid_access_token = access_token @@ -83,7 +84,12 @@ def add_bank_accounts(response, bank, company): if not acc_subtype: add_account_subtype(account["subtype"]) - if not frappe.db.exists("Bank Account", dict(integration_id=account["id"])): + existing_bank_account = frappe.db.exists("Bank Account", { + 'account_name': account["name"], + 'bank': bank["bank_name"] + }) + + if not existing_bank_account: try: new_account = frappe.get_doc({ "doctype": "Bank Account", @@ -103,10 +109,27 @@ def add_bank_accounts(response, bank, company): except frappe.UniqueValidationError: frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"])) except Exception: - frappe.throw(frappe.get_traceback()) + frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error")) + frappe.throw(_("There was an error creating Bank Account while linking with Plaid."), + title=_("Plaid Link Failed")) else: - result.append(frappe.db.get_value("Bank Account", dict(integration_id=account["id"]), "name")) + try: + existing_account = frappe.get_doc('Bank Account', existing_bank_account) + existing_account.update({ + "bank": bank["bank_name"], + "account_name": account["name"], + "account_type": account.get("type", ""), + "account_subtype": account.get("subtype", ""), + "mask": account.get("mask", ""), + "integration_id": account["id"] + }) + existing_account.save() + result.append(existing_bank_account) + except Exception: + frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error")) + frappe.throw(_("There was an error updating Bank Account {} while linking with Plaid.").format( + existing_bank_account), title=_("Plaid Link Failed")) return result @@ -172,9 +195,16 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): account_id = None plaid = PlaidConnector(access_token) - transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) - return transactions + try: + transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) + except ItemError as e: + if e.code == "ITEM_LOGIN_REQUIRED": + msg = _("There was an error syncing transactions.") + " " + msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " " + frappe.log_error(msg, title=_("Plaid Link Refresh Required")) + + return transactions or [] def new_bank_transaction(transaction): From bbce2e91a375feef3b1dd7e26c2239769e11a730 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 11:56:47 +0530 Subject: [PATCH 0928/1264] fix: reset plaid link button --- erpnext/accounts/doctype/bank/bank.js | 4 ---- .../doctype/plaid_settings/plaid_settings.js | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 337c93ef61..f2c599c5c1 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -25,10 +25,6 @@ frappe.ui.form.on('Bank', { frm.add_custom_button(__('Refresh Plaid Link'), () => { new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token); }); - - frm.add_custom_button(__('Reset Plaid Link'), () => { - new erpnext.integrations.plaidLink(frm); - }); } } }); diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js index bbc2ca8846..37bf282450 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -16,6 +16,10 @@ frappe.ui.form.on('Plaid Settings', { new erpnext.integrations.plaidLink(frm); }); + frm.add_custom_button(__('Reset Plaid Link'), () => { + new erpnext.integrations.plaidLink(frm); + }); + frm.add_custom_button(__("Sync Now"), () => { frappe.call({ method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization", From 20be7fb93dbe7db28920c44fd1815607fe9da249 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 28 May 2021 11:57:38 +0530 Subject: [PATCH 0929/1264] fix(India): Show only company addresses for ITC reversal entry --- .../doctype/journal_entry/regional/india.js | 17 +++++++++++++++++ .../doctype/gstr_3b_report/gstr_3b_report.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 erpnext/accounts/doctype/journal_entry/regional/india.js diff --git a/erpnext/accounts/doctype/journal_entry/regional/india.js b/erpnext/accounts/doctype/journal_entry/regional/india.js new file mode 100644 index 0000000000..75a69ac0cf --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry/regional/india.js @@ -0,0 +1,17 @@ +frappe.ui.form.on("Journal Entry", { + refresh: function(frm) { + frm.set_query('company_address', function(doc) { + if(!doc.company) { + frappe.throw(__('Please set Company')); + } + + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { + link_doctype: 'Company', + link_name: doc.company + } + }; + }); + } +}); \ No newline at end of file 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 3ddcc58867..641520437f 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -310,7 +310,7 @@ class GSTR3BReport(Document): self.report_dict['sup_details']['osup_det']['txval'] += taxable_value gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category') - place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply', '00-Other Territory') + place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply') or '00-Other Territory' if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \ self.gst_details.get("gst_state") != place_of_supply.split("-")[1]: From 05386ff12f22ac672bb0b87c10257a185dc1db78 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 12:58:18 +0530 Subject: [PATCH 0930/1264] chore: remove unwanted method --- erpnext/accounts/doctype/bank/bank.js | 100 +------------------------- 1 file changed, 1 insertion(+), 99 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index f2c599c5c1..19041a3f73 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -120,102 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { plaid_success(token, response) { frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' }); } -}; - -erpnext.integrations.plaidLink = class plaidLink { - constructor(parent) { - this.frm = parent; - this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; - this.init_config(); - } - - async init_config() { - this.product = ["auth", "transactions"]; - this.plaid_env = this.frm.doc.plaid_env; - this.client_name = frappe.boot.sitename; - this.token = await this.get_link_token(); - this.init_plaid(); - } - - async get_link_token() { - const token = await this.frm.call("get_link_token").then(resp => resp.message); - if (!token) { - frappe.throw(__('Cannot retrieve link token. Check Error Log for more information')); - } - return token; - } - - init_plaid() { - const me = this; - me.loadScript(me.plaidUrl) - .then(() => { - me.onScriptLoaded(me); - }) - .then(() => { - if (me.linkHandler) { - me.linkHandler.open(); - } - }) - .catch((error) => { - me.onScriptError(error); - }); - } - - loadScript(src) { - return new Promise(function (resolve, reject) { - if (document.querySelector('script[src="' + src + '"]')) { - resolve(); - return; - } - const el = document.createElement('script'); - el.type = 'text/javascript'; - el.async = true; - el.src = src; - el.addEventListener('load', resolve); - el.addEventListener('error', reject); - el.addEventListener('abort', reject); - document.head.appendChild(el); - }); - } - - onScriptLoaded(me) { - me.linkHandler = Plaid.create({ - clientName: me.client_name, - product: me.product, - env: me.plaid_env, - token: me.token, - onSuccess: me.plaid_success - }); - } - - onScriptError(error) { - frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information")); - console.log(error); - } - - plaid_success(token, response) { - const me = this; - - frappe.prompt({ - fieldtype: "Link", - options: "Company", - label: __("Company"), - fieldname: "company", - reqd: 1 - }, (data) => { - me.company = data.company; - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', { - token: token, - response: response - }).then((result) => { - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', { - response: response, - bank: result, - company: me.company - }); - }).then(() => { - frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' }); - }); - }, __("Select a company"), __("Continue")); - } -}; +}; \ No newline at end of file From 6fb218e03344ec00a8af0a8d8df57beacf89cf4b Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 28 May 2021 16:15:50 +0530 Subject: [PATCH 0931/1264] refactor: suggested changes --- .../doctype/maintenance_schedule/maintenance_schedule.js | 2 +- .../doctype/maintenance_visit/maintenance_visit.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index e2de941963..44712d543b 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -67,7 +67,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let schedules = me.frm.doc.schedules; let flag = schedules.some(schedule => schedule.completion_status === "Pending"); if (flag) { - this.frm.add_custom_button(__('Create Maintenance Visit'), function () { + this.frm.add_custom_button(__('Maintenance Visit'), function () { let options = ""; me.frm.call('get_pending_data', {data_type: "items"}).then(r => { diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 79d65c921a..7fffc942a0 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -20,9 +20,10 @@ class MaintenanceVisit(TransactionBase): def validate_maintenance_date(self): if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference') - start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) - if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): - frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date)) + if item_ref: + start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) + if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): + frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date)) def validate(self): self.validate_serial_no() From b72b4c0bf9f2f19d80456407cddc4062a7174198 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 17:38:01 +0530 Subject: [PATCH 0932/1264] fix(pos): rendering of broken image on pos --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 12 +++++++++++- .../selling/page/point_of_sale/pos_item_selector.js | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 11a63b3d4a..8a989731cc 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -636,13 +636,23 @@ erpnext.PointOfSale.ItemCart = class { function get_item_image_html() { const { image, item_name } = item_data; if (image) { - return `
    ${image}
    `; + return ` +
    + ${frappe.get_abbr(item_name)} +
    `; } else { return `
    ${frappe.get_abbr(item_name)}
    `; } } } + handle_broken_image($img) { + const item_abbr = $($img).attr('alt'); + $($img).parent().replaceWith(`
    ${item_abbr}
    `); + } + 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(); diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index b8a82a9eda..b6109acc57 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -94,7 +94,11 @@ erpnext.PointOfSale.ItemSelector = class { ${qty_to_display}
    - ${frappe.get_abbr(item.item_name)} + ${frappe.get_abbr(item.item_name)}
    `; } else { return `
    @@ -122,6 +126,11 @@ erpnext.PointOfSale.ItemSelector = class { ); } + handle_broken_image($img) { + const item_abbr = $($img).attr('alt'); + $($img).parent().replaceWith(`
    ${item_abbr}
    `); + } + make_search_bar() { const me = this; const doc = me.events.get_frm().doc; From 431d3295b444d46b4bc44b5e882de507be006b42 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 28 May 2021 21:12:25 +0530 Subject: [PATCH 0933/1264] fix: use dictionary filter instead of list (#25874) Item query doesn't support list filter anymore. --- erpnext/manufacturing/doctype/work_order/work_order.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index a6086fb88d..3e5a72db9a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -76,9 +76,9 @@ frappe.ui.form.on("Work Order", { frm.set_query("production_item", function() { return { query: "erpnext.controllers.queries.item_query", - filters:[ - ['is_stock_item', '=',1] - ] + filters: { + "is_stock_item": 1, + } }; }); From 48b1a82fa1d873470c6b5513742b104ef3f3573e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 29 May 2021 23:54:51 +0530 Subject: [PATCH 0934/1264] fix: Add accounts and templates for reverse charge --- erpnext/accounts/doctype/account/account.py | 6 - erpnext/accounts/utils.py | 2 +- erpnext/regional/india/setup.py | 68 +++++------ erpnext/setup/doctype/company/company.py | 2 +- .../setup_wizard/data/country_wise_tax.json | 115 +++++++++++++++++- .../setup_wizard/operations/company_setup.py | 23 ---- 6 files changed, 148 insertions(+), 68 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 645f49bcdc..1be2fbf5c8 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -28,12 +28,6 @@ class Account(NestedSet): from erpnext.accounts.utils import get_autoname_with_number self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company) - def before_insert(self): - # Update Bank account name if conflicting with any other account - if frappe.flags.in_install and self.account_type == 'Bank': - if frappe.db.get_value('Account', {'account_name': self.account_name}): - self.account_name = self.account_name + '-1' - def validate(self): from erpnext.accounts.utils import validate_field_number if frappe.local.flags.allow_unverified_charts: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5a64e27ccb..121589930b 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -784,7 +784,7 @@ def get_children(doctype, parent, company, is_root=False): return acc def create_payment_gateway_account(gateway, payment_channel="Email"): - from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account + from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account company = frappe.db.get_value("Global Defaults", None, "default_company") if not company: diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 28f49e0a7e..d79ce64fb3 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -698,6 +698,7 @@ def setup_gst_settings(company): # Will only add default GST accounts if present input_account_names = ['Input Tax CGST', 'Input Tax SGST', 'Input Tax IGST'] output_account_names = ['Output Tax CGST', 'Output Tax SGST', 'Output Tax IGST'] + rcm_accounts = ['Input Tax CGST RCM', 'Input Tax SGST RCM', 'Input Tax IGST RCM'] gst_settings = frappe.get_single('GST Settings') existing_account_list = [] @@ -706,45 +707,40 @@ def setup_gst_settings(company): existing_account_list.append(account.get(key)) gst_accounts = frappe._dict(frappe.get_all("Account", - {'company': company, 'name': ('like', "%GST%")}, ['account_name', 'name'], as_list=1)) + {'company': company, 'account_name': ('in', input_account_names + + output_account_names + rcm_accounts)}, ['account_name', 'name'], as_list=1)) - all_input_account_exists = 0 - all_output_account_exists = 0 - - for account in input_account_names: - if not gst_accounts.get(account): - all_input_account_exists = 1 - - # Check if already added in GST Settings - if gst_accounts.get(account) in existing_account_list: - all_input_account_exists = 1 - - for account in output_account_names: - if not gst_accounts.get(account): - all_output_account_exists = 1 - - # Check if already added in GST Settings - if gst_accounts.get(account) in existing_account_list: - all_output_account_exists = 1 - - if not all_input_account_exists: - gst_settings.append('gst_accounts', { - 'company': company, - 'cgst_account': gst_accounts.get(input_account_names[0]), - 'sgst_account': gst_accounts.get(input_account_names[1]), - 'igst_account': gst_accounts.get(input_account_names[2]) - }) - - if not all_output_account_exists: - gst_settings.append('gst_accounts', { - 'company': company, - 'cgst_account': gst_accounts.get(output_account_names[0]), - 'sgst_account': gst_accounts.get(output_account_names[1]), - 'igst_account': gst_accounts.get(output_account_names[2]) - }) + add_accounts_in_gst_settings(company, input_account_names, gst_accounts, + existing_account_list, gst_settings) + add_accounts_in_gst_settings(company, output_account_names, gst_accounts, + existing_account_list, gst_settings) + add_accounts_in_gst_settings(company, rcm_accounts, gst_accounts, + existing_account_list, gst_settings, is_reverse_charge=1) gst_settings.save() +def add_accounts_in_gst_settings(company, account_names, gst_accounts, + existing_account_list, gst_settings, is_reverse_charge=0): + accounts_not_added = 1 + + for account in account_names: + # Default Account Added does not exists + if not gst_accounts.get(account): + accounts_not_added = 0 + + # Check if already added in GST Settings + if gst_accounts.get(account) in existing_account_list: + accounts_not_added = 0 + + if accounts_not_added: + gst_settings.append('gst_accounts', { + 'company': company, + 'cgst_account': gst_accounts.get(account_names[0]), + 'sgst_account': gst_accounts.get(account_names[1]), + 'igst_account': gst_accounts.get(account_names[2]), + 'is_reverse_charge_account': is_reverse_charge + }) + def set_salary_components(docs): docs.extend([ {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', @@ -797,7 +793,7 @@ def set_tax_withholding_category(company): doc.append("rates", d.get('rates')[0]) doc.flags.ignore_permissions = True - doc.flags.ignore_validdate = True + doc.flags.ignore_validate = True doc.flags.ignore_mandatory = True doc.flags.ignore_links = True doc.save() diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 61d63a3f29..ff96c0b4c4 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -110,8 +110,8 @@ class Company(NestedSet): self.create_default_warehouses() if frappe.flags.country_change: - install_country_fixtures(self.name, self.country) self.create_default_tax_template() + install_country_fixtures(self.name, self.country) if not frappe.db.get_value("Department", {"company": self.name}): from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index d21ef03e19..ee42a87320 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -830,7 +830,12 @@ "gst_state": "" }, { - "title": "Reverse Charge", + "title": "Reverse Charge In-State", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Reverse Charge Out-State", "is_inter_state": 0, "gst_state": "" }, @@ -879,6 +884,24 @@ "account_name": "Input Tax IGST", "tax_rate": 18.00 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 18.00 + } } ] }, @@ -920,6 +943,24 @@ "account_name": "Input Tax IGST", "tax_rate": 5.0 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 2.50 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 2.50 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 5.00 + } } ] }, @@ -961,6 +1002,24 @@ "account_name": "Input Tax IGST", "tax_rate": 12.0 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 6.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 6.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 12.00 + } } ] }, @@ -1002,6 +1061,24 @@ "account_name": "Input Tax IGST", "tax_rate": 28.0 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 14.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 14.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 28.00 + } } ] }, @@ -1110,6 +1187,42 @@ } ], "tax_category": "Out-State" + }, + { + "title": "Input GST RCM In-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 9.00, + "root_type": "Asset", + "account_type": "Tax" + } + }, + { + "account_head": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 9.00, + "root_type": "Asset", + "account_type": "Tax" + } + } + ], + "tax_category": "Reverse Charge In-State" + }, + { + "title": "Input GST RCM Out-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 18.00, + "root_type": "Asset", + "account_type": "Tax" + } + } + ], + "tax_category": "Reverse Charge Out-State" } ], "*": [ diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py index 3f0bb14649..4edf9485dc 100644 --- a/erpnext/setup/setup_wizard/operations/company_setup.py +++ b/erpnext/setup/setup_wizard/operations/company_setup.py @@ -42,29 +42,6 @@ def enable_shopping_cart(args): 'quotation_series': "QTN-", }).insert() -def create_bank_account(args): - if args.get("bank_account"): - company_name = args.get('company_name') - bank_account_group = frappe.db.get_value("Account", - {"account_type": "Bank", "is_group": 1, "root_type": "Asset", - "company": company_name}) - if bank_account_group: - bank_account = frappe.get_doc({ - "doctype": "Account", - 'account_name': args.get("bank_account"), - 'parent_account': bank_account_group, - 'is_group':0, - 'company': company_name, - "account_type": "Bank", - }) - try: - return bank_account.insert() - except RootNotEditable: - frappe.throw(_("Bank account cannot be named as {0}").format(args.get("bank_account"))) - except frappe.DuplicateEntryError: - # bank account same as a CoA entry - pass - def create_email_digest(): from frappe.utils.user import get_system_managers system_managers = get_system_managers(only_name=True) From 3be28054de5e6d917af9608d53c48604e3285450 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 31 May 2021 09:11:42 +0530 Subject: [PATCH 0935/1264] refactor: Vehicle Expenses Report (#25727) * refactor: Vehicle Expense Report * test: Vehicle Expenses Report * feat: Added Employee filter to report - fix Vehicle Log form view * fix: set currency fieldtype for chart data - added filters for employee and vehicle * fix: service expenses not getting set --- .../doctype/vehicle_log/test_vehicle_log.py | 81 +++--- .../hr/doctype/vehicle_log/vehicle_log.json | 12 +- .../vehicle_expenses/test_vehicle_expenses.py | 73 ++++++ .../vehicle_expenses/vehicle_expenses.js | 77 +++--- .../vehicle_expenses/vehicle_expenses.json | 35 +-- .../vehicle_expenses/vehicle_expenses.py | 231 ++++++++++++++---- 6 files changed, 374 insertions(+), 135 deletions(-) create mode 100644 erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py index cf0048c1a7..ed52c4e122 100644 --- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py +++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate,flt, cstr,random_string +from frappe.utils import nowdate, flt, cstr, random_string from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim @@ -18,23 +18,13 @@ class TestVehicleLog(unittest.TestCase): self.employee_id = make_employee("testdriver@example.com", company="_Test Company") self.license_plate = get_vehicle(self.employee_id) - + def tearDown(self): frappe.delete_doc("Vehicle", self.license_plate, force=1) frappe.delete_doc("Employee", self.employee_id, force=1) def test_make_vehicle_log_and_syncing_of_odometer_value(self): - vehicle_log = frappe.get_doc({ - "doctype": "Vehicle Log", - "license_plate": cstr(self.license_plate), - "employee": self.employee_id, - "date":frappe.utils.nowdate(), - "odometer":5010, - "fuel_qty":frappe.utils.flt(50), - "price": frappe.utils.flt(500) - }) - vehicle_log.save() - vehicle_log.submit() + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id) #checking value of vehicle odometer value on submit. vehicle = frappe.get_doc("Vehicle", self.license_plate) @@ -51,19 +41,9 @@ class TestVehicleLog(unittest.TestCase): self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled) vehicle_log.delete() - + def test_vehicle_log_fuel_expense(self): - vehicle_log = frappe.get_doc({ - "doctype": "Vehicle Log", - "license_plate": cstr(self.license_plate), - "employee": self.employee_id, - "date": frappe.utils.nowdate(), - "odometer":5010, - "fuel_qty":frappe.utils.flt(50), - "price": frappe.utils.flt(500) - }) - vehicle_log.save() - vehicle_log.submit() + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id) expense_claim = make_expense_claim(vehicle_log.name) fuel_expense = expense_claim.expenses[0].amount @@ -73,6 +53,18 @@ class TestVehicleLog(unittest.TestCase): frappe.delete_doc("Expense Claim", expense_claim.name) frappe.delete_doc("Vehicle Log", vehicle_log.name) + def test_vehicle_log_with_service_expenses(self): + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True) + + expense_claim = make_expense_claim(vehicle_log.name) + expenses = expense_claim.expenses[0].amount + self.assertEqual(expenses, 27000) + + vehicle_log.cancel() + frappe.delete_doc("Expense Claim", expense_claim.name) + frappe.delete_doc("Vehicle Log", vehicle_log.name) + + def get_vehicle(employee_id): license_plate=random_string(10).upper() vehicle = frappe.get_doc({ @@ -81,15 +73,46 @@ def get_vehicle(employee_id): "make": "Maruti", "model": "PCM", "employee": employee_id, - "last_odometer":5000, - "acquisition_date":frappe.utils.nowdate(), + "last_odometer": 5000, + "acquisition_date": nowdate(), "location": "Mumbai", "chassis_no": "1234ABCD", "uom": "Litre", - "vehicle_value":frappe.utils.flt(500000) + "vehicle_value": flt(500000) }) try: vehicle.insert() except frappe.DuplicateEntryError: pass - return license_plate \ No newline at end of file + return license_plate + + +def make_vehicle_log(license_plate, employee_id, with_services=False): + vehicle_log = frappe.get_doc({ + "doctype": "Vehicle Log", + "license_plate": cstr(license_plate), + "employee": employee_id, + "date": nowdate(), + "odometer": 5010, + "fuel_qty": flt(50), + "price": flt(500) + }) + + if with_services: + vehicle_log.append("service_detail", { + "service_item": "Oil Change", + "type": "Inspection", + "frequency": "Mileage", + "expense_amount": flt(500) + }) + vehicle_log.append("service_detail", { + "service_item": "Wheels", + "type": "Change", + "frequency": "Half Yearly", + "expense_amount": flt(1500) + }) + + vehicle_log.save() + vehicle_log.submit() + + return vehicle_log \ No newline at end of file diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.json b/erpnext/hr/doctype/vehicle_log/vehicle_log.json index 619e295ebe..4ea904542d 100644 --- a/erpnext/hr/doctype/vehicle_log/vehicle_log.json +++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2016-09-03 14:14:51.788550", "doctype": "DocType", @@ -10,7 +11,6 @@ "naming_series", "license_plate", "employee", - "column_break_4", "column_break_7", "model", "make", @@ -65,10 +65,6 @@ "options": "Employee", "reqd": 1 }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, { "fieldname": "column_break_7", "fieldtype": "Column Break" @@ -142,7 +138,6 @@ { "fieldname": "service_detail", "fieldtype": "Table", - "label": "Service Detail", "options": "Vehicle Service" }, { @@ -158,7 +153,7 @@ "fetch_from": "license_plate.last_odometer", "fieldname": "last_odometer", "fieldtype": "Int", - "label": "last Odometer Value ", + "label": "Last Odometer Value ", "read_only": 1, "reqd": 1 }, @@ -168,7 +163,8 @@ } ], "is_submittable": 1, - "modified": "2020-03-18 16:45:45.060761", + "links": [], + "modified": "2021-05-17 00:10:21.188352", "modified_by": "Administrator", "module": "HR", "name": "Vehicle Log", diff --git a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py new file mode 100644 index 0000000000..26e0f26392 --- /dev/null +++ b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py @@ -0,0 +1,73 @@ +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import unittest +import frappe +from frappe.utils import getdate +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim +from erpnext.hr.doctype.vehicle_log.test_vehicle_log import get_vehicle, make_vehicle_log +from erpnext.hr.report.vehicle_expenses.vehicle_expenses import execute +from erpnext.accounts.utils import get_fiscal_year + +class TestVehicleExpenses(unittest.TestCase): + @classmethod + def setUpClass(self): + frappe.db.sql('delete from `tabVehicle Log`') + + employee_id = frappe.db.sql('''select name from `tabEmployee` where name="testdriver@example.com"''') + self.employee_id = employee_id[0][0] if employee_id else None + if not self.employee_id: + self.employee_id = make_employee('testdriver@example.com', company='_Test Company') + + self.license_plate = get_vehicle(self.employee_id) + + def test_vehicle_expenses_based_on_fiscal_year(self): + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True) + expense_claim = make_expense_claim(vehicle_log.name) + + # Based on Fiscal Year + filters = { + 'filter_based_on': 'Fiscal Year', + 'fiscal_year': get_fiscal_year(getdate())[0] + } + + report = execute(filters) + + expected_data = [{ + 'vehicle': self.license_plate, + 'make': 'Maruti', + 'model': 'PCM', + 'location': 'Mumbai', + 'log_name': vehicle_log.name, + 'odometer': 5010, + 'date': getdate(), + 'fuel_qty': 50.0, + 'fuel_price': 500.0, + 'fuel_expense': 25000.0, + 'service_expense': 2000.0, + 'employee': self.employee_id + }] + + self.assertEqual(report[1], expected_data) + + # Based on Date Range + fiscal_year = get_fiscal_year(getdate(), as_dict=True) + filters = { + 'filter_based_on': 'Date Range', + 'from_date': fiscal_year.year_start_date, + 'to_date': fiscal_year.year_end_date + } + + report = execute(filters) + self.assertEqual(report[1], expected_data) + + # clean up + vehicle_log.cancel() + frappe.delete_doc('Expense Claim', expense_claim.name) + frappe.delete_doc('Vehicle Log', vehicle_log.name) + + def tearDown(self): + frappe.delete_doc('Vehicle', self.license_plate, force=1) + frappe.delete_doc('Employee', self.employee_id, force=1) diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js index b66bebbec1..879acd18ef 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js @@ -1,31 +1,52 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Vehicle Expenses"] = { - "filters": [ - { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1, - "on_change": function(query_report) { - var fiscal_year = query_report.get_values().fiscal_year; - if (!fiscal_year) { - return; - } - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - - frappe.query_report.set_filter({ - from_date: fy.year_start_date, - to_date: fy.year_end_date - }); - }); - } - } - ] - } -}); +frappe.query_reports["Vehicle Expenses"] = { + "filters": [ + { + "fieldname": "filter_based_on", + "label": __("Filter Based On"), + "fieldtype": "Select", + "options": ["Fiscal Year", "Date Range"], + "default": ["Fiscal Year"], + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", + "reqd": 1 + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "depends_on": "eval: doc.filter_based_on == 'Date Range'", + "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12) + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "depends_on": "eval: doc.filter_based_on == 'Date Range'", + "default": frappe.datetime.nowdate() + }, + { + "fieldname": "vehicle", + "label": __("Vehicle"), + "fieldtype": "Link", + "options": "Vehicle" + }, + { + "fieldname": "employee", + "label": __("Employee"), + "fieldtype": "Link", + "options": "Employee" + } + ] +}; diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json index 2ab0c143b8..1a3e5a93bb 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json @@ -1,20 +1,23 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2016-09-09 03:33:40.605734", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 2, - "is_standard": "Yes", - "modified": "2017-02-24 19:59:18.641284", - "modified_by": "Administrator", - "module": "HR", - "name": "Vehicle Expenses", - "owner": "Administrator", - "ref_doctype": "Vehicle", - "report_name": "Vehicle Expenses", - "report_type": "Script Report", + "add_total_row": 1, + "columns": [], + "creation": "2016-09-09 03:33:40.605734", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 2, + "is_standard": "Yes", + "modified": "2021-05-16 22:48:22.767535", + "modified_by": "Administrator", + "module": "HR", + "name": "Vehicle Expenses", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Vehicle", + "report_name": "Vehicle Expenses", + "report_type": "Script Report", "roles": [ { "role": "Fleet Manager" diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py index eab58ffbbc..d847cbb5c9 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py @@ -5,86 +5,209 @@ from __future__ import unicode_literals import frappe import erpnext from frappe import _ -from frappe.utils import flt,cstr +from frappe.utils import flt from erpnext.accounts.report.financial_statements import get_period_list def execute(filters=None): - columns, data, chart = [], [], [] - if filters.get('fiscal_year'): - company = erpnext.get_default_company() - period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'), - '', '', 'Fiscal Year', 'Monthly', company=company) - columns=get_columns() - data=get_log_data(filters) - chart=get_chart_data(data,period_list) + filters = frappe._dict(filters or {}) + + columns = get_columns() + data = get_vehicle_log_data(filters) + chart = get_chart_data(data, filters) + return columns, data, None, chart def get_columns(): - columns = [_("License") + ":Link/Vehicle:100", _('Create') + ":data:50", - _("Model") + ":data:50", _("Location") + ":data:100", - _("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80", - _("Date") + ":Date:100", _("Fuel Qty") + ":Float:80", - _("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100", - _("Service Expense") + ":Float:100" + return [ + { + 'fieldname': 'vehicle', + 'fieldtype': 'Link', + 'label': _('Vehicle'), + 'options': 'Vehicle', + 'width': 150 + }, + { + 'fieldname': 'make', + 'fieldtype': 'Data', + 'label': _('Make'), + 'width': 100 + }, + { + 'fieldname': 'model', + 'fieldtype': 'Data', + 'label': _('Model'), + 'width': 80 + }, + { + 'fieldname': 'location', + 'fieldtype': 'Data', + 'label': _('Location'), + 'width': 100 + }, + { + 'fieldname': 'log_name', + 'fieldtype': 'Link', + 'label': _('Vehicle Log'), + 'options': 'Vehicle Log', + 'width': 100 + }, + { + 'fieldname': 'odometer', + 'fieldtype': 'Int', + 'label': _('Odometer Value'), + 'width': 120 + }, + { + 'fieldname': 'date', + 'fieldtype': 'Date', + 'label': _('Date'), + 'width': 100 + }, + { + 'fieldname': 'fuel_qty', + 'fieldtype': 'Float', + 'label': _('Fuel Qty'), + 'width': 80 + }, + { + 'fieldname': 'fuel_price', + 'fieldtype': 'Float', + 'label': _('Fuel Price'), + 'width': 100 + }, + { + 'fieldname': 'fuel_expense', + 'fieldtype': 'Currency', + 'label': _('Fuel Expense'), + 'width': 150 + }, + { + 'fieldname': 'service_expense', + 'fieldtype': 'Currency', + 'label': _('Service Expense'), + 'width': 150 + }, + { + 'fieldname': 'employee', + 'fieldtype': 'Link', + 'label': _('Employee'), + 'options': 'Employee', + 'width': 150 + } ] + return columns -def get_log_data(filters): - fy = frappe.db.get_value('Fiscal Year', filters.get('fiscal_year'), ['year_start_date', 'year_end_date'], as_dict=True) - data = frappe.db.sql("""select - vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model", - vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer", - log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price", - log.fuel_qty * log.price as "Fuel Expense" - from + +def get_vehicle_log_data(filters): + start_date, end_date = get_period_dates(filters) + conditions, values = get_conditions(filters) + + data = frappe.db.sql(""" + SELECT + vhcl.license_plate as vehicle, vhcl.make, vhcl.model, + vhcl.location, log.name as log_name, log.odometer, + log.date, log.employee, log.fuel_qty, + log.price as fuel_price, + log.fuel_qty * log.price as fuel_expense + FROM `tabVehicle` vhcl,`tabVehicle Log` log - where - vhcl.license_plate = log.license_plate and log.docstatus = 1 and date between %s and %s - order by date""" ,(fy.year_start_date, fy.year_end_date), as_dict=1) - dl=list(data) - for row in dl: - row["Service Expense"]= get_service_expense(row["Log"]) - return dl + WHERE + vhcl.license_plate = log.license_plate + and log.docstatus = 1 + and date between %(start_date)s and %(end_date)s + {0} + ORDER BY date""".format(conditions), values, as_dict=1) + + for row in data: + row['service_expense'] = get_service_expense(row.log_name) + + return data + + +def get_conditions(filters): + conditions = '' + + start_date, end_date = get_period_dates(filters) + values = { + 'start_date': start_date, + 'end_date': end_date + } + + if filters.employee: + conditions += ' and log.employee = %(employee)s' + values['employee'] = filters.employee + + if filters.vehicle: + conditions += ' and vhcl.license_plate = %(vehicle)s' + values['vehicle'] = filters.vehicle + + return conditions, values + + +def get_period_dates(filters): + if filters.filter_based_on == 'Fiscal Year' and filters.fiscal_year: + fy = frappe.db.get_value('Fiscal Year', filters.fiscal_year, + ['year_start_date', 'year_end_date'], as_dict=True) + return fy.year_start_date, fy.year_end_date + else: + return filters.from_date, filters.to_date + def get_service_expense(logname): - expense_amount = frappe.db.sql("""select sum(expense_amount) - from `tabVehicle Log` log,`tabVehicle Service` ser - where ser.parent=log.name and log.name=%s""",logname) - return flt(expense_amount[0][0]) if expense_amount else 0 + expense_amount = frappe.db.sql(""" + SELECT sum(expense_amount) + FROM + `tabVehicle Log` log, `tabVehicle Service` service + WHERE + service.parent=log.name and log.name=%s + """, logname) + + return flt(expense_amount[0][0]) if expense_amount else 0.0 + + +def get_chart_data(data, filters): + period_list = get_period_list(filters.fiscal_year, filters.fiscal_year, + filters.from_date, filters.to_date, filters.filter_based_on, 'Monthly') + + fuel_data, service_data = [], [] -def get_chart_data(data,period_list): - fuel_exp_data,service_exp_data,fueldata,servicedata = [],[],[],[] - service_exp_data = [] - fueldata = [] for period in period_list: - total_fuel_exp=0 - total_ser_exp=0 - for row in data: - if row["Date"] <= period.to_date and row["Date"] >= period.from_date: - total_fuel_exp+=flt(row["Fuel Expense"]) - total_ser_exp+=flt(row["Service Expense"]) - fueldata.append([period.key,total_fuel_exp]) - servicedata.append([period.key,total_ser_exp]) + total_fuel_exp = 0 + total_service_exp = 0 + + for row in data: + if row.date <= period.to_date and row.date >= period.from_date: + total_fuel_exp += flt(row.fuel_expense) + total_service_exp += flt(row.service_expense) + + fuel_data.append([period.key, total_fuel_exp]) + service_data.append([period.key, total_service_exp]) + + labels = [period.label for period in period_list] + fuel_exp_data= [row[1] for row in fuel_data] + service_exp_data= [row[1] for row in service_data] - labels = [period.key for period in period_list] - fuel_exp_data= [row[1] for row in fueldata] - service_exp_data= [row[1] for row in servicedata] datasets = [] if fuel_exp_data: datasets.append({ - 'name': 'Fuel Expenses', + 'name': _('Fuel Expenses'), 'values': fuel_exp_data }) + if service_exp_data: datasets.append({ - 'name': 'Service Expenses', + 'name': _('Service Expenses'), 'values': service_exp_data }) + chart = { - "data": { + 'data': { 'labels': labels, 'datasets': datasets - } + }, + 'type': 'line', + 'fieldtype': 'Currency' } - chart["type"] = "line" + return chart From 89f621baa465942efb98487684c7d4feff1db2ca Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 31 May 2021 09:56:49 +0530 Subject: [PATCH 0936/1264] fix(pos): closing entry shows incorrect expected amount (#25868) --- .../accounts/doctype/pos_closing_entry/pos_closing_entry.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 8c5a34a0d8..6418d73090 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -107,7 +107,7 @@ frappe.ui.form.on('POS Closing Entry', { frm.set_value("taxes", []); for (let row of frm.doc.payment_reconciliation) { - row.expected_amount = 0; + row.expected_amount = row.opening_amount; } for (let row of frm.doc.pos_transactions) { @@ -154,6 +154,9 @@ function add_to_pos_transaction(d, frm) { function refresh_payments(d, frm) { d.payments.forEach(p => { const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment); + if (p.account == d.account_for_change_amount) { + p.amount -= flt(d.change_amount); + } if (payment) { payment.expected_amount += flt(p.amount); payment.difference = payment.closing_amount - payment.expected_amount; From 80540d38ba5c17ec12eab15531bc63be09eeb609 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 31 May 2021 12:06:20 +0200 Subject: [PATCH 0937/1264] fix: add_deduct_tax in Purchase Taxes setup (#25871) --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 429a558c58..5019837441 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -106,6 +106,9 @@ def make_taxes_and_charges_template(company_name, doctype, template): 'charge_type': 'On Net Total' } + if doctype == 'Purchase Taxes and Charges Template': + tax_row_defaults['add_deduct_tax'] = 'Add' + # if account_head is a dict, search or create the account and get it's name if isinstance(account_data, dict): tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate')) From 0358b6474368462690211a0191138b644c3bd3be Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 31 May 2021 15:59:32 +0530 Subject: [PATCH 0938/1264] fix: featching serialized items --- .../stock_reconciliation/stock_reconciliation.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 7e216d6181..32e0ccb93f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -477,19 +477,19 @@ class StockReconciliation(StockController): def get_items(warehouse, posting_date, posting_time, company): lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) items = frappe.db.sql(""" - select i.name, i.item_name, bin.warehouse + select i.name, i.item_name, bin.warehouse, i.has_serial_no from tabBin bin, tabItem i where i.name=bin.item_code and i.disabled=0 and i.is_stock_item = 1 - and i.has_variants = 0 and i.has_serial_no = 0 and i.has_batch_no = 0 + and i.has_variants = 0 and i.has_batch_no = 0 and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse) """, (lft, rgt)) items += frappe.db.sql(""" - select i.name, i.item_name, id.default_warehouse + select i.name, i.item_name, id.default_warehouse, i.has_serial_no from tabItem i, `tabItem Default` id where i.name = id.parent and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse) - and i.is_stock_item = 1 and i.has_serial_no = 0 and i.has_batch_no = 0 + and i.is_stock_item = 1 and i.has_batch_no = 0 and i.has_variants = 0 and i.disabled = 0 and id.company=%s group by i.name """, (lft, rgt, company)) @@ -497,7 +497,7 @@ def get_items(warehouse, posting_date, posting_time, company): res = [] for d in set(items): stock_bal = get_stock_balance(d[0], d[2], posting_date, posting_time, - with_valuation_rate=True) + with_valuation_rate=True , with_serial_no=cint(d[3])) if frappe.db.get_value("Item", d[0], "disabled") == 0: res.append({ @@ -507,7 +507,9 @@ def get_items(warehouse, posting_date, posting_time, company): "item_name": d[1], "valuation_rate": stock_bal[1], "current_qty": stock_bal[0], - "current_valuation_rate": stock_bal[1] + "current_valuation_rate": stock_bal[1], + "current_serial_no": stock_bal[2] if cint(d[3]) else '', + "serial_no": stock_bal[2] if cint(d[3]) else '' }) return res From 0ea6725baaf61637aa1625098ccc5e3365c40730 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 31 May 2021 19:48:31 +0530 Subject: [PATCH 0939/1264] fix: Rename Loan Management workspace to Loans (#25856) --- .../workspace/loan_management/loan_management.json | 6 +++--- erpnext/patches.txt | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json index 18559dceef..d0b67f7c64 100644 --- a/erpnext/loan_management/workspace/loan_management/loan_management.json +++ b/erpnext/loan_management/workspace/loan_management/loan_management.json @@ -12,7 +12,7 @@ "idx": 0, "is_default": 0, "is_standard": 1, - "label": "Loan Management", + "label": "Loans", "links": [ { "hidden": 0, @@ -220,10 +220,10 @@ "type": "Link" } ], - "modified": "2021-02-18 17:31:53.586508", + "modified": "2021-05-25 17:31:53.586508", "modified_by": "Administrator", "module": "Loan Management", - "name": "Loan Management", + "name": "Loans", "owner": "Administrator", "pin_to_bottom": 0, "pin_to_top": 0, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3a7aa1bce3..93689a0ef3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -779,6 +779,7 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed +execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True) erpnext.patches.v13_0.update_timesheet_changes erpnext.patches.v13_0.set_training_event_attendance erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold From 908b0090e967cd2503d69e4df99878c048ab35ba Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 31 May 2021 19:49:03 +0530 Subject: [PATCH 0940/1264] fix: Create POS Invoice for Product Bundles (#25847) Co-authored-by: Saqib --- .../doctype/pos_invoice/pos_invoice.py | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index f55fdab21c..8ec4ef224c 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -140,6 +140,7 @@ class POSInvoice(SalesInvoice): return available_stock = get_stock_availability(d.item_code, d.warehouse) + item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty) if flt(available_stock) <= 0: frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.') @@ -213,8 +214,9 @@ class POSInvoice(SalesInvoice): for d in self.get("items"): is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item") if not is_stock_item: - frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ") - .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) + if not frappe.db.exists('Product Bundle', d.item_code): + frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.") + .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) def validate_mode_of_payment(self): if len(self.payments) == 0: @@ -455,15 +457,36 @@ class POSInvoice(SalesInvoice): @frappe.whitelist() def get_stock_availability(item_code, warehouse): + if frappe.db.get_value('Item', item_code, 'is_stock_item'): + bin_qty = get_bin_qty(item_code, warehouse) + pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) + return bin_qty - pos_sales_qty + else: + if frappe.db.exists('Product Bundle', item_code): + return get_bundle_availability(item_code, warehouse) + +def get_bundle_availability(bundle_item_code, warehouse): + product_bundle = frappe.get_doc('Product Bundle', bundle_item_code) + + bundle_bin_qty = 1000000 + for item in product_bundle.items: + item_bin_qty = get_bin_qty(item.item_code, warehouse) + item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse) + available_qty = item_bin_qty - item_pos_reserved_qty + + max_available_bundles = available_qty / item.qty + if bundle_bin_qty > max_available_bundles: + bundle_bin_qty = max_available_bundles + + pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse) + return bundle_bin_qty - pos_sales_qty + +def get_bin_qty(item_code, warehouse): bin_qty = frappe.db.sql("""select actual_qty from `tabBin` where item_code = %s and warehouse = %s limit 1""", (item_code, warehouse), as_dict=1) - pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) - - bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0 - - return bin_qty - pos_sales_qty + return bin_qty[0].actual_qty or 0 if bin_qty else 0 def get_pos_reserved_qty(item_code, warehouse): reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty @@ -522,4 +545,4 @@ def add_return_modes(doc, pos_profile): mode_of_payment = pos_payment_method.mode_of_payment if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]: payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) - append_payment(payment_mode[0]) \ No newline at end of file + append_payment(payment_mode[0]) From 022b5c973a6fced4271bb4b77360dbd4c1425d03 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 31 May 2021 19:49:53 +0530 Subject: [PATCH 0941/1264] fix: wrap dates in getdate for leave application (#25899) * fix: wrap dates in getdate for leave application * fix: translation issue * fix: replaced today with getdate --- .../leave_application/leave_application.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 0bf551e178..cee6f374fd 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -4,8 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ - comma_or, get_fullname, add_days, nowdate, get_datetime_str +from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee @@ -85,7 +84,7 @@ class LeaveApplication(Document): def validate_dates(self): if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"): - if self.from_date and self.from_date < frappe.utils.today(): + if self.from_date and getdate(self.from_date) < getdate(): allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application") if allowed_role not in frappe.get_roles(): frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role)) @@ -248,9 +247,9 @@ class LeaveApplication(Document): self.throw_overlap_error(d) def throw_overlap_error(self, d): - msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee, - d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \ - + """ {0}""".format(d["name"]) + form_link = get_link_to_form("Leave Application", d.name) + msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(self.employee, + d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date']), form_link) frappe.throw(msg, OverlapError) def get_total_leaves_on_half_day(self): @@ -356,7 +355,7 @@ class LeaveApplication(Document): sender = dict() sender['email'] = frappe.get_doc('User', frappe.session.user).email - sender['full_name'] = frappe.utils.get_fullname(sender['email']) + sender['full_name'] = get_fullname(sender['email']) try: frappe.sendmail( @@ -823,4 +822,4 @@ def get_leave_approver(employee): leave_approver = frappe.db.get_value('Department Approver', {'parent': department, 'parentfield': 'leave_approvers', 'idx': 1}, 'approver') - return leave_approver \ No newline at end of file + return leave_approver From c8b34798feadff95d38f7d5961d7b8aa83730a8e Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 1 Jun 2021 10:44:26 +0530 Subject: [PATCH 0942/1264] fix(Delivery Note): Assign Product Bundle's conversion_factor to Packed Items (#25840) --- erpnext/stock/doctype/packed_item/packed_item.json | 8 +++++++- erpnext/stock/doctype/packed_item/packed_item.py | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index f1d7f8c8c9..bb396e806f 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -13,6 +13,7 @@ "section_break_6", "warehouse", "target_warehouse", + "conversion_factor", "column_break_9", "qty", "uom", @@ -209,13 +210,18 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-24 09:25:13.050151", + "modified": "2021-05-26 07:08:05.111385", "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 5341f29853..4ab71bdf62 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -53,6 +53,7 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip pi.parent_detail_docname = main_item_row.name pi.uom = item.stock_uom pi.qty = flt(qty) + pi.conversion_factor = main_item_row.conversion_factor if description and not pi.description: pi.description = description if not pi.warehouse and not doc.amended_from: From 1175e0686bd920356264522200c40769f76bf658 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 1 Jun 2021 10:53:00 +0530 Subject: [PATCH 0943/1264] refactor: leave balance report (#25771) * refactor: leave balance report * refactor: sql to orm --- .../employee_leave_balance.js | 19 +++- .../employee_leave_balance.py | 88 ++++++++++++++----- erpnext/hr/utils.py | 1 + 3 files changed, 85 insertions(+), 23 deletions(-) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js index 05728a297b..8bb3457190 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js @@ -37,5 +37,22 @@ frappe.query_reports["Employee Leave Balance"] = { "fieldtype": "Link", "options": "Employee", } - ] + ], + + onload: () => { + frappe.call({ + type: "GET", + method: "erpnext.hr.utils.get_leave_period", + args: { + "from_date": frappe.defaults.get_default("year_start_date"), + "to_date": frappe.defaults.get_default("year_end_date"), + "company": frappe.defaults.get_user_default("Company") + }, + freeze: true, + callback: (data) => { + frappe.query_report.set_filter_value("from_date", data.message[0].from_date); + frappe.query_report.set_filter_value("to_date", data.message[0].to_date); + } + }); + } } diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 06f9160363..4dd4570e8c 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -6,15 +6,16 @@ import frappe from frappe.utils import flt, add_days from frappe import _ from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on +from itertools import groupby def execute(filters=None): if filters.to_date <= filters.from_date: - frappe.throw(_('"From date" can not be greater than or equal to "To date"')) + frappe.throw(_('"From Date" can not be greater than or equal to "To Date"')) columns = get_columns() data = get_data(filters) - - return columns, data + charts = get_chart_data(data) + return columns, data, None, charts def get_columns(): columns = [{ @@ -31,9 +32,10 @@ def get_columns(): 'options': 'Employee' }, { 'label': _('Employee Name'), - 'fieldtype': 'Data', + 'fieldtype': 'Dynamic Link', 'fieldname': 'employee_name', 'width': 100, + 'options': 'employee' }, { 'label': _('Opening Balance'), 'fieldtype': 'float', @@ -64,8 +66,7 @@ def get_columns(): return columns def get_data(filters): - leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC") - + leave_types = frappe.db.get_list('Leave Type', pluck='name', order_by='name') conditions = get_conditions(filters) user = frappe.session.user @@ -113,12 +114,8 @@ def get_data(filters): # not be shown on the basis of days left it create in user mind for carry_forward leave row.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken)) - - row.indent = 1 data.append(row) - new_leaves_allocated = 0 - return data @@ -129,27 +126,37 @@ def get_conditions(filters): if filters.get('employee'): conditions['name'] = filters.get('employee') - if filters.get('employee'): - conditions['name'] = filters.get('employee') - if filters.get('company'): conditions['company'] = filters.get('company') + if filters.get('department'): + conditions['department'] = filters.get('department') + return conditions def get_department_leave_approver_map(department=None): - conditions='' - if department: - conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department} # get current department and all its child - department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec - + department_list = frappe.get_list('Department', + filters={ + 'disabled': 0 + }, + or_filters={ + 'name': department, + 'parent_department': department + }, + fields=['name'], + pluck='name' + ) # retrieve approvers list from current department and from its subsequent child departments - approver_list = frappe.get_all('Department Approver', filters={ - 'parentfield': 'leave_approvers', - 'parent': ('in', department_list) - }, fields=['parent', 'approver'], as_list=1) + approver_list = frappe.get_all('Department Approver', + filters={ + 'parentfield': 'leave_approvers', + 'parent': ('in', department_list) + }, + fields=['parent', 'approver'], + as_list=1 + ) approvers = {} @@ -190,3 +197,40 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type): new_allocation += record.leaves return new_allocation, expired_leaves + +def get_chart_data(data): + labels = [] + datasets = [] + employee_data = data + + if data and data[0].get('employee_name'): + get_dataset_for_chart(employee_data, datasets, labels) + + chart = { + 'data': { + 'labels': labels, + 'datasets': datasets + }, + 'type': 'bar', + 'colors': ['#456789', '#EE8888', '#7E77BF'] + } + + return chart + +def get_dataset_for_chart(employee_data, datasets, labels): + leaves = [] + employee_data = sorted(employee_data, key=lambda k: k['employee_name']) + + for key, group in groupby(employee_data, lambda x: x['employee_name']): + for grp in group: + if grp.closing_balance: + leaves.append(frappe._dict({ + 'leave_type': grp.leave_type, + 'closing_balance': grp.closing_balance + })) + + if leaves: + labels.append(key) + + for leave in leaves: + datasets.append({'name': leave.leave_type, 'values': [leave.closing_balance]}) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 80189e87b7..ebb1734347 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -269,6 +269,7 @@ def get_total_exemption_amount(declarations): total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()]) return total_exemption_amount +@frappe.whitelist() def get_leave_period(from_date, to_date, company): leave_period = frappe.db.sql(""" select name, from_date, to_date From 2f5885627db3f33d3e3f5138bf29ad57f6f46f09 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 1 Jun 2021 11:07:18 +0530 Subject: [PATCH 0944/1264] fix: Labeling and other fixes --- .../advance_taxes_and_charges.json | 80 ++++--------- .../doctype/payment_entry/payment_entry.js | 6 +- .../doctype/payment_entry/payment_entry.json | 5 +- .../purchase_taxes_and_charges.json | 105 +++++++----------- .../sales_taxes_and_charges.json | 38 ++++--- .../public/js/controllers/taxes_and_totals.js | 2 +- 6 files changed, 92 insertions(+), 144 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index aac7eb6bcb..c127601259 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -36,10 +36,8 @@ "label": "Type", "oldfieldname": "charge_type", "oldfieldtype": "Select", - "options": "\nActual\nOn Paid Amount\nIncluded In Paid Amount\nOn Previous Row Amount\nOn Previous Row Total", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total", + "reqd": 1 }, { "depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1", @@ -47,9 +45,7 @@ "fieldtype": "Data", "label": "Reference Row #", "oldfieldname": "row_id", - "oldfieldtype": "Data", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Data" }, { "columns": 2, @@ -61,15 +57,11 @@ "oldfieldtype": "Link", "options": "Account", "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "col_break_1", "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -80,16 +72,12 @@ "oldfieldtype": "Small Text", "print_width": "300px", "reqd": 1, - "show_days": 1, - "show_seconds": 1, "width": "300px" }, { "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions" }, { "default": ":Company", @@ -98,21 +86,15 @@ "label": "Cost Center", "oldfieldname": "cost_center_other_charges", "oldfieldtype": "Link", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "section_break_8", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "columns": 2, @@ -121,15 +103,11 @@ "in_list_view": 1, "label": "Rate", "oldfieldname": "rate", - "oldfieldtype": "Currency", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Currency" }, { "fieldname": "section_break_9", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "columns": 2, @@ -137,9 +115,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Amount", - "options": "currency", - "show_days": 1, - "show_seconds": 1 + "options": "currency" }, { "columns": 2, @@ -148,15 +124,11 @@ "in_list_view": 1, "label": "Total", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_13", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "base_tax_amount", @@ -165,9 +137,7 @@ "oldfieldname": "tax_amount", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total", @@ -176,48 +146,38 @@ "oldfieldname": "total", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "add_deduct_tax", "fieldtype": "Select", "label": "Add Or Deduct", "options": "Add\nDeduct", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "default": "0", "fieldname": "included_in_paid_amount", "fieldtype": "Check", - "label": "Included In Paid Amount", - "show_days": 1, - "show_seconds": 1 + "label": "Included In Paid Amount" }, { "fieldname": "allocated_amount", "fieldtype": "Currency", "label": "Allocated Amount", - "options": "currency", - "show_days": 1, - "show_seconds": 1 + "options": "currency" }, { "fieldname": "base_allocated_amount", "fieldtype": "Currency", "label": "Allocated Amount (Company Currency)", - "options": "Company:company:default_currency", - "show_days": 1, - "show_seconds": 1 + "options": "Company:company:default_currency" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-29 19:06:14.666460", + "modified": "2021-05-31 02:03:57.444647", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index a4022ef40a..b1cf9e02e6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -230,7 +230,7 @@ frappe.ui.form.on('Payment Entry', { var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: ""; frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount", - "difference_amount", "base_paid_amount_after_tax"], company_currency); + "difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency); frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency); frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency); @@ -238,11 +238,13 @@ frappe.ui.form.on('Payment Entry', { var party_account_currency = frm.doc.payment_type=="Receive" ? frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency; - frm.set_currency_labels(["total_allocated_amount", "unallocated_amount"], party_account_currency); + frm.set_currency_labels(["total_allocated_amount", "unallocated_amount", + "total_taxes_and_charges"], party_account_currency); var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency" frm.set_df_property("total_allocated_amount", "options", currency_field); frm.set_df_property("unallocated_amount", "options", currency_field); + frm.set_df_property("total_taxes_and_charges", "options", currency_field); frm.set_df_property("party_balance", "options", currency_field); frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"], diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 1e2bd8194a..0b4178a547 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -335,6 +335,7 @@ "reqd": 1 }, { + "depends_on": "doc.received_amount", "fieldname": "base_received_amount", "fieldtype": "Currency", "label": "Received Amount (Company Currency)", @@ -683,6 +684,7 @@ "fieldname": "advance_tax_account", "fieldtype": "Link", "label": "Advance Tax Account", + "mandatory_depends_on": "doc.base_total_taxes_and_charges", "options": "Account" }, { @@ -692,6 +694,7 @@ "options": "paid_to_account_currency" }, { + "depends_on": "doc.received_amount", "fieldname": "base_received_amount_after_tax", "fieldtype": "Currency", "label": "Received Amount After Tax (Company Currency)", @@ -701,7 +704,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-20 02:04:56.766124", + "modified": "2021-05-31 01:54:18.378910", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index 0db0b6823d..f6703315fa 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -12,6 +12,7 @@ "charge_type", "row_id", "included_in_print_rate", + "included_in_paid_amount", "col_break1", "account_head", "description", @@ -21,6 +22,7 @@ "cost_center", "dimension_col_break", "section_break_9", + "currency", "tax_amount", "tax_amount_after_discount_amount", "total", @@ -39,9 +41,7 @@ "oldfieldname": "category", "oldfieldtype": "Select", "options": "Valuation and Total\nValuation\nTotal", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "default": "Add", @@ -51,9 +51,7 @@ "oldfieldname": "add_deduct_tax", "oldfieldtype": "Select", "options": "Add\nDeduct", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "columns": 2, @@ -65,9 +63,7 @@ "oldfieldname": "charge_type", "oldfieldtype": "Select", "options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total\nOn Item Quantity", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1", @@ -75,9 +71,7 @@ "fieldtype": "Data", "label": "Reference Row #", "oldfieldname": "row_id", - "oldfieldtype": "Data", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Data" }, { "default": "0", @@ -85,15 +79,11 @@ "fieldname": "included_in_print_rate", "fieldtype": "Check", "label": "Is this Tax included in Basic Rate?", - "report_hide": 1, - "show_days": 1, - "show_seconds": 1 + "report_hide": 1 }, { "fieldname": "col_break1", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "columns": 2, @@ -104,9 +94,7 @@ "oldfieldname": "account_head", "oldfieldtype": "Link", "options": "Account", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "default": ":Company", @@ -115,9 +103,7 @@ "label": "Cost Center", "oldfieldname": "cost_center", "oldfieldtype": "Link", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "description", @@ -127,15 +113,11 @@ "oldfieldtype": "Small Text", "print_width": "300px", "reqd": 1, - "show_days": 1, - "show_seconds": 1, "width": "300px" }, { "fieldname": "section_break_10", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "columns": 2, @@ -144,15 +126,11 @@ "in_list_view": 1, "label": "Rate", "oldfieldname": "rate", - "oldfieldtype": "Currency", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Currency" }, { "fieldname": "section_break_9", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "columns": 2, @@ -162,9 +140,7 @@ "label": "Amount", "oldfieldname": "tax_amount", "oldfieldtype": "Currency", - "options": "currency", - "show_days": 1, - "show_seconds": 1 + "options": "currency" }, { "fieldname": "tax_amount_after_discount_amount", @@ -172,9 +148,7 @@ "label": "Tax Amount After Discount Amount", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "columns": 2, @@ -185,15 +159,11 @@ "oldfieldname": "total", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_14", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "base_tax_amount", @@ -201,9 +171,7 @@ "label": "Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total", @@ -211,9 +179,7 @@ "hidden": 1, "label": "Total (Company Currency)", "options": "Company:company:default_currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_tax_amount_after_discount_amount", @@ -221,9 +187,7 @@ "label": "Tax Amount After Discount Amount", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "item_wise_tax_detail", @@ -233,28 +197,37 @@ "oldfieldname": "item_wise_tax_detail", "oldfieldtype": "Small Text", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions" }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 + }, + { + "default": "0", + "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", + "fieldname": "included_in_paid_amount", + "fieldtype": "Check", + "label": "Included In Paid Amount" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-11-29 19:11:58.826078", + "modified": "2021-05-31 03:41:38.298937", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 3c8cb6b851..36fd634b50 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -1,8 +1,10 @@ { + "actions": [], "creation": "2013-04-24 11:39:32", "doctype": "DocType", "document_type": "Setup", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "charge_type", "row_id", @@ -10,12 +12,14 @@ "col_break_1", "description", "included_in_print_rate", + "included_in_paid_amount", "accounting_dimensions_section", "cost_center", "dimension_col_break", "section_break_8", "rate", "section_break_9", + "currency", "tax_amount", "total", "tax_amount_after_discount_amount", @@ -23,8 +27,7 @@ "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", - "item_wise_tax_detail", - "parenttype" + "item_wise_tax_detail" ], "fields": [ { @@ -173,17 +176,6 @@ "oldfieldtype": "Small Text", "read_only": 1 }, - { - "fieldname": "parenttype", - "fieldtype": "Data", - "hidden": 1, - "in_filter": 1, - "label": "Parenttype", - "oldfieldname": "parenttype", - "oldfieldtype": "Data", - "print_hide": 1, - "search_index": 1 - }, { "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", @@ -192,15 +184,33 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 + }, + { + "default": "0", + "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", + "fieldname": "included_in_paid_amount", + "fieldtype": "Check", + "label": "Included In Paid Amount" } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-05-25 22:59:38.740883", + "links": [], + "modified": "2021-05-31 05:40:32.856780", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", "owner": "Administrator", "permissions": [], + "sort_field": "modified", "sort_order": "ASC" } \ 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 2e133bed2e..3097f72201 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -464,7 +464,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, calculate_totals: function() { - // Changing sequence can cause rounding_adjustmentng issue and on-screen discrepency + // Changing sequence can because of rounding adjustment issue and on-screen discrepancy var me = this; var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0; this.frm.doc.grand_total = flt(tax_count From 2c7fac0e7689b1eba05cc18abeee535fd52726b0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 1 Jun 2021 11:56:00 +0530 Subject: [PATCH 0945/1264] fix: reload doc for possible future schema changes (#25904) --- erpnext/patches/v12_0/purchase_receipt_status.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/patches/v12_0/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py index 1a99b3163b..459221e769 100644 --- a/erpnext/patches/v12_0/purchase_receipt_status.py +++ b/erpnext/patches/v12_0/purchase_receipt_status.py @@ -19,6 +19,9 @@ def execute(): logger.info("purchase_receipt_status: begin patch, PR count: {}" .format(len(affected_purchase_receipts))) + frappe.reload_doc("stock", "doctype", "Purchase Receipt") + frappe.reload_doc("stock", "doctype", "Purchase Receipt Item") + for pr in affected_purchase_receipts: pr_name = pr[0] From 865663857c30de89f5b0a7808421ecf05c53224e Mon Sep 17 00:00:00 2001 From: Fisher Yu <12823863+szufisher@users.noreply.github.com> Date: Tue, 1 Jun 2021 14:45:58 +0800 Subject: [PATCH 0946/1264] fix: chart of accounts importer always error (#25882) * fix: chart of accounts importer always error chart of accounts importer always error Parent Account Missing * Update chart_of_accounts_importer.py --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index ef44626b37..3b764aab10 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -293,7 +293,7 @@ def validate_accounts(file_name): accounts_dict = {} for account in accounts: accounts_dict.setdefault(account["account_name"], account) - if not hasattr(account, "parent_account"): + if "parent_account" not in account: msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.") msg += "

    " msg += _("Alternatively, you can download the template and fill your data in.") From 6432aaa07abc3abcb339bf73a096ce93b78269d0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 16:34:09 +0530 Subject: [PATCH 0947/1264] feat: added reports to check incorrect qty and valuation for serialized items --- .../__init__.py | 0 ...incorrect_balance_qty_after_transaction.js | 27 ++++ ...correct_balance_qty_after_transaction.json | 32 ++++ ...incorrect_balance_qty_after_transaction.py | 111 +++++++++++++ .../incorrect_serial_no_valuation/__init__.py | 0 .../incorrect_serial_no_valuation.js | 35 +++++ .../incorrect_serial_no_valuation.json | 36 +++++ .../incorrect_serial_no_valuation.py | 148 ++++++++++++++++++ erpnext/stock/workspace/stock/stock.json | 38 ++++- 9 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py create mode 100644 erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js create mode 100644 erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json create mode 100644 erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py create mode 100644 erpnext/stock/report/incorrect_serial_no_valuation/__init__.py create mode 100644 erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js create mode 100644 erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json create mode 100644 erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js new file mode 100644 index 0000000000..bf11277d9c --- /dev/null +++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js @@ -0,0 +1,27 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Incorrect Balance Qty After Transaction"] = { + "filters": [ + { + label: __("Company"), + fieldtype: "Link", + fieldname: "company", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __('Item Code'), + fieldtype: 'Link', + fieldname: 'item_code', + options: 'Item' + }, + { + label: __('Warehouse'), + fieldtype: 'Link', + fieldname: 'warehouse' + } + ] +}; diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json new file mode 100644 index 0000000000..a5815bcca4 --- /dev/null +++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-05-12 16:47:58.717853", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-05-12 16:48:28.347575", + "modified_by": "Administrator", + "module": "Stock", + "name": "Incorrect Balance Qty After Transaction", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Incorrect Balance Qty After Transaction", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Stock Manager" + }, + { + "role": "Purchase User" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py new file mode 100644 index 0000000000..cf174c9368 --- /dev/null +++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py @@ -0,0 +1,111 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from six import iteritems +from frappe.utils import flt + +def execute(filters=None): + columns, data = [], [] + columns = get_columns() + data = get_data(filters) + return columns, data + +def get_data(filters): + data = get_stock_ledger_entries(filters) + itewise_balance_qty = {} + + for row in data: + key = (row.item_code, row.warehouse) + itewise_balance_qty.setdefault(key, []).append(row) + + res = validate_data(itewise_balance_qty) + return res + +def validate_data(itewise_balance_qty): + res = [] + for key, data in iteritems(itewise_balance_qty): + row = get_incorrect_data(data) + if row: + res.append(row) + res.append({}) + + return res + +def get_incorrect_data(data): + balance_qty = 0.0 + for row in data: + balance_qty += row.actual_qty + if row.voucher_type == "Stock Reconciliation" and not row.batch_no: + balance_qty = flt(row.qty_after_transaction) + + row.expected_balance_qty = balance_qty + if abs(flt(row.expected_balance_qty) - flt(row.qty_after_transaction)) > 0.5: + row.differnce = abs(flt(row.expected_balance_qty) - flt(row.qty_after_transaction)) + return row + +def get_stock_ledger_entries(report_filters): + filters = {} + fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'actual_qty', + 'posting_date', 'posting_time', 'company', 'warehouse', 'qty_after_transaction', 'batch_no'] + + for field in ['warehouse', 'item_code', 'company']: + if report_filters.get(field): + filters[field] = report_filters.get(field) + + return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters, + order_by = 'timestamp(posting_date, posting_time) asc, creation asc') + +def get_columns(): + return [{ + 'label': _('Id'), + 'fieldtype': 'Link', + 'fieldname': 'name', + 'options': 'Stock Ledger Entry', + 'width': 120 + }, { + 'label': _('Posting Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date', + 'width': 110 + }, { + 'label': _('Voucher Type'), + 'fieldtype': 'Link', + 'fieldname': 'voucher_type', + 'options': 'DocType', + 'width': 120 + }, { + 'label': _('Voucher No'), + 'fieldtype': 'Dynamic Link', + 'fieldname': 'voucher_no', + 'options': 'voucher_type', + 'width': 120 + }, { + 'label': _('Item Code'), + 'fieldtype': 'Link', + 'fieldname': 'item_code', + 'options': 'Item', + 'width': 120 + }, { + 'label': _('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + 'width': 120 + }, { + 'label': _('Expected Balance Qty'), + 'fieldtype': 'Float', + 'fieldname': 'expected_balance_qty', + 'width': 170 + }, { + 'label': _('Actual Balance Qty'), + 'fieldtype': 'Float', + 'fieldname': 'qty_after_transaction', + 'width': 150 + }, { + 'label': _('Difference'), + 'fieldtype': 'Float', + 'fieldname': 'differnce', + 'width': 110 + }] \ No newline at end of file diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py b/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js new file mode 100644 index 0000000000..c62d48081c --- /dev/null +++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js @@ -0,0 +1,35 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Incorrect Serial No Valuation"] = { + "filters": [ + { + label: __('Item Code'), + fieldtype: 'Link', + fieldname: 'item_code', + options: 'Item', + get_query: function() { + return { + filters: { + 'has_serial_no': 1 + } + } + } + }, + { + label: __('From Date'), + fieldtype: 'Date', + fieldname: 'from_date', + reqd: 1, + default: frappe.defaults.get_user_default("year_start_date") + }, + { + label: __('To Date'), + fieldtype: 'Date', + fieldname: 'to_date', + reqd: 1, + default: frappe.defaults.get_user_default("year_end_date") + } + ] +}; diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json new file mode 100644 index 0000000000..cc384a5bd0 --- /dev/null +++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json @@ -0,0 +1,36 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-05-13 13:07:00.767845", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2021-05-13 13:07:00.767845", + "modified_by": "Administrator", + "module": "Stock", + "name": "Incorrect Serial No Valuation", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Incorrect Serial No Valuation", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + }, + { + "role": "Stock Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py new file mode 100644 index 0000000000..e54cf4c66c --- /dev/null +++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py @@ -0,0 +1,148 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +import copy +from frappe import _ +from six import iteritems +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + +def execute(filters=None): + columns, data = [], [] + columns = get_columns() + data = get_data(filters) + return columns, data + +def get_data(filters): + data = get_stock_ledger_entries(filters) + serial_nos_data = prepare_serial_nos(data) + data = get_incorrect_serial_nos(serial_nos_data) + + return data + +def prepare_serial_nos(data): + serial_no_wise_data = {} + for row in data: + if not row.serial_nos: + continue + + for serial_no in get_serial_nos(row.serial_nos): + sle = copy.deepcopy(row) + sle.serial_no = serial_no + sle.qty = 1 if sle.actual_qty > 0 else -1 + sle.valuation_rate = sle.valuation_rate if sle.actual_qty > 0 else sle.valuation_rate * -1 + serial_no_wise_data.setdefault(serial_no, []).append(sle) + + return serial_no_wise_data + +def get_incorrect_serial_nos(serial_nos_data): + result = [] + + total_value = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Balance'))}) + + for serial_no, data in iteritems(serial_nos_data): + total_dict = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Total'))}) + + if check_incorrect_serial_data(data, total_dict): + result.extend(data) + + total_value.qty += total_dict.qty + total_value.valuation_rate += total_dict.valuation_rate + + result.append(total_dict) + result.append({}) + + result.append(total_value) + + return result + +def check_incorrect_serial_data(data, total_dict): + incorrect_data = False + for row in data: + total_dict.qty += row.qty + total_dict.valuation_rate += row.valuation_rate + + if ((total_dict.qty == 0 and abs(total_dict.valuation_rate) > 0) or total_dict.qty < 0): + incorrect_data = True + + return incorrect_data + +def get_stock_ledger_entries(report_filters): + fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'serial_no as serial_nos', 'actual_qty', + 'posting_date', 'posting_time', 'company', 'warehouse', '(stock_value_difference / actual_qty) as valuation_rate'] + + filters = {'serial_no': ("is", "set")} + + if report_filters.get('item_code'): + filters['item_code'] = report_filters.get('item_code') + + if report_filters.get('from_date') and report_filters.get('to_date'): + filters['posting_date'] = ('between', [report_filters.get('from_date'), report_filters.get('to_date')]) + + return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters, + order_by = 'timestamp(posting_date, posting_time) asc, creation asc') + +def get_columns(): + return [{ + 'label': _('Company'), + 'fieldtype': 'Link', + 'fieldname': 'company', + 'options': 'Company', + 'width': 120 + }, { + 'label': _('Id'), + 'fieldtype': 'Link', + 'fieldname': 'name', + 'options': 'Stock Ledger Entry', + 'width': 120 + }, { + 'label': _('Posting Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date', + 'width': 90 + }, { + 'label': _('Posting Time'), + 'fieldtype': 'Time', + 'fieldname': 'posting_time', + 'width': 90 + }, { + 'label': _('Voucher Type'), + 'fieldtype': 'Link', + 'fieldname': 'voucher_type', + 'options': 'DocType', + 'width': 100 + }, { + 'label': _('Voucher No'), + 'fieldtype': 'Dynamic Link', + 'fieldname': 'voucher_no', + 'options': 'voucher_type', + 'width': 110 + }, { + 'label': _('Item Code'), + 'fieldtype': 'Link', + 'fieldname': 'item_code', + 'options': 'Item', + 'width': 120 + }, { + 'label': _('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + 'width': 120 + }, { + 'label': _('Serial No'), + 'fieldtype': 'Link', + 'fieldname': 'serial_no', + 'options': 'Serial No', + 'width': 100 + }, { + 'label': _('Qty'), + 'fieldtype': 'Float', + 'fieldname': 'qty', + 'width': 80 + }, { + 'label': _('Valuation Rate (In / Out)'), + 'fieldtype': 'Currency', + 'fieldname': 'valuation_rate', + 'width': 110 + }] \ No newline at end of file diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 3221dc4365..529ce8eb61 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "stock", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Stock", "links": [ @@ -653,9 +654,44 @@ "link_type": "Report", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Incorrect Data Report", + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Incorrect Serial No Qty and Valuation", + "link_to": "Incorrect Serial No Valuation", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Incorrect Balance Qty After Transaction", + "link_to": "Incorrect Balance Qty After Transaction", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Stock and Account Value Comparison", + "link_to": "Stock and Account Value Comparison", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2020-12-01 13:38:36.282890", + "modified": "2021-05-13 13:10:24.914983", "modified_by": "Administrator", "module": "Stock", "name": "Stock", From c6dcc9d94a19fef68ebaf792063beb0fd19c8c3a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 1 Jun 2021 13:13:04 +0530 Subject: [PATCH 0948/1264] fix(India): Taxable value for invoices with additional discount --- erpnext/regional/india/e_invoice/utils.py | 30 ++++++----------------- erpnext/regional/india/utils.py | 8 ++---- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 843fb012b9..95b4e16afd 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -38,7 +38,7 @@ def validate_eligibility(doc): einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): return False - + invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') @@ -135,7 +135,7 @@ def validate_address_fields(address, is_shipping_address): def get_party_details(address_name, is_shipping_address=False): addr = frappe.get_doc('Address', address_name) - + validate_address_fields(addr, is_shipping_address) if addr.gst_state_number == 97: @@ -188,11 +188,6 @@ def get_item_list(invoice): item.qty = abs(item.qty) - if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: - item.discount_amount = abs(item.base_amount - item.base_net_amount) - else: - item.discount_amount = 0 - item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty) item.gross_amount = abs(item.taxable_value) + item.discount_amount item.taxable_value = abs(item.taxable_value) @@ -254,18 +249,8 @@ def update_item_taxes(invoice, item): def get_invoice_value_details(invoice): invoice_value_details = frappe._dict(dict()) - - if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: - # Discount already applied on net total which means on items - invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) - invoice_value_details.invoice_discount_amt = 0 - elif invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount: - invoice_value_details.invoice_discount_amt = invoice.base_discount_amount - invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) - else: - invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) - # since tax already considers discount amount - invoice_value_details.invoice_discount_amt = 0 + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) + invoice_value_details.invoice_discount_amt = 0 invoice_value_details.round_off = invoice.base_rounding_adjustment invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) @@ -287,8 +272,7 @@ def update_invoice_taxes(invoice, invoice_value_details): considered_rows = [] for t in invoice.taxes: - tax_amount = t.base_tax_amount if (invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount) \ - else t.base_tax_amount_after_discount_amount + tax_amount = t.base_tax_amount_after_discount_amount if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: # using after discount amt since item also uses after discount amt for cess calc @@ -995,7 +979,7 @@ class GSPConnector(): self.invoice.failure_description = self.get_failure_message(errors) if errors else "" self.update_invoice() frappe.db.commit() - + def get_failure_message(self, errors): if isinstance(errors, list): errors = ', '.join(errors) @@ -1052,7 +1036,7 @@ def generate_einvoices(docnames): _('{} e-invoices generated successfully').format(success), title=_('Bulk E-Invoice Generation Complete') ) - + else: enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index fc227defbf..ea61502099 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -817,12 +817,8 @@ def update_taxable_values(doc, method): considered_rows.append(prev_row_id) for item in doc.get('items'): - if doc.apply_discount_on == 'Grand Total' and doc.discount_amount: - proportionate_value = item.base_amount if doc.base_total else item.qty - total_value = doc.base_total if doc.base_total else doc.total_qty - else: - proportionate_value = item.base_net_amount if doc.base_net_total else item.qty - total_value = doc.base_net_total if doc.base_net_total else doc.total_qty + proportionate_value = item.base_net_amount if doc.base_net_total else item.qty + total_value = doc.base_net_total if doc.base_net_total else doc.total_qty applicable_charges = flt(flt(proportionate_value * (flt(additional_taxes) / flt(total_value)), item.precision('taxable_value'))) From b3ed807b70d5bd1b1b94442fd0c4f1d774846d9d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Jun 2021 13:26:21 +0530 Subject: [PATCH 0949/1264] fix: Regional settings setup --- erpnext/regional/india/setup.py | 3 +-- erpnext/setup/doctype/company/company.py | 2 +- erpnext/setup/setup_wizard/operations/taxes_setup.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index d79ce64fb3..fa9f1b7e9e 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -15,7 +15,6 @@ def setup(company=None, patch=True): setup_company_independent_fixtures(patch=patch) if not patch: make_fixtures(company) - setup_gst_settings(company) # TODO: for all countries def setup_company_independent_fixtures(patch=False): @@ -694,7 +693,7 @@ def make_fixtures(company=None): # create records for Tax Withholding Category set_tax_withholding_category(company) -def setup_gst_settings(company): +def update_regional_tax_settings(country, company): # Will only add default GST accounts if present input_account_names = ['Input Tax CGST', 'Input Tax SGST', 'Input Tax IGST'] output_account_names = ['Output Tax CGST', 'Output Tax SGST', 'Output Tax IGST'] diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index ff96c0b4c4..61d63a3f29 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -110,8 +110,8 @@ class Company(NestedSet): self.create_default_warehouses() if frappe.flags.country_change: - self.create_default_tax_template() install_country_fixtures(self.name, self.country) + self.create_default_tax_template() if not frappe.db.get_value("Department", {"company": self.name}): from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index a644da9292..6ac561223a 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -24,6 +24,7 @@ def setup_taxes_and_charges(company_name: str, country: str): country_wise_tax = simple_to_detailed(country_wise_tax) from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) + update_regional_tax_settings(country, company_name) def simple_to_detailed(templates): @@ -97,6 +98,17 @@ def from_detailed_data(company_name, data): make_item_tax_template(company_name, template) +def update_regional_tax_settings(country, company): + path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) + if os.path.exists(path.encode("utf-8")): + try: + module_name = "erpnext.regional.{0}.setup.update_regional_tax_settings".format(frappe.scrub(country)) + frappe.get_attr(module_name)(country, company) + except Exception as e: + # Log error and ignore if failed to setup regional tax settings + frappe.log_error() + pass + def make_taxes_and_charges_template(company_name, doctype, template): template['company'] = company_name template['doctype'] = doctype From 7c7c084159b68f99927cc28b4b3b88fa8c415b80 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Jun 2021 14:12:28 +0530 Subject: [PATCH 0950/1264] fix: Check for tax category --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 4c49609039..9a830d4f2e 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -176,7 +176,7 @@ def make_item_tax_template(company_name, template): def make_tax_category(tax_category): """ Make tax category based on title if not already created """ doctype = 'Tax Category' - if not frappe.db.exists(doctype, tax_category): + if not frappe.db.exists(doctype, tax_category['title']): tax_category['doctype'] = doctype doc = frappe.get_doc(tax_category) doc.flags.ignore_links = True From 721b4130db37e0219843b0fbca9acd1a36ee816b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 2 Jun 2021 14:13:09 +0530 Subject: [PATCH 0951/1264] fix: not able to select the item code in work order --- erpnext/controllers/queries.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 46301b7587..638503edfa 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -204,7 +204,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if "description" in searchfields: searchfields.remove("description") - + columns = '' extra_searchfields = [field for field in searchfields if not field in ["name", "item_group", "description"]] @@ -216,9 +216,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if not field in searchfields] searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) - if filters.get('supplier'): - item_group_list = frappe.get_all('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) - + if filters and isinstance(filters, dict) and filters.get('supplier'): + item_group_list = frappe.get_all('Supplier Item Group', + filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + item_groups = [] for i in item_group_list: item_groups.append(i.item_group) @@ -227,7 +228,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if item_groups: filters['item_group'] = ['in', item_groups] - + description_cond = '' if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 From a06ec03efcaedeeffaf9b8f9818bfb866f44e2d1 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 2 Jun 2021 14:55:31 +0530 Subject: [PATCH 0952/1264] test: add test for new QI function --- erpnext/controllers/stock_controller.py | 5 +- erpnext/public/js/controllers/transaction.js | 2 +- .../test_quality_inspection.py | 152 ++++++++++++------ 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index abc966a477..0da723d56e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -504,9 +504,10 @@ class StockController(AccountsController): @frappe.whitelist() def make_quality_inspections(doctype, docname, items): - items = json.loads(items).get('items') - inspections = [] + if isinstance(items, str): + items = json.loads(items) + inspections = [] for item in items: if flt(item.get("sample_size")) > flt(item.get("qty")): frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 95562baa8e..982b1fe1eb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2035,7 +2035,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ args: { doctype: me.frm.doc.doctype, docname: me.frm.doc.name, - items: data + items: data.items }, freeze: true, callback: function (r) { diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 56b046a92e..7f3d701034 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -1,29 +1,45 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + +import frappe from frappe.utils import nowdate -from erpnext.stock.doctype.item.test_item import create_item + +from erpnext.controllers.stock_controller import ( + QualityInspectionNotSubmittedError, + QualityInspectionRejectedError, + QualityInspectionRequiredError, + make_quality_inspections, +) from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry -from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError # test_records = frappe.get_test_records('Quality Inspection') + class TestQualityInspection(unittest.TestCase): def setUp(self): create_item("_Test Item with QA") - frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1) + frappe.db.set_value( + "Item", "_Test Item with QA", "inspection_required_before_delivery", 1 + ) def test_qa_for_delivery(self): - make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100) + make_stock_entry( + item_code="_Test Item with QA", + target="_Test Warehouse - _TC", + qty=1, + basic_rate=100 + ) dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) self.assertRaises(QualityInspectionRequiredError, dn.submit) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected") + qa = create_quality_inspection( + reference_type="Delivery Note", reference_name=dn.name, status="Rejected" + ) dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) @@ -38,7 +54,9 @@ class TestQualityInspection(unittest.TestCase): def test_qa_not_submit(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True) + qa = create_quality_inspection( + reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True + ) dn.items[0].quality_inspection = qa.name self.assertRaises(QualityInspectionNotSubmittedError, dn.submit) @@ -48,21 +66,28 @@ class TestQualityInspection(unittest.TestCase): def test_value_based_qi_readings(self): # Test QI based on acceptance values (Non formula) dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - readings = [{ - "specification": "Iron Content", # numeric reading - "min_value": 0.1, - "max_value": 0.9, - "reading_1": "0.4" - }, - { - "specification": "Particle Inspection Needed", # non-numeric reading - "numeric": 0, - "value": "Yes", - "reading_value": "Yes" - }] + readings = [ + { + "specification": "Iron Content", # numeric reading + "min_value": 0.1, + "max_value": 0.9, + "reading_1": "0.4" + }, + { + "specification": "Particle Inspection Needed", # non-numeric reading + "numeric": 0, + "value": "Yes", + "reading_value": "Yes" + } + ] + + qa = create_quality_inspection( + reference_type="Delivery Note", + reference_name=dn.name, + readings=readings, + do_not_save=True + ) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, - readings=readings, do_not_save=True) qa.save() # status must be auto set as per formula @@ -74,36 +99,43 @@ class TestQualityInspection(unittest.TestCase): def test_formula_based_qi_readings(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - readings = [{ - "specification": "Iron Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", - "reading_1": "0.4" - }, - { - "specification": "Calcium Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", - "reading_1": "0.7" - }, - { - "specification": "Mg Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "mean < 0.9", - "reading_1": "0.5", - "reading_2": "0.7", - "reading_3": "random text" # check if random string input causes issues - }, - { - "specification": "Calcium Content", # non-numeric reading - "formula_based_criteria": 1, - "numeric": 0, - "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", - "reading_value": "Grade B" - }] + readings = [ + { + "specification": "Iron Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", + "reading_1": "0.4" + }, + { + "specification": "Calcium Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", + "reading_1": "0.7" + }, + { + "specification": "Mg Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "mean < 0.9", + "reading_1": "0.5", + "reading_2": "0.7", + "reading_3": "random text" # check if random string input causes issues + }, + { + "specification": "Calcium Content", # non-numeric reading + "formula_based_criteria": 1, + "numeric": 0, + "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", + "reading_value": "Grade B" + } + ] + + qa = create_quality_inspection( + reference_type="Delivery Note", + reference_name=dn.name, + readings=readings, + do_not_save=True + ) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, - readings=readings, do_not_save=True) qa.save() # status must be auto set as per formula @@ -115,6 +147,19 @@ class TestQualityInspection(unittest.TestCase): qa.delete() dn.delete() + def test_make_quality_inspections_from_linked_document(self): + dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + for item in dn.items: + item.sample_size = item.qty + quality_inspections = make_quality_inspections(dn.doctype, dn.name, dn.items) + self.assertEqual(len(dn.items), len(quality_inspections)) + + # cleanup + for qi in quality_inspections: + frappe.delete_doc("Quality Inspection", qi) + dn.delete() + + def create_quality_inspection(**args): args = frappe._dict(args) qa = frappe.new_doc("Quality Inspection") @@ -134,7 +179,7 @@ def create_quality_inspection(**args): readings = args.readings if args.status == "Rejected": - readings["reading_1"] = "12" # status is auto set in child on save + readings["reading_1"] = "12" # status is auto set in child on save if isinstance(readings, list): for entry in readings: @@ -150,10 +195,11 @@ def create_quality_inspection(**args): return qa + def create_quality_inspection_parameter(parameter): if not frappe.db.exists("Quality Inspection Parameter", parameter): frappe.get_doc({ "doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter - }).insert() \ No newline at end of file + }).insert() From f259bf48aaa40630b0a7b08b6d323a14ffc8aa03 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:20:03 +0200 Subject: [PATCH 0953/1264] refactor: make tax category --- .../setup_wizard/data/country_wise_tax.json | 4 +++ .../setup_wizard/operations/taxes_setup.py | 33 +++++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 2e2a0ca726..5ed8ab92bf 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -481,6 +481,10 @@ }, "Germany": { + "tax_categories": [ + "Umsatzsteuer", + "Vorsteuer" + ], "chart_of_accounts": { "SKR04 mit Kontonummern": { "sales_tax_templates": [ diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 4a3de957b9..f4fe18e116 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -26,7 +26,7 @@ def setup_taxes_and_charges(company_name: str, country: str): if 'chart_of_accounts' not in country_wise_tax: country_wise_tax = simple_to_detailed(country_wise_tax) - from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) + from_detailed_data(company_name, country_wise_tax) def simple_to_detailed(templates): @@ -77,10 +77,16 @@ def simple_to_detailed(templates): def from_detailed_data(company_name, data): """Create Taxes and Charges Templates from detailed data.""" coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') - tax_templates = data.get(coa_name) or data.get('*') - sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') - purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*') - item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') + coa_data = data.get('chart_of_accounts', {}) + tax_templates = coa_data.get(coa_name) or coa_data.get('*', {}) + tax_categories = data.get('tax_categories') + sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*', {}) + purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*', {}) + item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*', {}) + + if tax_categories: + for tax_category in tax_categories: + make_tax_catgory(tax_category) if sales_tax_templates: for template in sales_tax_templates: @@ -102,9 +108,6 @@ def make_taxes_and_charges_template(company_name, doctype, template): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - if template.get('tax_category'): - ensure_tax_category_exists(template.get('tax_category')) - for tax_row in template.get('taxes'): account_data = tax_row.get('account_head') tax_row_defaults = { @@ -241,8 +244,12 @@ def get_or_create_tax_group(company_name, root_type): return tax_group_name -def ensure_tax_category_exists(name): - if not frappe.db.exists('Tax Category', name): - doc = frappe.new_doc('Tax Category') - doc.title = name - doc.save() +def make_tax_catgory(tax_category): + doctype = 'Tax Category' + if isinstance(tax_category, str): + tax_category = {'title': tax_category} + + tax_category['doctype'] = doctype + if not frappe.db.exists(doctype, tax_category['title']): + doc = frappe.get_doc(tax_category) + doc.insert(ignore_permissions=True) From b6f27a4caef1fd479762579fd6f612fdc78fa69d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:20:33 +0200 Subject: [PATCH 0954/1264] feat: set is_default for german tax templates --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 5ed8ab92bf..f4728ef398 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -491,6 +491,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -515,6 +516,7 @@ { "title": "Vorsteuer", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -567,6 +569,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -591,6 +594,7 @@ { "title": "Vorsteuer", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -619,6 +623,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -643,6 +648,7 @@ { "title": "Vorsteuer", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -761,6 +767,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -783,6 +790,7 @@ { "title": "Vorsteuer 19%", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { From 3c748efae3cfc78a88447ac9edeccfea860f1358 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:27:15 +0200 Subject: [PATCH 0955/1264] feat: add Item Tax Templates for german COAs "SKR03" and "SKR04" --- .../setup_wizard/data/country_wise_tax.json | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index f4728ef398..8305cae73e 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -562,6 +562,96 @@ } ] } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + }, + { + "title": "Vorsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + } ] }, "SKR03 mit Kontonummern": { @@ -616,6 +706,96 @@ } ] } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + }, + { + "title": "Vorsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + } ] }, "Standard with Numbers": { From f6627550d11ea95170ce5f208a257bb31218585f Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 15:17:00 +0200 Subject: [PATCH 0956/1264] fix: remove wrong tax_category --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 8305cae73e..ec9a6d6b70 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -540,7 +540,6 @@ }, { "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", - "tax_category": "Innergemeinschaftlicher Erwerb 19%", "taxes": [ { "account_head": { From 311b378328ec3d8ea774749f69188210eef237cd Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 3 Jun 2021 12:58:53 +0530 Subject: [PATCH 0957/1264] fix(pos): searching items with barcode or serial no --- .../page/point_of_sale/point_of_sale.py | 88 ++++++++++--------- .../page/point_of_sale/pos_item_selector.js | 6 +- 2 files changed, 49 insertions(+), 45 deletions(-) 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 cb811df8e8..7742f24385 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -8,38 +8,52 @@ 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 +def search_by_term(search_term, warehouse, price_list): + result = search_for_serial_or_batch_or_barcode_number(search_term) + + item_code = result.get("item_code") or search_term + serial_no = result.get("serial_no") or "" + batch_no = result.get("batch_no") or "" + barcode = result.get("barcode") or "" + + if result: + item_info = frappe.db.get_value("Item", item_code, + ["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"], + as_dict=1) + + item_stock_qty = get_stock_availability(item_code, warehouse) + price_list_rate, currency = frappe.db.get_value('Item Price', { + 'price_list': price_list, + 'item_code': item_code + }, ["price_list_rate", "currency"]) + + item_info.update({ + 'serial_no': serial_no, + 'batch_no': batch_no, + 'barcode': barcode, + 'price_list_rate': price_list_rate, + 'currency': currency, + 'actual_qty': item_stock_qty + }) + + return {'items': [item_info]} @frappe.whitelist() -def get_items(start, page_length, price_list, item_group, pos_profile, search_value=""): - data = dict() +def get_items(start, page_length, price_list, item_group, pos_profile, search_term=""): + warehouse, hide_unavailable_items = frappe.db.get_value( + 'POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items']) + result = [] - warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items']) + if search_term: + result = search_by_term(search_term, warehouse, price_list) + if result: + return result if not frappe.db.exists('Item Group', item_group): item_group = get_root_of('Item Group') - if search_value: - data = search_serial_or_batch_or_barcode_number(search_value) - - item_code = data.get("item_code") if data.get("item_code") else search_value - serial_no = data.get("serial_no") if data.get("serial_no") else "" - batch_no = data.get("batch_no") if data.get("batch_no") else "" - barcode = data.get("barcode") if data.get("barcode") else "" - - if data: - item_info = frappe.db.get_value( - "Item", data.get("item_code"), - ["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"] - , as_dict=1) - item_info.setdefault('serial_no', serial_no) - item_info.setdefault('batch_no', batch_no) - item_info.setdefault('barcode', barcode) - - return { 'items': [item_info] } - - condition = get_conditions(item_code, serial_no, batch_no, barcode) + condition = get_conditions(search_term) condition += get_item_group_condition(pos_profile) lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt']) @@ -106,14 +120,10 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va }) result.append(row) - res = { - 'items': result - } - - return res + return {'items': result} @frappe.whitelist() -def search_serial_or_batch_or_barcode_number(search_value): +def search_for_serial_or_batch_or_barcode_number(search_value): # search barcode no barcode_data = frappe.db.get_value('Item Barcode', {'barcode': search_value}, ['barcode', 'parent as item_code'], as_dict=True) if barcode_data: @@ -139,27 +149,21 @@ def filter_service_items(items): return items -def get_conditions(item_code, serial_no, batch_no, barcode): - if serial_no or batch_no or barcode: - return "item.name = {0}".format(frappe.db.escape(item_code)) - - return make_condition(item_code) - -def make_condition(item_code): +def get_conditions(search_term): condition = "(" - condition += """item.name like {item_code} - or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%')) - condition += add_search_fields_condition(item_code) + condition += """item.name like {search_term} + or item.item_name like {search_term}""".format(search_term=frappe.db.escape('%' + search_term + '%')) + condition += add_search_fields_condition(search_term) condition += ")" return condition -def add_search_fields_condition(item_code): +def add_search_fields_condition(search_term): condition = '' search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname']) if search_fields: for field in search_fields: - condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%')) + condition += " or item.`{0}` like {1}".format(field['fieldname'], frappe.db.escape('%' + search_term + '%')) return condition def get_item_group_condition(pos_profile): diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 5b48725d9c..3e3377e5dc 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -51,7 +51,7 @@ erpnext.PointOfSale.ItemSelector = class { }); } - get_items({start = 0, page_length = 40, search_value=''}) { + get_items({start = 0, page_length = 40, search_term=''}) { const doc = this.events.get_frm().doc; const price_list = (doc && doc.selling_price_list) || this.price_list; let { item_group, pos_profile } = this; @@ -61,7 +61,7 @@ erpnext.PointOfSale.ItemSelector = class { 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 }, + args: { start, page_length, price_list, item_group, search_term, pos_profile }, }); } @@ -302,7 +302,7 @@ erpnext.PointOfSale.ItemSelector = class { } } - this.get_items({ search_value: search_term }) + this.get_items({ search_term }) .then(({ message }) => { // eslint-disable-next-line no-unused-vars const { items, serial_no, batch_no, barcode } = message; From 81b9a5ee4776613ad8720e7004267a82037715f4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 3 Jun 2021 12:59:09 +0530 Subject: [PATCH 0958/1264] fix(pos): cannot cancel consolidated sales invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 1808005f62..f813425e6b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte var me = this; this._super(); - this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet']; + this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry']; if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { // show debit_to in print format this.frm.set_df_property("debit_to", "print_hide", 0); From 10558344b0fa25b12f71fda9ca21bb8c5170cb83 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 27 May 2021 17:05:36 +0530 Subject: [PATCH 0959/1264] fix: timeout error in the repost item valuation --- erpnext/hooks.py | 4 +++- .../repost_item_valuation/repost_item_valuation.py | 13 ++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 55169dffba..8ad77a1524 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -332,7 +332,9 @@ scheduler_events = { "erpnext.projects.doctype.project.project.collect_project_status", "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", - "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", + "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders" + ], + "hourly_long": [ "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries" ], "daily": [ diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 5b626ea345..55f2ebb224 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -37,6 +37,9 @@ class RepostItemValuation(Document): self.db_set('status', status) def on_submit(self): + if not frappe.flags.in_test: + return + frappe.enqueue(repost, timeout=1800, queue='long', job_name='repost_sle', now=frappe.flags.in_test, doc=self) @@ -115,12 +118,6 @@ def notify_error_to_stock_managers(doc, traceback): frappe.sendmail(recipients=recipients, subject=subject, message=message) def repost_entries(): - job_log = frappe.get_all('Scheduled Job Log', fields = ['status', 'creation'], - filters = {'scheduled_job_type': 'repost_item_valuation.repost_entries'}, order_by='creation desc', limit=1) - - if job_log and job_log[0]['status'] == 'Start' and time_diff_in_hours(now(), job_log[0]['creation']) < 2: - return - riv_entries = get_repost_item_valuation_entries() for row in riv_entries: @@ -135,9 +132,7 @@ def repost_entries(): check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): - date = add_to_date(now(), hours=-3) - return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, date, as_dict=1) + """, now(), as_dict=1) From c59371ab0c500d9d863db334931d527e8face6eb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 3 Jun 2021 20:02:58 +0530 Subject: [PATCH 0960/1264] fix: filter type for item query (#25942) --- erpnext/controllers/queries.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 638503edfa..81ac234e70 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe import erpnext +import json from frappe.desk.reportview import get_match_cond, get_filters_cond from frappe.utils import nowdate, getdate from collections import defaultdict @@ -198,6 +199,9 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters): def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] + if isinstance(filters, str): + filters = json.loads(filters) + #Get searchfields from meta and use in Item Link field query meta = frappe.get_meta("Item", cached=True) searchfields = meta.get_search_fields() From 48036b4bab25fcecf2728decc78ae2c598f54c0e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 4 Jun 2021 09:54:34 +0530 Subject: [PATCH 0961/1264] fix: invoices can alter profit and loss of a closed year --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 948c51364e..11465b711e 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -121,8 +121,7 @@ class GLEntry(Document): def check_pl_account(self): if self.is_opening=='Yes' and \ - frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss" and \ - self.voucher_type not in ['Purchase Invoice', 'Sales Invoice']: + frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss": frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry") .format(self.voucher_type, self.voucher_no, self.account)) From c6e016e5452950f393348710f6872febc9f9fb04 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 4 Jun 2021 10:08:22 +0530 Subject: [PATCH 0962/1264] fix: wrong round off gl entry posted in case of purchase invoice (#25775) --- erpnext/accounts/general_ledger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index f1717c50d8..d4b249429b 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -185,10 +185,10 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): for d in gl_map: if d.account == round_off_account: round_off_gle = d - if d.debit_in_account_currency: - debit_credit_diff -= flt(d.debit_in_account_currency) + if d.debit: + debit_credit_diff -= flt(d.debit) else: - debit_credit_diff += flt(d.credit_in_account_currency) + debit_credit_diff += flt(d.credit) round_off_account_exists = True if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)): From af4794b2d13b9499bff8333e1cb0b479e8405032 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 4 Jun 2021 10:53:44 +0530 Subject: [PATCH 0963/1264] fix: custom conversion factor field not mapped from job card to stock entry --- erpnext/manufacturing/doctype/job_card/job_card.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index fb26062566..d764db33f8 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -433,7 +433,8 @@ def make_material_request(source_name, target_doc=None): def make_stock_entry(source_name, target_doc=None): def update_item(obj, target, source_parent): target.t_warehouse = source_parent.wip_warehouse - target.conversion_factor = 1 + if not target.conversion_factor: + target.conversion_factor = 1 def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" From 3f45901f257f69d9e3155507da020c18c5155cdc Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 4 Jun 2021 11:16:38 +0530 Subject: [PATCH 0964/1264] fix: invalid 'depends_on' expression in opportunity (#25955) --- erpnext/crm/doctype/opportunity/opportunity.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 2e09a76c0f..4ba4140244 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -280,7 +280,6 @@ "read_only": 1 }, { - "depends_on": "eval:", "fieldname": "territory", "fieldtype": "Link", "label": "Territory", @@ -431,7 +430,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2021-01-06 19:42:46.190051", + "modified": "2021-06-04 10:11:22.831139", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", From 215516f81940c5e743380d4f97a13eda7d9969be Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 4 Jun 2021 13:09:14 +0530 Subject: [PATCH 0965/1264] fix: AttributeError: 'PurchaseReceiptItem' object has no attribute 'purchase_invoice' (#25902) (#25957) * fix: AttributeError: 'PurchaseReceiptItem' object has no attribute 'purchase_invoice' This error occurs when upgrading from erpnext 13.0.1 to 13.4.0 after typing bench update --patch --reset * fix(minor): use .get instead of getattr Co-authored-by: D Tim Cummings --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index f1292d8cbd..83ba324495 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -497,7 +497,7 @@ class PurchaseReceipt(BuyingController): def update_billing_status(self, update_modified=True): updated_pr = [self.name] for d in self.get("items"): - if d.purchase_invoice and d.purchase_invoice_item: + if d.get("purchase_invoice") and d.get("purchase_invoice_item"): d.db_set('billed_amt', d.amount, update_modified=update_modified) elif d.purchase_order_item: updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified) @@ -748,4 +748,3 @@ def get_item_account_wise_additional_cost(purchase_document): account.base_amount * item.get(based_on_field) / total_item_cost return item_account_wise_cost - From 672c8bb11230692cf24c81b85d9d0fd84f27d910 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 4 Jun 2021 16:44:30 +0530 Subject: [PATCH 0966/1264] feature: report for cost of goods sold by item group --- .../report/cogs_by_item_group/__init__.py | 0 .../cogs_by_item_group/cogs_by_item_group.js | 46 ++++++ .../cogs_by_item_group.json | 32 ++++ .../cogs_by_item_group/cogs_by_item_group.py | 155 ++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 erpnext/stock/report/cogs_by_item_group/__init__.py create mode 100644 erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js create mode 100644 erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json create mode 100644 erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py diff --git a/erpnext/stock/report/cogs_by_item_group/__init__.py b/erpnext/stock/report/cogs_by_item_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js new file mode 100644 index 0000000000..c17da4ed97 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -0,0 +1,46 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["COGS By Item Group"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + mandatory: true, + default: frappe.defaults.get_user_default("Company"), + }, + { + label: __("Account"), + fieldname: "account", + fieldtype: "Link", + options: "Account", + mandatory: true, + get_query() { + var company = frappe.query_report.get_filter_value('company'); + return { + "doctype": "Account", + "filters": { + "company": company, + } + } + }, + }, + { + label: __("From Date"), + fieldname: "from_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.year_start(), + }, + { + label: __("To Date"), + fieldname: "to_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.get_today(), + }, + ] +}; diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json new file mode 100644 index 0000000000..a14adf8a45 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-06-02 18:59:19.830928", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-06-02 18:59:55.470621", + "modified_by": "Administrator", + "module": "Stock", + "name": "COGS By Item Group", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "COGS By Item Group", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py new file mode 100644 index 0000000000..d4ddd595d9 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -0,0 +1,155 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import date_diff +from collections import OrderedDict +from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries + + +def execute(filters=None): + print(filters) + validate_filters(filters) + columns = get_columns() + data = get_data(filters) + return columns, data + + +def validate_filters(filters): + if not filters.get("from_date") and not filters.get("to_date"): + frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) + + if filters.from_date > filters.to_date: + frappe.throw(_("From Date must be before To Date")) + + +def get_columns(): + return [ + { + 'label': 'Item Group', + 'fieldname': 'item_group', + 'fieldtype': 'Data', + 'width': '200' + }, + { + 'label': 'COGS Debit', + 'fieldname': 'cogs_debit', + 'fieldtype': 'Currency', + 'width': '200' + } + ] + + +def get_data(filters): + entries = get_filtered_entries(filters) + item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) + item_groups_dict = get_item_groups_dict(item_groups_list) + levels_dict = get_levels_dict(item_groups_dict) + + update_levels_dict(levels_dict) + assign_self_values(levels_dict, entries) + assign_agg_values(levels_dict) + + data = [] + for _, i in levels_dict.items(): + if i['agg_value'] == 0: + continue + data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level'])) + if i['self_value'] < i['agg_value'] and i['self_value'] > 0: + data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1)) + return data + + +def get_filtered_entries(filters): + gl_entries = get_gl_entries(filters, []) + entries = [frappe.get_doc(gle.voucher_type, gle.voucher_no)for gle in gl_entries] + filtered_entries = [] + for entry in entries: + posting_date = entry.get("posting_date") + from_date = filters.get("from_date") + if date_diff(from_date, posting_date) > 0: + continue + filtered_entries.append(entry) + return filtered_entries + + +def append_blank(data): + if len(data) == 0: + data.append(get_row("", 0, 0, 0)) + + +def get_item_groups_dict(item_groups_list): + return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} + for i in item_groups_list } + + +def get_levels_dict(item_groups_dict): + lr_list = sorted(item_groups_dict, key=lambda x : x[0]) + levels = OrderedDict() + current_level = 0 + nesting_r = [] + for l,r in lr_list: + while current_level > 0 and nesting_r[-1] < l: + nesting_r.pop() + current_level -= 1 + + levels[(l,r)] = { + 'level' : current_level, + 'name' : item_groups_dict[(l,r)]['name'], + 'is_group' : item_groups_dict[(l,r)]['is_group'] + } + + if r - l > 1: + current_level += 1 + nesting_r.append(r) + return levels + + +def update_levels_dict(levels_dict): + for k in levels_dict: levels_dict[k].update({'self_value':0, 'agg_value':0}) + + +def assign_self_values(levels_dict, entries): + names_dict = {v['name']:k for k, v in levels_dict.items()} + for entry in entries: + items = entry.get("items") + items = [] if items is None else items + for item in items: + qty = item.get("qty") + incoming_rate = item.get("incoming_rate") + item_group = item.get("item_group") + key = names_dict[item_group] + levels_dict[key]['self_value'] += (incoming_rate * qty) + + +def assign_agg_values(levels_dict): + keys = list(levels_dict.keys())[::-1] + prev_level = levels_dict[keys[-1]]['level'] + accu = [0] + for k in keys[:-1]: + curr_level = levels_dict[k]['level'] + if curr_level == prev_level: + accu[-1] += levels_dict[k]['self_value'] + levels_dict[k]['agg_value'] = levels_dict[k]['self_value'] + + elif curr_level > prev_level: + accu.append(levels_dict[k]['self_value']) + levels_dict[k]['agg_value'] = accu[-1] + + elif curr_level < prev_level: + accu[-1] += levels_dict[k]['self_value'] + levels_dict[k]['agg_value'] = accu[-1] + + prev_level = curr_level + + # root node + rk = keys[-1] + levels_dict[rk]['agg_value'] = sum(accu) + levels_dict[rk]['self_value'] + + +def get_row(name:str, value:float, is_bold:int, indent:int): + item_group = name + if is_bold: + item_group = frappe.bold(item_group) + return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent) From 8a7e283926d51626d9114eaa4f6647b920ad8c9f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 4 Jun 2021 22:53:26 +0530 Subject: [PATCH 0967/1264] feat: Item Taxes based on net rate --- erpnext/controllers/taxes_and_totals.py | 4 +- .../public/js/controllers/taxes_and_totals.js | 90 ++++++++++++++++--- erpnext/public/js/controllers/transaction.js | 66 +------------- erpnext/stock/doctype/item/item.py | 6 ++ erpnext/stock/doctype/item_tax/item_tax.json | 20 ++++- erpnext/stock/get_item_details.py | 34 +++++-- 6 files changed, 137 insertions(+), 83 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 9fae49482d..8040435634 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -54,6 +54,7 @@ class calculate_taxes_and_totals(object): if item.item_code and item.get('item_tax_template'): item_doc = frappe.get_cached_doc("Item", item.item_code) args = { + 'net_rate': item.net_rate or item.rate, 'tax_category': self.doc.get('tax_category'), 'posting_date': self.doc.get('posting_date'), 'bill_date': self.doc.get('bill_date'), @@ -78,7 +79,8 @@ class calculate_taxes_and_totals(object): taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True) if item.item_tax_template not in taxes: - frappe.throw(_("Row {0}: Invalid Item Tax Template for item {1}").format( + item.item_tax_template = taxes[0] + frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format( item.idx, frappe.bold(item.item_code) )) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 2e133bed2e..ff4898eeff 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -12,7 +12,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) { effective_item_rate = item.blanket_order_rate; } - if(item.margin_type == "Percentage"){ + if(item.margin_type == "Percentage") { item.rate_with_margin = flt(effective_item_rate) + flt(effective_item_rate) * ( flt(item.margin_rate_or_amount) / 100); } else { @@ -73,15 +73,18 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, _calculate_taxes_and_totals: function() { - this.validate_conversion_rate(); - this.calculate_item_values(); - this.initialize_taxes(); - this.determine_exclusive_rate(); - this.calculate_net_total(); - this.calculate_taxes(); - this.manipulate_grand_total_for_inclusive_tax(); - this.calculate_totals(); - this._cleanup(); + frappe.run_serially([ + () => this.validate_conversion_rate(), + () => this.calculate_item_values(), + () => this.update_item_tax_map(), + () => this.initialize_taxes(), + () => this.determine_exclusive_rate(), + () => this.calculate_net_total(), + () => this.calculate_taxes(), + () => this.manipulate_grand_total_for_inclusive_tax(), + () => this.calculate_totals(), + () => this._cleanup() + ]); }, validate_conversion_rate: function() { @@ -263,6 +266,68 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); }, + update_item_tax_map: function() { + let me = this; + let item_codes = []; + let item_rates = {}; + $.each(this.frm.doc.items || [], function(i, item) { + if(item.item_code) { + // Use combination of name and item code in case same item is added multiple times + item_codes.push([item.item_code, item.name]); + item_rates[item.name] = item.net_rate; + } + }); + + if(item_codes.length) { + return this.frm.call({ + method: "erpnext.stock.get_item_details.get_item_tax_info", + args: { + company: me.frm.doc.company, + tax_category: cstr(me.frm.doc.tax_category), + item_codes: item_codes, + item_rates: item_rates + }, + callback: function(r) { + if(!r.exc) { + $.each(me.frm.doc.items || [], function(i, item) { + if(item.name && r.message.hasOwnProperty(item.name)) { + item.item_tax_template = r.message[item.name].item_tax_template; + item.item_tax_rate = r.message[item.name].item_tax_rate; + me.add_taxes_from_item_tax_template(item.item_tax_rate); + } + else { + item.item_tax_template = ""; + item.item_tax_rate = "{}"; + } + }); + } + + this.frm.refresh_fields(); + } + }); + } + }, + + add_taxes_from_item_tax_template: function(item_tax_map) { + let me = this; + + if(item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) { + if(typeof (item_tax_map) == "string") { + item_tax_map = JSON.parse(item_tax_map); + } + + $.each(item_tax_map, function(tax, rate) { + let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax); + if(!found) { + let child = frappe.model.add_child(me.frm.doc, "taxes"); + child.charge_type = "On Net Total"; + child.account_head = tax; + child.rate = 0; + } + }); + } + }, + calculate_taxes: function() { var me = this; this.frm.doc.rounding_adjustment = 0; @@ -406,6 +471,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ let tax_detail = tax.item_wise_tax_detail; let key = item.item_code || item.item_name; + if(typeof (tax_detail) == "string") { + tax.item_wise_tax_detail = JSON.parse(tax.item_wise_tax_detail); + tax_detail = tax.item_wise_tax_detail; + } + let item_wise_tax_amount = current_tax_amount * this.frm.doc.conversion_rate; if (tax_detail && tax_detail[key]) item_wise_tax_amount += tax_detail[key][1]; diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index ad1976d2d2..ee6696b7d1 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -6,6 +6,7 @@ frappe.provide('erpnext.accounts.dimensions'); erpnext.TransactionController = erpnext.taxes_and_totals.extend({ setup: function() { this._super(); + let me = this; frappe.flags.hide_serial_batch_dialog = true; frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); @@ -43,8 +44,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ cur_frm.cscript.calculate_stock_uom_rate(frm, cdt, cdn); }); - - frappe.ui.form.on(this.frm.cscript.tax_table, "rate", function(frm, cdt, cdn) { cur_frm.cscript.calculate_taxes_and_totals(); }); @@ -121,7 +120,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }); - var me = this; if(this.frm.fields_dict["items"].grid.get_field('batch_no')) { this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) { return me.set_query_for_batch(doc, cdt, cdn); @@ -556,6 +554,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ name: me.frm.doc.name, project: item.project || me.frm.doc.project, qty: item.qty || 1, + net_rate: item.rate, stock_qty: item.stock_qty, conversion_factor: item.conversion_factor, weight_per_unit: item.weight_per_unit, @@ -712,26 +711,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); }, - add_taxes_from_item_tax_template: function(item_tax_map) { - let me = this; - - if(item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) { - if(typeof (item_tax_map) == "string") { - item_tax_map = JSON.parse(item_tax_map); - } - - $.each(item_tax_map, function(tax, rate) { - let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax); - if(!found) { - let child = frappe.model.add_child(me.frm.doc, "taxes"); - child.charge_type = "On Net Total"; - child.account_head = tax; - child.rate = 0; - } - }); - } - }, - serial_no: function(doc, cdt, cdn) { var me = this; var item = frappe.get_doc(cdt, cdn); @@ -835,9 +814,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.run_serially([ () => me.frm.script_manager.trigger("currency"), - () => me.update_item_tax_map(), () => me.apply_default_taxes(), - () => me.apply_pricing_rule() + () => me.apply_pricing_rule(), + () => me.calculate_taxes_and_totals() ]); } } @@ -1816,7 +1795,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if(!r.exc) { item.item_tax_rate = r.message; - me.add_taxes_from_item_tax_template(item.item_tax_rate); me.calculate_taxes_and_totals(); } } @@ -1827,43 +1805,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - update_item_tax_map: function() { - var me = this; - var item_codes = []; - $.each(this.frm.doc.items || [], function(i, item) { - if(item.item_code) { - item_codes.push(item.item_code); - } - }); - if(item_codes.length) { - return this.frm.call({ - method: "erpnext.stock.get_item_details.get_item_tax_info", - args: { - company: me.frm.doc.company, - tax_category: cstr(me.frm.doc.tax_category), - item_codes: item_codes - }, - callback: function(r) { - if(!r.exc) { - $.each(me.frm.doc.items || [], function(i, item) { - if(item.item_code && r.message.hasOwnProperty(item.item_code)) { - if (!item.item_tax_template) { - item.item_tax_template = r.message[item.item_code].item_tax_template; - item.item_tax_rate = r.message[item.item_code].item_tax_rate; - } - me.add_taxes_from_item_tax_template(item.item_tax_rate); - } else { - item.item_tax_template = ""; - item.item_tax_rate = "{}"; - } - }); - me.calculate_taxes_and_totals(); - } - } - }); - } - }, is_recurring: function() { // set default values for recurring documents diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index dbac79465e..60f468e82e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -128,6 +128,7 @@ class Item(WebsiteGenerator): self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() self.update_show_in_website() + self.validate_item_tax_net_rate_range() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -490,6 +491,11 @@ class Item(WebsiteGenerator): if self.disabled: self.show_in_website = False + def validate_item_tax_net_rate_range(self): + for tax in self.get('taxes'): + if tax.maximum_net_rate < tax.minimum_net_rate: + frappe.throw(_("Row #{0}: Maximum Net Rate cannot be greater than Minimum Net Rate")) + def update_template_tables(self): template = frappe.get_doc("Item", self.variant_of) diff --git a/erpnext/stock/doctype/item_tax/item_tax.json b/erpnext/stock/doctype/item_tax/item_tax.json index ae36efc7e3..fb100967f3 100644 --- a/erpnext/stock/doctype/item_tax/item_tax.json +++ b/erpnext/stock/doctype/item_tax/item_tax.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2013-02-22 01:28:01", "doctype": "DocType", "editable_grid": 1, @@ -6,7 +7,9 @@ "field_order": [ "item_tax_template", "tax_category", - "valid_from" + "valid_from", + "minimum_net_rate", + "maximum_net_rate" ], "fields": [ { @@ -33,11 +36,24 @@ "fieldtype": "Date", "in_list_view": 1, "label": "Valid From" + }, + { + "fieldname": "maximum_net_rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Maximum Net Rate" + }, + { + "fieldname": "minimum_net_rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Minimum Net Rate" } ], "idx": 1, "istable": 1, - "modified": "2020-06-25 01:40:28.859752", + "links": [], + "modified": "2021-06-03 13:20:06.982303", "modified_by": "Administrator", "module": "Stock", "name": "Item Tax", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index d1dcdc21c8..6f510539a4 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -436,18 +436,22 @@ def get_barcode_data(items_list): return itemwise_barcode @frappe.whitelist() -def get_item_tax_info(company, tax_category, item_codes): +def get_item_tax_info(company, tax_category, item_codes, item_rates=None): out = {} if isinstance(item_codes, string_types): item_codes = json.loads(item_codes) + if isinstance(item_rates, string_types): + item_rates = json.loads(item_rates) + for item_code in item_codes: - if not item_code or item_code in out: + if not item_code or item_code[1] in out: continue - out[item_code] = {} - item = frappe.get_cached_doc("Item", item_code) - get_item_tax_template({"company": company, "tax_category": tax_category}, item, out[item_code]) - out[item_code]["item_tax_rate"] = get_item_tax_map(company, out[item_code].get("item_tax_template"), as_json=True) + out[item_code[1]] = {} + item = frappe.get_cached_doc("Item", item_code[0]) + args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]]} + get_item_tax_template(args, item, out[item_code[1]]) + out[item_code[1]]["item_tax_rate"] = get_item_tax_map(company, out[item_code[1]].get("item_tax_template"), as_json=True) return out @@ -478,12 +482,13 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): for tax in taxes: tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company') - if tax.valid_from and tax_company == args['company']: + if (tax.valid_from or tax.maximum_net_rate) and tax_company == args['company']: # In purchase Invoice first preference will be given to supplier invoice date # if supplier date is not present then posting date validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') - if getdate(tax.valid_from) <= getdate(validation_date): + if getdate(tax.valid_from) <= getdate(validation_date) \ + and is_within_valid_range(args, tax): taxes_with_validity.append(tax) else: if tax_company == args['company']: @@ -502,12 +507,25 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): if not taxes_with_validity and (not taxes_with_no_validity): return None + # do not change if already a valid template + if args.get('item_tax_template') in taxes: + return arg.get('item_tax_template') + for tax in taxes: if cstr(tax.tax_category) == cstr(args.get("tax_category")): out["item_tax_template"] = tax.item_tax_template return tax.item_tax_template return None +def is_within_valid_range(args, tax): + if not flt(tax.maximum_net_rate): + # No range specified, just ignore + return True + elif flt(tax.minimum_net_rate) <= flt(args.get('net_rate')) <= flt(tax.maximum_net_rate): + return True + + return False + @frappe.whitelist() def get_item_tax_map(company, item_tax_template, as_json=True): item_tax_map = {} From e8a78bd43d027ea36ff2902ecf48835b76109038 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 5 Jun 2021 13:16:39 +0530 Subject: [PATCH 0968/1264] fix: Add test cases --- .../sales_invoice/test_sales_invoice.py | 69 +++++++++++++------ erpnext/stock/doctype/item/item.py | 2 +- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index df6d483904..1b1bb3b18f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1963,6 +1963,54 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(value_details['TotInvVal'], si.base_grand_total) self.assertTrue(einvoice['EwbDtls']) + def test_item_tax_validity(self): + item = frappe.get_doc("Item", "_Test Item 2") + + if item.taxes: + item.taxes = [] + item.save() + + item.append("taxes", { + "item_tax_template": "_Test Item Tax Template 1 - _TC", + "valid_from": add_days(nowdate(), 1) + }) + + item.save() + + sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1) + sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC" + self.assertRaises(frappe.ValidationError, sales_invoice.save) + + item.taxes = [] + item.save() + + def test_item_tax_net_range(self): + item = create_item("T Shirt") + + item.set('taxes', []) + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", + "minimum_net_rate": 0, + "maximum_net_rate": 500 + }) + + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", + "minimum_net_rate": 501, + "maximum_net_rate": 1000 + }) + + item.save() + + sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True) + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") + + # Apply discount + sales_invoice.apply_discount_on = 'Net Total' + sales_invoice.discount_amount = 300 + sales_invoice.save() + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): address = frappe.get_doc({ @@ -2085,27 +2133,6 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): doc.assertEqual(expected_gle[i][2], gle.credit) doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) - def test_item_tax_validity(self): - item = frappe.get_doc("Item", "_Test Item 2") - - if item.taxes: - item.taxes = [] - item.save() - - item.append("taxes", { - "item_tax_template": "_Test Item Tax Template 1 - _TC", - "valid_from": add_days(nowdate(), 1) - }) - - item.save() - - sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1) - sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC" - self.assertRaises(frappe.ValidationError, sales_invoice.save) - - item.taxes = [] - item.save() - def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") args = frappe._dict(args) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 60f468e82e..0420ea7c54 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -493,7 +493,7 @@ class Item(WebsiteGenerator): def validate_item_tax_net_rate_range(self): for tax in self.get('taxes'): - if tax.maximum_net_rate < tax.minimum_net_rate: + if flt(tax.maximum_net_rate) < flt(tax.minimum_net_rate): frappe.throw(_("Row #{0}: Maximum Net Rate cannot be greater than Minimum Net Rate")) def update_template_tables(self): From 3646c301edc1380efcd0eedcd590e8124dfe90d8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 5 Jun 2021 13:21:03 +0530 Subject: [PATCH 0969/1264] fix: Linting issues --- .../public/js/controllers/taxes_and_totals.js | 25 +++++++++---------- erpnext/stock/get_item_details.py | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index ff4898eeff..0b9d771119 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -12,7 +12,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) { effective_item_rate = item.blanket_order_rate; } - if(item.margin_type == "Percentage") { + if (item.margin_type == "Percentage") { item.rate_with_margin = flt(effective_item_rate) + flt(effective_item_rate) * ( flt(item.margin_rate_or_amount) / 100); } else { @@ -22,7 +22,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ item_rate = flt(item.rate_with_margin , precision("rate", item)); - if(item.discount_percentage){ + if (item.discount_percentage) { item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100; } @@ -271,14 +271,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ let item_codes = []; let item_rates = {}; $.each(this.frm.doc.items || [], function(i, item) { - if(item.item_code) { + if (item.item_code) { // Use combination of name and item code in case same item is added multiple times item_codes.push([item.item_code, item.name]); item_rates[item.name] = item.net_rate; } }); - if(item_codes.length) { + if (item_codes.length) { return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_tax_info", args: { @@ -288,21 +288,18 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ item_rates: item_rates }, callback: function(r) { - if(!r.exc) { + if (!r.exc) { $.each(me.frm.doc.items || [], function(i, item) { - if(item.name && r.message.hasOwnProperty(item.name)) { + if (item.name && r.message.hasOwnProperty(item.name)) { item.item_tax_template = r.message[item.name].item_tax_template; item.item_tax_rate = r.message[item.name].item_tax_rate; me.add_taxes_from_item_tax_template(item.item_tax_rate); - } - else { + } else { item.item_tax_template = ""; item.item_tax_rate = "{}"; } }); } - - this.frm.refresh_fields(); } }); } @@ -311,14 +308,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ add_taxes_from_item_tax_template: function(item_tax_map) { let me = this; - if(item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) { - if(typeof (item_tax_map) == "string") { + if (item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) { + if (typeof (item_tax_map) == "string") { item_tax_map = JSON.parse(item_tax_map); } $.each(item_tax_map, function(tax, rate) { let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax); - if(!found) { + if (!found) { let child = frappe.model.add_child(me.frm.doc, "taxes"); child.charge_type = "On Net Total"; child.account_head = tax; @@ -632,6 +629,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); }); } + + this.frm.refresh_fields(); }, set_discount_amount: function() { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 6f510539a4..746cbbf601 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -509,7 +509,7 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): # do not change if already a valid template if args.get('item_tax_template') in taxes: - return arg.get('item_tax_template') + return args.get('item_tax_template') for tax in taxes: if cstr(tax.tax_category) == cstr(args.get("tax_category")): From a85c2c16b42d0ba5bc5f0157eaf87a062052ab6b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 6 Jun 2021 11:07:20 +0530 Subject: [PATCH 0970/1264] fix(pos): item rate precision in item selector --- erpnext/selling/page/point_of_sale/pos_item_selector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 3e3377e5dc..64c529ee4a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -80,6 +80,7 @@ erpnext.PointOfSale.ItemSelector = class { // eslint-disable-next-line no-unused-vars const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; + const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0; let qty_to_display = actual_qty; @@ -121,7 +122,7 @@ erpnext.PointOfSale.ItemSelector = class {
    ${frappe.ellipsis(item.item_name, 18)}
    -
    ${format_currency(price_list_rate, item.currency, 0) || 0}
    +
    ${format_currency(price_list_rate, item.currency, precision) || 0}
    ` ); From bc46a9772d7daaa50d0ecdac301e604cb43b5878 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 6 Jun 2021 11:11:07 +0530 Subject: [PATCH 0971/1264] fix(pos): cash shortcuts not working --- erpnext/selling/page/point_of_sale/pos_payment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 600f160490..156fb777fe 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -171,7 +171,7 @@ erpnext.PointOfSale.Payment = class { this.setup_listener_for_payments(); - this.$payment_modes.on('click', '.shortcut', () => { + this.$payment_modes.on('click', '.shortcut', function() { const value = $(this).attr('data-value'); me.selected_mode.set_value(value); }); From 3f32969bee847e9a3abfc89ae2c7c84c07a7e53e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 6 Jun 2021 11:18:20 +0530 Subject: [PATCH 0972/1264] fix(pos): broken image in item details section --- .../selling/page/point_of_sale/pos_item_details.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index df62696c4b..5e09df8efe 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -133,13 +133,24 @@ erpnext.PointOfSale.ItemDetails = class { 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}`); + this.$item_image.html( + `${frappe.get_abbr(item_name)}` + ); } else { this.$item_image.html(`
    ${frappe.get_abbr(item_name)}
    `); } } + handle_broken_image($img) { + const item_abbr = $($img).attr('alt'); + $($img).replaceWith(`
    ${item_abbr}
    `); + } + render_discount_dom(item) { if (item.discount_percentage) { this.$dicount_section.html( From 18be767f75b9c858fa66294d037f1a9b9d2c6ace Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 6 Jun 2021 13:25:34 +0530 Subject: [PATCH 0973/1264] fix: Remove invalid test --- .../sales_invoice/test_sales_invoice.py | 21 ------------------- erpnext/controllers/taxes_and_totals.py | 11 +++++----- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 1b1bb3b18f..0d61f67e52 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1963,27 +1963,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(value_details['TotInvVal'], si.base_grand_total) self.assertTrue(einvoice['EwbDtls']) - def test_item_tax_validity(self): - item = frappe.get_doc("Item", "_Test Item 2") - - if item.taxes: - item.taxes = [] - item.save() - - item.append("taxes", { - "item_tax_template": "_Test Item Tax Template 1 - _TC", - "valid_from": add_days(nowdate(), 1) - }) - - item.save() - - sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1) - sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC" - self.assertRaises(frappe.ValidationError, sales_invoice.save) - - item.taxes = [] - item.save() - def test_item_tax_net_range(self): item = create_item("T Shirt") diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 8040435634..fb22a1d608 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -78,11 +78,12 @@ class calculate_taxes_and_totals(object): taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True) - if item.item_tax_template not in taxes: - item.item_tax_template = taxes[0] - frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format( - item.idx, frappe.bold(item.item_code) - )) + if taxes: + if item.item_tax_template not in taxes: + item.item_tax_template = taxes[0] + frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format( + item.idx, frappe.bold(item.item_code) + )) def validate_conversion_rate(self): # validate conversion rate From 7a20c3b92ae05395bf75703a1e6aaad31f537a79 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 6 Jun 2021 19:23:21 +0530 Subject: [PATCH 0974/1264] fix: Ignore internal transfer inoices from GST Reports --- erpnext/regional/report/gstr_1/gstr_1.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index b7c096248f..80e2d725a2 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -147,6 +147,13 @@ class Gstr1Report(object): def get_invoice_data(self): self.invoices = frappe._dict() conditions = self.get_conditions() + + company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True) + + self.filters.update({ + 'company_gstins': company_gstins + }) + invoice_data = frappe.db.sql(""" select {select_columns} @@ -193,6 +200,9 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ + + conditions += " AND billing_address_gstin NOT IN %(company_gstins)s" + return conditions def get_invoice_items(self): @@ -810,7 +820,8 @@ def get_rate_and_tax_details(row, gstin): return {"num": int(num), "itm_det": itm_det} -def get_company_gstin_number(company, address=None): +def get_company_gstin_number(company, address=None, all_gstins=False): + gstin = '' if address: gstin = frappe.db.get_value("Address", address, "gstin") @@ -822,9 +833,9 @@ def get_company_gstin_number(company, address=None): ["Dynamic Link", "parenttype", "=", "Address"], ] gstin = frappe.get_all("Address", filters=filters, pluck="gstin") - if gstin: - gstin[0] - + if gstin and not all_gstins: + gstin = gstin[0] + if not gstin: address = frappe.bold(address) if address else "" frappe.throw(_("Please set valid GSTIN No. in Company Address {} for company {}").format( From 23b907df1af0a84a25954079afeac1179eccdea4 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 7 Jun 2021 13:52:26 +0530 Subject: [PATCH 0975/1264] fix: use stock value diff for calculation --- .../cogs_by_item_group/cogs_by_item_group.py | 127 ++++++++++-------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index d4ddd595d9..7599da4322 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -9,7 +9,6 @@ from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries def execute(filters=None): - print(filters) validate_filters(filters) columns = get_columns() data = get_data(filters) @@ -17,9 +16,6 @@ def execute(filters=None): def validate_filters(filters): - if not filters.get("from_date") and not filters.get("to_date"): - frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) - if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) @@ -42,110 +38,100 @@ def get_columns(): def get_data(filters): - entries = get_filtered_entries(filters) - item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) - item_groups_dict = get_item_groups_dict(item_groups_list) - levels_dict = get_levels_dict(item_groups_dict) + filtered_entries = get_filtered_entries(filters) + svd_list = get_stock_value_difference_list(filtered_entries) + leveled_dict = get_leveled_dict() - update_levels_dict(levels_dict) - assign_self_values(levels_dict, entries) - assign_agg_values(levels_dict) + assign_self_values(leveled_dict, svd_list) + assign_agg_values(leveled_dict) data = [] - for _, i in levels_dict.items(): + for _, i in leveled_dict.items(): if i['agg_value'] == 0: continue data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level'])) if i['self_value'] < i['agg_value'] and i['self_value'] > 0: data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1)) + # append_blank() return data def get_filtered_entries(filters): gl_entries = get_gl_entries(filters, []) - entries = [frappe.get_doc(gle.voucher_type, gle.voucher_no)for gle in gl_entries] filtered_entries = [] - for entry in entries: - posting_date = entry.get("posting_date") - from_date = filters.get("from_date") + for entry in gl_entries: + posting_date = entry.get('posting_date') + from_date = filters.get('from_date') if date_diff(from_date, posting_date) > 0: continue filtered_entries.append(entry) return filtered_entries -def append_blank(data): - if len(data) == 0: - data.append(get_row("", 0, 0, 0)) +def get_stock_value_difference_list(filtered_entries): + voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] + svd_list = frappe.get_list('Stock Ledger Entry', + fields=['item_code','stock_value_difference'], + filters=[('voucher_no', 'in', voucher_nos)]) + assign_item_groups_to_svd_list(svd_list) + return svd_list -def get_item_groups_dict(item_groups_list): - return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} - for i in item_groups_list } - - -def get_levels_dict(item_groups_dict): - lr_list = sorted(item_groups_dict, key=lambda x : x[0]) - levels = OrderedDict() +def get_leveled_dict(): + item_groups_dict = get_item_groups_dict() + lr_list = sorted(item_groups_dict, key=lambda x : int(x[0])) + leveled_dict = OrderedDict() current_level = 0 nesting_r = [] - for l,r in lr_list: + for l, r in lr_list: while current_level > 0 and nesting_r[-1] < l: nesting_r.pop() current_level -= 1 - levels[(l,r)] = { + leveled_dict[(l,r)] = { 'level' : current_level, 'name' : item_groups_dict[(l,r)]['name'], 'is_group' : item_groups_dict[(l,r)]['is_group'] } - if r - l > 1: + if int(r) - int(l) > 1: current_level += 1 nesting_r.append(r) - return levels - -def update_levels_dict(levels_dict): - for k in levels_dict: levels_dict[k].update({'self_value':0, 'agg_value':0}) + update_leveled_dict(leveled_dict) + return leveled_dict -def assign_self_values(levels_dict, entries): - names_dict = {v['name']:k for k, v in levels_dict.items()} - for entry in entries: - items = entry.get("items") - items = [] if items is None else items - for item in items: - qty = item.get("qty") - incoming_rate = item.get("incoming_rate") - item_group = item.get("item_group") - key = names_dict[item_group] - levels_dict[key]['self_value'] += (incoming_rate * qty) +def assign_self_values(leveled_dict, svd_list): + key_dict = {v['name']:k for k, v in leveled_dict.items()} + for item in svd_list: + key = key_dict[item.get("item_group")] + leveled_dict[key]['self_value'] += -item.get("stock_value_difference") -def assign_agg_values(levels_dict): - keys = list(levels_dict.keys())[::-1] - prev_level = levels_dict[keys[-1]]['level'] +def assign_agg_values(leveled_dict): + keys = list(leveled_dict.keys())[::-1] + prev_level = leveled_dict[keys[-1]]['level'] accu = [0] for k in keys[:-1]: - curr_level = levels_dict[k]['level'] + curr_level = leveled_dict[k]['level'] if curr_level == prev_level: - accu[-1] += levels_dict[k]['self_value'] - levels_dict[k]['agg_value'] = levels_dict[k]['self_value'] + accu[-1] += leveled_dict[k]['self_value'] + leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value'] elif curr_level > prev_level: - accu.append(levels_dict[k]['self_value']) - levels_dict[k]['agg_value'] = accu[-1] + accu.append(leveled_dict[k]['self_value']) + leveled_dict[k]['agg_value'] = accu[-1] elif curr_level < prev_level: - accu[-1] += levels_dict[k]['self_value'] - levels_dict[k]['agg_value'] = accu[-1] + accu[-1] += leveled_dict[k]['self_value'] + leveled_dict[k]['agg_value'] = accu[-1] prev_level = curr_level # root node rk = keys[-1] - levels_dict[rk]['agg_value'] = sum(accu) + levels_dict[rk]['self_value'] + leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value'] def get_row(name:str, value:float, is_bold:int, indent:int): @@ -153,3 +139,32 @@ def get_row(name:str, value:float, is_bold:int, indent:int): if is_bold: item_group = frappe.bold(item_group) return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent) + + +def assign_item_groups_to_svd_list(svd_list): + ig_map = get_item_groups_map(svd_list) + for item in svd_list: + item.item_group = ig_map[item.get("item_code")] + +def get_item_groups_map(svd_list): + # for items in svd_list: [{'item_code':'item_group'}] + item_codes = set([i['item_code'] for i in svd_list]) + ig_list = frappe.get_list('Item', + fields=['item_code','item_group'], + filters=[('item_code', 'in', item_codes)]) + return {i['item_code']:i['item_group'] for i in ig_list} + + +def append_blank(data): + if len(data) == 0: + data.append(get_row("", 0, 0, 0)) + + +def get_item_groups_dict(): + item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) + return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} + for i in item_groups_list } + + +def update_leveled_dict(leveled_dict): + for k in leveled_dict: leveled_dict[k].update({'self_value':0, 'agg_value':0}) From 6f79c4c3481b89fa080e69e5ce5a567b1610ea13 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 7 Jun 2021 13:58:45 +0530 Subject: [PATCH 0976/1264] fix: add account filter --- .../cogs_by_item_group/cogs_by_item_group.js | 35 ++++++++++--------- .../cogs_by_item_group/cogs_by_item_group.py | 6 ++++ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js index c17da4ed97..bb780e50b2 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -2,8 +2,9 @@ // For license information, please see license.txt /* eslint-disable */ + frappe.query_reports["COGS By Item Group"] = { - "filters": [ + filters: [ { label: __("Company"), fieldname: "company", @@ -12,22 +13,22 @@ frappe.query_reports["COGS By Item Group"] = { mandatory: true, default: frappe.defaults.get_user_default("Company"), }, - { - label: __("Account"), - fieldname: "account", - fieldtype: "Link", - options: "Account", - mandatory: true, - get_query() { - var company = frappe.query_report.get_filter_value('company'); - return { - "doctype": "Account", - "filters": { - "company": company, - } - } - }, - }, + // { + // label: __("Account"), + // fieldname: "account", + // fieldtype: "Link", + // options: "Account", + // mandatory: true, + // get_query() { + // const company = frappe.query_report.get_filter_value('company'); + // return { + // "doctype": "Account", + // "filters": { + // "company": company, + // } + // } + // }, + // }, { label: __("From Date"), fieldname: "from_date", diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index 7599da4322..e2c6f7928c 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -9,12 +9,18 @@ from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries def execute(filters=None): + update_filters_with_account(filters) validate_filters(filters) columns = get_columns() data = get_data(filters) return columns, data +def update_filters_with_account(filters): + account = frappe.get_value("Company", filters.get("company"), "default_expense_account") + filters.update(dict(account=account)) + + def validate_filters(filters): if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) From d4398fd84aee28ef82c98ab44d42082550195c39 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 7 Jun 2021 16:20:21 +0530 Subject: [PATCH 0977/1264] fix: update cost center from pos (#25971) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 023f4b049c..f8b5179d2c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -531,7 +531,7 @@ class SalesInvoice(SellingController): # 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) + profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos, update_data=True) for fname, val in iteritems(profile_details): if (not for_validate) or (for_validate and not item.get(fname)): item.set(fname, val) From 447c978757ffeef8ebaeedc2dd34124ed9b45fff Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 28 May 2021 21:28:42 +0530 Subject: [PATCH 0978/1264] fix: choose correct Salary Structure Assignment when getting data for formula eval --- .../doctype/salary_slip/salary_slip.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index afdf081ac8..cc9e8d1043 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -493,8 +493,28 @@ class SalarySlip(TransactionBase): '''Returns data for evaluating formula''' data = frappe._dict() + salary_structure_assignment = frappe.get_value( + "Salary Structure Assignment", + { + "employee": self.employee, + "salary_structure": self.salary_structure, + "from_date": ("<=", self.start_date), + "docstatus": 1, + }, + order_by="from_date desc", + ) + + if not salary_structure_assignment: + frappe.throw( + _("Please assign a Salary Structure for Employee {0} " + "applicable from or before {1} first").format( + frappe.bold(self.employee_name), + frappe.bold(self.start_date) + ) + ) + data.update(frappe.get_doc("Salary Structure Assignment", - {"employee": self.employee, "salary_structure": self.salary_structure}).as_dict()) + salary_structure_assignment).as_dict()) data.update(frappe.get_doc("Employee", self.employee).as_dict()) data.update(self.as_dict()) From 74818c7b624534a39b647476587abb02f84b1e88 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 29 May 2021 00:04:26 +0530 Subject: [PATCH 0979/1264] fix: improve filter for `from_date`; validation for joining and relieving date --- .../doctype/salary_slip/salary_slip.py | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index cc9e8d1043..2b35d94dfc 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -115,10 +115,23 @@ class SalarySlip(TransactionBase): status = "Cancelled" return status - def validate_dates(self): + def validate_dates(self, joining_date=None, relieving_date=None): if date_diff(self.end_date, self.start_date) < 0: frappe.throw(_("To date cannot be before From date")) + if not joining_date: + joining_date, relieving_date = frappe.get_cached_value( + "Employee", + self.employee, + ("date_of_joining", "relieving_date") + ) + + if date_diff(self.end_date, joining_date) < 0: + frappe.throw(_("Cannot create Salary Slip for Employee joining after Payroll Period")) + + if relieving_date and date_diff(relieving_date, self.start_date) < 0: + frappe.throw(_("Cannot create Salary Slip for Employee who has left before Payroll Period")) + def is_rounding_total_disabled(self): return cint(frappe.db.get_single_value("Payroll Settings", "disable_rounded_total")) @@ -154,9 +167,14 @@ class SalarySlip(TransactionBase): if not self.salary_slip_based_on_timesheet: self.get_date_details() - self.validate_dates() - joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, - ["date_of_joining", "relieving_date"]) + + joining_date, relieving_date = frappe.get_cached_value( + "Employee", + self.employee, + ("date_of_joining", "relieving_date") + ) + + self.validate_dates(joining_date, relieving_date) #getin leave details self.get_working_days_details(joining_date, relieving_date) @@ -492,13 +510,21 @@ class SalarySlip(TransactionBase): def get_data_for_eval(self): '''Returns data for evaluating formula''' data = frappe._dict() + employee = frappe.get_doc("Employee", self.employee).as_dict() + + start_date = getdate(self.start_date) + date_to_validate = ( + employee.date_of_joining + if employee.date_of_joining > start_date + else start_date + ) salary_structure_assignment = frappe.get_value( "Salary Structure Assignment", { "employee": self.employee, "salary_structure": self.salary_structure, - "from_date": ("<=", self.start_date), + "from_date": ("<=", date_to_validate), "docstatus": 1, }, order_by="from_date desc", @@ -509,14 +535,14 @@ class SalarySlip(TransactionBase): _("Please assign a Salary Structure for Employee {0} " "applicable from or before {1} first").format( frappe.bold(self.employee_name), - frappe.bold(self.start_date) + frappe.bold(formatdate(date_to_validate)), ) ) data.update(frappe.get_doc("Salary Structure Assignment", salary_structure_assignment).as_dict()) - data.update(frappe.get_doc("Employee", self.employee).as_dict()) + data.update(employee) data.update(self.as_dict()) # set values for components From 0e5e1350b241e9ac0b1c93b92da1d8999cd9723c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 4 Jun 2021 13:51:45 +0530 Subject: [PATCH 0980/1264] perf: use frappe.get_value with wildcard instead of another frappe.get_doc call --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 2b35d94dfc..877503b41c 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -527,7 +527,9 @@ class SalarySlip(TransactionBase): "from_date": ("<=", date_to_validate), "docstatus": 1, }, + "*", order_by="from_date desc", + as_dict=True, ) if not salary_structure_assignment: @@ -539,9 +541,7 @@ class SalarySlip(TransactionBase): ) ) - data.update(frappe.get_doc("Salary Structure Assignment", - salary_structure_assignment).as_dict()) - + data.update(salary_structure_assignment) data.update(employee) data.update(self.as_dict()) From ca205be5ac62ff25815df81bde6e2a8a7852b26c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jun 2021 18:40:54 +0530 Subject: [PATCH 0981/1264] fix: tests --- .../doctype/salary_slip/test_salary_slip.py | 51 +++++++++++-------- .../salary_structure/test_salary_structure.py | 39 +++++++++----- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 01e4170d31..9e7db977ab 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -8,7 +8,6 @@ import erpnext import calendar import random from erpnext.accounts.utils import get_fiscal_year -from frappe.utils.make_random import get_random from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details @@ -155,12 +154,14 @@ class TestSalarySlip(unittest.TestCase): self.assertEqual(ss.gross_pay, 78000) def test_payment_days(self): + from erpnext.payroll.doctype.salary_structure.test_salary_structure import create_salary_structure_assignment + no_of_days = self.get_no_of_days() # Holidays not included in working days frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1) # set joinng date in the same month - make_employee("test_payment_days@salary.com") + employee = make_employee("test_payment_days@salary.com") if getdate(nowdate()).day >= 15: relieving_date = getdate(add_days(nowdate(),-10)) date_of_joining = getdate(add_days(nowdate(),-10)) @@ -174,25 +175,30 @@ class TestSalarySlip(unittest.TestCase): date_of_joining = getdate(nowdate()) relieving_date = getdate(nowdate()) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", date_of_joining) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active") + frappe.db.set_value("Employee", employee, { + "date_of_joining": date_of_joining, + "relieving_date": None, + "status": "Active" + }) - ss = make_employee_salary_slip("test_payment_days@salary.com", "Monthly", "Test Payment Days") + salary_structure = "Test Payment Days" + ss = make_employee_salary_slip("test_payment_days@salary.com", "Monthly", salary_structure) self.assertEqual(ss.total_working_days, no_of_days[0]) self.assertEqual(ss.payment_days, (no_of_days[0] - getdate(date_of_joining).day + 1)) # set relieving date in the same month - frappe.db.set_value("Employee",frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60))) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", relieving_date) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Left") + frappe.db.set_value("Employee", employee, { + "date_of_joining": add_days(nowdate(),-60), + "relieving_date": relieving_date, + "status": "Left" + }) + + if date_of_joining.day > 1: + self.assertRaises(frappe.ValidationError, ss.save) + + create_salary_structure_assignment(employee, salary_structure) + ss.reload() ss.save() self.assertEqual(ss.total_working_days, no_of_days[0]) @@ -285,6 +291,7 @@ class TestSalarySlip(unittest.TestCase): def test_multi_currency_salary_slip(self): from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + applicant = make_employee("test_multi_currency_salary_slip@salary.com", company="_Test Company") frappe.db.sql("""delete from `tabSalary Structure` where name='Test Multi Currency Salary Slip'""") salary_structure = make_salary_structure("Test Multi Currency Salary Slip", "Monthly", employee=applicant, company="_Test Company", currency='USD') @@ -325,7 +332,8 @@ class TestSalarySlip(unittest.TestCase): def test_component_wise_year_to_date_computation(self): from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure - applicant = make_employee("test_ytd@salary.com", company="_Test Company") + employee_name = "test_component_wise_ytd@salary.com" + applicant = make_employee(employee_name, company="_Test Company") payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company") @@ -336,13 +344,13 @@ class TestSalarySlip(unittest.TestCase): "Monthly", employee=applicant, company="_Test Company", currency="INR", payroll_period=payroll_period) # clear salary slip for this employee - frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'") + frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = '%s'" % employee_name) create_salary_slips_for_payroll_period(applicant, salary_structure.name, payroll_period, deduct_random=False, num=3) salary_slips = frappe.get_all("Salary Slip", fields=["name"], filters={"employee_name": - "test_ytd@salary.com"}, order_by = "posting_date") + employee_name}, order_by="posting_date") year_to_date = dict() for slip in salary_slips: @@ -380,10 +388,10 @@ class TestSalarySlip(unittest.TestCase): from erpnext.payroll.doctype.salary_structure.test_salary_structure import \ make_salary_structure, create_salary_structure_assignment + salary_structure = make_salary_structure("Stucture to test tax", "Monthly", - other_details={"max_benefits": 100000}, test_tax=True) - create_salary_structure_assignment(employee, salary_structure.name, - payroll_period.start_date) + other_details={"max_benefits": 100000}, test_tax=True, + employee=employee, payroll_period=payroll_period) # create salary slip for whole period deducting tax only on last period # to find the total tax amount paid @@ -469,6 +477,7 @@ class TestSalarySlip(unittest.TestCase): def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + if not salary_structure: salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index 36387f23df..26cd9922e4 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -6,7 +6,7 @@ import frappe import unittest import erpnext from frappe.utils.make_random import get_random -from frappe.utils import nowdate, add_days, add_years, getdate, add_months +from frappe.utils import nowdate, add_days, add_years, getdate, add_months, get_first_day, date_diff from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\ make_deduction_salary_component, make_employee_salary_slip, create_tax_slab @@ -113,8 +113,9 @@ class TestSalaryStructure(unittest.TestCase): sal_struct = make_salary_structure("Salary Structure Multi Currency", "Monthly", currency='USD') self.assertEqual(sal_struct.currency, 'USD') -def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None, - test_tax=False, company=None, currency=erpnext.get_default_currency(), payroll_period=None): +def make_salary_structure(salary_structure, payroll_frequency, employee=None, + from_date=None, dont_submit=False, other_details=None,test_tax=False, + company=None, currency=erpnext.get_default_currency(), payroll_period=None): if test_tax: frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) @@ -139,10 +140,23 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do else: salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure) + filters = {'employee':employee, 'docstatus': 1} + if not from_date and payroll_period: + from_date = payroll_period.start_date + + if from_date: + filters['from_date'] = from_date + if employee and not frappe.db.get_value("Salary Structure Assignment", - {'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1: - create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency, - payroll_period=payroll_period) + filters) and salary_structure_doc.docstatus==1: + create_salary_structure_assignment( + employee, + salary_structure, + from_date=from_date, + company=company, + currency=currency, + payroll_period=payroll_period + ) return salary_structure_doc @@ -165,12 +179,13 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non salary_structure_assignment.base = 50000 salary_structure_assignment.variable = 5000 - if getdate(nowdate()).day == 1: - date = from_date or nowdate() - else: - date = from_date or add_days(nowdate(), -1) + if not from_date: + from_date = get_first_day(nowdate()) + joining_date = frappe.get_cached_value("Employee", employee, "date_of_joining") + if date_diff(joining_date, from_date) > 0: + from_date = joining_date - salary_structure_assignment.from_date = date + salary_structure_assignment.from_date = from_date salary_structure_assignment.salary_structure = salary_structure salary_structure_assignment.currency = currency salary_structure_assignment.payroll_payable_account = get_payable_account(company) @@ -183,4 +198,4 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non def get_payable_account(company=None): if not company: company = erpnext.get_default_company() - return frappe.db.get_value("Company", company, "default_payroll_payable_account") \ No newline at end of file + return frappe.db.get_value("Company", company, "default_payroll_payable_account") From 062e247353a2277727050c648a4633a2d24fb1b8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jun 2021 19:34:02 +0530 Subject: [PATCH 0982/1264] test: remove unused imports --- .../payroll/doctype/salary_structure/test_salary_structure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index 26cd9922e4..dce6b7aa3d 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -6,7 +6,7 @@ import frappe import unittest import erpnext from frappe.utils.make_random import get_random -from frappe.utils import nowdate, add_days, add_years, getdate, add_months, get_first_day, date_diff +from frappe.utils import nowdate, add_years, get_first_day, date_diff from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\ make_deduction_salary_component, make_employee_salary_slip, create_tax_slab From e4fcc5562f3e4a26177eacc9d455596da1135aa8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 8 Jun 2021 16:35:47 +0530 Subject: [PATCH 0983/1264] refactor: Advance taxes and charges calculation --- .../advance_taxes_and_charges.json | 11 +- .../doctype/payment_entry/payment_entry.js | 162 +++++++++++++---- .../doctype/payment_entry/payment_entry.py | 163 +++++++++++++----- 3 files changed, 255 insertions(+), 81 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index c127601259..3e4e679c91 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -19,6 +19,7 @@ "section_break_8", "rate", "section_break_9", + "currency", "tax_amount", "total", "allocated_amount", @@ -172,12 +173,20 @@ "fieldtype": "Currency", "label": "Allocated Amount (Company Currency)", "options": "Company:company:default_currency" + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-31 02:03:57.444647", + "modified": "2021-06-08 07:04:44.287002", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index b1cf9e02e6..6a3b717b12 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -1071,7 +1071,7 @@ frappe.ui.form.on('Payment Entry', { } me.frm.add_child("taxes", tax); } - frm.trigger('calculate_taxes'); + frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); } } @@ -1079,52 +1079,112 @@ frappe.ui.form.on('Payment Entry', { }); }, + apply_taxes: function(frm) { + frm.events.initialize_taxes(frm); + frm.events.determine_exclusive_rate(frm); + frm.events.calculate_taxes(frm); + }, + + initialize_taxes: function(frm) { + $.each(frm.doc["taxes"] || [], function(i, tax) { + tax.item_wise_tax_detail = {}; + let tax_fields = ["total", "tax_fraction_for_current_item", + "grand_total_fraction_for_current_item"]; + + if (cstr(tax.charge_type) != "Actual") { + tax_fields.push("tax_amount"); + } + + $.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; }); + + frm.doc.paid_amount_after_tax = frm.doc.paid_amount; + }); + }, + + determine_exclusive_rate: function(frm) { + let has_inclusive_tax = false; + $.each(frm.doc["taxes"] || [], function(i, row) { + if(cint(row.included_in_paid_amount)) has_inclusive_tax = true; + }); + if(has_inclusive_tax==false) return; + + let cumulated_tax_fraction = 0.0; + $.each(frm.doc["taxes"] || [], function(i, tax) { + let current_tax_fraction = frm.events.get_current_tax_fraction(frm, tax); + tax.tax_fraction_for_current_item = current_tax_fraction[0]; + + if(i==0) { + tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; + } else { + tax.grand_total_fraction_for_current_item = + me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item + + tax.tax_fraction_for_current_item; + } + + cumulated_tax_fraction += tax.tax_fraction_for_current_item; + frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction)) + }); + }, + + get_current_tax_fraction: function(frm, tax) { + let current_tax_fraction = 0.0; + + if(cint(tax.included_in_paid_amount)) { + let tax_rate = tax.rate; + + if (tax.charge_type == "Actual") { + current_tax_fraction = tax.tax_amount/frm.doc.paid_amount_after_tax; + } else if(tax.charge_type == "On Paid Amount") { + current_tax_fraction = (tax_rate / 100.0); + } else if(tax.charge_type == "On Previous Row Amount") { + current_tax_fraction = (tax_rate / 100.0) * + frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item; + } else if(tax.charge_type == "On Previous Row Total") { + current_tax_fraction = (tax_rate / 100.0) * + frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item; + } + } + + if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { + current_tax_fraction *= -1; + inclusive_tax_amount_per_qty *= -1; + } + return current_tax_fraction; + }, + + calculate_taxes: function(frm) { frm.doc.total_taxes_and_charges = 0.0; frm.doc.base_total_taxes_and_charges = 0.0; - $.each(me.frm.doc["taxes"] || [], function(i, tax) { - let tax_rate = tax.rate; - let current_tax_amount = 0.0; + let actual_tax_dict = {}; - // To set row_id by default as previous row. - if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) { - if (tax.idx === 1) { - frappe.throw(__("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")); - } - if (!tax.row_id) { - tax.row_id = tax.idx - 1; - } + // maintain actual tax rate based on idx + $.each(frm.doc["taxes"] || [], function(i, tax) { + if (tax.charge_type == "Actual") { + actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax)); } + }); - if(tax.charge_type == "Actual") { - current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax)); - } else if(tax.charge_type == "On Paid Amount") { - current_tax_amount = (tax_rate / 100.0) * frm.doc.paid_amount; - } else if(tax.charge_type == "On Previous Row Amount") { - current_tax_amount = (tax_rate / 100.0) * - frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount; + $.each(me.frm.doc["taxes"] || [], function(i, tax) { + let current_tax_amount = frm.events.get_current_tax_amount(frm, tax); - } else if(tax.charge_type == "On Previous Row Total") { - current_tax_amount = (tax_rate / 100.0) * - frm.doc["taxes"][cint(tax.row_id) - 1].total; + // Adjust divisional loss to the last item + if (tax.charge_type == "Actual") { + actual_tax_dict[tax.idx] -= current_tax_amount; + if (i == frm.doc["taxes"].length - 1) { + current_tax_amount += actual_tax_dict[tax.idx]; + } } tax.tax_amount = current_tax_amount; tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate; - current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; - let applicable_tax_amount = 0 - - if (!tax.included_in_paid_amount) { - applicable_tax_amount = current_tax_amount - } - if(i==0) { - tax.total = flt(frm.doc.paid_amount + applicable_tax_amount, precision("total", tax)); + tax.total = flt(frm.doc.paid_amount_after_tax + current_tax_amount, precision("total", tax)); } else { - tax.total = flt(frm.doc["taxes"][i-1].total + applicable_tax_amount, precision("total", tax)); + tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax)); } tax.base_total = tax.total * frm.doc.source_exchange_rate; @@ -1136,6 +1196,36 @@ frappe.ui.form.on('Payment Entry', { frm.refresh_field('base_total_taxes_and_charges'); }); }, + + get_current_tax_amount: function(frm, tax) { + let tax_rate = tax.rate; + let current_tax_amount = 0.0; + + // To set row_id by default as previous row. + if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) { + if (tax.idx === 1) { + frappe.throw( + __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")); + } + if (!tax.row_id) { + tax.row_id = tax.idx - 1; + } + } + if(tax.charge_type == "Actual") { + current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax)) + } else if(tax.charge_type == "On Paid Amount") { + current_tax_amount = flt((tax_rate / 100.0) * frm.doc.paid_amount_after_tax); + } else if(tax.charge_type == "On Previous Row Amount") { + current_tax_amount = flt((tax_rate / 100.0) * + frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount); + + } else if(tax.charge_type == "On Previous Row Total") { + current_tax_amount = flt((tax_rate / 100.0) * + frm.doc["taxes"][cint(tax.row_id) - 1].total); + } + + return current_tax_amount; + }, }); @@ -1184,27 +1274,27 @@ frappe.ui.form.on('Payment Entry Reference', { frappe.ui.form.on('Advance Taxes and Charges', { rate: function(frm) { - frm.events.calculate_taxes(frm); + frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); }, tax_amount : function(frm) { - frm.events.calculate_taxes(frm); + frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); }, row_id: function(frm) { - frm.events.calculate_taxes(frm); + frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); }, taxes_remove: function(frm) { - frm.events.calculate_taxes(frm); + frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); }, included_in_paid_amount: function(frm) { - frm.events.calculate_taxes(frm); + frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); } }) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index d960156670..83d52fa5f6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -54,7 +54,7 @@ class PaymentEntry(AccountsController): self.validate_mandatory() self.validate_reference_documents() self.set_tax_withholding() - self.calculate_taxes() + self.apply_taxes() self.set_amounts() self.clear_unallocated_reference_document_rows() self.validate_payment_against_negative_invoice() @@ -65,7 +65,6 @@ class PaymentEntry(AccountsController): self.validate_allocated_amount() self.validate_paid_invoices() self.ensure_supplier_is_not_blocked() - self.set_advance_tax_account() self.set_status() def on_submit(self): @@ -311,14 +310,6 @@ class PaymentEntry(AccountsController): + "

    " + _("If this is undesirable please cancel the corresponding Payment Entry."), title=_("Warning"), indicator="orange") - def set_advance_tax_account(self): - if self.get('taxes') and not self.advance_tax_account: - unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account') - if not unrealized_profit_loss_account: - frappe.throw(_("Please select advance tax account or add Unrealized Profit / Loss Account in company master")) - - self.advance_tax_account = unrealized_profit_loss_account - def validate_journal_entry(self): for d in self.get("references"): if d.allocated_amount and d.reference_doctype == "Journal Entry": @@ -461,6 +452,11 @@ class PaymentEntry(AccountsController): for d in to_remove: self.remove(d) + def apply_taxes(self): + self.initialize_taxes() + self.determine_exclusive_rate() + self.calculate_taxes() + def set_amounts(self): self.set_received_amount() self.set_amounts_in_company_currency() @@ -715,12 +711,12 @@ class PaymentEntry(AccountsController): if account_currency != self.company_currency: frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency)) - if self.payment_type == 'Pay': + if (self.payment_type == 'Pay' and self.advance_tax_account) or self.payment_type == 'Receive': dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" - rev_dr_cr = "credit" if d.add_deduct_tax == "Add" else "debit" - elif self.payment_type == 'Receive': + elif (self.payment_type == 'Receive' and self.advance_tax_account) or self.payment_type == 'Pay': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if d.add_deduct_tax == "Add" else "credit" + + payment_or_advance_account = self.get_party_account_for_taxes() gl_entries.append( self.get_gl_dict({ @@ -733,15 +729,16 @@ class PaymentEntry(AccountsController): "cost_center": d.cost_center }, account_currency, item=d)) + #Intentionally use -1 to get net values in party account gl_entries.append( self.get_gl_dict({ - "account": self.advance_tax_account, + "account": payment_or_advance_account, "against": self.party if self.payment_type=="Receive" else self.paid_from, - rev_dr_cr: d.base_tax_amount, - rev_dr_cr + "_in_account_currency": d.base_tax_amount + dr_or_cr: -1 * d.base_tax_amount, + dr_or_cr + "_in_account_currency": -1*d.base_tax_amount if account_currency==self.company_currency else d.tax_amount, - "cost_center": d.cost_center or self.cost_center + "cost_center": self.cost_center, }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): @@ -762,6 +759,14 @@ class PaymentEntry(AccountsController): }, item=d) ) + def get_party_account_for_taxes(self): + if self.advance_tax_account: + return self.advance_tax_account + elif self.payment_type == 'Pay': + return self.paid_from + elif self.payment_type == 'Receive': + return self.paid_to + def update_advance_paid(self): if self.payment_type in ("Receive", "Pay") and self.party: for d in self.get("references"): @@ -808,32 +813,52 @@ class PaymentEntry(AccountsController): self.append('deductions', row) self.set_unallocated_amount() + def initialize_taxes(self): + for tax in self.get("taxes"): + tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] + + if tax.charge_type != "Actual": + tax_fields.append("tax_amount") + + for fieldname in tax_fields: + tax.set(fieldname, 0.0) + + self.paid_amount_after_tax = self.paid_amount + + def determine_exclusive_rate(self): + if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))): + return + + cumulated_tax_fraction = 0 + total_inclusive_tax_amount_per_qty = 0 + for i, tax in enumerate(self.get("taxes")): + + tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax) + if i==0: + tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item + else: + tax.grand_total_fraction_for_current_item = \ + self.get("taxes")[i-1].grand_total_fraction_for_current_item \ + + tax.tax_fraction_for_current_item + + cumulated_tax_fraction += tax.tax_fraction_for_current_item + + self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction)) + def calculate_taxes(self): self.total_taxes_and_charges = 0.0 self.base_total_taxes_and_charges = 0.0 + actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))] + for tax in self.get("taxes") if tax.charge_type == "Actual"]) + for i, tax in enumerate(self.get('taxes')): - tax_rate = tax.rate - - # To set row_id by default as previous row. - if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]: - if tax.idx == 1: - frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) - - if not tax.row_id: - tax.row_id = tax.idx - 1 + current_tax_amount = self.get_current_tax_amount(tax) if tax.charge_type == "Actual": - current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax)) - elif tax.charge_type == "On Paid Amount": - current_tax_amount = (tax_rate / 100.0) * self.paid_amount - elif tax.charge_type == "On Previous Row Amount": - current_tax_amount = (tax_rate / 100.0) * \ - self.get('taxes')[cint(tax.row_id) - 1].tax_amount - - elif tax.charge_type == "On Previous Row Total": - current_tax_amount = (tax_rate / 100.0) * \ - self.get('taxes')[cint(tax.row_id) - 1].total + actual_tax_dict[tax.idx] -= current_tax_amount + if i == len(self.get("taxes")) - 1: + current_tax_amount += actual_tax_dict[tax.idx] tax.tax_amount = current_tax_amount tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate @@ -843,21 +868,68 @@ class PaymentEntry(AccountsController): else: current_tax_amount *= 1.0 - if not tax.included_in_paid_amount: - applicable_tax = current_tax_amount - else: - applicable_tax = 0 - if i == 0: - tax.total = flt(self.paid_amount + applicable_tax, self.precision("total", tax)) + tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax)) else: - tax.total = flt(self.get('taxes')[i-1].total + applicable_tax, self.precision("total", tax)) + tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax)) tax.base_total = tax.total * self.source_exchange_rate self.total_taxes_and_charges += current_tax_amount self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate + if self.get('taxes'): + self.paid_amount_after_tax = self.get('taxes')[-1].base_total + + def get_current_tax_amount(self, tax): + tax_rate = tax.rate + + # To set row_id by default as previous row. + if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]: + if tax.idx == 1: + frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) + + if not tax.row_id: + tax.row_id = tax.idx - 1 + + if tax.charge_type == "Actual": + current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax)) + elif tax.charge_type == "On Paid Amount": + current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax + elif tax.charge_type == "On Previous Row Amount": + current_tax_amount = (tax_rate / 100.0) * \ + self.get('taxes')[cint(tax.row_id) - 1].tax_amount + + elif tax.charge_type == "On Previous Row Total": + current_tax_amount = (tax_rate / 100.0) * \ + self.get('taxes')[cint(tax.row_id) - 1].total + + return current_tax_amount + + def get_current_tax_fraction(self, tax): + current_tax_fraction = 0 + + if cint(tax.included_in_paid_amount): + tax_rate = tax.rate + + if tax.charge_type == 'Actual': + current_tax_fraction = tax.tax_amount/self.paid_amount_after_tax + elif tax.charge_type == "On Paid Amount": + current_tax_fraction = tax_rate / 100.0 + + elif tax.charge_type == "On Previous Row Amount": + current_tax_fraction = (tax_rate / 100.0) * \ + self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item + + elif tax.charge_type == "On Previous Row Total": + current_tax_fraction = (tax_rate / 100.0) * \ + self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item + + if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct": + current_tax_fraction *= -1.0 + + return current_tax_fraction + @frappe.whitelist() def get_outstanding_reference_documents(args): @@ -1426,6 +1498,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.apply_tax_withholding_amount = 1 pe.tax_withholding_category = doc.tax_withholding_category + if not pe.advance_tax_account: + pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account') + return pe def get_bank_cash_account(doc, bank_account): From bbf07d9214bf86eb120a7ad14989b2bd21a5d9d7 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 8 Jun 2021 17:05:44 +0530 Subject: [PATCH 0984/1264] fix: quiz timer issues --- erpnext/education/utils.py | 13 +++++++------ erpnext/public/js/education/lms/quiz.js | 10 ++++------ erpnext/www/lms/content.html | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 8f51fef847..9d5653c170 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -219,7 +219,6 @@ def get_quiz(quiz_name, course): try: quiz = frappe.get_doc("Quiz", quiz_name) questions = quiz.get_questions() - duration = quiz.duration except: frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError) return None @@ -236,15 +235,17 @@ def get_quiz(quiz_name, course): return { 'questions': questions, 'activity': None, - 'duration':duration + 'is_time_bound': quiz.is_time_bound, + 'duration': quiz.duration } student = get_current_student() course_enrollment = get_enrollment("course", course, student.name) status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment) return { - 'questions': questions, + 'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken}, + 'is_time_bound': quiz.is_time_bound, 'duration': quiz.duration } @@ -372,9 +373,9 @@ def check_content_completion(content_name, content_type, enrollment_name): def check_quiz_completion(quiz, enrollment_name): attempts = frappe.get_all("Quiz Activity", filters={ - 'enrollment': enrollment_name, + 'enrollment': enrollment_name, 'quiz': quiz.name - }, + }, fields=["name", "activity_date", "score", "status", "time_taken"] ) status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) @@ -389,4 +390,4 @@ def check_quiz_completion(quiz, enrollment_name): time_taken = attempts[0]['time_taken'] if result == 'Pass': status = True - return status, score, result, time_taken \ No newline at end of file + return status, score, result, time_taken diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index 32fa4ab1ec..5bb8425c85 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -20,10 +20,8 @@ class Quiz { } make(data) { - if (data.duration) { - const timer_display = document.createElement("div"); - timer_display.classList.add("lms-timer", "float-right", "font-weight-bold"); - document.getElementsByClassName("lms-title")[0].appendChild(timer_display); + if (data.is_time_bound) { + $(".lms-timer").removeClass("hide") if (!data.activity || (data.activity && !data.activity.is_complete)) { this.initialiseTimer(data.duration); this.is_time_bound = true; @@ -118,7 +116,7 @@ class Quiz { quiz_response: this.get_selected(), course: this.course, program: this.program, - time_taken: this.is_time_bound ? this.time_taken : "" + time_taken: this.is_time_bound ? this.time_taken : 0 }).then(res => { this.submit_btn.remove() if (!res.message) { @@ -237,4 +235,4 @@ class Question { this.options = option_list this.wrapper.appendChild(options_wrapper) } -} \ No newline at end of file +} diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index 15afb097b9..d22ef66d2a 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -64,6 +64,7 @@

    {{ content.name }} ({{ position + 1 }}/{{length}})

    +
    {% endmacro %} From 0ea4d850e1c3870c750072e0ce89a6cd73605266 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 8 Jun 2021 17:23:44 +0530 Subject: [PATCH 0985/1264] fix: Allow all System Managers to delete company transactions (#25834) --- .../transaction_deletion_record.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 38f8de7a66..ece9fb5699 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -12,10 +12,6 @@ from frappe.desk.notifications import clear_notifications class TransactionDeletionRecord(Document): def validate(self): frappe.only_for('System Manager') - company_obj = frappe.get_doc('Company', self.company) - if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator': - frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'), - frappe.PermissionError) doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in self.doctypes_to_be_ignored: if doctype.doctype_name not in doctypes_to_be_ignored_list: From 7ace06ac21c7d29d8470df139979c15f46a82ed1 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 8 Jun 2021 18:26:23 +0530 Subject: [PATCH 0986/1264] fix: sider --- erpnext/education/utils.py | 2 +- erpnext/public/js/education/lms/quiz.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 9d5653c170..9db8a4a90d 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -245,7 +245,7 @@ def get_quiz(quiz_name, course): return { 'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken}, - 'is_time_bound': quiz.is_time_bound, + 'is_time_bound': quiz.is_time_bound, 'duration': quiz.duration } diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index 5bb8425c85..66160a7610 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -21,7 +21,7 @@ class Quiz { make(data) { if (data.is_time_bound) { - $(".lms-timer").removeClass("hide") + $(".lms-timer").removeClass("hide"); if (!data.activity || (data.activity && !data.activity.is_complete)) { this.initialiseTimer(data.duration); this.is_time_bound = true; From 18eed58fc5afa1ef95037a1c297b308c34de58ee Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 8 Jun 2021 20:52:14 +0530 Subject: [PATCH 0987/1264] fix(e-invoicing): service item check --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 843fb012b9..a1179ff9b6 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -199,7 +199,7 @@ def get_item_list(invoice): item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None - item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' + item.is_service_item = 'Y' if item.gst_hsn_code[:2] == "99" else 'N' item.serial_no = "" item = update_item_taxes(invoice, item) From 99b583f688966cc89d069f8c5b7c4cf88108abb5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 9 Jun 2021 00:06:35 +0530 Subject: [PATCH 0988/1264] fix: on click of duplicate button system copy the difference account from first row --- erpnext/stock/doctype/stock_entry/stock_entry.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 93a6fc0e0a..1a25994b24 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -986,7 +986,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ items_add: function(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); - this.frm.script_manager.copy_from_first_row("items", row, ["expense_account", "cost_center"]); + + if (!(row.expense_account && row.cost_center)) { + this.frm.script_manager.copy_from_first_row("items", row, ["expense_account", "cost_center"]); + } if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse; if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; From 21b8e2f0d8f18972b05783eface42404cc104c30 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 9 Jun 2021 12:57:21 +0530 Subject: [PATCH 0989/1264] fix: hiding Rounding Adjustment field (#25380) * fix: hiding Rounding Adjustment field * fix: updating purchase_invoice.json --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 4 +++- erpnext/buying/doctype/purchase_order/purchase_order.json | 4 +++- .../buying/doctype/supplier_quotation/supplier_quotation.json | 4 +++- erpnext/stock/doctype/purchase_receipt/purchase_receipt.json | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d3d3ffa17f..9157821520 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -837,6 +837,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -883,6 +884,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -1380,7 +1382,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-04-30 22:45:58.334107", + "modified": "2021-06-09 12:30:25.632109", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ee2beea67f..8677c71bc5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -765,6 +765,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -810,6 +811,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -1124,7 +1126,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-01-20 22:07:23.487138", + "modified": "2021-04-19 00:55:30.781375", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 40fbe2c26e..0a51a8e9a1 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -576,6 +576,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency", @@ -620,6 +621,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -802,7 +804,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-03 15:18:29.073368", + "modified": "2021-04-19 00:58:20.995491", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 32d349f303..ad350d344f 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -762,6 +762,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -805,6 +806,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -1147,7 +1149,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-12-26 20:49:39.106049", + "modified": "2021-04-19 01:01:00.754119", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 8090965162fc314ff8f41d71e5890f8c7f060802 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 9 Jun 2021 13:45:42 +0530 Subject: [PATCH 0990/1264] fix: Debug test --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 8afb9a29f4..5187ca1b5e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1017,6 +1017,9 @@ class TestPurchaseInvoice(unittest.TestCase): where voucher_type='Purchase Invoice' and voucher_no=%s order by account asc""", (purchase_invoice.name), as_dict=1) + for i, gle in enumerate(gl_entries): + print(gle.account, gle.debit, gle.credit) + for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.debit) From 4c1dad820780b88ca7c5710e570003974c11bd54 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 9 Jun 2021 14:18:23 +0530 Subject: [PATCH 0991/1264] fix: Debug tests --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 5187ca1b5e..3299ecc5bc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -991,6 +991,9 @@ class TestPurchaseInvoice(unittest.TestCase): where voucher_type='Payment Entry' and voucher_no=%s order by account asc""", (payment_entry.name), as_dict=1) + for i, gle in enumerate(gl_entries): + print(gle.account, gle.debit, gle.credit) + for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.debit) From b57ebba3fd8af3cb70b16a340d41961ba6eb7223 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 9 Jun 2021 17:56:41 +0530 Subject: [PATCH 0992/1264] fix: Validate negative allocated amount in Payment Entry (#25799) --- .../invoice_discounting/invoice_discounting.py | 7 +++++-- .../doctype/payment_entry/payment_entry.py | 14 ++++++++++---- .../doctype/payment_request/payment_request.py | 3 +-- .../accounts_receivable/accounts_receivable.py | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 7b62b617f9..95d2ee56d9 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -49,11 +49,11 @@ class InvoiceDiscounting(AccountsController): self.make_gl_entries() def on_cancel(self): - self.set_status() + self.set_status(cancel=1) self.update_sales_invoice() self.make_gl_entries() - def set_status(self, status=None): + def set_status(self, status=None, cancel=0): if status: self.status = status self.db_set("status", status) @@ -66,6 +66,9 @@ class InvoiceDiscounting(AccountsController): elif self.docstatus == 2: self.status = "Cancelled" + if cancel: + self.db_set('status', self.status, update_modified = True) + def update_sales_invoice(self): for d in self.invoices: if self.docstatus == 1: diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 62ab76c323..e01c651a93 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -65,7 +65,6 @@ class PaymentEntry(AccountsController): self.set_status() def on_submit(self): - self.setup_party_account_field() if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) self.make_gl_entries() @@ -78,7 +77,6 @@ class PaymentEntry(AccountsController): def on_cancel(self): self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') - self.setup_party_account_field() self.make_gl_entries(cancel=1) self.update_outstanding_amounts() self.update_advance_paid() @@ -122,6 +120,11 @@ class PaymentEntry(AccountsController): if flt(d.allocated_amount) > flt(d.outstanding_amount): frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)) + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0: + if flt(d.allocated_amount) < flt(d.outstanding_amount): + frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)) + def delink_advance_entry_references(self): for reference in self.references: if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"): @@ -177,7 +180,7 @@ class PaymentEntry(AccountsController): for field, value in iteritems(ref_details): if field == 'exchange_rate' or not d.get(field) or force: - d.set(field, value) + d.db_set(field, value) def validate_payment_type(self): if self.payment_type not in ("Receive", "Pay", "Internal Transfer"): @@ -386,6 +389,8 @@ class PaymentEntry(AccountsController): else: self.status = 'Draft' + self.db_set('status', self.status, update_modified = True) + def set_amounts(self): self.set_amounts_in_company_currency() self.set_total_allocated_amount() @@ -791,7 +796,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): outstanding_invoices.pop(idx - 1) outstanding_invoices += invoice_ref_based_on_payment_terms[idx] - + return outstanding_invoices def get_orders_to_be_billed(posting_date, party_type, party, @@ -989,6 +994,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre outstanding_amount = ref_doc.get("outstanding_amount") elif reference_doctype == "Donation": total_amount = ref_doc.get("amount") + outstanding_amount = total_amount exchange_rate = 1 elif reference_doctype == "Dunning": total_amount = ref_doc.get("dunning_amount") diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 53ac996290..468978785b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -101,7 +101,7 @@ class PaymentRequest(Document): controller.validate_transaction_currency(self.currency) controller.request_for_payment(**payment_record) - + def get_request_amount(self): data_of_completed_requests = frappe.get_all("Integration Request", filters={ 'reference_doctype': self.doctype, @@ -492,7 +492,6 @@ def update_payment_req_status(doc, method): status = 'Requested' pay_req_doc.db_set('status', status) - frappe.db.commit() def get_dummy_message(doc): return frappe.render_template("""{% if doc.contact_person -%} diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index db605f7285..a11b77a6f6 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -584,6 +584,7 @@ class ReceivablePayableReport(object): `tabGL Entry` where docstatus < 2 + and is_cancelled = 0 and party_type=%s and (party is not null and party != '') {1} {2} {3}""" From 9e648183dbc52ee526b3a3dd15b1f57110ebccb5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 9 Jun 2021 18:26:16 +0530 Subject: [PATCH 0993/1264] fix: Test case --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 -- .../doctype/purchase_invoice/test_purchase_invoice.py | 8 +------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5796fc0800..d04d6a0d15 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -835,9 +835,7 @@ class PaymentEntry(AccountsController): return cumulated_tax_fraction = 0 - total_inclusive_tax_amount_per_qty = 0 for i, tax in enumerate(self.get("taxes")): - tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax) if i==0: tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 3299ecc5bc..8e72dc6321 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -991,9 +991,6 @@ class TestPurchaseInvoice(unittest.TestCase): where voucher_type='Payment Entry' and voucher_no=%s order by account asc""", (payment_entry.name), as_dict=1) - for i, gle in enumerate(gl_entries): - print(gle.account, gle.debit, gle.credit) - for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.debit) @@ -1011,8 +1008,8 @@ class TestPurchaseInvoice(unittest.TestCase): ['_Test Account Cost for Goods Sold - _TC', 30000, 0], ['_Test Account Excise Duty - _TC', 0, 3000], ['Creditors - _TC', 0, 27000], - ['TDS Payable - _TC', 0, 3000], ['TDS Payable - _TC', 3000, 0] + ['TDS Payable - _TC', 0, 3000], ] gl_entries = frappe.db.sql("""select account, debit, credit @@ -1020,9 +1017,6 @@ class TestPurchaseInvoice(unittest.TestCase): where voucher_type='Purchase Invoice' and voucher_no=%s order by account asc""", (purchase_invoice.name), as_dict=1) - for i, gle in enumerate(gl_entries): - print(gle.account, gle.debit, gle.credit) - for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.debit) From 26f06093903036bdb222e523d3dba12888869086 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 9 Jun 2021 19:47:28 +0530 Subject: [PATCH 0994/1264] feat: enable/disable gl entry posting for change given in pos (#25822) --- .../accounts_settings/accounts_settings.json | 9 +++- .../doctype/sales_invoice/sales_invoice.py | 12 ++++- .../sales_invoice/test_sales_invoice.py | 44 +++++++++++++++++-- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 781f94e203..2735b1ccee 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -18,6 +18,7 @@ "delete_linked_ledger_entries", "book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order", + "post_change_gl_entries", "tax_settings_section", "determine_address_tax_category_from", "column_break_19", @@ -253,6 +254,12 @@ { "fieldname": "column_break_19", "fieldtype": "Column Break" + }, + { + "default": "1", + "fieldname": "post_change_gl_entries", + "fieldtype": "Check", + "label": "Post Ledger Entries for Given Change" } ], "icon": "icon-cog", @@ -260,7 +267,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-30 15:25:10.381008", + "modified": "2021-05-25 12:34:05.858669", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f8b5179d2c..0b8d28aef0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -849,7 +849,6 @@ class SalesInvoice(SellingController): self.make_loyalty_point_redemption_gle(gl_entries) self.make_pos_gl_entries(gl_entries) - self.make_gle_for_change_amount(gl_entries) self.make_write_off_gl_entry(gl_entries) self.make_gle_for_rounding_adjustment(gl_entries) @@ -983,7 +982,13 @@ class SalesInvoice(SellingController): def make_pos_gl_entries(self, gl_entries): if cint(self.is_pos): + + skip_change_gl_entries = not cint(frappe.db.get_single_value('Accounts Settings', 'post_change_gl_entries')) + for payment_mode in self.payments: + if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount: + payment_mode.base_amount -= self.change_amount + if payment_mode.amount: # POS, make payment entries gl_entries.append( @@ -1015,8 +1020,11 @@ class SalesInvoice(SellingController): }, payment_mode_account_currency, item=self) ) + if not skip_change_gl_entries: + self.make_gle_for_change_amount(gl_entries) + def make_gle_for_change_amount(self, gl_entries): - if cint(self.is_pos) and self.change_amount: + if self.change_amount: if self.account_for_change_amount: gl_entries.append( self.get_gl_dict({ diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index df6d483904..5409a6f192 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -713,7 +713,7 @@ class TestSalesInvoice(unittest.TestCase): si.submit() self.assertEqual(si.paid_amount, 100.0) - self.pos_gl_entry(si, pos, 50) + self.validate_pos_gl_entry(si, pos, 50) def test_pos_returns_with_repayment(self): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return @@ -749,7 +749,7 @@ class TestSalesInvoice(unittest.TestCase): make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1") - pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", + 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", @@ -770,7 +770,45 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(pos.grand_total, 100.0) self.assertEqual(pos.write_off_amount, -5) - def pos_gl_entry(self, si, pos, cash_amount): + def test_pos_with_no_gl_entry_for_change_amount(self): + frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 0) + + make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", + expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1") + + 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 + + taxes = get_taxes_and_charges() + pos.taxes = [] + for tax in taxes: + pos.append("taxes", tax) + + 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': 60}) + + pos.insert() + pos.submit() + + self.assertEqual(pos.grand_total, 100.0) + self.assertEqual(pos.change_amount, 10) + + self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True) + + frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 1) + + def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False): + if validate_without_change_gle: + cash_amount -= pos.change_amount + # check stock ledger entries sle = frappe.db.sql("""select * from `tabStock Ledger Entry` where voucher_type = 'Sales Invoice' and voucher_no = %s""", From 42557c4ad387db7ea4956ac80dfaea085614481d Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 9 Jun 2021 19:48:31 +0530 Subject: [PATCH 0995/1264] feat: cost-center wise period closing entry (#25766) --- .../period_closing_voucher.json | 452 +++++------------- .../period_closing_voucher.py | 111 +++-- .../test_period_closing_voucher.py | 85 ++++ 3 files changed, 277 insertions(+), 371 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json index 47546c07a4..84c941ecc1 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -1,350 +1,138 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "ACC-PCV-.YYYY.-.#####", - "beta": 0, - "creation": "2013-01-10 16:34:07", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, - "engine": "InnoDB", + "actions": [], + "autoname": "ACC-PCV-.YYYY.-.#####", + "creation": "2013-01-10 16:34:07", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "transaction_date", + "posting_date", + "fiscal_year", + "amended_from", + "company", + "cost_center_wise_pnl", + "column_break1", + "closing_account_head", + "remarks" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_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": "Transaction Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "transaction_date", - "oldfieldtype": "Date", - "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 - }, + "fieldname": "transaction_date", + "fieldtype": "Date", + "label": "Transaction Date", + "oldfieldname": "transaction_date", + "oldfieldtype": "Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "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": 0, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "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 - }, + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "fiscal_year", - "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": 1, - "label": "Closing Fiscal Year", - "length": 0, - "no_copy": 0, - "oldfieldname": "fiscal_year", - "oldfieldtype": "Select", - "options": "Fiscal Year", - "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 - }, + "fieldname": "fiscal_year", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Closing Fiscal Year", + "oldfieldname": "fiscal_year", + "oldfieldtype": "Select", + "options": "Fiscal Year", + "reqd": 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": 1, - "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, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Period Closing Voucher", - "permlevel": 0, - "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": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Period Closing Voucher", + "read_only": 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, - "oldfieldname": "company", - "oldfieldtype": "Select", - "options": "Company", - "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 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Select", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "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, - "oldfieldtype": "Column Break", - "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 - }, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "The account head under Liability or Equity, in which Profit/Loss will be booked", - "fieldname": "closing_account_head", - "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": "Closing Account Head", - "length": 0, - "no_copy": 0, - "oldfieldname": "closing_account_head", - "oldfieldtype": "Link", - "options": "Account", - "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 - }, + "description": "The account head under Liability or Equity, in which Profit/Loss will be booked", + "fieldname": "closing_account_head", + "fieldtype": "Link", + "label": "Closing Account Head", + "oldfieldname": "closing_account_head", + "oldfieldtype": "Link", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "remarks", - "fieldtype": "Small Text", - "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": "Remarks", - "length": 0, - "no_copy": 0, - "oldfieldname": "remarks", - "oldfieldtype": "Small Text", - "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 + "fieldname": "remarks", + "fieldtype": "Small Text", + "label": "Remarks", + "oldfieldname": "remarks", + "oldfieldtype": "Small Text", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "cost_center_wise_pnl", + "fieldtype": "Check", + "label": "Book Cost Center Wise Profit/Loss" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-file-text", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Period Closing Voucher", - "owner": "Administrator", + ], + "icon": "fa fa-file-text", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-05-20 15:27:37.210458", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Period Closing Voucher", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "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, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "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 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "posting_date, fiscal_year", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "closing_account_head", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "posting_date, fiscal_year", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "closing_account_head" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index a74fa062b6..b0a5b04de6 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -51,63 +51,96 @@ class PeriodClosingVoucher(AccountsController): def make_gl_entries(self): gl_entries = [] - net_pl_balance = 0 - dimension_fields = ['t1.cost_center'] + net_pl_balance = 0 - accounting_dimensions = get_accounting_dimensions() - for dimension in accounting_dimensions: - dimension_fields.append('t1.{0}'.format(dimension)) - - dimension_filters, default_dimensions = get_dimensions() - - pl_accounts = self.get_pl_balances(dimension_fields) + pl_accounts = self.get_pl_balances() for acc in pl_accounts: - if flt(acc.balance_in_company_currency): + if flt(acc.bal_in_company_currency): gl_entries.append(self.get_gl_dict({ "account": acc.account, "cost_center": acc.cost_center, "account_currency": acc.account_currency, - "debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \ - if flt(acc.balance_in_account_currency) < 0 else 0, - "debit": abs(flt(acc.balance_in_company_currency)) \ - if flt(acc.balance_in_company_currency) < 0 else 0, - "credit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \ - if flt(acc.balance_in_account_currency) > 0 else 0, - "credit": abs(flt(acc.balance_in_company_currency)) \ - if flt(acc.balance_in_company_currency) > 0 else 0 + "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0, + "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0, + "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0, + "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0 }, item=acc)) - net_pl_balance += flt(acc.balance_in_company_currency) + net_pl_balance += flt(acc.bal_in_company_currency) if net_pl_balance: - cost_center = frappe.db.get_value("Company", self.company, "cost_center") - gl_entry = self.get_gl_dict({ - "account": self.closing_account_head, - "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, - "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, - "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, - "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0, - "cost_center": cost_center - }) - - for dimension in accounting_dimensions: - gl_entry.update({ - dimension: default_dimensions.get(self.company, {}).get(dimension) - }) - - gl_entries.append(gl_entry) + if self.cost_center_wise_pnl: + costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts) + gl_entries += costcenter_wise_gl_entries + else: + gl_entry = self.get_pnl_gl_entry(net_pl_balance) + gl_entries.append(gl_entry) from erpnext.accounts.general_ledger import make_gl_entries make_gl_entries(gl_entries) + + def get_pnl_gl_entry(self, net_pl_balance): + cost_center = frappe.db.get_value("Company", self.company, "cost_center") + gl_entry = self.get_gl_dict({ + "account": self.closing_account_head, + "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, + "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, + "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, + "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0, + "cost_center": cost_center + }) + + self.update_default_dimensions(gl_entry) + + return gl_entry + + def get_costcenter_wise_pnl_gl_entries(self, pl_accounts): + company_cost_center = frappe.db.get_value("Company", self.company, "cost_center") + gl_entries = [] + + for acc in pl_accounts: + if flt(acc.bal_in_company_currency): + gl_entry = self.get_gl_dict({ + "account": self.closing_account_head, + "cost_center": acc.cost_center or company_cost_center, + "account_currency": acc.account_currency, + "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0, + "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0, + "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0, + "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0 + }, item=acc) + + self.update_default_dimensions(gl_entry) + + gl_entries.append(gl_entry) + + return gl_entries + + def update_default_dimensions(self, gl_entry): + if not self.accounting_dimensions: + self.accounting_dimensions = get_accounting_dimensions() + + _, default_dimensions = get_dimensions() + for dimension in self.accounting_dimensions: + gl_entry.update({ + dimension: default_dimensions.get(self.company, {}).get(dimension) + }) + + def get_pl_balances(self): + """Get balance for dimension-wise pl accounts""" + + dimension_fields = ['t1.cost_center'] + + self.accounting_dimensions = get_accounting_dimensions() + for dimension in self.accounting_dimensions: + dimension_fields.append('t1.{0}'.format(dimension)) - def get_pl_balances(self, dimension_fields): - """Get balance for pl accounts""" return frappe.db.sql(""" select t1.account, t2.account_currency, {dimension_fields}, - sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency, - sum(t1.debit) - sum(t1.credit) as balance_in_company_currency + sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency, + sum(t1.debit) - sum(t1.credit) as bal_in_company_currency from `tabGL Entry` t1, `tabAccount` t2 where t1.account = t2.name and t2.report_type = 'Profit and Loss' and t2.docstatus < 2 and t2.company = %s diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index eb02d97b78..2f29372b01 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -8,6 +8,7 @@ import frappe from frappe.utils import flt, today from erpnext.accounts.utils import get_fiscal_year, now from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice class TestPeriodClosingVoucher(unittest.TestCase): def test_closing_entry(self): @@ -65,6 +66,58 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency, -1*random_expense_account[0].balance_in_account_currency) + def test_cost_center_wise_posting(self): + frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") + + company = create_company() + surplus_account = create_account() + + cost_center1 = create_cost_center("Test Cost Center 1") + cost_center2 = create_cost_center("Test Cost Center 2") + + create_sales_invoice( + company=company, + cost_center=cost_center1, + income_account="Sales - TPC", + expense_account="Cost of Goods Sold - TPC", + rate=400, + debit_to="Debtors - TPC" + ) + create_sales_invoice( + company=company, + cost_center=cost_center2, + income_account="Sales - TPC", + expense_account="Cost of Goods Sold - TPC", + rate=200, + debit_to="Debtors - TPC" + ) + + pcv = frappe.get_doc({ + "transaction_date": today(), + "posting_date": today(), + "fiscal_year": get_fiscal_year(today())[0], + "company": "Test PCV Company", + "cost_center_wise_pnl": 1, + "closing_account_head": surplus_account, + "remarks": "Test", + "doctype": "Period Closing Voucher" + }) + pcv.insert() + pcv.submit() + + expected_gle = ( + ('Sales - TPC', 200.0, 0.0, cost_center2), + (surplus_account, 0.0, 200.0, cost_center2), + ('Sales - TPC', 400.0, 0.0, cost_center1), + (surplus_account, 0.0, 400.0, cost_center1) + ) + + pcv_gle = frappe.db.sql(""" + select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s + """, (pcv.name)) + + self.assertTrue(pcv_gle, expected_gle) + def make_period_closing_voucher(self): pcv = frappe.get_doc({ "doctype": "Period Closing Voucher", @@ -80,6 +133,38 @@ class TestPeriodClosingVoucher(unittest.TestCase): return pcv +def create_company(): + company = frappe.get_doc({ + 'doctype': 'Company', + 'company_name': "Test PCV Company", + 'country': 'United States', + 'default_currency': 'USD' + }) + company.insert(ignore_if_duplicate = True) + return company.name + +def create_account(): + account = frappe.get_doc({ + "account_name": "Reserve and Surplus", + "is_group": 0, + "company": "Test PCV Company", + "root_type": "Liability", + "report_type": "Balance Sheet", + "account_currency": "USD", + "parent_account": "Current Liabilities - TPC", + "doctype": "Account" + }).insert(ignore_if_duplicate = True) + return account.name + +def create_cost_center(cc_name): + costcenter = frappe.get_doc({ + "company": "Test PCV Company", + "cost_center_name": cc_name, + "doctype": "Cost Center", + "parent_cost_center": "Test PCV Company - TPC" + }) + costcenter.insert(ignore_if_duplicate = True) + return costcenter.name test_dependencies = ["Customer", "Cost Center"] test_records = frappe.get_test_records("Period Closing Voucher") From 911818a9e2101c63f7bdacc06a07f1c511ba1578 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 9 Jun 2021 22:55:10 +0530 Subject: [PATCH 0996/1264] fix: Add multiple fixes --- .../advance_taxes_and_charges.json | 4 ++-- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- erpnext/accounts/doctype/payment_entry/payment_entry.json | 7 +++++-- erpnext/accounts/doctype/payment_entry/payment_entry.py | 5 ++++- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 3 --- .../doctype/purchase_invoice/test_purchase_invoice.py | 1 + .../purchase_taxes_and_charges.json | 4 ++-- .../sales_taxes_and_charges/sales_taxes_and_charges.json | 4 ++-- erpnext/controllers/accounts_controller.py | 5 ++++- 9 files changed, 21 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index 3e4e679c91..4d63499431 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -160,7 +160,7 @@ "default": "0", "fieldname": "included_in_paid_amount", "fieldtype": "Check", - "label": "Included In Paid Amount" + "label": "Considered In Paid Amount" }, { "fieldname": "allocated_amount", @@ -186,7 +186,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-08 07:04:44.287002", + "modified": "2021-06-09 11:46:58.373170", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 6a3b717b12..939f3546ff 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -1133,7 +1133,7 @@ frappe.ui.form.on('Payment Entry', { let tax_rate = tax.rate; if (tax.charge_type == "Actual") { - current_tax_fraction = tax.tax_amount/frm.doc.paid_amount_after_tax; + current_tax_fraction = tax.tax_amount/(frm.doc.paid_amount_after_tax + frm.doc.tax_amount); } else if(tax.charge_type == "On Paid Amount") { current_tax_fraction = (tax_rate / 100.0); } else if(tax.charge_type == "On Previous Row Amount") { diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 0b4178a547..54623dd6cd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -681,13 +681,16 @@ "hide_border": 1 }, { + "depends_on": "eval:doc.apply_tax_withholding_amount", + "description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices", "fieldname": "advance_tax_account", "fieldtype": "Link", "label": "Advance Tax Account", - "mandatory_depends_on": "doc.base_total_taxes_and_charges", + "mandatory_depends_on": "eval:doc.apply_tax_withholding_amount", "options": "Account" }, { + "depends_on": "eval:doc.received_amount", "fieldname": "received_amount_after_tax", "fieldtype": "Currency", "label": "Received Amount After Tax", @@ -704,7 +707,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-31 01:54:18.378910", + "modified": "2021-06-09 11:55:04.215050", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index d04d6a0d15..edca210142 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -400,6 +400,9 @@ class PaymentEntry(AccountsController): if not self.apply_tax_withholding_amount: return + if not self.advance_tax_account: + frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction")) + reference_doclist = [] net_total = self.paid_amount included_in_paid_amount = 0 @@ -916,7 +919,7 @@ class PaymentEntry(AccountsController): tax_rate = tax.rate if tax.charge_type == 'Actual': - current_tax_fraction = tax.tax_amount/self.paid_amount_after_tax + current_tax_fraction = tax.tax_amount/ (self.paid_amount_after_tax + tax.tax_amount) elif tax.charge_type == "On Paid Amount": current_tax_fraction = tax_rate / 100.0 diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 934c731cf1..0ee0bc7e11 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -68,9 +68,6 @@ class PurchaseInvoice(BuyingController): super(PurchaseInvoice, self).validate() - # apply tax withholding only if checked and applicable - self.set_tax_withholding() - if not self.is_return: self.po_required() self.pr_required() diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 8e72dc6321..fb4b8d4c1f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -975,6 +975,7 @@ class TestPurchaseInvoice(unittest.TestCase): # Create Payment Entry Against the order payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name) + payment_entry.paid_from = 'Cash - _TC' payment_entry.save() payment_entry.submit() diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index f6703315fa..9b07645ccc 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -221,13 +221,13 @@ "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", "fieldname": "included_in_paid_amount", "fieldtype": "Check", - "label": "Included In Paid Amount" + "label": "Considered In Paid Amount" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-31 03:41:38.298937", + "modified": "2021-06-09 11:48:25.335733", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 36fd634b50..170d34e651 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -198,14 +198,14 @@ "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", "fieldname": "included_in_paid_amount", "fieldtype": "Check", - "label": "Included In Paid Amount" + "label": "Considered In Paid Amount" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-31 05:40:32.856780", + "modified": "2021-06-09 11:48:04.691596", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d8d0312ff6..53ded33b6f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -116,6 +116,8 @@ class AccountsController(TransactionBase): if self.doctype == 'Purchase Invoice': self.calculate_paid_amount() + # apply tax withholding only if checked and applicable + self.set_tax_withholding() if self.doctype in ['Purchase Invoice', 'Sales Invoice']: pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" @@ -742,7 +744,8 @@ class AccountsController(TransactionBase): def allocate_advance_taxes(self, gl_entries): tax_map = self.get_tax_map() for pe in self.get("advances"): - if pe.reference_type == "Payment Entry": + if pe.reference_type == "Payment Entry" and \ + frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'): pe = frappe.get_doc("Payment Entry", pe.reference_name) for tax in pe.get("taxes"): account_currency = get_account_currency(tax.account_head) From 0b5fe1b5eb0fb24a4a37a6513e76f26b9a1f3bee Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 10 Jun 2021 10:34:49 +0530 Subject: [PATCH 0997/1264] fix: Failing test case --- .../doctype/purchase_invoice/test_purchase_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index fb4b8d4c1f..c656c8aeec 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1009,8 +1009,8 @@ class TestPurchaseInvoice(unittest.TestCase): ['_Test Account Cost for Goods Sold - _TC', 30000, 0], ['_Test Account Excise Duty - _TC', 0, 3000], ['Creditors - _TC', 0, 27000], - ['TDS Payable - _TC', 3000, 0] - ['TDS Payable - _TC', 0, 3000], + ['TDS Payable - _TC', 3000, 0], + ['TDS Payable - _TC', 0, 3000] ] gl_entries = frappe.db.sql("""select account, debit, credit From edecd5b0c686f653454fa3d85fa87e058ad81f9b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 10 Jun 2021 12:04:30 +0530 Subject: [PATCH 0998/1264] fix: Update einvoice json test --- .../sales_invoice/test_sales_invoice.py | 98 ++++++++----------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index df6d483904..b35686f4f0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1899,69 +1899,53 @@ class TestSalesInvoice(unittest.TestCase): frappe.flags.country = country def test_einvoice_json(self): - from erpnext.regional.india.e_invoice.utils import make_einvoice + from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals - si = make_sales_invoice_for_ewaybill() - si.naming_series = 'INV-2020-.#####' - si.items = [] - si.append("items", { - "item_code": "_Test Item", - "uom": "Nos", - "warehouse": "_Test Warehouse - _TC", - "qty": 2000, - "rate": 12, - "income_account": "Sales - _TC", - "expense_account": "Cost of Goods Sold - _TC", - "cost_center": "_Test Cost Center - _TC", - }) - si.append("items", { - "item_code": "_Test Item 2", - "uom": "Nos", - "warehouse": "_Test Warehouse - _TC", - "qty": 420, - "rate": 15, - "income_account": "Sales - _TC", - "expense_account": "Cost of Goods Sold - _TC", - "cost_center": "_Test Cost Center - _TC", - }) + si = get_sales_invoice_for_e_invoice() si.discount_amount = 100 si.save() einvoice = make_einvoice(si) - - total_item_ass_value = 0 - total_item_cgst_value = 0 - total_item_sgst_value = 0 - total_item_igst_value = 0 - total_item_value = 0 - - for item in einvoice['ItemList']: - total_item_ass_value += item['AssAmt'] - total_item_cgst_value += item['CgstAmt'] - total_item_sgst_value += item['SgstAmt'] - total_item_igst_value += item['IgstAmt'] - total_item_value += item['TotItemVal'] - - self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount']) - self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt']) - - value_details = einvoice['ValDtls'] - - self.assertEqual(einvoice['Version'], '1.1') - self.assertEqual(value_details['AssVal'], total_item_ass_value) - self.assertEqual(value_details['CgstVal'], total_item_cgst_value) - self.assertEqual(value_details['SgstVal'], total_item_sgst_value) - self.assertEqual(value_details['IgstVal'], total_item_igst_value) - - calculated_invoice_value = \ - value_details['AssVal'] + value_details['CgstVal'] \ - + value_details['SgstVal'] + value_details['IgstVal'] \ - + value_details['OthChrg'] - value_details['Discount'] - - self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1) - - self.assertEqual(value_details['TotInvVal'], si.base_grand_total) self.assertTrue(einvoice['EwbDtls']) + validate_totals(einvoice) + + si.apply_discount_on = 'Net Total' + si.save() + einvoice = make_einvoice(si) + validate_totals(einvoice) + + [d.set('included_in_print_rate', 1) for d in si.taxes] + si.save() + einvoice = make_einvoice(si) + validate_totals(einvoice) + +def get_sales_invoice_for_e_invoice(): + si = make_sales_invoice_for_ewaybill() + si.naming_series = 'INV-2020-.#####' + si.items = [] + si.append("items", { + "item_code": "_Test Item", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 2000, + "rate": 12, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + + si.append("items", { + "item_code": "_Test Item 2", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 420, + "rate": 15, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + + return si def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): From 076bd5eaab52c9ad7805ff511787f8de045743b3 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 10 Jun 2021 12:08:44 +0530 Subject: [PATCH 0999/1264] fix: Only display GST card in Accounting Workspace if it's in India --- .../workspace/accounting/accounting.json | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index df68318052..10a4001502 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -445,15 +445,15 @@ "type": "Link" }, { - "dependencies": "GL Entry", - "hidden": 0, - "is_query_report": 1, - "label": "UAE VAT 201", - "link_to": "UAE VAT 201", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "UAE VAT 201", + "link_to": "UAE VAT 201", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -684,6 +684,7 @@ "is_query_report": 0, "label": "Goods and Services Tax (GST India)", "onboard": 0, + "only_for": "India", "type": "Card Break" }, { @@ -694,6 +695,7 @@ "link_to": "GST Settings", "link_type": "DocType", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -704,6 +706,7 @@ "link_to": "GST HSN Code", "link_type": "DocType", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -714,6 +717,7 @@ "link_to": "GSTR-1", "link_type": "Report", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -724,6 +728,7 @@ "link_to": "GSTR-2", "link_type": "Report", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -734,6 +739,7 @@ "link_to": "GSTR 3B Report", "link_type": "DocType", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -744,6 +750,7 @@ "link_to": "GST Sales Register", "link_type": "Report", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -754,6 +761,7 @@ "link_to": "GST Purchase Register", "link_type": "Report", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -764,6 +772,7 @@ "link_to": "GST Itemised Sales Register", "link_type": "Report", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -774,6 +783,7 @@ "link_to": "GST Itemised Purchase Register", "link_type": "Report", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -784,6 +794,7 @@ "link_to": "C-Form", "link_type": "DocType", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -794,6 +805,7 @@ "link_to": "Lower Deduction Certificate", "link_type": "DocType", "onboard": 0, + "only_for": "India", "type": "Link" }, { @@ -1052,7 +1064,7 @@ "type": "Link" } ], - "modified": "2021-05-12 11:48:01.905144", + "modified": "2021-06-10 03:17:31.427945", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", @@ -1107,4 +1119,4 @@ "type": "Dashboard" } ] -} +} \ No newline at end of file From d063f9a5d185e9f481991b11c8fa8c0dcf9a39a5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 10 Jun 2021 12:26:21 +0530 Subject: [PATCH 1000/1264] fix: GL Entry ordering --- .../doctype/purchase_invoice/test_purchase_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c656c8aeec..723d151ad8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1000,6 +1000,7 @@ class TestPurchaseInvoice(unittest.TestCase): # Create Purchase Invoice against Purchase Order purchase_invoice = get_mapped_purchase_invoice(po.name) purchase_invoice.allocate_advances_automatically = 1 + purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC' purchase_invoice.save() purchase_invoice.submit() @@ -1009,8 +1010,7 @@ class TestPurchaseInvoice(unittest.TestCase): ['_Test Account Cost for Goods Sold - _TC', 30000, 0], ['_Test Account Excise Duty - _TC', 0, 3000], ['Creditors - _TC', 0, 27000], - ['TDS Payable - _TC', 3000, 0], - ['TDS Payable - _TC', 0, 3000] + ['TDS Payable - _TC', 3000, 3000] ] gl_entries = frappe.db.sql("""select account, debit, credit From aedf25ba46d20d8a7d40b6c77f880cfdc426cf90 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 4 Jun 2021 11:44:40 +0530 Subject: [PATCH 1001/1264] chore: Drop old patches v7 backup was restored and upgraded to latest v10.x.x branch. The patches run uptil the upgrade are removed in this change. This means only existing v10 sites are allowed direct upgrade to v13 and newer --- erpnext/patches/repair_tools/__init__.py | 0 .../set_stock_balance_as_per_serial_no.py | 13 - erpnext/patches/v10_0/__init__.py | 0 .../patches/v10_0/add_agriculture_domain.py | 13 - .../add_guardian_role_for_parent_portal.py | 23 -- .../patches/v10_0/add_non_profit_domain.py | 20 -- .../allow_operators_in_supplier_scorecard.py | 23 -- .../v10_0/copy_projects_renamed_fields.py | 13 - ..._regional_print_format_based_on_country.py | 22 -- .../fix_reserved_qty_for_sub_contract.py | 31 --- .../recalculate_gross_margin_for_project.py | 14 - .../v10_0/rename_schools_to_education.py | 32 --- ...r_purchase_receipts_with_rejected_items.py | 32 --- ...t_requested_qty_for_non_stock_uom_items.py | 21 -- ...t_auto_created_serial_no_in_stock_entry.py | 56 ---- erpnext/patches/v10_0/set_b2c_limit.py | 12 - ..._default_payment_terms_based_on_company.py | 37 --- erpnext/patches/v10_0/set_discount_amount.py | 35 --- ...set_numeric_ranges_in_template_if_blank.py | 35 --- .../v10_0/set_primary_contact_for_customer.py | 21 -- ...n_transactions_based_on_serial_no_input.py | 21 -- .../patches/v10_0/set_student_party_type.py | 8 - .../setup_vat_for_uae_and_saudi_arabia.py | 13 - ...s_of_all_department_members_in_calendar.py | 6 - erpnext/patches/v10_0/taxes_issue_with_pos.py | 26 -- .../update_address_template_for_india.py | 12 - .../patches/v10_0/update_assessment_plan.py | 17 -- .../patches/v10_0/update_assessment_result.py | 20 -- .../update_asset_calculate_depreciation.py | 12 - .../v10_0/update_hub_connector_domain.py | 10 - .../v10_0/update_lft_rgt_for_employee.py | 9 - .../patches/v10_0/update_project_in_sle.py | 15 -- .../update_reserved_qty_for_purchase_order.py | 53 ---- ...date_sales_order_link_to_purchase_order.py | 18 -- ...update_status_for_multiple_source_in_po.py | 40 --- .../update_status_in_purchase_receipt.py | 8 - .../update_territory_and_customer_group.py | 29 --- .../v10_0/update_user_image_in_employee.py | 19 -- .../v10_0/update_warehouse_address_details.py | 37 --- erpnext/patches/v10_1/__init__.py | 0 erpnext/patches/v11_0/__init__.py | 1 - .../v11_0/remove_subscriber_doctype.py | 16 -- erpnext/patches/v11_1/__init__.py | 0 erpnext/patches/v12_0/__init__.py | 0 erpnext/patches/v13_0/__init__.py | 1 - erpnext/patches/v4_0/__init__.py | 0 .../patches/v4_0/apply_user_permissions.py | 50 ---- erpnext/patches/v4_0/countrywise_coa.py | 29 --- ...custom_fields_for_india_specific_fields.py | 63 ----- .../v4_0/create_price_list_if_missing.py | 35 --- .../v4_0/customer_discount_to_pricing_rule.py | 33 --- erpnext/patches/v4_0/fields_to_be_renamed.py | 109 -------- erpnext/patches/v4_0/fix_address_template.py | 12 - .../patches/v4_0/fix_case_of_hr_module_def.py | 13 - erpnext/patches/v4_0/fix_contact_address.py | 13 - erpnext/patches/v4_0/fix_employee_user_id.py | 23 -- .../global_defaults_to_system_settings.py | 39 --- erpnext/patches/v4_0/import_country_codes.py | 15 -- .../v4_0/map_charge_to_taxes_and_charges.py | 16 -- .../move_warehouse_user_to_restrictions.py | 13 - erpnext/patches/v4_0/new_address_template.py | 14 - .../patches/v4_0/reload_sales_print_format.py | 8 - .../remove_employee_role_if_no_employee.py | 18 -- .../patches/v4_0/remove_module_home_pages.py | 10 - .../patches/v4_0/rename_sitemap_to_route.py | 17 -- .../v4_0/reset_permissions_for_masters.py | 20 -- .../patches/v4_0/save_default_letterhead.py | 13 - .../set_pricing_rule_for_buying_or_selling.py | 13 - erpnext/patches/v4_0/split_email_settings.py | 67 ----- .../patches/v4_0/update_account_root_type.py | 34 --- ...custom_print_formats_for_renamed_fields.py | 36 --- ...to_sales_person_in_maintenance_schedule.py | 12 - ...harges_in_custom_purchase_print_formats.py | 12 - .../v4_0/update_tax_amount_after_discount.py | 20 -- .../patches/v4_0/update_user_properties.py | 51 ---- .../v4_0/update_users_report_view_settings.py | 12 - erpnext/patches/v4_0/validate_v3_patch.py | 11 - erpnext/patches/v4_1/__init__.py | 0 .../v4_1/fix_delivery_and_billing_status.py | 12 - erpnext/patches/v4_1/fix_jv_remarks.py | 21 -- .../v4_1/fix_sales_order_delivered_status.py | 15 -- .../patches/v4_1/set_outgoing_email_footer.py | 12 - erpnext/patches/v4_2/__init__.py | 0 .../patches/v4_2/add_currency_turkish_lira.py | 10 - erpnext/patches/v4_2/default_website_style.py | 11 - ...elete_gl_entries_for_cancelled_invoices.py | 14 - .../patches/v4_2/delete_old_print_formats.py | 23 -- erpnext/patches/v4_2/discount_amount.py | 12 - .../patches/v4_2/fix_account_master_type.py | 12 - .../fix_gl_entries_for_stock_transactions.py | 54 ---- erpnext/patches/v4_2/fix_recurring_orders.py | 41 --- erpnext/patches/v4_2/party_model.py | 117 --------- erpnext/patches/v4_2/recalculate_bom_cost.py | 16 -- .../repost_sle_for_si_with_no_warehouse.py | 34 --- .../v4_2/repost_stock_reconciliation.py | 31 --- erpnext/patches/v4_2/reset_bom_costs.py | 17 -- .../v4_2/seprate_manufacture_and_repack.py | 9 - erpnext/patches/v4_2/set_company_country.py | 15 -- erpnext/patches/v4_2/set_item_has_batch.py | 65 ----- erpnext/patches/v4_2/toggle_rounded_total.py | 9 - .../v4_2/update_landed_cost_voucher.py | 10 - .../patches/v4_2/update_project_milestones.py | 9 - .../update_sales_order_invoice_field_name.py | 7 - .../v4_2/update_stock_uom_for_dn_in_sle.py | 11 - erpnext/patches/v4_4/__init__.py | 0 erpnext/patches/v4_4/make_email_accounts.py | 96 ------- erpnext/patches/v5_0/__init__.py | 0 .../v5_0/convert_stock_reconciliation.py | 31 --- .../patches/v5_0/execute_on_doctype_update.py | 9 - .../fix_taxes_and_totals_in_party_currency.py | 66 ----- .../v5_0/index_on_account_and_gl_entry.py | 30 --- erpnext/patches/v5_0/is_group.py | 9 - erpnext/patches/v5_0/item_patches.py | 6 - .../v5_0/link_warehouse_with_account.py | 10 - erpnext/patches/v5_0/new_crm_module.py | 24 -- erpnext/patches/v5_0/newsletter.py | 38 --- .../v5_0/opportunity_not_submittable.py | 10 - erpnext/patches/v5_0/party_model_patch_fix.py | 18 -- erpnext/patches/v5_0/portal_fixes.py | 7 - erpnext/patches/v5_0/project_costing.py | 8 - .../v5_0/recalculate_total_amount_in_jv.py | 26 -- ...nned_operating_cost_in_production_order.py | 13 - .../patches/v5_0/remove_birthday_events.py | 7 - erpnext/patches/v5_0/rename_customer_issue.py | 6 - erpnext/patches/v5_0/rename_pos_setting.py | 6 - .../patches/v5_0/rename_table_fieldnames.py | 243 ------------------ .../v5_0/rename_taxes_and_charges_master.py | 14 - erpnext/patches/v5_0/rename_total_fields.py | 55 ---- ...lds_in_custom_scripts_and_print_formats.py | 65 ----- .../repost_gle_for_jv_with_multiple_party.py | 26 -- erpnext/patches/v5_0/repost_requested_qty.py | 21 -- erpnext/patches/v5_0/reset_values_in_tools.py | 12 - erpnext/patches/v5_0/set_appraisal_remarks.py | 9 - .../v5_0/set_default_company_in_bom.py | 10 - erpnext/patches/v5_0/set_footer_address.py | 9 - .../patches/v5_0/stock_entry_update_value.py | 8 - .../taxes_and_totals_in_party_currency.py | 80 ------ erpnext/patches/v5_0/update_account_types.py | 20 -- erpnext/patches/v5_0/update_advance_paid.py | 13 - .../update_companywise_payment_account.py | 21 -- .../v5_0/update_dn_against_doc_fields.py | 14 - erpnext/patches/v5_0/update_from_bom.py | 9 - .../update_frozen_accounts_permission_role.py | 13 - .../v5_0/update_item_and_description_again.py | 50 ---- .../v5_0/update_item_desc_in_invoice.py | 52 ---- .../v5_0/update_item_description_and_image.py | 54 ---- .../patches/v5_0/update_item_name_in_bom.py | 18 -- .../v5_0/update_journal_entry_title.py | 12 - ...pdate_material_transfer_for_manufacture.py | 6 - ..._material_transferred_for_manufacturing.py | 10 - ...ial_transferred_for_manufacturing_again.py | 19 -- .../v5_0/update_operation_description.py | 11 - erpnext/patches/v5_0/update_opportunity.py | 14 - erpnext/patches/v5_0/update_projects.py | 34 --- erpnext/patches/v5_0/update_sms_sender.py | 9 - ...amount_after_discount_in_purchase_cycle.py | 17 -- .../patches/v5_0/update_temporary_account.py | 9 - erpnext/patches/v5_0/update_time_log_title.py | 12 - erpnext/patches/v5_1/__init__.py | 0 erpnext/patches/v5_1/default_bom.py | 7 - erpnext/patches/v5_1/fix_against_account.py | 37 --- erpnext/patches/v5_1/rename_roles.py | 10 - erpnext/patches/v5_1/sales_bom_rename.py | 12 - erpnext/patches/v5_2/__init__.py | 1 - .../v5_2/change_item_selects_to_checks.py | 19 -- erpnext/patches/v5_4/__init__.py | 0 erpnext/patches/v5_4/cleanup_journal_entry.py | 21 -- .../patches/v5_4/fix_invoice_outstanding.py | 13 - .../patches/v5_4/fix_missing_item_images.py | 126 --------- ...x_reserved_qty_and_sle_for_packed_items.py | 22 -- ...anagers_regarding_wrong_tax_calculation.py | 41 --- .../patches/v5_4/set_root_and_report_type.py | 12 - .../v5_4/stock_entry_additional_costs.py | 53 ---- .../update_purchase_cost_against_project.py | 13 - erpnext/patches/v5_7/__init__.py | 1 - .../patches/v5_7/item_template_attributes.py | 124 --------- erpnext/patches/v5_8/__init__.py | 1 - .../v5_8/add_credit_note_print_heading.py | 14 - erpnext/patches/v5_8/tax_rule.py | 31 --- ...pdate_order_reference_in_return_entries.py | 92 ------- erpnext/patches/v6_0/__init__.py | 0 erpnext/patches/v6_0/default_activity_rate.py | 14 - .../patches/v6_0/fix_outstanding_amount.py | 16 -- erpnext/patches/v6_0/fix_planned_qty.py | 14 - erpnext/patches/v6_0/multi_currency.py | 65 ----- erpnext/patches/v6_0/set_default_title.py | 36 --- erpnext/patches/v6_10/__init__.py | 1 - .../v6_10/email_digest_default_quote.py | 6 - .../fix_billed_amount_in_drop_ship_po.py | 18 -- .../fix_delivery_status_of_drop_ship_item.py | 10 - erpnext/patches/v6_10/fix_jv_total_amount.py | 14 - .../v6_10/fix_ordered_received_billed.py | 17 -- erpnext/patches/v6_12/__init__.py | 0 .../repost_entries_with_target_warehouse.py | 175 ------------- erpnext/patches/v6_12/set_overdue_tasks.py | 8 - erpnext/patches/v6_16/__init__.py | 1 - .../v6_16/create_manufacturer_records.py | 19 -- .../update_billing_status_in_dn_and_pr.py | 42 --- erpnext/patches/v6_19/__init__.py | 1 - .../v6_19/comment_feed_communication.py | 7 - erpnext/patches/v6_2/__init__.py | 1 - .../fix_missing_default_taxes_and_lead.py | 25 -- .../v6_2/remove_newsletter_duplicates.py | 13 - erpnext/patches/v6_20/__init__.py | 0 .../set_party_account_currency_in_orders.py | 24 -- erpnext/patches/v6_20x/__init__.py | 1 - .../v6_20x/remove_customer_supplier_roles.py | 23 -- .../remove_fiscal_year_from_holiday_list.py | 19 -- .../v6_20x/rename_project_name_to_project.py | 17 -- ...t_valuation_rate_for_negative_inventory.py | 11 - erpnext/patches/v6_20x/set_compact_print.py | 8 - .../update_product_bundle_description.py | 11 - erpnext/patches/v6_21/__init__.py | 1 - erpnext/patches/v6_21/fix_reorder_level.py | 24 -- .../v6_21/rename_material_request_fields.py | 14 - erpnext/patches/v6_23/__init__.py | 0 .../v6_23/update_stopped_status_to_closed.py | 9 - erpnext/patches/v6_24/__init__.py | 0 ...tomer_address_to_shipping_address_on_po.py | 19 -- erpnext/patches/v6_24/set_recurring_id.py | 13 - erpnext/patches/v6_27/__init__.py | 1 - .../v6_27/fix_recurring_order_status.py | 54 ---- erpnext/patches/v6_3/__init__.py | 0 .../v6_3/convert_applicable_territory.py | 24 -- erpnext/patches/v6_4/__init__.py | 1 - erpnext/patches/v6_4/email_digest_update.py | 10 - erpnext/patches/v6_4/fix_duplicate_bins.py | 20 -- .../v6_4/fix_expense_included_in_valuation.py | 74 ------ ...x_journal_entries_due_to_reconciliation.py | 53 ---- ...ified_in_sales_order_and_purchase_order.py | 10 - .../fix_sales_order_maintenance_status.py | 8 - .../fix_status_in_sales_and_purchase_order.py | 8 - erpnext/patches/v6_4/make_image_thumbnail.py | 15 -- ...al_entries_where_reference_name_missing.py | 23 -- .../v6_4/round_status_updater_percentages.py | 14 - erpnext/patches/v6_4/set_user_in_contact.py | 7 - erpnext/patches/v6_5/__init__.py | 1 - .../v6_5/show_in_website_for_template_item.py | 15 -- erpnext/patches/v6_6/__init__.py | 1 - erpnext/patches/v6_6/fix_website_image.py | 32 --- ...emove_fiscal_year_from_leave_allocation.py | 17 -- erpnext/patches/v6_8/__init__.py | 0 erpnext/patches/v6_8/make_webform_standard.py | 14 - .../v6_8/move_drop_ship_to_po_items.py | 43 ---- erpnext/patches/v7_0/__init__.py | 0 .../v7_0/calculate_total_costing_amount.py | 18 -- .../v7_0/convert_timelog_to_timesheet.py | 69 ----- .../v7_0/convert_timelogbatch_to_timesheet.py | 32 --- erpnext/patches/v7_0/create_budget_record.py | 57 ---- .../v7_0/create_warehouse_nestedset.py | 128 --------- erpnext/patches/v7_0/fix_duplicate_icons.py | 27 -- ...ouse_ledger_gl_entries_for_transactions.py | 51 ---- erpnext/patches/v7_0/make_guardian.py | 37 --- .../v7_0/make_is_group_fieldtype_as_check.py | 18 -- ...count_type_stock_and_warehouse_to_stock.py | 10 - .../v7_0/migrate_mode_of_payments_v6_to_v7.py | 38 --- .../v7_0/migrate_schools_to_erpnext.py | 30 --- ...lesinvoiceitem_to_salesinvoicetimesheet.py | 17 -- .../v7_0/po_status_issue_for_pr_return.py | 40 --- erpnext/patches/v7_0/re_route.py | 5 - .../remove_administrator_role_in_doctypes.py | 9 - .../v7_0/remove_doctypes_and_reports.py | 27 -- erpnext/patches/v7_0/remove_features_setup.py | 29 --- .../remove_old_earning_deduction_doctypes.py | 16 -- .../v7_0/rename_advance_table_fields.py | 18 -- .../v7_0/rename_examination_to_assessment.py | 24 -- .../rename_fee_amount_to_fee_component.py | 16 -- erpnext/patches/v7_0/rename_prevdoc_fields.py | 76 ------ .../patches/v7_0/rename_salary_components.py | 149 ----------- .../patches/v7_0/rename_time_sheet_doctype.py | 14 - .../repost_bin_qty_and_item_projected_qty.py | 15 -- .../repost_gle_for_pi_with_update_stock.py | 20 -- .../v7_0/repost_gle_for_pos_sales_return.py | 25 -- ...et_base_amount_in_invoice_payment_table.py | 24 -- .../v7_0/set_is_group_for_warehouse.py | 7 - .../v7_0/set_material_request_type_in_item.py | 16 -- .../v7_0/set_naming_series_for_timesheet.py | 15 -- .../v7_0/set_party_name_in_payment_entry.py | 20 -- erpnext/patches/v7_0/set_portal_settings.py | 28 -- ..._table_for_expense_claim_type_if_exists.py | 20 -- .../v7_0/system_settings_setup_complete.py | 16 -- erpnext/patches/v7_0/update_autoname_field.py | 14 - .../v7_0/update_change_amount_account.py | 19 -- ...rsion_factor_in_supplier_quotation_item.py | 19 -- erpnext/patches/v7_0/update_home_page.py | 27 -- .../update_maintenance_module_in_doctype.py | 11 - .../v7_0/update_mins_to_first_response.py | 24 -- .../update_missing_employee_in_timesheet.py | 20 -- .../v7_0/update_mode_of_payment_type.py | 29 --- erpnext/patches/v7_0/update_party_status.py | 21 -- ...vdoc_values_for_supplier_quotation_item.py | 9 - .../v7_0/update_project_in_gl_entry.py | 21 -- .../update_refdoc_in_landed_cost_voucher.py | 15 -- .../v7_0/update_status_for_timesheet.py | 11 - .../patches/v7_0/update_status_of_po_so.py | 68 ----- ...pdate_status_of_zero_amount_sales_order.py | 7 - .../v7_0/update_timesheet_communications.py | 27 -- erpnext/patches/v7_1/__init__.py | 1 - .../add_account_user_role_for_timesheet.py | 31 --- .../v7_1/add_field_for_task_dependent.py | 10 - .../v7_1/fix_link_for_customer_from_lead.py | 7 - ..._invoice_from_parent_to_child_timesheet.py | 20 -- .../patches/v7_1/rename_field_timesheet.py | 11 - .../v7_1/rename_quality_inspection_field.py | 38 --- ...tock_for_deleted_bins_for_merging_items.py | 44 ---- erpnext/patches/v7_1/save_stock_settings.py | 15 -- .../v7_1/set_budget_against_as_cost_center.py | 11 - .../v7_1/set_currency_exchange_date.py | 10 - .../v7_1/set_prefered_contact_email.py | 17 -- .../patches/v7_1/set_sales_person_status.py | 8 - erpnext/patches/v7_1/set_student_guardian.py | 23 -- .../v7_1/set_total_amount_currency_in_je.py | 24 -- .../patches/v7_1/update_bom_base_currency.py | 20 -- erpnext/patches/v7_1/update_component_type.py | 15 -- erpnext/patches/v7_1/update_invoice_status.py | 34 --- erpnext/patches/v7_1/update_lead_source.py | 29 --- .../update_missing_salary_component_type.py | 50 ---- erpnext/patches/v7_1/update_portal_roles.py | 21 -- .../v7_1/update_total_billing_hours.py | 14 - erpnext/patches/v7_2/__init__.py | 1 - ...ar_leave_encashment_as_salary_component.py | 36 --- erpnext/patches/v7_2/contact_address_links.py | 32 --- .../delete_fleet_management_module_def.py | 10 - ...ty_supplied_items_for_non_subcontracted.py | 14 - .../patches/v7_2/make_all_assessment_group.py | 14 - erpnext/patches/v7_2/mark_students_active.py | 9 - .../v7_2/rename_att_date_attendance.py | 15 -- .../v7_2/rename_evaluation_criteria.py | 39 --- .../patches/v7_2/set_null_value_to_fields.py | 11 - .../patches/v7_2/setup_auto_close_settings.py | 18 -- erpnext/patches/v7_2/stock_uom_in_selling.py | 15 -- .../v7_2/update_abbr_in_salary_slips.py | 14 - .../patches/v7_2/update_assessment_modules.py | 51 ---- .../v7_2/update_attendance_docstatus.py | 10 - erpnext/patches/v7_2/update_doctype_status.py | 11 - .../update_guardian_name_in_student_master.py | 14 - erpnext/patches/v7_2/update_party_type.py | 16 -- erpnext/patches/v7_2/update_salary_slips.py | 22 -- .../v7_2/update_website_for_variant.py | 13 - erpnext/patches/v8_0/__init__.py | 1 - .../patches/v8_0/addresses_linked_to_lead.py | 5 - .../v8_0/change_in_words_varchar_length.py | 16 -- ...dress_doc_from_address_field_in_company.py | 33 --- erpnext/patches/v8_0/create_domain_docs.py | 53 ---- erpnext/patches/v8_0/delete_bin_indexes.py | 16 -- .../delete_schools_depricated_doctypes.py | 14 - .../patches/v8_0/disable_instructor_role.py | 18 -- ...ooking_asset_depreciation_automatically.py | 9 - ..._for_invoices_with_negative_outstanding.py | 23 -- ...ayments_table_blank_for_non_pos_invoice.py | 15 -- .../merge_student_batch_and_student_group.py | 73 ------ ...from_account_to_warehouse_for_inventory.py | 15 -- .../v8_0/move_perpetual_inventory_setting.py | 13 - ...ample_item_to_allow_zero_valuation_rate.py | 13 - ...ems_in_status_field_of_material_request.py | 25 -- ...rename_total_margin_to_rate_with_margin.py | 24 -- ...ost_reserved_qty_for_multiple_sales_uom.py | 19 -- .../revert_manufacturers_table_from_item.py | 22 -- erpnext/patches/v8_0/save_system_settings.py | 20 -- ..._serial_nos_for_disabled_sales_invoices.py | 14 - .../patches/v8_0/set_project_copied_from.py | 11 - ...nvoice_serial_number_from_delivery_note.py | 42 --- .../patches/v8_0/update_customer_pos_id.py | 9 - .../patches/v8_0/update_production_orders.py | 49 ---- .../v8_0/update_sales_cost_in_project.py | 11 - ...tus_as_paid_for_completed_expense_claim.py | 19 -- .../update_stock_qty_value_in_bom_item.py | 14 - ...ate_stock_qty_value_in_purchase_invoice.py | 9 - ...ate_student_groups_from_student_batches.py | 38 --- .../update_supplier_address_in_stock_entry.py | 22 -- erpnext/patches/v8_1/__init__.py | 0 erpnext/patches/v8_1/add_hsn_sac_codes.py | 11 - .../add_indexes_in_transaction_doctypes.py | 10 - ...allow_invoice_copy_to_edit_after_submit.py | 13 - .../patches/v8_1/delete_deprecated_reports.py | 33 --- erpnext/patches/v8_1/gst_fixes.py | 62 ----- ...e_sales_invoice_from_returned_serial_no.py | 18 -- .../v8_1/removed_report_support_hours.py | 14 - .../v8_1/set_delivery_date_in_so_item.py | 22 -- .../v8_1/update_expense_claim_status.py | 23 -- erpnext/patches/v8_1/update_gst_state.py | 15 -- erpnext/patches/v8_10/__init__.py | 0 .../change_default_customer_credit_days.py | 89 ------- erpnext/patches/v8_3/__init__.py | 0 .../set_restrict_to_domain_for_module_def.py | 9 - .../v8_3/update_company_total_sales.py | 15 -- erpnext/patches/v8_4/__init__.py | 1 - .../patches/v8_4/make_scorecard_records.py | 11 - erpnext/patches/v8_5/__init__.py | 0 .../fix_tax_breakup_for_non_invoice_docs.py | 48 ---- .../remove_project_type_property_setter.py | 18 -- .../remove_quotations_route_in_sidebar.py | 16 -- .../v8_5/set_default_mode_of_payment.py | 17 -- .../update_customer_group_in_POS_profile.py | 9 - .../update_existing_data_in_project_type.py | 19 -- erpnext/patches/v8_6/__init__.py | 0 ...point_sms_doctype_module_to_frappe_core.py | 9 - .../patches/v8_6/rename_bom_update_tool.py | 9 - ...mission_for_quotation_for_sales_manager.py | 11 - .../v8_6/update_timesheet_company_from_PO.py | 15 -- erpnext/patches/v8_7/__init__.py | 0 .../v8_7/fix_purchase_receipt_status.py | 13 - .../make_subscription_from_recurring_data.py | 58 ----- erpnext/patches/v8_8/__init__.py | 0 .../add_new_fields_in_accounts_settings.py | 9 - .../patches/v8_8/set_bom_rate_as_per_uom.py | 13 - erpnext/patches/v8_9/__init__.py | 1 - .../v8_9/add_setup_progress_actions.py | 47 ---- ...gst_doctypes_for_outside_india_accounts.py | 14 - ...e_employee_from_salary_structure_parent.py | 6 - .../v8_9/rename_company_sales_target_field.py | 8 - .../v8_9/set_default_customer_group.py | 8 - .../set_default_fields_in_variant_settings.py | 13 - erpnext/patches/v8_9/set_member_party_type.py | 9 - .../v8_9/set_print_zero_amount_taxes.py | 9 - ...update_billing_gstin_for_indian_account.py | 15 -- erpnext/patches/v9_0/__init__.py | 1 - erpnext/patches/v9_0/add_healthcare_domain.py | 13 - .../add_user_to_child_table_in_pos_profile.py | 38 --- .../patches/v9_0/copy_old_fees_field_data.py | 15 -- ..._existing_warehouse_from_stock_settings.py | 8 - .../v9_0/remove_subscription_module.py | 9 - .../v9_0/revert_manufacturing_user_role.py | 22 -- erpnext/patches/v9_0/set_pos_profile_name.py | 24 -- ...for_material_request_and_purchase_order.py | 24 -- ...ipping_type_for_existing_shipping_rules.py | 18 -- .../patches/v9_0/set_uoms_in_variant_field.py | 14 - .../v9_0/set_variant_item_description.py | 46 ---- .../student_admission_childtable_migrate.py | 31 --- .../v9_0/update_employee_loan_details.py | 24 -- ...te_multi_uom_fields_in_material_request.py | 12 - erpnext/patches/v9_1/__init__.py | 0 .../v9_1/create_issue_opportunity_type.py | 34 --- erpnext/patches/v9_2/__init__.py | 0 .../delete_healthcare_domain_default_items.py | 17 -- .../patches/v9_2/delete_process_payroll.py | 5 - .../v9_2/remove_company_from_patient.py | 7 - .../v9_2/rename_net_weight_in_item_master.py | 8 - .../v9_2/rename_translated_domains_in_en.py | 39 --- .../repost_reserved_qty_for_production.py | 9 - .../v9_2/set_item_name_in_production_order.py | 12 - 441 files changed, 9599 deletions(-) delete mode 100644 erpnext/patches/repair_tools/__init__.py delete mode 100644 erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py delete mode 100644 erpnext/patches/v10_0/__init__.py delete mode 100644 erpnext/patches/v10_0/add_agriculture_domain.py delete mode 100644 erpnext/patches/v10_0/add_guardian_role_for_parent_portal.py delete mode 100644 erpnext/patches/v10_0/add_non_profit_domain.py delete mode 100644 erpnext/patches/v10_0/allow_operators_in_supplier_scorecard.py delete mode 100644 erpnext/patches/v10_0/copy_projects_renamed_fields.py delete mode 100644 erpnext/patches/v10_0/enabled_regional_print_format_based_on_country.py delete mode 100644 erpnext/patches/v10_0/fix_reserved_qty_for_sub_contract.py delete mode 100644 erpnext/patches/v10_0/recalculate_gross_margin_for_project.py delete mode 100644 erpnext/patches/v10_0/rename_schools_to_education.py delete mode 100644 erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py delete mode 100644 erpnext/patches/v10_0/repost_requested_qty_for_non_stock_uom_items.py delete mode 100644 erpnext/patches/v10_0/set_auto_created_serial_no_in_stock_entry.py delete mode 100644 erpnext/patches/v10_0/set_b2c_limit.py delete mode 100644 erpnext/patches/v10_0/set_default_payment_terms_based_on_company.py delete mode 100644 erpnext/patches/v10_0/set_discount_amount.py delete mode 100644 erpnext/patches/v10_0/set_numeric_ranges_in_template_if_blank.py delete mode 100644 erpnext/patches/v10_0/set_primary_contact_for_customer.py delete mode 100644 erpnext/patches/v10_0/set_qty_in_transactions_based_on_serial_no_input.py delete mode 100644 erpnext/patches/v10_0/set_student_party_type.py delete mode 100644 erpnext/patches/v10_0/setup_vat_for_uae_and_saudi_arabia.py delete mode 100644 erpnext/patches/v10_0/show_leaves_of_all_department_members_in_calendar.py delete mode 100644 erpnext/patches/v10_0/taxes_issue_with_pos.py delete mode 100644 erpnext/patches/v10_0/update_address_template_for_india.py delete mode 100644 erpnext/patches/v10_0/update_assessment_plan.py delete mode 100644 erpnext/patches/v10_0/update_assessment_result.py delete mode 100644 erpnext/patches/v10_0/update_asset_calculate_depreciation.py delete mode 100644 erpnext/patches/v10_0/update_hub_connector_domain.py delete mode 100644 erpnext/patches/v10_0/update_lft_rgt_for_employee.py delete mode 100644 erpnext/patches/v10_0/update_project_in_sle.py delete mode 100644 erpnext/patches/v10_0/update_reserved_qty_for_purchase_order.py delete mode 100644 erpnext/patches/v10_0/update_sales_order_link_to_purchase_order.py delete mode 100644 erpnext/patches/v10_0/update_status_for_multiple_source_in_po.py delete mode 100644 erpnext/patches/v10_0/update_status_in_purchase_receipt.py delete mode 100644 erpnext/patches/v10_0/update_territory_and_customer_group.py delete mode 100644 erpnext/patches/v10_0/update_user_image_in_employee.py delete mode 100644 erpnext/patches/v10_0/update_warehouse_address_details.py delete mode 100644 erpnext/patches/v10_1/__init__.py delete mode 100644 erpnext/patches/v11_0/__init__.py delete mode 100644 erpnext/patches/v11_0/remove_subscriber_doctype.py delete mode 100644 erpnext/patches/v11_1/__init__.py delete mode 100644 erpnext/patches/v12_0/__init__.py delete mode 100644 erpnext/patches/v13_0/__init__.py delete mode 100644 erpnext/patches/v4_0/__init__.py delete mode 100644 erpnext/patches/v4_0/apply_user_permissions.py delete mode 100644 erpnext/patches/v4_0/countrywise_coa.py delete mode 100644 erpnext/patches/v4_0/create_custom_fields_for_india_specific_fields.py delete mode 100644 erpnext/patches/v4_0/create_price_list_if_missing.py delete mode 100644 erpnext/patches/v4_0/customer_discount_to_pricing_rule.py delete mode 100644 erpnext/patches/v4_0/fields_to_be_renamed.py delete mode 100644 erpnext/patches/v4_0/fix_address_template.py delete mode 100644 erpnext/patches/v4_0/fix_case_of_hr_module_def.py delete mode 100644 erpnext/patches/v4_0/fix_contact_address.py delete mode 100644 erpnext/patches/v4_0/fix_employee_user_id.py delete mode 100644 erpnext/patches/v4_0/global_defaults_to_system_settings.py delete mode 100644 erpnext/patches/v4_0/import_country_codes.py delete mode 100644 erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py delete mode 100644 erpnext/patches/v4_0/move_warehouse_user_to_restrictions.py delete mode 100644 erpnext/patches/v4_0/new_address_template.py delete mode 100644 erpnext/patches/v4_0/reload_sales_print_format.py delete mode 100644 erpnext/patches/v4_0/remove_employee_role_if_no_employee.py delete mode 100644 erpnext/patches/v4_0/remove_module_home_pages.py delete mode 100644 erpnext/patches/v4_0/rename_sitemap_to_route.py delete mode 100644 erpnext/patches/v4_0/reset_permissions_for_masters.py delete mode 100644 erpnext/patches/v4_0/save_default_letterhead.py delete mode 100644 erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py delete mode 100644 erpnext/patches/v4_0/split_email_settings.py delete mode 100644 erpnext/patches/v4_0/update_account_root_type.py delete mode 100644 erpnext/patches/v4_0/update_custom_print_formats_for_renamed_fields.py delete mode 100644 erpnext/patches/v4_0/update_incharge_name_to_sales_person_in_maintenance_schedule.py delete mode 100644 erpnext/patches/v4_0/update_other_charges_in_custom_purchase_print_formats.py delete mode 100644 erpnext/patches/v4_0/update_tax_amount_after_discount.py delete mode 100644 erpnext/patches/v4_0/update_user_properties.py delete mode 100644 erpnext/patches/v4_0/update_users_report_view_settings.py delete mode 100644 erpnext/patches/v4_0/validate_v3_patch.py delete mode 100644 erpnext/patches/v4_1/__init__.py delete mode 100644 erpnext/patches/v4_1/fix_delivery_and_billing_status.py delete mode 100644 erpnext/patches/v4_1/fix_jv_remarks.py delete mode 100644 erpnext/patches/v4_1/fix_sales_order_delivered_status.py delete mode 100644 erpnext/patches/v4_1/set_outgoing_email_footer.py delete mode 100644 erpnext/patches/v4_2/__init__.py delete mode 100644 erpnext/patches/v4_2/add_currency_turkish_lira.py delete mode 100644 erpnext/patches/v4_2/default_website_style.py delete mode 100644 erpnext/patches/v4_2/delete_gl_entries_for_cancelled_invoices.py delete mode 100644 erpnext/patches/v4_2/delete_old_print_formats.py delete mode 100644 erpnext/patches/v4_2/discount_amount.py delete mode 100644 erpnext/patches/v4_2/fix_account_master_type.py delete mode 100644 erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py delete mode 100644 erpnext/patches/v4_2/fix_recurring_orders.py delete mode 100644 erpnext/patches/v4_2/party_model.py delete mode 100644 erpnext/patches/v4_2/recalculate_bom_cost.py delete mode 100644 erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py delete mode 100644 erpnext/patches/v4_2/repost_stock_reconciliation.py delete mode 100644 erpnext/patches/v4_2/reset_bom_costs.py delete mode 100644 erpnext/patches/v4_2/seprate_manufacture_and_repack.py delete mode 100644 erpnext/patches/v4_2/set_company_country.py delete mode 100644 erpnext/patches/v4_2/set_item_has_batch.py delete mode 100644 erpnext/patches/v4_2/toggle_rounded_total.py delete mode 100644 erpnext/patches/v4_2/update_landed_cost_voucher.py delete mode 100644 erpnext/patches/v4_2/update_project_milestones.py delete mode 100644 erpnext/patches/v4_2/update_sales_order_invoice_field_name.py delete mode 100644 erpnext/patches/v4_2/update_stock_uom_for_dn_in_sle.py delete mode 100644 erpnext/patches/v4_4/__init__.py delete mode 100644 erpnext/patches/v4_4/make_email_accounts.py delete mode 100644 erpnext/patches/v5_0/__init__.py delete mode 100644 erpnext/patches/v5_0/convert_stock_reconciliation.py delete mode 100644 erpnext/patches/v5_0/execute_on_doctype_update.py delete mode 100644 erpnext/patches/v5_0/fix_taxes_and_totals_in_party_currency.py delete mode 100644 erpnext/patches/v5_0/index_on_account_and_gl_entry.py delete mode 100644 erpnext/patches/v5_0/is_group.py delete mode 100644 erpnext/patches/v5_0/item_patches.py delete mode 100644 erpnext/patches/v5_0/link_warehouse_with_account.py delete mode 100644 erpnext/patches/v5_0/new_crm_module.py delete mode 100644 erpnext/patches/v5_0/newsletter.py delete mode 100644 erpnext/patches/v5_0/opportunity_not_submittable.py delete mode 100644 erpnext/patches/v5_0/party_model_patch_fix.py delete mode 100644 erpnext/patches/v5_0/portal_fixes.py delete mode 100644 erpnext/patches/v5_0/project_costing.py delete mode 100644 erpnext/patches/v5_0/recalculate_total_amount_in_jv.py delete mode 100644 erpnext/patches/v5_0/reclculate_planned_operating_cost_in_production_order.py delete mode 100644 erpnext/patches/v5_0/remove_birthday_events.py delete mode 100644 erpnext/patches/v5_0/rename_customer_issue.py delete mode 100644 erpnext/patches/v5_0/rename_pos_setting.py delete mode 100644 erpnext/patches/v5_0/rename_table_fieldnames.py delete mode 100644 erpnext/patches/v5_0/rename_taxes_and_charges_master.py delete mode 100644 erpnext/patches/v5_0/rename_total_fields.py delete mode 100644 erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py delete mode 100644 erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py delete mode 100644 erpnext/patches/v5_0/repost_requested_qty.py delete mode 100644 erpnext/patches/v5_0/reset_values_in_tools.py delete mode 100644 erpnext/patches/v5_0/set_appraisal_remarks.py delete mode 100644 erpnext/patches/v5_0/set_default_company_in_bom.py delete mode 100644 erpnext/patches/v5_0/set_footer_address.py delete mode 100644 erpnext/patches/v5_0/stock_entry_update_value.py delete mode 100644 erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py delete mode 100644 erpnext/patches/v5_0/update_account_types.py delete mode 100644 erpnext/patches/v5_0/update_advance_paid.py delete mode 100644 erpnext/patches/v5_0/update_companywise_payment_account.py delete mode 100644 erpnext/patches/v5_0/update_dn_against_doc_fields.py delete mode 100644 erpnext/patches/v5_0/update_from_bom.py delete mode 100644 erpnext/patches/v5_0/update_frozen_accounts_permission_role.py delete mode 100644 erpnext/patches/v5_0/update_item_and_description_again.py delete mode 100644 erpnext/patches/v5_0/update_item_desc_in_invoice.py delete mode 100644 erpnext/patches/v5_0/update_item_description_and_image.py delete mode 100644 erpnext/patches/v5_0/update_item_name_in_bom.py delete mode 100644 erpnext/patches/v5_0/update_journal_entry_title.py delete mode 100644 erpnext/patches/v5_0/update_material_transfer_for_manufacture.py delete mode 100644 erpnext/patches/v5_0/update_material_transferred_for_manufacturing.py delete mode 100644 erpnext/patches/v5_0/update_material_transferred_for_manufacturing_again.py delete mode 100644 erpnext/patches/v5_0/update_operation_description.py delete mode 100644 erpnext/patches/v5_0/update_opportunity.py delete mode 100644 erpnext/patches/v5_0/update_projects.py delete mode 100644 erpnext/patches/v5_0/update_sms_sender.py delete mode 100644 erpnext/patches/v5_0/update_tax_amount_after_discount_in_purchase_cycle.py delete mode 100644 erpnext/patches/v5_0/update_temporary_account.py delete mode 100644 erpnext/patches/v5_0/update_time_log_title.py delete mode 100644 erpnext/patches/v5_1/__init__.py delete mode 100644 erpnext/patches/v5_1/default_bom.py delete mode 100644 erpnext/patches/v5_1/fix_against_account.py delete mode 100644 erpnext/patches/v5_1/rename_roles.py delete mode 100644 erpnext/patches/v5_1/sales_bom_rename.py delete mode 100644 erpnext/patches/v5_2/__init__.py delete mode 100644 erpnext/patches/v5_2/change_item_selects_to_checks.py delete mode 100644 erpnext/patches/v5_4/__init__.py delete mode 100644 erpnext/patches/v5_4/cleanup_journal_entry.py delete mode 100644 erpnext/patches/v5_4/fix_invoice_outstanding.py delete mode 100644 erpnext/patches/v5_4/fix_missing_item_images.py delete mode 100644 erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py delete mode 100644 erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py delete mode 100644 erpnext/patches/v5_4/set_root_and_report_type.py delete mode 100644 erpnext/patches/v5_4/stock_entry_additional_costs.py delete mode 100644 erpnext/patches/v5_4/update_purchase_cost_against_project.py delete mode 100644 erpnext/patches/v5_7/__init__.py delete mode 100644 erpnext/patches/v5_7/item_template_attributes.py delete mode 100644 erpnext/patches/v5_8/__init__.py delete mode 100644 erpnext/patches/v5_8/add_credit_note_print_heading.py delete mode 100644 erpnext/patches/v5_8/tax_rule.py delete mode 100644 erpnext/patches/v5_8/update_order_reference_in_return_entries.py delete mode 100644 erpnext/patches/v6_0/__init__.py delete mode 100644 erpnext/patches/v6_0/default_activity_rate.py delete mode 100644 erpnext/patches/v6_0/fix_outstanding_amount.py delete mode 100644 erpnext/patches/v6_0/fix_planned_qty.py delete mode 100644 erpnext/patches/v6_0/multi_currency.py delete mode 100644 erpnext/patches/v6_0/set_default_title.py delete mode 100644 erpnext/patches/v6_10/__init__.py delete mode 100644 erpnext/patches/v6_10/email_digest_default_quote.py delete mode 100644 erpnext/patches/v6_10/fix_billed_amount_in_drop_ship_po.py delete mode 100644 erpnext/patches/v6_10/fix_delivery_status_of_drop_ship_item.py delete mode 100644 erpnext/patches/v6_10/fix_jv_total_amount.py delete mode 100644 erpnext/patches/v6_10/fix_ordered_received_billed.py delete mode 100644 erpnext/patches/v6_12/__init__.py delete mode 100644 erpnext/patches/v6_12/repost_entries_with_target_warehouse.py delete mode 100644 erpnext/patches/v6_12/set_overdue_tasks.py delete mode 100644 erpnext/patches/v6_16/__init__.py delete mode 100644 erpnext/patches/v6_16/create_manufacturer_records.py delete mode 100644 erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py delete mode 100644 erpnext/patches/v6_19/__init__.py delete mode 100644 erpnext/patches/v6_19/comment_feed_communication.py delete mode 100644 erpnext/patches/v6_2/__init__.py delete mode 100644 erpnext/patches/v6_2/fix_missing_default_taxes_and_lead.py delete mode 100644 erpnext/patches/v6_2/remove_newsletter_duplicates.py delete mode 100644 erpnext/patches/v6_20/__init__.py delete mode 100644 erpnext/patches/v6_20/set_party_account_currency_in_orders.py delete mode 100644 erpnext/patches/v6_20x/__init__.py delete mode 100644 erpnext/patches/v6_20x/remove_customer_supplier_roles.py delete mode 100644 erpnext/patches/v6_20x/remove_fiscal_year_from_holiday_list.py delete mode 100644 erpnext/patches/v6_20x/rename_project_name_to_project.py delete mode 100644 erpnext/patches/v6_20x/repost_valuation_rate_for_negative_inventory.py delete mode 100644 erpnext/patches/v6_20x/set_compact_print.py delete mode 100644 erpnext/patches/v6_20x/update_product_bundle_description.py delete mode 100644 erpnext/patches/v6_21/__init__.py delete mode 100644 erpnext/patches/v6_21/fix_reorder_level.py delete mode 100644 erpnext/patches/v6_21/rename_material_request_fields.py delete mode 100644 erpnext/patches/v6_23/__init__.py delete mode 100644 erpnext/patches/v6_23/update_stopped_status_to_closed.py delete mode 100644 erpnext/patches/v6_24/__init__.py delete mode 100644 erpnext/patches/v6_24/map_customer_address_to_shipping_address_on_po.py delete mode 100644 erpnext/patches/v6_24/set_recurring_id.py delete mode 100644 erpnext/patches/v6_27/__init__.py delete mode 100644 erpnext/patches/v6_27/fix_recurring_order_status.py delete mode 100644 erpnext/patches/v6_3/__init__.py delete mode 100644 erpnext/patches/v6_3/convert_applicable_territory.py delete mode 100644 erpnext/patches/v6_4/__init__.py delete mode 100644 erpnext/patches/v6_4/email_digest_update.py delete mode 100644 erpnext/patches/v6_4/fix_duplicate_bins.py delete mode 100644 erpnext/patches/v6_4/fix_expense_included_in_valuation.py delete mode 100644 erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py delete mode 100644 erpnext/patches/v6_4/fix_modified_in_sales_order_and_purchase_order.py delete mode 100644 erpnext/patches/v6_4/fix_sales_order_maintenance_status.py delete mode 100644 erpnext/patches/v6_4/fix_status_in_sales_and_purchase_order.py delete mode 100644 erpnext/patches/v6_4/make_image_thumbnail.py delete mode 100644 erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py delete mode 100644 erpnext/patches/v6_4/round_status_updater_percentages.py delete mode 100644 erpnext/patches/v6_4/set_user_in_contact.py delete mode 100644 erpnext/patches/v6_5/__init__.py delete mode 100644 erpnext/patches/v6_5/show_in_website_for_template_item.py delete mode 100644 erpnext/patches/v6_6/__init__.py delete mode 100644 erpnext/patches/v6_6/fix_website_image.py delete mode 100644 erpnext/patches/v6_6/remove_fiscal_year_from_leave_allocation.py delete mode 100644 erpnext/patches/v6_8/__init__.py delete mode 100644 erpnext/patches/v6_8/make_webform_standard.py delete mode 100644 erpnext/patches/v6_8/move_drop_ship_to_po_items.py delete mode 100644 erpnext/patches/v7_0/__init__.py delete mode 100644 erpnext/patches/v7_0/calculate_total_costing_amount.py delete mode 100644 erpnext/patches/v7_0/convert_timelog_to_timesheet.py delete mode 100644 erpnext/patches/v7_0/convert_timelogbatch_to_timesheet.py delete mode 100644 erpnext/patches/v7_0/create_budget_record.py delete mode 100644 erpnext/patches/v7_0/create_warehouse_nestedset.py delete mode 100644 erpnext/patches/v7_0/fix_duplicate_icons.py delete mode 100644 erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py delete mode 100644 erpnext/patches/v7_0/make_guardian.py delete mode 100644 erpnext/patches/v7_0/make_is_group_fieldtype_as_check.py delete mode 100644 erpnext/patches/v7_0/merge_account_type_stock_and_warehouse_to_stock.py delete mode 100644 erpnext/patches/v7_0/migrate_mode_of_payments_v6_to_v7.py delete mode 100644 erpnext/patches/v7_0/migrate_schools_to_erpnext.py delete mode 100644 erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py delete mode 100644 erpnext/patches/v7_0/po_status_issue_for_pr_return.py delete mode 100644 erpnext/patches/v7_0/re_route.py delete mode 100644 erpnext/patches/v7_0/remove_administrator_role_in_doctypes.py delete mode 100644 erpnext/patches/v7_0/remove_doctypes_and_reports.py delete mode 100644 erpnext/patches/v7_0/remove_features_setup.py delete mode 100644 erpnext/patches/v7_0/remove_old_earning_deduction_doctypes.py delete mode 100644 erpnext/patches/v7_0/rename_advance_table_fields.py delete mode 100644 erpnext/patches/v7_0/rename_examination_to_assessment.py delete mode 100644 erpnext/patches/v7_0/rename_fee_amount_to_fee_component.py delete mode 100644 erpnext/patches/v7_0/rename_prevdoc_fields.py delete mode 100644 erpnext/patches/v7_0/rename_salary_components.py delete mode 100644 erpnext/patches/v7_0/rename_time_sheet_doctype.py delete mode 100644 erpnext/patches/v7_0/repost_bin_qty_and_item_projected_qty.py delete mode 100644 erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py delete mode 100644 erpnext/patches/v7_0/repost_gle_for_pos_sales_return.py delete mode 100644 erpnext/patches/v7_0/set_base_amount_in_invoice_payment_table.py delete mode 100644 erpnext/patches/v7_0/set_is_group_for_warehouse.py delete mode 100644 erpnext/patches/v7_0/set_material_request_type_in_item.py delete mode 100644 erpnext/patches/v7_0/set_naming_series_for_timesheet.py delete mode 100644 erpnext/patches/v7_0/set_party_name_in_payment_entry.py delete mode 100644 erpnext/patches/v7_0/set_portal_settings.py delete mode 100644 erpnext/patches/v7_0/setup_account_table_for_expense_claim_type_if_exists.py delete mode 100644 erpnext/patches/v7_0/system_settings_setup_complete.py delete mode 100644 erpnext/patches/v7_0/update_autoname_field.py delete mode 100644 erpnext/patches/v7_0/update_change_amount_account.py delete mode 100644 erpnext/patches/v7_0/update_conversion_factor_in_supplier_quotation_item.py delete mode 100644 erpnext/patches/v7_0/update_home_page.py delete mode 100644 erpnext/patches/v7_0/update_maintenance_module_in_doctype.py delete mode 100644 erpnext/patches/v7_0/update_mins_to_first_response.py delete mode 100644 erpnext/patches/v7_0/update_missing_employee_in_timesheet.py delete mode 100644 erpnext/patches/v7_0/update_mode_of_payment_type.py delete mode 100644 erpnext/patches/v7_0/update_party_status.py delete mode 100644 erpnext/patches/v7_0/update_prevdoc_values_for_supplier_quotation_item.py delete mode 100644 erpnext/patches/v7_0/update_project_in_gl_entry.py delete mode 100644 erpnext/patches/v7_0/update_refdoc_in_landed_cost_voucher.py delete mode 100644 erpnext/patches/v7_0/update_status_for_timesheet.py delete mode 100644 erpnext/patches/v7_0/update_status_of_po_so.py delete mode 100644 erpnext/patches/v7_0/update_status_of_zero_amount_sales_order.py delete mode 100644 erpnext/patches/v7_0/update_timesheet_communications.py delete mode 100644 erpnext/patches/v7_1/__init__.py delete mode 100644 erpnext/patches/v7_1/add_account_user_role_for_timesheet.py delete mode 100644 erpnext/patches/v7_1/add_field_for_task_dependent.py delete mode 100644 erpnext/patches/v7_1/fix_link_for_customer_from_lead.py delete mode 100644 erpnext/patches/v7_1/move_sales_invoice_from_parent_to_child_timesheet.py delete mode 100644 erpnext/patches/v7_1/rename_field_timesheet.py delete mode 100644 erpnext/patches/v7_1/rename_quality_inspection_field.py delete mode 100644 erpnext/patches/v7_1/repost_stock_for_deleted_bins_for_merging_items.py delete mode 100644 erpnext/patches/v7_1/save_stock_settings.py delete mode 100644 erpnext/patches/v7_1/set_budget_against_as_cost_center.py delete mode 100644 erpnext/patches/v7_1/set_currency_exchange_date.py delete mode 100644 erpnext/patches/v7_1/set_prefered_contact_email.py delete mode 100644 erpnext/patches/v7_1/set_sales_person_status.py delete mode 100644 erpnext/patches/v7_1/set_student_guardian.py delete mode 100644 erpnext/patches/v7_1/set_total_amount_currency_in_je.py delete mode 100644 erpnext/patches/v7_1/update_bom_base_currency.py delete mode 100644 erpnext/patches/v7_1/update_component_type.py delete mode 100644 erpnext/patches/v7_1/update_invoice_status.py delete mode 100644 erpnext/patches/v7_1/update_lead_source.py delete mode 100644 erpnext/patches/v7_1/update_missing_salary_component_type.py delete mode 100644 erpnext/patches/v7_1/update_portal_roles.py delete mode 100644 erpnext/patches/v7_1/update_total_billing_hours.py delete mode 100644 erpnext/patches/v7_2/__init__.py delete mode 100644 erpnext/patches/v7_2/arrear_leave_encashment_as_salary_component.py delete mode 100644 erpnext/patches/v7_2/contact_address_links.py delete mode 100644 erpnext/patches/v7_2/delete_fleet_management_module_def.py delete mode 100644 erpnext/patches/v7_2/empty_supplied_items_for_non_subcontracted.py delete mode 100644 erpnext/patches/v7_2/make_all_assessment_group.py delete mode 100644 erpnext/patches/v7_2/mark_students_active.py delete mode 100644 erpnext/patches/v7_2/rename_att_date_attendance.py delete mode 100644 erpnext/patches/v7_2/rename_evaluation_criteria.py delete mode 100644 erpnext/patches/v7_2/set_null_value_to_fields.py delete mode 100644 erpnext/patches/v7_2/setup_auto_close_settings.py delete mode 100644 erpnext/patches/v7_2/stock_uom_in_selling.py delete mode 100644 erpnext/patches/v7_2/update_abbr_in_salary_slips.py delete mode 100644 erpnext/patches/v7_2/update_assessment_modules.py delete mode 100644 erpnext/patches/v7_2/update_attendance_docstatus.py delete mode 100644 erpnext/patches/v7_2/update_doctype_status.py delete mode 100644 erpnext/patches/v7_2/update_guardian_name_in_student_master.py delete mode 100644 erpnext/patches/v7_2/update_party_type.py delete mode 100644 erpnext/patches/v7_2/update_salary_slips.py delete mode 100644 erpnext/patches/v7_2/update_website_for_variant.py delete mode 100644 erpnext/patches/v8_0/__init__.py delete mode 100644 erpnext/patches/v8_0/addresses_linked_to_lead.py delete mode 100644 erpnext/patches/v8_0/change_in_words_varchar_length.py delete mode 100644 erpnext/patches/v8_0/create_address_doc_from_address_field_in_company.py delete mode 100644 erpnext/patches/v8_0/create_domain_docs.py delete mode 100644 erpnext/patches/v8_0/delete_bin_indexes.py delete mode 100644 erpnext/patches/v8_0/delete_schools_depricated_doctypes.py delete mode 100644 erpnext/patches/v8_0/disable_instructor_role.py delete mode 100644 erpnext/patches/v8_0/enable_booking_asset_depreciation_automatically.py delete mode 100644 erpnext/patches/v8_0/fix_status_for_invoices_with_negative_outstanding.py delete mode 100644 erpnext/patches/v8_0/make_payments_table_blank_for_non_pos_invoice.py delete mode 100644 erpnext/patches/v8_0/merge_student_batch_and_student_group.py delete mode 100644 erpnext/patches/v8_0/move_account_head_from_account_to_warehouse_for_inventory.py delete mode 100644 erpnext/patches/v8_0/move_perpetual_inventory_setting.py delete mode 100644 erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py delete mode 100644 erpnext/patches/v8_0/rename_items_in_status_field_of_material_request.py delete mode 100644 erpnext/patches/v8_0/rename_total_margin_to_rate_with_margin.py delete mode 100644 erpnext/patches/v8_0/repost_reserved_qty_for_multiple_sales_uom.py delete mode 100644 erpnext/patches/v8_0/revert_manufacturers_table_from_item.py delete mode 100644 erpnext/patches/v8_0/save_system_settings.py delete mode 100644 erpnext/patches/v8_0/set_null_to_serial_nos_for_disabled_sales_invoices.py delete mode 100644 erpnext/patches/v8_0/set_project_copied_from.py delete mode 100644 erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py delete mode 100644 erpnext/patches/v8_0/update_customer_pos_id.py delete mode 100644 erpnext/patches/v8_0/update_production_orders.py delete mode 100644 erpnext/patches/v8_0/update_sales_cost_in_project.py delete mode 100644 erpnext/patches/v8_0/update_status_as_paid_for_completed_expense_claim.py delete mode 100644 erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py delete mode 100644 erpnext/patches/v8_0/update_stock_qty_value_in_purchase_invoice.py delete mode 100644 erpnext/patches/v8_0/update_student_groups_from_student_batches.py delete mode 100644 erpnext/patches/v8_0/update_supplier_address_in_stock_entry.py delete mode 100644 erpnext/patches/v8_1/__init__.py delete mode 100644 erpnext/patches/v8_1/add_hsn_sac_codes.py delete mode 100644 erpnext/patches/v8_1/add_indexes_in_transaction_doctypes.py delete mode 100644 erpnext/patches/v8_1/allow_invoice_copy_to_edit_after_submit.py delete mode 100644 erpnext/patches/v8_1/delete_deprecated_reports.py delete mode 100644 erpnext/patches/v8_1/gst_fixes.py delete mode 100644 erpnext/patches/v8_1/remove_sales_invoice_from_returned_serial_no.py delete mode 100644 erpnext/patches/v8_1/removed_report_support_hours.py delete mode 100644 erpnext/patches/v8_1/set_delivery_date_in_so_item.py delete mode 100644 erpnext/patches/v8_1/update_expense_claim_status.py delete mode 100644 erpnext/patches/v8_1/update_gst_state.py delete mode 100644 erpnext/patches/v8_10/__init__.py delete mode 100644 erpnext/patches/v8_10/change_default_customer_credit_days.py delete mode 100644 erpnext/patches/v8_3/__init__.py delete mode 100644 erpnext/patches/v8_3/set_restrict_to_domain_for_module_def.py delete mode 100644 erpnext/patches/v8_3/update_company_total_sales.py delete mode 100644 erpnext/patches/v8_4/__init__.py delete mode 100644 erpnext/patches/v8_4/make_scorecard_records.py delete mode 100644 erpnext/patches/v8_5/__init__.py delete mode 100644 erpnext/patches/v8_5/fix_tax_breakup_for_non_invoice_docs.py delete mode 100644 erpnext/patches/v8_5/remove_project_type_property_setter.py delete mode 100644 erpnext/patches/v8_5/remove_quotations_route_in_sidebar.py delete mode 100644 erpnext/patches/v8_5/set_default_mode_of_payment.py delete mode 100644 erpnext/patches/v8_5/update_customer_group_in_POS_profile.py delete mode 100644 erpnext/patches/v8_5/update_existing_data_in_project_type.py delete mode 100644 erpnext/patches/v8_6/__init__.py delete mode 100644 erpnext/patches/v8_6/point_sms_doctype_module_to_frappe_core.py delete mode 100644 erpnext/patches/v8_6/rename_bom_update_tool.py delete mode 100644 erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py delete mode 100644 erpnext/patches/v8_6/update_timesheet_company_from_PO.py delete mode 100644 erpnext/patches/v8_7/__init__.py delete mode 100644 erpnext/patches/v8_7/fix_purchase_receipt_status.py delete mode 100644 erpnext/patches/v8_7/make_subscription_from_recurring_data.py delete mode 100644 erpnext/patches/v8_8/__init__.py delete mode 100644 erpnext/patches/v8_8/add_new_fields_in_accounts_settings.py delete mode 100644 erpnext/patches/v8_8/set_bom_rate_as_per_uom.py delete mode 100644 erpnext/patches/v8_9/__init__.py delete mode 100644 erpnext/patches/v8_9/add_setup_progress_actions.py delete mode 100644 erpnext/patches/v8_9/delete_gst_doctypes_for_outside_india_accounts.py delete mode 100644 erpnext/patches/v8_9/remove_employee_from_salary_structure_parent.py delete mode 100644 erpnext/patches/v8_9/rename_company_sales_target_field.py delete mode 100644 erpnext/patches/v8_9/set_default_customer_group.py delete mode 100644 erpnext/patches/v8_9/set_default_fields_in_variant_settings.py delete mode 100644 erpnext/patches/v8_9/set_member_party_type.py delete mode 100644 erpnext/patches/v8_9/set_print_zero_amount_taxes.py delete mode 100644 erpnext/patches/v8_9/update_billing_gstin_for_indian_account.py delete mode 100644 erpnext/patches/v9_0/__init__.py delete mode 100644 erpnext/patches/v9_0/add_healthcare_domain.py delete mode 100644 erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py delete mode 100644 erpnext/patches/v9_0/copy_old_fees_field_data.py delete mode 100644 erpnext/patches/v9_0/remove_non_existing_warehouse_from_stock_settings.py delete mode 100644 erpnext/patches/v9_0/remove_subscription_module.py delete mode 100644 erpnext/patches/v9_0/revert_manufacturing_user_role.py delete mode 100644 erpnext/patches/v9_0/set_pos_profile_name.py delete mode 100644 erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py delete mode 100644 erpnext/patches/v9_0/set_shipping_type_for_existing_shipping_rules.py delete mode 100644 erpnext/patches/v9_0/set_uoms_in_variant_field.py delete mode 100644 erpnext/patches/v9_0/set_variant_item_description.py delete mode 100644 erpnext/patches/v9_0/student_admission_childtable_migrate.py delete mode 100644 erpnext/patches/v9_0/update_employee_loan_details.py delete mode 100644 erpnext/patches/v9_0/update_multi_uom_fields_in_material_request.py delete mode 100644 erpnext/patches/v9_1/__init__.py delete mode 100644 erpnext/patches/v9_1/create_issue_opportunity_type.py delete mode 100644 erpnext/patches/v9_2/__init__.py delete mode 100644 erpnext/patches/v9_2/delete_healthcare_domain_default_items.py delete mode 100644 erpnext/patches/v9_2/delete_process_payroll.py delete mode 100644 erpnext/patches/v9_2/remove_company_from_patient.py delete mode 100644 erpnext/patches/v9_2/rename_net_weight_in_item_master.py delete mode 100644 erpnext/patches/v9_2/rename_translated_domains_in_en.py delete mode 100644 erpnext/patches/v9_2/repost_reserved_qty_for_production.py delete mode 100644 erpnext/patches/v9_2/set_item_name_in_production_order.py diff --git a/erpnext/patches/repair_tools/__init__.py b/erpnext/patches/repair_tools/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py b/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py deleted file mode 100644 index 5a421d146f..0000000000 --- a/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py +++ /dev/null @@ -1,13 +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 frappe - -def execute(): - from erpnext.stock.stock_balance import set_stock_balance_as_per_serial_no - frappe.db.auto_commit_on_many_writes = 1 - - set_stock_balance_as_per_serial_no() - - frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/patches/v10_0/__init__.py b/erpnext/patches/v10_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v10_0/add_agriculture_domain.py b/erpnext/patches/v10_0/add_agriculture_domain.py deleted file mode 100644 index c18e69f3e6..0000000000 --- a/erpnext/patches/v10_0/add_agriculture_domain.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(): - domain = 'Agriculture' - if not frappe.db.exists('Domain', domain): - frappe.get_doc({ - 'doctype': 'Domain', - 'domain': domain - }).insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/patches/v10_0/add_guardian_role_for_parent_portal.py b/erpnext/patches/v10_0/add_guardian_role_for_parent_portal.py deleted file mode 100644 index 0b891f21f4..0000000000 --- a/erpnext/patches/v10_0/add_guardian_role_for_parent_portal.py +++ /dev/null @@ -1,23 +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(): - # create guardian role - if not frappe.get_value('Role', dict(role_name='Guardian')): - frappe.get_doc({ - 'doctype': 'Role', - 'role_name': 'Guardian', - 'desk_access': 0, - 'restrict_to_domain': 'Education' - }).insert(ignore_permissions=True) - - # set guardian roles in already created users - if frappe.db.exists("Doctype", "Guardian"): - for user in frappe.db.sql_list("""select u.name from `tabUser` u , `tabGuardian` g where g.email_address = u.name"""): - user = frappe.get_doc('User', user) - user.flags.ignore_validate = True - user.flags.ignore_mandatory = True - user.save() diff --git a/erpnext/patches/v10_0/add_non_profit_domain.py b/erpnext/patches/v10_0/add_non_profit_domain.py deleted file mode 100644 index b03d669515..0000000000 --- a/erpnext/patches/v10_0/add_non_profit_domain.py +++ /dev/null @@ -1,20 +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(): - domain = 'Non Profit' - if not frappe.db.exists('Domain', domain): - frappe.get_doc({ - 'doctype': 'Domain', - 'domain': domain - }).insert(ignore_permissions=True) - - frappe.get_doc({ - 'doctype': 'Role', - 'role_name': 'Non Profit Portal User', - 'desk_access': 0, - 'restrict_to_domain': domain - }).insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/patches/v10_0/allow_operators_in_supplier_scorecard.py b/erpnext/patches/v10_0/allow_operators_in_supplier_scorecard.py deleted file mode 100644 index 827f9bc94f..0000000000 --- a/erpnext/patches/v10_0/allow_operators_in_supplier_scorecard.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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('buying', 'doctype', 'supplier_scorecard_criteria') - frappe.reload_doc('buying', 'doctype', 'supplier_scorecard_scoring_criteria') - frappe.reload_doc('buying', 'doctype', 'supplier_scorecard') - - for criteria in frappe.get_all('Supplier Scorecard Criteria', fields=['name', 'formula'], limit_page_length=None): - frappe.db.set_value('Supplier Scorecard Criteria', criteria.name, - 'formula', criteria.formula.replace('<','<').replace('>','>')) - - for criteria in frappe.get_all('Supplier Scorecard Scoring Criteria', fields=['name', 'formula'], limit_page_length=None): - if criteria.formula: # not mandatory - frappe.db.set_value('Supplier Scorecard Scoring Criteria', criteria.name, - 'formula', criteria.formula.replace('<','<').replace('>','>')) - - for sc in frappe.get_all('Supplier Scorecard', fields=['name', 'weighting_function'], limit_page_length=None): - frappe.db.set_value('Supplier Scorecard', sc.name, 'weighting_function', - sc.weighting_function.replace('<','<').replace('>','>')) \ No newline at end of file diff --git a/erpnext/patches/v10_0/copy_projects_renamed_fields.py b/erpnext/patches/v10_0/copy_projects_renamed_fields.py deleted file mode 100644 index 80db3bdd1e..0000000000 --- a/erpnext/patches/v10_0/copy_projects_renamed_fields.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - """ copy data from old fields to new """ - frappe.reload_doc("projects", "doctype", "project") - - if frappe.db.has_column('Project', 'total_sales_cost'): - rename_field('Project', "total_sales_cost", "total_sales_amount") - - if frappe.db.has_column('Project', 'total_billing_amount'): - rename_field('Project', "total_billing_amount", "total_billable_amount") \ No newline at end of file diff --git a/erpnext/patches/v10_0/enabled_regional_print_format_based_on_country.py b/erpnext/patches/v10_0/enabled_regional_print_format_based_on_country.py deleted file mode 100644 index 38b04cebc2..0000000000 --- a/erpnext/patches/v10_0/enabled_regional_print_format_based_on_country.py +++ /dev/null @@ -1,22 +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(): - print_format_mapper = { - 'India': ['GST POS Invoice', 'GST Tax Invoice'], - 'Saudi Arabia': ['Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice'], - 'United Arab Emirates': ['Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice'] - } - - frappe.db.sql(""" update `tabPrint Format` set disabled = 1 where name - in ('GST POS Invoice', 'GST Tax Invoice', 'Simplified Tax Invoice', 'Detailed Tax Invoice')""") - - for d in frappe.get_all('Company', fields = ["country"], - filters={'country': ('in', ['India', 'Saudi Arabia', 'United Arab Emirates'])}): - if print_format_mapper.get(d.country): - print_formats = print_format_mapper.get(d.country) - frappe.db.sql(""" update `tabPrint Format` set disabled = 0 - where name in (%s)""" % ", ".join(["%s"]*len(print_formats)), tuple(print_formats)) \ No newline at end of file diff --git a/erpnext/patches/v10_0/fix_reserved_qty_for_sub_contract.py b/erpnext/patches/v10_0/fix_reserved_qty_for_sub_contract.py deleted file mode 100644 index c0a9e5eb5b..0000000000 --- a/erpnext/patches/v10_0/fix_reserved_qty_for_sub_contract.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.stock.utils import get_bin - -def execute(): - frappe.reload_doc("stock", "doctype", "bin") - frappe.reload_doc("buying", "doctype", "purchase_order_item_supplied") - for d in frappe.db.sql(""" - select distinct rm_item_code, reserve_warehouse - from `tabPurchase Order Item Supplied` - where docstatus=1 and reserve_warehouse is not null and reserve_warehouse != ''"""): - - try: - bin_doc = get_bin(d[0], d[1]) - bin_doc.update_reserved_qty_for_sub_contracting() - except: - pass - - for d in frappe.db.sql("""select distinct item_code, source_warehouse - from `tabWork Order Item` - where docstatus=1 and transferred_qty > required_qty - and source_warehouse is not null and source_warehouse != ''""", as_list=1): - - try: - bin_doc = get_bin(d[0], d[1]) - bin_doc.update_reserved_qty_for_production() - except: - pass diff --git a/erpnext/patches/v10_0/recalculate_gross_margin_for_project.py b/erpnext/patches/v10_0/recalculate_gross_margin_for_project.py deleted file mode 100644 index 6d461f3bc9..0000000000 --- a/erpnext/patches/v10_0/recalculate_gross_margin_for_project.py +++ /dev/null @@ -1,14 +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('projects', 'doctype', 'project') - for d in frappe.db.sql(""" select name from `tabProject` where - ifnull(total_consumed_material_cost, 0 ) > 0 and ifnull(total_billed_amount, 0) > 0""", as_dict=1): - doc = frappe.get_doc("Project", d.name) - doc.calculate_gross_margin() - doc.db_set('gross_margin', doc.gross_margin) - doc.db_set('per_gross_margin', doc.per_gross_margin) \ No newline at end of file diff --git a/erpnext/patches/v10_0/rename_schools_to_education.py b/erpnext/patches/v10_0/rename_schools_to_education.py deleted file mode 100644 index 85c25a8943..0000000000 --- a/erpnext/patches/v10_0/rename_schools_to_education.py +++ /dev/null @@ -1,32 +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(): - # rename the School module as Education - - # rename the school module - if frappe.db.exists('Module Def', 'Schools') and not frappe.db.exists('Module Def', 'Education'): - frappe.rename_doc("Module Def", "Schools", "Education") - - # delete the school module - if frappe.db.exists('Module Def', 'Schools') and frappe.db.exists('Module Def', 'Education'): - frappe.db.sql("""delete from `tabModule Def` where module_name = 'Schools'""") - - - # rename "School Settings" to the "Education Settings - if frappe.db.exists('DocType', 'School Settings'): - frappe.rename_doc("DocType", "School Settings", "Education Settings", force=True) - frappe.reload_doc("education", "doctype", "education_settings") - - # delete the discussion web form if exists - if frappe.db.exists('Web Form', 'Discussion'): - frappe.db.sql("""delete from `tabWeb Form` where name = 'discussion'""") - - # rename the select option field from "School Bus" to "Institute's Bus" - frappe.reload_doc("education", "doctype", "Program Enrollment") - if "mode_of_transportation" in frappe.db.get_table_columns("Program Enrollment"): - frappe.db.sql("""update `tabProgram Enrollment` set mode_of_transportation = "Institute's Bus" - where mode_of_transportation = "School Bus" """) diff --git a/erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py b/erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py deleted file mode 100644 index e6546e386b..0000000000 --- a/erpnext/patches/v10_0/repost_gle_for_purchase_receipts_with_rejected_items.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe, erpnext - -def execute(): - for company in frappe.get_all("Company"): - if not erpnext.is_perpetual_inventory_enabled(company.name): - continue - - acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto") or "1900-01-01" - pr_with_rejected_warehouse = frappe.db.sql(""" - select pr.name - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item - where pr.name = pr_item.parent - and pr.posting_date > %s - and pr.docstatus=1 - and pr.company = %s - and pr_item.rejected_qty > 0 - """, (acc_frozen_upto, company.name), as_dict=1) - - for d in pr_with_rejected_warehouse: - doc = frappe.get_doc("Purchase Receipt", d.name) - - doc.docstatus = 2 - doc.make_gl_entries_on_cancel() - - - # update gl entries for submit state of PR - doc.docstatus = 1 - doc.make_gl_entries() diff --git a/erpnext/patches/v10_0/repost_requested_qty_for_non_stock_uom_items.py b/erpnext/patches/v10_0/repost_requested_qty_for_non_stock_uom_items.py deleted file mode 100644 index 4fe4e97cf5..0000000000 --- a/erpnext/patches/v10_0/repost_requested_qty_for_non_stock_uom_items.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2019, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty - - count=0 - for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse - from `tabMaterial Request Item` where docstatus = 1 and stock_uom<>uom"""): - try: - count += 1 - update_bin_qty(item_code, warehouse, { - "indented_qty": get_indented_qty(item_code, warehouse), - }) - if count % 200 == 0: - frappe.db.commit() - except: - frappe.db.rollback() diff --git a/erpnext/patches/v10_0/set_auto_created_serial_no_in_stock_entry.py b/erpnext/patches/v10_0/set_auto_created_serial_no_in_stock_entry.py deleted file mode 100644 index c6470f21d7..0000000000 --- a/erpnext/patches/v10_0/set_auto_created_serial_no_in_stock_entry.py +++ /dev/null @@ -1,56 +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(): - serialised_items = [d.name for d in frappe.get_all("Item", filters={"has_serial_no": 1})] - - if not serialised_items: - return - - for dt in ["Stock Entry Detail", "Purchase Receipt Item", "Purchase Invoice Item"]: - cond = "" - if dt=="Purchase Invoice Item": - cond = """ and parent in (select name from `tabPurchase Invoice` - where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.parent and update_stock=1)""" - - item_rows = frappe.db.sql(""" - select name - from `tab{0}` - where conversion_factor != 1 - and docstatus = 1 - and ifnull(serial_no, '') = '' - and item_code in ({1}) - {2} - """.format(dt, ', '.join(['%s']*len(serialised_items)), cond), tuple(serialised_items)) - - if item_rows: - sle_serial_nos = dict(frappe.db.sql(""" - select voucher_detail_no, serial_no - from `tabStock Ledger Entry` - where ifnull(serial_no, '') != '' - and voucher_detail_no in (%s) - """.format(', '.join(['%s']*len(item_rows))), - tuple([d[0] for d in item_rows]))) - - batch_size = 100 - for i in range(0, len(item_rows), batch_size): - batch_item_rows = item_rows[i:i + batch_size] - when_then = [] - for item_row in batch_item_rows: - - when_then.append('WHEN `name` = "{row_name}" THEN "{value}"'.format( - row_name=item_row[0], - value=sle_serial_nos.get(item_row[0]))) - - frappe.db.sql(""" - update - `tab{doctype}` - set - serial_no = CASE {when_then_cond} ELSE `serial_no` END - """.format( - doctype = dt, - when_then_cond=" ".join(when_then) - )) \ No newline at end of file diff --git a/erpnext/patches/v10_0/set_b2c_limit.py b/erpnext/patches/v10_0/set_b2c_limit.py deleted file mode 100644 index 5d964e681a..0000000000 --- a/erpnext/patches/v10_0/set_b2c_limit.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("regional", "doctype", "gst_settings") - frappe.reload_doc("accounts", "doctype", "gst_account") - gst_settings = frappe.get_doc("GST Settings") - gst_settings.b2c_limit = 250000 - gst_settings.save() diff --git a/erpnext/patches/v10_0/set_default_payment_terms_based_on_company.py b/erpnext/patches/v10_0/set_default_payment_terms_based_on_company.py deleted file mode 100644 index a90e096390..0000000000 --- a/erpnext/patches/v10_0/set_default_payment_terms_based_on_company.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.patches.v8_10.change_default_customer_credit_days import make_payment_term, make_template - -def execute(): - for dt in ("Company", "Customer Group"): - frappe.reload_doc("setup", "doctype", frappe.scrub(dt)) - - credit_records = frappe.db.sql(""" - SELECT DISTINCT `credit_days`, `credit_days_based_on`, `name` - from `tab{0}` - where - ((credit_days_based_on='Fixed Days' or credit_days_based_on is null) and credit_days is not null) - or credit_days_based_on='Last Day of the Next Month' - """.format(dt), as_dict=1) - - for d in credit_records: - template = create_payment_terms_template(d) - - frappe.db.sql(""" - update `tab{0}` - set `payment_terms` = %s - where name = %s - """.format(dt), (template.name, d.name)) - -def create_payment_terms_template(data): - if data.credit_days_based_on == "Fixed Days": - pyt_template_name = 'Default Payment Term - N{0}'.format(data.credit_days) - else: - pyt_template_name = 'Default Payment Term - EO2M' - - if not frappe.db.exists("Payment Terms Template", pyt_template_name): - payment_term = make_payment_term(data.credit_days, data.credit_days_based_on) - template = make_template(payment_term) - else: - template = frappe.get_doc("Payment Terms Template", pyt_template_name) - return template \ No newline at end of file diff --git a/erpnext/patches/v10_0/set_discount_amount.py b/erpnext/patches/v10_0/set_discount_amount.py deleted file mode 100644 index d5e2c5a84b..0000000000 --- a/erpnext/patches/v10_0/set_discount_amount.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def execute(): - frappe.reload_doc("accounts", "doctype", "sales_invoice_item") - frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_item') - frappe.reload_doc('buying', 'doctype', 'purchase_order_item') - frappe.reload_doc('buying', 'doctype', 'supplier_quotation_item') - frappe.reload_doc('selling', 'doctype', 'sales_order_item') - frappe.reload_doc('selling', 'doctype', 'quotation_item') - frappe.reload_doc('stock', 'doctype', 'delivery_note_item') - frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item') - - selling_doctypes = ["Sales Order Item", "Sales Invoice Item", "Delivery Note Item", "Quotation Item"] - buying_doctypes = ["Purchase Order Item", "Purchase Invoice Item", "Purchase Receipt Item", "Supplier Quotation Item"] - - for doctype in selling_doctypes: - frappe.db.sql(''' - UPDATE - `tab%s` - SET - discount_amount = if(rate_with_margin > 0, rate_with_margin, price_list_rate) * discount_percentage / 100 - WHERE - discount_percentage > 0 - ''' % (doctype)) - for doctype in buying_doctypes: - frappe.db.sql(''' - UPDATE - `tab%s` - SET - discount_amount = price_list_rate * discount_percentage / 100 - WHERE - discount_percentage > 0 - ''' % (doctype)) \ No newline at end of file diff --git a/erpnext/patches/v10_0/set_numeric_ranges_in_template_if_blank.py b/erpnext/patches/v10_0/set_numeric_ranges_in_template_if_blank.py deleted file mode 100644 index 6825f19d74..0000000000 --- a/erpnext/patches/v10_0/set_numeric_ranges_in_template_if_blank.py +++ /dev/null @@ -1,35 +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(): - item_numeric_attributes = frappe.db.sql(""" - select name, numeric_values, from_range, to_range, increment - from `tabItem Attribute` - where numeric_values = 1 - """, as_dict=1) - - for d in item_numeric_attributes: - frappe.db.sql(""" - update `tabItem Variant Attribute` - set - from_range = CASE - WHEN from_range = 0 THEN %(from_range)s - ELSE from_range - END, - to_range = CASE - WHEN to_range = 0 THEN %(to_range)s - ELSE to_range - END, - increment = CASE - WHEN increment = 0 THEN %(increment)s - ELSE increment - END, - numeric_values = %(numeric_values)s - where - attribute = %(name)s - and exists(select name from tabItem - where name=`tabItem Variant Attribute`.parent and has_variants=1) - """, d) \ No newline at end of file diff --git a/erpnext/patches/v10_0/set_primary_contact_for_customer.py b/erpnext/patches/v10_0/set_primary_contact_for_customer.py deleted file mode 100644 index ae0b31c21f..0000000000 --- a/erpnext/patches/v10_0/set_primary_contact_for_customer.py +++ /dev/null @@ -1,21 +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_doctype('Customer') - - frappe.db.sql(""" - update - `tabCustomer`, ( - select `tabContact`.name, `tabContact`.mobile_no, `tabContact`.email_id, - `tabDynamic Link`.link_name from `tabContact`, `tabDynamic Link` - where `tabContact`.name = `tabDynamic Link`.parent and - `tabDynamic Link`.link_doctype = 'Customer' and `tabContact`.is_primary_contact = 1 - ) as contact - set - `tabCustomer`.customer_primary_contact = contact.name, - `tabCustomer`.mobile_no = contact.mobile_no, `tabCustomer`.email_id = contact.email_id - where `tabCustomer`.name = contact.link_name""") \ No newline at end of file diff --git a/erpnext/patches/v10_0/set_qty_in_transactions_based_on_serial_no_input.py b/erpnext/patches/v10_0/set_qty_in_transactions_based_on_serial_no_input.py deleted file mode 100644 index 083b7f4b20..0000000000 --- a/erpnext/patches/v10_0/set_qty_in_transactions_based_on_serial_no_input.py +++ /dev/null @@ -1,21 +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("stock", "doctype", "stock_settings") - - ss = frappe.get_doc("Stock Settings") - ss.set_qty_in_transactions_based_on_serial_no_input = 1 - - if ss.default_warehouse \ - and not frappe.db.exists("Warehouse", ss.default_warehouse): - ss.default_warehouse = None - - if ss.stock_uom and not frappe.db.exists("UOM", ss.stock_uom): - ss.stock_uom = None - - ss.flags.ignore_mandatory = True - ss.save() \ No newline at end of file diff --git a/erpnext/patches/v10_0/set_student_party_type.py b/erpnext/patches/v10_0/set_student_party_type.py deleted file mode 100644 index 08376ae894..0000000000 --- a/erpnext/patches/v10_0/set_student_party_type.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if not frappe.db.exists("Party Type", "Student"): - party = frappe.new_doc("Party Type") - party.party_type = "Student" - party.save() diff --git a/erpnext/patches/v10_0/setup_vat_for_uae_and_saudi_arabia.py b/erpnext/patches/v10_0/setup_vat_for_uae_and_saudi_arabia.py deleted file mode 100644 index a8d90499d8..0000000000 --- a/erpnext/patches/v10_0/setup_vat_for_uae_and_saudi_arabia.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 -from erpnext.setup.doctype.company.company import install_country_fixtures - -def execute(): - frappe.reload_doc("accounts", "doctype", "account") - frappe.reload_doc("accounts", "doctype", "payment_schedule") - for d in frappe.get_all('Company', - filters={'country': ('in', ['Saudi Arabia', 'United Arab Emirates'])}): - install_country_fixtures(d.name) \ No newline at end of file diff --git a/erpnext/patches/v10_0/show_leaves_of_all_department_members_in_calendar.py b/erpnext/patches/v10_0/show_leaves_of_all_department_members_in_calendar.py deleted file mode 100644 index 7e2ff7a8a7..0000000000 --- a/erpnext/patches/v10_0/show_leaves_of_all_department_members_in_calendar.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("hr", "doctype", "hr_settings") - frappe.db.set_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar", 1) \ No newline at end of file diff --git a/erpnext/patches/v10_0/taxes_issue_with_pos.py b/erpnext/patches/v10_0/taxes_issue_with_pos.py deleted file mode 100644 index 2a3275ac2c..0000000000 --- a/erpnext/patches/v10_0/taxes_issue_with_pos.py +++ /dev/null @@ -1,26 +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(): - for d in frappe.get_all('Sales Invoice', fields=["name"], - filters = {'is_pos':1, 'docstatus': 1, 'creation': ('>', '2018-04-23')}): - doc = frappe.get_doc('Sales Invoice', d.name) - if (not doc.taxes and doc.taxes_and_charges and doc.pos_profile and doc.outstanding_amount != 0 and - frappe.db.get_value('POS Profile', doc.pos_profile, 'taxes_and_charges', cache=True) == doc.taxes_and_charges): - - doc.append_taxes_from_master() - doc.calculate_taxes_and_totals() - for d in doc.taxes: - d.db_update() - - doc.db_update() - - delete_gle_for_voucher(doc.name) - doc.make_gl_entries() - -def delete_gle_for_voucher(voucher_no): - frappe.db.sql("""delete from `tabGL Entry` where voucher_no = %(voucher_no)s""", - {'voucher_no': voucher_no}) \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_address_template_for_india.py b/erpnext/patches/v10_0/update_address_template_for_india.py deleted file mode 100644 index 1ddca93760..0000000000 --- a/erpnext/patches/v10_0/update_address_template_for_india.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.regional.address_template.setup import set_up_address_templates - -def execute(): - if frappe.db.get_value('Company', {'country': 'India'}, 'name'): - address_template = frappe.db.get_value('Address Template', 'India', 'template') - if not address_template or "gstin" not in address_template: - set_up_address_templates(default_country='India') diff --git a/erpnext/patches/v10_0/update_assessment_plan.py b/erpnext/patches/v10_0/update_assessment_plan.py deleted file mode 100644 index 174623c1a4..0000000000 --- a/erpnext/patches/v10_0/update_assessment_plan.py +++ /dev/null @@ -1,17 +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('education', 'doctype', 'assessment_plan') - - frappe.db.sql(""" - UPDATE `tabAssessment Plan` as ap - INNER JOIN `tabStudent Group` as sg ON sg.name = ap.student_group - SET ap.academic_term = sg.academic_term, - ap.academic_year = sg.academic_year, - ap.program = sg.program - WHERE ap.docstatus = 1 - """) \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_assessment_result.py b/erpnext/patches/v10_0/update_assessment_result.py deleted file mode 100644 index 96218db972..0000000000 --- a/erpnext/patches/v10_0/update_assessment_result.py +++ /dev/null @@ -1,20 +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('education', 'doctype', 'assessment_result') - - frappe.db.sql(""" - UPDATE `tabAssessment Result` AS ar - INNER JOIN `tabAssessment Plan` AS ap ON ap.name = ar.assessment_plan - SET ar.academic_term = ap.academic_term, - ar.academic_year = ap.academic_year, - ar.program = ap.program, - ar.course = ap.course, - ar.assessment_group = ap.assessment_group, - ar.student_group = ap.student_group - WHERE ap.docstatus = 1 - """) \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_asset_calculate_depreciation.py b/erpnext/patches/v10_0/update_asset_calculate_depreciation.py deleted file mode 100644 index b947a40b4a..0000000000 --- a/erpnext/patches/v10_0/update_asset_calculate_depreciation.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('assets', 'doctype', 'asset') - frappe.reload_doc('assets', 'doctype', 'depreciation_schedule') - - frappe.db.sql(""" - update tabAsset a - set calculate_depreciation = 1 - where exists(select ds.name from `tabDepreciation Schedule` ds where ds.parent=a.name) - """) \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_hub_connector_domain.py b/erpnext/patches/v10_0/update_hub_connector_domain.py deleted file mode 100644 index baf580a369..0000000000 --- a/erpnext/patches/v10_0/update_hub_connector_domain.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.table_exists("Data Migration Connector"): - frappe.db.sql(""" - UPDATE `tabData Migration Connector` - SET hostname = 'https://hubmarket.org' - WHERE connector_name = 'Hub Connector' - """) \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_lft_rgt_for_employee.py b/erpnext/patches/v10_0/update_lft_rgt_for_employee.py deleted file mode 100644 index 46ca786e0d..0000000000 --- a/erpnext/patches/v10_0/update_lft_rgt_for_employee.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils.nestedset import rebuild_tree - -def execute(): - """ assign lft and rgt appropriately """ - frappe.reload_doc("hr", "doctype", "employee") - - rebuild_tree("Employee", "reports_to") \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_project_in_sle.py b/erpnext/patches/v10_0/update_project_in_sle.py deleted file mode 100644 index 08c64f18d8..0000000000 --- a/erpnext/patches/v10_0/update_project_in_sle.py +++ /dev/null @@ -1,15 +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(): - for doctype in ['Sales Invoice', 'Delivery Note', 'Stock Entry']: - frappe.db.sql(""" update - `tabStock Ledger Entry` sle, `tab{0}` parent_doc - set - sle.project = parent_doc.project - where - sle.voucher_no = parent_doc.name and sle.voucher_type = %s and sle.project is null - and parent_doc.project is not null and parent_doc.project != ''""".format(doctype), doctype) diff --git a/erpnext/patches/v10_0/update_reserved_qty_for_purchase_order.py b/erpnext/patches/v10_0/update_reserved_qty_for_purchase_order.py deleted file mode 100644 index 7b2c36698a..0000000000 --- a/erpnext/patches/v10_0/update_reserved_qty_for_purchase_order.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.stock.utils import get_bin - -def execute(): - po_item = list(frappe.db.sql((""" - select distinct po.name as poname, poitem.rm_item_code as rm_item_code, po.company - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitem - where po.name = poitem.parent - and po.is_subcontracted = "Yes" - and po.docstatus = 1"""), as_dict=1)) - if not po_item: - return - - frappe.reload_doc("stock", "doctype", "bin") - frappe.reload_doc("buying", "doctype", "purchase_order_item_supplied") - company_warehouse = frappe._dict(frappe.db.sql("""select company, min(name) from `tabWarehouse` - where is_group = 0 group by company""")) - - items = list(set([d.rm_item_code for d in po_item])) - item_wh = frappe._dict(frappe.db.sql("""select item_code, default_warehouse - from `tabItem` where name in ({0})""".format(", ".join(["%s"] * len(items))), items)) - - # Update reserved warehouse - for item in po_item: - reserve_warehouse = get_warehouse(item.rm_item_code, item.company, company_warehouse, item_wh) - frappe.db.sql("""update `tabPurchase Order Item Supplied` - set reserve_warehouse = %s - where parent = %s and rm_item_code = %s - """, (reserve_warehouse, item["poname"], item["rm_item_code"])) - - # Update bin - item_wh_bin = frappe.db.sql((""" - select distinct poitemsup.rm_item_code as rm_item_code, - poitemsup.reserve_warehouse as reserve_warehouse - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup - where po.name = poitemsup.parent - and po.is_subcontracted = "Yes" - and po.docstatus = 1"""), as_dict=1) - for d in item_wh_bin: - try: - stock_bin = get_bin(d["rm_item_code"], d["reserve_warehouse"]) - stock_bin.update_reserved_qty_for_sub_contracting() - except: - pass - -def get_warehouse(item_code, company, company_warehouse, item_wh): - reserve_warehouse = item_wh.get(item_code) - if frappe.db.get_value("Warehouse", reserve_warehouse, "company") != company: - reserve_warehouse = None - if not reserve_warehouse: - reserve_warehouse = company_warehouse.get(company) - return reserve_warehouse diff --git a/erpnext/patches/v10_0/update_sales_order_link_to_purchase_order.py b/erpnext/patches/v10_0/update_sales_order_link_to_purchase_order.py deleted file mode 100644 index b4f58384bf..0000000000 --- a/erpnext/patches/v10_0/update_sales_order_link_to_purchase_order.py +++ /dev/null @@ -1,18 +0,0 @@ -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("buying", "doctype", "supplier_quotation_item") - - for doctype in ['Purchase Order','Supplier Quotation']: - frappe.db.sql(""" - Update - `tab{doctype} Item`, `tabMaterial Request Item` - set - `tab{doctype} Item`.sales_order = `tabMaterial Request Item`.sales_order - where - `tab{doctype} Item`.material_request= `tabMaterial Request Item`.parent - and `tab{doctype} Item`.material_request_item = `tabMaterial Request Item`.name - and `tabMaterial Request Item`.sales_order is not null""".format(doctype=doctype)) \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_status_for_multiple_source_in_po.py b/erpnext/patches/v10_0/update_status_for_multiple_source_in_po.py deleted file mode 100644 index fd3be08b89..0000000000 --- a/erpnext/patches/v10_0/update_status_for_multiple_source_in_po.py +++ /dev/null @@ -1,40 +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(): - - - # update the sales order item in the material request - frappe.reload_doc('stock', 'doctype', 'material_request_item') - frappe.db.sql('''update `tabMaterial Request Item` mri, `tabSales Order Item` soi - set mri.sales_order_item = soi.name - where ifnull(mri.sales_order, "")!="" and soi.parent=mri.sales_order - and soi.item_code=mri.item_code and mri.docstatus=1 - ''') - - # update the sales order item in the purchase order - frappe.db.sql('''update `tabPurchase Order Item` poi, `tabSales Order Item` soi - set poi.sales_order_item = soi.name - where ifnull(poi.sales_order, "")!="" and soi.parent=poi.sales_order - and soi.item_code=poi.item_code and poi.docstatus = 1 - ''') - - # Update the status in material request and sales order - po_list = frappe.db.sql(''' - select parent from `tabPurchase Order Item` where ifnull(material_request, "")!="" and - ifnull(sales_order, "")!="" and docstatus=1 - ''',as_dict=1) - - for po in list(set([d.get("parent") for d in po_list if d.get("parent")])): - try: - po_doc = frappe.get_doc("Purchase Order", po) - - # update the so in the status updater - po_doc.update_status_updater() - po_doc.update_qty(update_modified=False) - - except Exception: - pass diff --git a/erpnext/patches/v10_0/update_status_in_purchase_receipt.py b/erpnext/patches/v10_0/update_status_in_purchase_receipt.py deleted file mode 100644 index a0bdd9e2cc..0000000000 --- a/erpnext/patches/v10_0/update_status_in_purchase_receipt.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("stock", "doctype", "purchase_receipt") - frappe.db.sql(''' - UPDATE `tabPurchase Receipt` SET status = "Completed" WHERE per_billed = 100 AND docstatus = 1 - ''') \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_territory_and_customer_group.py b/erpnext/patches/v10_0/update_territory_and_customer_group.py deleted file mode 100644 index 7f3dae991d..0000000000 --- a/erpnext/patches/v10_0/update_territory_and_customer_group.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.rename_doc import get_fetch_fields - -def execute(): - ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"] - customers = frappe.get_all('Customer', fields=["name", "customer_group"]) - customer_group_fetch = get_fetch_fields('Customer', 'Customer Group', ignore_doctypes) - - batch_size = 1000 - for i in range(0, len(customers), batch_size): - batch_customers = customers[i:i + batch_size] - for d in customer_group_fetch: - when_then = [] - for customer in batch_customers: - value = frappe.db.escape(frappe.as_unicode(customer.get("customer_group"))) - - when_then.append(''' - WHEN `%s` = %s and %s != %s - THEN %s - '''%(d["master_fieldname"], frappe.db.escape(frappe.as_unicode(customer.name)), - d["linked_to_fieldname"], value, value)) - - frappe.db.sql(""" - update - `tab%s` - set - %s = CASE %s ELSE `%s` END - """%(d['doctype'], d.linked_to_fieldname, " ".join(when_then), d.linked_to_fieldname)) diff --git a/erpnext/patches/v10_0/update_user_image_in_employee.py b/erpnext/patches/v10_0/update_user_image_in_employee.py deleted file mode 100644 index 72d5d2a857..0000000000 --- a/erpnext/patches/v10_0/update_user_image_in_employee.py +++ /dev/null @@ -1,19 +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('hr', 'doctype', 'employee') - - frappe.db.sql(""" - UPDATE - `tabEmployee`, `tabUser` - SET - `tabEmployee`.image = `tabUser`.user_image - WHERE - `tabEmployee`.user_id = `tabUser`.name and - `tabEmployee`.user_id is not null and - `tabEmployee`.user_id != '' and `tabEmployee`.image is null - """) diff --git a/erpnext/patches/v10_0/update_warehouse_address_details.py b/erpnext/patches/v10_0/update_warehouse_address_details.py deleted file mode 100644 index b982b9a662..0000000000 --- a/erpnext/patches/v10_0/update_warehouse_address_details.py +++ /dev/null @@ -1,37 +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(): - warehouse = frappe.db.sql("""select name, email_id, phone_no, mobile_no, address_line_1, - address_line_2, city, state, pin from `tabWarehouse` where ifnull(address_line_1, '') != '' - or ifnull(mobile_no, '') != '' - or ifnull(email_id, '') != '' """, as_dict=1) - - for d in warehouse: - try: - address = frappe.new_doc('Address') - address.name = d.name - address.address_title = d.name - address.address_line1 = d.address_line_1 - address.city = d.city - address.state = d.state - address.pincode = d.pin - address.db_insert() - address.append('links',{'link_doctype':'Warehouse','link_name':d.name}) - address.links[0].db_insert() - if d.name and (d.email_id or d.mobile_no or d.phone_no): - contact = frappe.new_doc('Contact') - contact.name = d.name - contact.first_name = d.name - contact.mobile_no = d.mobile_no - contact.email_id = d.email_id - contact.phone = d.phone_no - contact.db_insert() - contact.append('links',{'link_doctype':'Warehouse','link_name':d.name}) - contact.links[0].db_insert() - except frappe.DuplicateEntryError: - pass - \ No newline at end of file diff --git a/erpnext/patches/v10_1/__init__.py b/erpnext/patches/v10_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v11_0/__init__.py b/erpnext/patches/v11_0/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v11_0/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v11_0/remove_subscriber_doctype.py b/erpnext/patches/v11_0/remove_subscriber_doctype.py deleted file mode 100644 index 4839a20f91..0000000000 --- a/erpnext/patches/v11_0/remove_subscriber_doctype.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - """ copy subscribe field to customer """ - frappe.reload_doc("accounts","doctype","subscription") - - if frappe.db.exists("DocType", "Subscriber"): - if frappe.db.has_column('Subscription','subscriber'): - frappe.db.sql(""" - update `tabSubscription` s1 - set customer=(select customer from tabSubscriber where name=s1.subscriber) - """) - - frappe.delete_doc("DocType", "Subscriber") \ No newline at end of file diff --git a/erpnext/patches/v11_1/__init__.py b/erpnext/patches/v11_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v12_0/__init__.py b/erpnext/patches/v12_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v13_0/__init__.py b/erpnext/patches/v13_0/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v13_0/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v4_0/__init__.py b/erpnext/patches/v4_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v4_0/apply_user_permissions.py b/erpnext/patches/v4_0/apply_user_permissions.py deleted file mode 100644 index 3c5d612c18..0000000000 --- a/erpnext/patches/v4_0/apply_user_permissions.py +++ /dev/null @@ -1,50 +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 frappe -from erpnext.hr.doctype.employee.employee import EmployeeUserDisabledError - -def execute(): - update_hr_permissions() - update_permissions() - remove_duplicate_user_permissions() - frappe.clear_cache() - -def update_hr_permissions(): - # add set user permissions rights to HR Manager - frappe.db.sql("""update `tabDocPerm` set `set_user_permissions`=1 where parent in ('Employee', 'Leave Application') - and role='HR Manager' and permlevel=0 and `read`=1""") - docperm_meta = frappe.get_meta('DocPerm') - if docperm_meta.get_field('apply_user_permissions'): - # apply user permissions on Employee and Leave Application - frappe.db.sql("""update `tabDocPerm` set `apply_user_permissions`=1 where parent in ('Employee', 'Leave Application') - and role in ('Employee', 'Leave Approver') and permlevel=0 and `read`=1""") - - frappe.clear_cache() - - # save employees to run on_update events - for employee in frappe.db.sql_list("""select name from `tabEmployee` where docstatus < 2"""): - try: - emp = frappe.get_doc("Employee", employee) - emp.flags.ignore_mandatory = True - emp.save() - except EmployeeUserDisabledError: - pass - -def update_permissions(): - # clear match conditions other than owner - frappe.db.sql("""update tabDocPerm set `match`='' - where ifnull(`match`,'') not in ('', 'owner')""") - -def remove_duplicate_user_permissions(): - # remove duplicate user_permissions (if they exist) - for d in frappe.db.sql("""select parent, defkey, defvalue, - count(*) as cnt from tabDefaultValue - where parent not in ('__global', '__default') - group by parent, defkey, defvalue""", as_dict=1): - if d.cnt > 1: - # order by parenttype so that user permission does not get removed! - frappe.db.sql("""delete from tabDefaultValue where `parent`=%s and `defkey`=%s and - `defvalue`=%s order by parenttype limit %s""", (d.parent, d.defkey, d.defvalue, d.cnt-1)) - diff --git a/erpnext/patches/v4_0/countrywise_coa.py b/erpnext/patches/v4_0/countrywise_coa.py deleted file mode 100644 index f45e6028c8..0000000000 --- a/erpnext/patches/v4_0/countrywise_coa.py +++ /dev/null @@ -1,29 +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 frappe - -def execute(): - frappe.reload_doc("setup", 'doctype', "company") - frappe.reload_doc("accounts", 'doctype', "account") - - frappe.db.sql("""update tabAccount set account_type='Cash' - where account_type='Bank or Cash' and account_name in ('Cash', 'Cash In Hand')""") - - frappe.db.sql("""update tabAccount set account_type='Stock' - where account_name = 'Stock Assets'""") - - ac_types = {"Fixed Asset Account": "Fixed Asset", "Bank or Cash": "Bank"} - for old, new in ac_types.items(): - frappe.db.sql("""update tabAccount set account_type=%s - where account_type=%s""", (new, old)) - - try: - frappe.db.sql("""update `tabAccount` set report_type = - if(is_pl_account='Yes', 'Profit and Loss', 'Balance Sheet')""") - - frappe.db.sql("""update `tabAccount` set balance_must_be=debit_or_credit - where ifnull(allow_negative_balance, 0) = 0""") - except: - pass diff --git a/erpnext/patches/v4_0/create_custom_fields_for_india_specific_fields.py b/erpnext/patches/v4_0/create_custom_fields_for_india_specific_fields.py deleted file mode 100644 index fe50e444b5..0000000000 --- a/erpnext/patches/v4_0/create_custom_fields_for_india_specific_fields.py +++ /dev/null @@ -1,63 +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 frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_field_if_values_exist - -def execute(): - frappe.reload_doc("stock", "doctype", "purchase_receipt") - frappe.reload_doc("hr", "doctype", "employee") - frappe.reload_doc("Payroll", "doctype", "salary_slip") - - india_specific_fields = { - "Purchase Receipt": [{ - "label": "Supplier Shipment No", - "fieldname": "challan_no", - "fieldtype": "Data", - "insert_after": "is_subcontracted" - }, { - "label": "Supplier Shipment Date", - "fieldname": "challan_date", - "fieldtype": "Date", - "insert_after": "is_subcontracted" - }], - "Employee": [{ - "label": "PAN Number", - "fieldname": "pan_number", - "fieldtype": "Data", - "insert_after": "company_email" - }, { - "label": "Gratuity LIC Id", - "fieldname": "gratuity_lic_id", - "fieldtype": "Data", - "insert_after": "company_email" - }, { - "label": "Esic Card No", - "fieldname": "esic_card_no", - "fieldtype": "Data", - "insert_after": "bank_ac_no" - }, { - "label": "PF Number", - "fieldname": "pf_number", - "fieldtype": "Data", - "insert_after": "bank_ac_no" - }], - "Salary Slip": [{ - "label": "Esic No", - "fieldname": "esic_no", - "fieldtype": "Data", - "insert_after": "letter_head", - "permlevel": 1 - }, { - "label": "PF Number", - "fieldname": "pf_no", - "fieldtype": "Data", - "insert_after": "letter_head", - "permlevel": 1 - }] - } - - for dt, docfields in india_specific_fields.items(): - for df in docfields: - create_custom_field_if_values_exist(dt, df) diff --git a/erpnext/patches/v4_0/create_price_list_if_missing.py b/erpnext/patches/v4_0/create_price_list_if_missing.py deleted file mode 100644 index 039e52111c..0000000000 --- a/erpnext/patches/v4_0/create_price_list_if_missing.py +++ /dev/null @@ -1,35 +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 frappe -from frappe import _ -from frappe.utils.nestedset import get_root_of - -def execute(): - # setup not complete - if not frappe.db.sql("""select name from tabCompany limit 1"""): - return - - if "shopping_cart" in frappe.get_installed_apps(): - frappe.reload_doc("shopping_cart", "doctype", "shopping_cart_settings") - - if not frappe.db.sql("select name from `tabPrice List` where buying=1"): - create_price_list(_("Standard Buying"), buying=1) - - if not frappe.db.sql("select name from `tabPrice List` where selling=1"): - create_price_list(_("Standard Selling"), selling=1) - -def create_price_list(pl_name, buying=0, selling=0): - price_list = frappe.get_doc({ - "doctype": "Price List", - "price_list_name": pl_name, - "enabled": 1, - "buying": buying, - "selling": selling, - "currency": frappe.db.get_default("currency"), - "territories": [{ - "territory": get_root_of("Territory") - }] - }) - price_list.insert() diff --git a/erpnext/patches/v4_0/customer_discount_to_pricing_rule.py b/erpnext/patches/v4_0/customer_discount_to_pricing_rule.py deleted file mode 100644 index 1b260c48cc..0000000000 --- a/erpnext/patches/v4_0/customer_discount_to_pricing_rule.py +++ /dev/null @@ -1,33 +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 frappe -from frappe.utils.nestedset import get_root_of - -def execute(): - frappe.reload_doc("accounts", "doctype", "pricing_rule") - - frappe.db.auto_commit_on_many_writes = True - - default_item_group = get_root_of("Item Group") - - for d in frappe.db.sql("""select * from `tabCustomer Discount` - where ifnull(parent, '') != ''""", as_dict=1): - if not d.discount: - continue - - frappe.get_doc({ - "doctype": "Pricing Rule", - "apply_on": "Item Group", - "item_group": d.item_group or default_item_group, - "applicable_for": "Customer", - "customer": d.parent, - "price_or_discount": "Discount Percentage", - "discount_percentage": d.discount, - "selling": 1 - }).insert() - - frappe.db.auto_commit_on_many_writes = False - - frappe.delete_doc("DocType", "Customer Discount") diff --git a/erpnext/patches/v4_0/fields_to_be_renamed.py b/erpnext/patches/v4_0/fields_to_be_renamed.py deleted file mode 100644 index cc17697132..0000000000 --- a/erpnext/patches/v4_0/fields_to_be_renamed.py +++ /dev/null @@ -1,109 +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 frappe -from frappe.model.utils.rename_field import rename_field -from frappe.modules import scrub, get_doctype_module - -rename_map = { - "Quotation Item": [ - ["ref_rate", "price_list_rate"], - ["base_ref_rate", "base_price_list_rate"], - ["adj_rate", "discount_percentage"], - ["export_rate", "rate"], - ["basic_rate", "base_rate"], - ["amount", "base_amount"], - ["export_amount", "amount"] - ], - - "Sales Order Item": [ - ["ref_rate", "price_list_rate"], - ["base_ref_rate", "base_price_list_rate"], - ["adj_rate", "discount_percentage"], - ["export_rate", "rate"], - ["basic_rate", "base_rate"], - ["amount", "base_amount"], - ["export_amount", "amount"], - ["reserved_warehouse", "warehouse"] - ], - - "Delivery Note Item": [ - ["ref_rate", "price_list_rate"], - ["base_ref_rate", "base_price_list_rate"], - ["adj_rate", "discount_percentage"], - ["export_rate", "rate"], - ["basic_rate", "base_rate"], - ["amount", "base_amount"], - ["export_amount", "amount"] - ], - - "Sales Invoice Item": [ - ["ref_rate", "price_list_rate"], - ["base_ref_rate", "base_price_list_rate"], - ["adj_rate", "discount_percentage"], - ["export_rate", "rate"], - ["basic_rate", "base_rate"], - ["amount", "base_amount"], - ["export_amount", "amount"] - ], - - "Supplier Quotation Item": [ - ["import_ref_rate", "price_list_rate"], - ["purchase_ref_rate", "base_price_list_rate"], - ["discount_rate", "discount_percentage"], - ["import_rate", "rate"], - ["purchase_rate", "base_rate"], - ["amount", "base_amount"], - ["import_amount", "amount"] - ], - - "Purchase Order Item": [ - ["import_ref_rate", "price_list_rate"], - ["purchase_ref_rate", "base_price_list_rate"], - ["discount_rate", "discount_percentage"], - ["import_rate", "rate"], - ["purchase_rate", "base_rate"], - ["amount", "base_amount"], - ["import_amount", "amount"] - ], - - "Purchase Receipt Item": [ - ["import_ref_rate", "price_list_rate"], - ["purchase_ref_rate", "base_price_list_rate"], - ["discount_rate", "discount_percentage"], - ["import_rate", "rate"], - ["purchase_rate", "base_rate"], - ["amount", "base_amount"], - ["import_amount", "amount"] - ], - - "Purchase Invoice Item": [ - ["import_ref_rate", "price_list_rate"], - ["purchase_ref_rate", "base_price_list_rate"], - ["discount_rate", "discount_percentage"], - ["import_rate", "rate"], - ["rate", "base_rate"], - ["amount", "base_amount"], - ["import_amount", "amount"], - ["expense_head", "expense_account"] - ], - - "Item": [ - ["purchase_account", "expense_account"], - ["default_sales_cost_center", "selling_cost_center"], - ["cost_center", "buying_cost_center"], - ["default_income_account", "income_account"], - ], - "Item Price": [ - ["ref_rate", "price_list_rate"] - ] -} - -def execute(): - for dn in rename_map: - frappe.reload_doc(get_doctype_module(dn), "doctype", scrub(dn)) - - for dt, field_list in rename_map.items(): - for field in field_list: - rename_field(dt, field[0], field[1]) diff --git a/erpnext/patches/v4_0/fix_address_template.py b/erpnext/patches/v4_0/fix_address_template.py deleted file mode 100644 index 905e3db3e8..0000000000 --- a/erpnext/patches/v4_0/fix_address_template.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors - -from __future__ import unicode_literals -import frappe - -def execute(): - missing_line = """{{ address_line1 }}
    """ - for name, template in frappe.db.sql("select name, template from `tabAddress Template`"): - if missing_line not in template: - d = frappe.get_doc("Address Template", name) - d.template = missing_line + d.template - d.save() diff --git a/erpnext/patches/v4_0/fix_case_of_hr_module_def.py b/erpnext/patches/v4_0/fix_case_of_hr_module_def.py deleted file mode 100644 index e77b427b77..0000000000 --- a/erpnext/patches/v4_0/fix_case_of_hr_module_def.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors - -from __future__ import unicode_literals -import frappe - -def execute(): - hr = frappe.db.get_value("Module Def", "HR") - if hr == "Hr": - frappe.rename_doc("Module Def", "Hr", "HR") - frappe.db.set_value("Module Def", "HR", "module_name", "HR") - - frappe.clear_cache() - frappe.setup_module_map() diff --git a/erpnext/patches/v4_0/fix_contact_address.py b/erpnext/patches/v4_0/fix_contact_address.py deleted file mode 100644 index 6a3e106b8c..0000000000 --- a/erpnext/patches/v4_0/fix_contact_address.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("website", "doctype", "contact_us_settings") - address = frappe.db.get_value("Contact Us Settings", None, "address") - if address: - address = frappe.get_doc("Address", address) - contact = frappe.get_doc("Contact Us Settings", "Contact Us Settings") - for f in ("address_title", "address_line1", "address_line2", "city", "state", "country", "pincode"): - contact.set(f, address.get(f)) - - contact.save() \ No newline at end of file diff --git a/erpnext/patches/v4_0/fix_employee_user_id.py b/erpnext/patches/v4_0/fix_employee_user_id.py deleted file mode 100644 index 6f449f97bb..0000000000 --- a/erpnext/patches/v4_0/fix_employee_user_id.py +++ /dev/null @@ -1,23 +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 frappe -from frappe.utils import get_fullname - -def execute(): - for user_id in frappe.db.sql_list("""select distinct user_id from `tabEmployee` - where ifnull(user_id, '')!='' - group by user_id having count(name) > 1"""): - - fullname = get_fullname(user_id) - employee = frappe.db.get_value("Employee", {"employee_name": fullname, "user_id": user_id}) - - if employee: - frappe.db.sql("""update `tabEmployee` set user_id=null - where user_id=%s and name!=%s""", (user_id, employee)) - else: - count = frappe.db.sql("""select count(*) from `tabEmployee` where user_id=%s""", user_id)[0][0] - frappe.db.sql("""update `tabEmployee` set user_id=null - where user_id=%s limit %s""", (user_id, count - 1)) diff --git a/erpnext/patches/v4_0/global_defaults_to_system_settings.py b/erpnext/patches/v4_0/global_defaults_to_system_settings.py deleted file mode 100644 index 2905fe1e6a..0000000000 --- a/erpnext/patches/v4_0/global_defaults_to_system_settings.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals - -import frappe -from collections import Counter -from frappe.core.doctype.user.user import STANDARD_USERS - -def execute(): - frappe.reload_doc("core", "doctype", "system_settings") - system_settings = frappe.get_doc("System Settings") - - # set values from global_defauls - global_defaults = frappe.db.get_value("Global Defaults", None, - ["time_zone", "date_format", "number_format", "float_precision", "session_expiry"], as_dict=True) - - if global_defaults: - for key, val in global_defaults.items(): - if not system_settings.get(key): - system_settings.set(key, val) - - # language - if not system_settings.get("language"): - # find most common language - lang = frappe.db.sql_list("""select language from `tabUser` - where ifnull(language, '')!='' and language not like "Loading%%" and name not in ({standard_users})""".format( - standard_users=", ".join(["%s"]*len(STANDARD_USERS))), tuple(STANDARD_USERS)) - lang = Counter(lang).most_common(1) - lang = (len(lang) > 0) and lang[0][0] or "english" - - system_settings.language = lang - - system_settings.flags.ignore_mandatory = True - system_settings.save() - - global_defaults = frappe.get_doc("Global Defaults") - global_defaults.flags.ignore_mandatory = True - global_defaults.save() diff --git a/erpnext/patches/v4_0/import_country_codes.py b/erpnext/patches/v4_0/import_country_codes.py deleted file mode 100644 index 43e23d5b63..0000000000 --- a/erpnext/patches/v4_0/import_country_codes.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.geo.country_info import get_all -from frappe.utils.install import import_country_and_currency - -from six import iteritems - -def execute(): - frappe.reload_doc("setup", "doctype", "country") - import_country_and_currency() - for name, country in iteritems(get_all()): - frappe.set_value("Country", name, "code", country.get("code")) \ No newline at end of file diff --git a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py b/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py deleted file mode 100644 index 97e217aa05..0000000000 --- a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py +++ /dev/null @@ -1,16 +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 frappe - -def execute(): - # update sales cycle - for d in ['Sales Invoice', 'Sales Order', 'Quotation', 'Delivery Note']: - frappe.db.sql("""update `tab%s` set taxes_and_charges=charge""" % d) - - # update purchase cycle - for d in ['Purchase Invoice', 'Purchase Order', 'Supplier Quotation', 'Purchase Receipt']: - frappe.db.sql("""update `tab%s` set taxes_and_charges=purchase_other_charges""" % d) - - frappe.db.sql("""update `tabPurchase Taxes and Charges` set parentfield='other_charges'""") diff --git a/erpnext/patches/v4_0/move_warehouse_user_to_restrictions.py b/erpnext/patches/v4_0/move_warehouse_user_to_restrictions.py deleted file mode 100644 index 8b81936d8d..0000000000 --- a/erpnext/patches/v4_0/move_warehouse_user_to_restrictions.py +++ /dev/null @@ -1,13 +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 frappe -import frappe.permissions - -def execute(): - for warehouse, user in frappe.db.sql("""select parent, user from `tabWarehouse User`"""): - frappe.permissions.add_user_permission("Warehouse", warehouse, user) - - frappe.delete_doc_if_exists("DocType", "Warehouse User") - frappe.reload_doc("stock", "doctype", "warehouse") diff --git a/erpnext/patches/v4_0/new_address_template.py b/erpnext/patches/v4_0/new_address_template.py deleted file mode 100644 index fa6602706e..0000000000 --- a/erpnext/patches/v4_0/new_address_template.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - frappe.reload_doc("utilities", "doctype", "address_template") - if not frappe.db.sql("select name from `tabAddress Template`"): - try: - d = frappe.new_doc("Address Template") - d.update({"country":frappe.db.get_default("country") or - frappe.db.get_value("Global Defaults", "Global Defaults", "country")}) - d.insert() - except: - print(frappe.get_traceback()) - diff --git a/erpnext/patches/v4_0/reload_sales_print_format.py b/erpnext/patches/v4_0/reload_sales_print_format.py deleted file mode 100644 index b8f090f9f3..0000000000 --- a/erpnext/patches/v4_0/reload_sales_print_format.py +++ /dev/null @@ -1,8 +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 frappe - -def execute(): - frappe.reload_doc('accounts', 'Print Format', 'POS Invoice') diff --git a/erpnext/patches/v4_0/remove_employee_role_if_no_employee.py b/erpnext/patches/v4_0/remove_employee_role_if_no_employee.py deleted file mode 100644 index 8766ace54f..0000000000 --- a/erpnext/patches/v4_0/remove_employee_role_if_no_employee.py +++ /dev/null @@ -1,18 +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 frappe -import frappe.permissions - -def execute(): - for user in frappe.db.sql_list("select distinct parent from `tabHas Role` where role='Employee'"): - # if employee record does not exists, remove employee role! - if not frappe.db.get_value("Employee", {"user_id": user}): - try: - user = frappe.get_doc("User", user) - for role in user.get("roles", {"role": "Employee"}): - user.get("roles").remove(role) - user.save() - except frappe.DoesNotExistError: - pass diff --git a/erpnext/patches/v4_0/remove_module_home_pages.py b/erpnext/patches/v4_0/remove_module_home_pages.py deleted file mode 100644 index a2720e0ebf..0000000000 --- a/erpnext/patches/v4_0/remove_module_home_pages.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - for page in ("accounts-home", "website-home", "support-home", "stock-home", "selling-home", "projects-home", - "manufacturing-home", "hr-home", "buying-home"): - frappe.delete_doc("Page", page) \ No newline at end of file diff --git a/erpnext/patches/v4_0/rename_sitemap_to_route.py b/erpnext/patches/v4_0/rename_sitemap_to_route.py deleted file mode 100644 index ffb1fda144..0000000000 --- a/erpnext/patches/v4_0/rename_sitemap_to_route.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals -import frappe -import frappe.model - -def execute(): - frappe.reload_doc("setup", "doctype", "item_group") - frappe.reload_doc("stock", "doctype", "item") - frappe.reload_doc("setup", "doctype", "sales_partner") - - try: - frappe.model.rename_field("Item Group", "parent_website_sitemap", "parent_website_route") - frappe.model.rename_field("Item", "parent_website_sitemap", "parent_website_route") - frappe.model.rename_field("Sales Partner", "parent_website_sitemap", - "parent_website_route") - except Exception as e: - if e.args[0]!=1054: - raise diff --git a/erpnext/patches/v4_0/reset_permissions_for_masters.py b/erpnext/patches/v4_0/reset_permissions_for_masters.py deleted file mode 100644 index bc1b438e2b..0000000000 --- a/erpnext/patches/v4_0/reset_permissions_for_masters.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -from frappe.permissions import reset_perms - -def execute(): - for doctype in ("About Us Settings", "Accounts Settings", "Activity Type", - "Blog Category", "Blog Settings", "Blogger", "Branch", "Brand", "Buying Settings", - "Communication", "Company", "Contact Us Settings", - "Country", "Currency", "Currency Exchange", "Deduction Type", "Department", - "Designation", "Earning Type", "Event", "Feed", "File", "Fiscal Year", - "HR Settings", "Industry Type", "Leave Type", "Letter Head", - "Mode of Payment", "Module Def", "Naming Series", "POS Setting", "Print Heading", - "Report", "Role", "Selling Settings", "Stock Settings", "Supplier Type", "UOM"): - try: - reset_perms(doctype) - except: - print("Error resetting perms for", doctype) - raise diff --git a/erpnext/patches/v4_0/save_default_letterhead.py b/erpnext/patches/v4_0/save_default_letterhead.py deleted file mode 100644 index 5d75f096b3..0000000000 --- a/erpnext/patches/v4_0/save_default_letterhead.py +++ /dev/null @@ -1,13 +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 frappe - -def execute(): - """save default letterhead to set default_letter_head_content""" - try: - letter_head = frappe.get_doc("Letter Head", {"is_default": 1}) - letter_head.save() - except frappe.DoesNotExistError: - pass diff --git a/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py b/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py deleted file mode 100644 index 7e472e2195..0000000000 --- a/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py +++ /dev/null @@ -1,13 +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 frappe - -def execute(): - frappe.reload_doc("accounts", "doctype", "pricing_rule") - frappe.db.sql("""update `tabPricing Rule` set selling=1 where ifnull(applicable_for, '') in - ('', 'Customer', 'Customer Group', 'Territory', 'Sales Partner', 'Campaign')""") - - frappe.db.sql("""update `tabPricing Rule` set buying=1 where ifnull(applicable_for, '') in - ('', 'Supplier', 'Supplier Type')""") diff --git a/erpnext/patches/v4_0/split_email_settings.py b/erpnext/patches/v4_0/split_email_settings.py deleted file mode 100644 index 5d1dea60ee..0000000000 --- a/erpnext/patches/v4_0/split_email_settings.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - print("WARNING!!!! Email Settings not migrated. Please setup your email again.") - - # this will happen if you are migrating very old accounts - # comment out this line below and remember to create new Email Accounts - # for incoming and outgoing mails - raise Exception - - return - - - frappe.reload_doc("core", "doctype", "outgoing_email_settings") - frappe.reload_doc("support", "doctype", "support_email_settings") - - email_settings = get_email_settings() - map_outgoing_email_settings(email_settings) - map_support_email_settings(email_settings) - - -def map_outgoing_email_settings(email_settings): - outgoing_email_settings = frappe.get_doc("Outgoing Email Settings") - for fieldname in (("outgoing_mail_server", "mail_server"), - "use_ssl", "mail_port", "mail_login", "mail_password", - "always_use_login_id_as_sender", "auto_email_id"): - - if isinstance(fieldname, tuple): - from_fieldname, to_fieldname = fieldname - else: - from_fieldname = to_fieldname = fieldname - - outgoing_email_settings.set(to_fieldname, email_settings.get(from_fieldname)) - - outgoing_email_settings._fix_numeric_types() - outgoing_email_settings.save() - -def map_support_email_settings(email_settings): - support_email_settings = frappe.get_doc("Support Email Settings") - - for fieldname in ("sync_support_mails", "support_email", - ("support_host", "mail_server"), - ("support_use_ssl", "use_ssl"), - ("support_username", "mail_login"), - ("support_password", "mail_password"), - "support_signature", "send_autoreply", "support_autoreply"): - - if isinstance(fieldname, tuple): - from_fieldname, to_fieldname = fieldname - else: - from_fieldname = to_fieldname = fieldname - - support_email_settings.set(to_fieldname, email_settings.get(from_fieldname)) - - support_email_settings._fix_numeric_types() - support_email_settings.save() - -def get_email_settings(): - ret = {} - for field, value in frappe.db.sql("select field, value from tabSingles where doctype='Email Settings'"): - ret[field] = value - return ret - diff --git a/erpnext/patches/v4_0/update_account_root_type.py b/erpnext/patches/v4_0/update_account_root_type.py deleted file mode 100644 index 15ddf032a4..0000000000 --- a/erpnext/patches/v4_0/update_account_root_type.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - frappe.reload_doc("accounts", "doctype", "account") - - account_table_columns = frappe.db.get_table_columns("Account") - if "debit_or_credit" in account_table_columns and "is_pl_account" in account_table_columns: - frappe.db.sql("""UPDATE tabAccount - SET root_type = CASE - WHEN (debit_or_credit='Debit' and is_pl_account = 'No') THEN 'Asset' - WHEN (debit_or_credit='Credit' and is_pl_account = 'No') THEN 'Liability' - WHEN (debit_or_credit='Debit' and is_pl_account = 'Yes') THEN 'Expense' - WHEN (debit_or_credit='Credit' and is_pl_account = 'Yes') THEN 'Income' - END - WHERE ifnull(parent_account, '') = '' - """) - - else: - for key, root_type in (("asset", "Asset"), ("liabilities", "Liability"), ("expense", "Expense"), - ("income", "Income")): - frappe.db.sql("""update tabAccount set root_type=%s where name like %s - and ifnull(parent_account, '')=''""", (root_type, "%" + key + "%")) - - for root in frappe.db.sql("""SELECT name, lft, rgt, root_type FROM `tabAccount` - WHERE ifnull(parent_account, '')=''""", as_dict=True): - if root.root_type: - frappe.db.sql("""UPDATE tabAccount SET root_type=%s WHERE lft>%s and rgt<%s""", - (root.root_type, root.lft, root.rgt)) - else: - print(b"Root type not found for {0}".format(root.name.encode("utf-8"))) diff --git a/erpnext/patches/v4_0/update_custom_print_formats_for_renamed_fields.py b/erpnext/patches/v4_0/update_custom_print_formats_for_renamed_fields.py deleted file mode 100644 index d784a1b182..0000000000 --- a/erpnext/patches/v4_0/update_custom_print_formats_for_renamed_fields.py +++ /dev/null @@ -1,36 +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 frappe -import re - -def execute(): - # NOTE: sequence is important - fields_list = ( - ("amount", "base_amount"), - ("ref_rate", "price_list_rate"), - ("base_ref_rate", "base_price_list_rate"), - ("adj_rate", "discount_percentage"), - ("export_rate", "rate"), - ("basic_rate", "base_rate"), - ("export_amount", "amount"), - ("reserved_warehouse", "warehouse"), - ("import_ref_rate", "price_list_rate"), - ("purchase_ref_rate", "base_price_list_rate"), - ("discount_rate", "discount_percentage"), - ("import_rate", "rate"), - ("purchase_rate", "base_rate"), - ("import_amount", "amount") - ) - - condition = " or ".join("""html like "%%{}%%" """.format(d[0].replace("_", "\\_")) for d in fields_list - if d[0] != "amount") - - for name, html in frappe.db.sql("""select name, html from `tabPrint Format` - where standard = 'No' and ({}) and html not like '%%frappe.%%'""".format(condition)): - html = html.replace("wn.", "frappe.") - for from_field, to_field in fields_list: - html = re.sub(r"\b{}\b".format(from_field), to_field, html) - - frappe.db.set_value("Print Format", name, "html", html) diff --git a/erpnext/patches/v4_0/update_incharge_name_to_sales_person_in_maintenance_schedule.py b/erpnext/patches/v4_0/update_incharge_name_to_sales_person_in_maintenance_schedule.py deleted file mode 100644 index fe66a5e3ef..0000000000 --- a/erpnext/patches/v4_0/update_incharge_name_to_sales_person_in_maintenance_schedule.py +++ /dev/null @@ -1,12 +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 frappe - -def execute(): - frappe.reload_doc("support", "doctype", "schedules") - frappe.reload_doc("support", "doctype", "maintenance_schedule_item") - - frappe.db.sql("""update `tabMaintenance Schedule Detail` set sales_person=incharge_name""") - frappe.db.sql("""update `tabMaintenance Schedule Item` set sales_person=incharge_name""") \ No newline at end of file diff --git a/erpnext/patches/v4_0/update_other_charges_in_custom_purchase_print_formats.py b/erpnext/patches/v4_0/update_other_charges_in_custom_purchase_print_formats.py deleted file mode 100644 index 2e2e77a9fc..0000000000 --- a/erpnext/patches/v4_0/update_other_charges_in_custom_purchase_print_formats.py +++ /dev/null @@ -1,12 +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 frappe -import re - -def execute(): - for name, html in frappe.db.sql("""select name, html from `tabPrint Format` - where standard = 'No' and html like '%%purchase\\_tax\\_details%%'"""): - html = re.sub(r"\bpurchase_tax_details\b", "taxes", html) - frappe.db.set_value("Print Format", name, "html", html) diff --git a/erpnext/patches/v4_0/update_tax_amount_after_discount.py b/erpnext/patches/v4_0/update_tax_amount_after_discount.py deleted file mode 100644 index d10329ef37..0000000000 --- a/erpnext/patches/v4_0/update_tax_amount_after_discount.py +++ /dev/null @@ -1,20 +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 frappe - -def execute(): - frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges") - docs_with_discount_amount = frappe._dict() - for dt in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: - records = frappe.db.sql_list("""select name from `tab%s` - where ifnull(discount_amount, 0) > 0 and docstatus=1""" % dt) - docs_with_discount_amount[dt] = records - - for dt, discounted_records in docs_with_discount_amount.items(): - frappe.db.sql("""update `tabSales Taxes and Charges` - set tax_amount_after_discount_amount = tax_amount - where parenttype = %s and parent not in (%s)""" % - ('%s', ', '.join(['%s']*(len(discounted_records)+1))), - tuple([dt, ''] + discounted_records)) diff --git a/erpnext/patches/v4_0/update_user_properties.py b/erpnext/patches/v4_0/update_user_properties.py deleted file mode 100644 index f2085ce4df..0000000000 --- a/erpnext/patches/v4_0/update_user_properties.py +++ /dev/null @@ -1,51 +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 frappe -import frappe.permissions -import frappe.defaults - -def execute(): - frappe.reload_doc("core", "doctype", "docfield") - frappe.reload_doc("hr", "doctype", "employee") - - set_print_email_permissions() - migrate_user_properties_to_user_permissions() - - frappe.clear_cache() - -def migrate_user_properties_to_user_permissions(): - for d in frappe.db.sql("""select parent, defkey, defvalue from tabDefaultValue - where parent not in ('__global', '__default')""", as_dict=True): - df = frappe.db.sql("""select options from tabDocField - where fieldname=%s and fieldtype='Link'""", d.defkey, as_dict=True) - - if df: - frappe.db.sql("""update tabDefaultValue - set defkey=%s, parenttype='User Permission' - where defkey=%s and - parent not in ('__global', '__default')""", (df[0].options, d.defkey)) - -def set_print_email_permissions(): - # reset Page perms - from frappe.core.page.permission_manager.permission_manager import reset - reset("Page") - reset("Report") - - if "allow_print" not in frappe.db.get_table_columns("DocType"): - return - - # patch to move print, email into DocPerm - # NOTE: allow_print and allow_email are misnamed. They were used to hide print / hide email - for doctype, hide_print, hide_email in frappe.db.sql("""select name, ifnull(allow_print, 0), ifnull(allow_email, 0) - from `tabDocType` where ifnull(issingle, 0)=0 and ifnull(istable, 0)=0 and - (ifnull(allow_print, 0)=0 or ifnull(allow_email, 0)=0)"""): - - if not hide_print: - frappe.db.sql("""update `tabDocPerm` set `print`=1 - where permlevel=0 and `read`=1 and parent=%s""", doctype) - - if not hide_email: - frappe.db.sql("""update `tabDocPerm` set `email`=1 - where permlevel=0 and `read`=1 and parent=%s""", doctype) diff --git a/erpnext/patches/v4_0/update_users_report_view_settings.py b/erpnext/patches/v4_0/update_users_report_view_settings.py deleted file mode 100644 index 6f216f5b38..0000000000 --- a/erpnext/patches/v4_0/update_users_report_view_settings.py +++ /dev/null @@ -1,12 +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 - -from frappe.model.utils.rename_field import update_users_report_view_settings -from erpnext.patches.v4_0.fields_to_be_renamed import rename_map - -def execute(): - for dt, field_list in rename_map.items(): - for field in field_list: - update_users_report_view_settings(dt, field[0], field[1]) diff --git a/erpnext/patches/v4_0/validate_v3_patch.py b/erpnext/patches/v4_0/validate_v3_patch.py deleted file mode 100644 index 3df39edea6..0000000000 --- a/erpnext/patches/v4_0/validate_v3_patch.py +++ /dev/null @@ -1,11 +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 frappe - -def execute(): - from frappe.modules.patch_handler import executed - last_v3_patch = 'patches.1401.fix_pos_outstanding' - if not executed(last_v3_patch): - raise Exception("site not ready to migrate to version 4") diff --git a/erpnext/patches/v4_1/__init__.py b/erpnext/patches/v4_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v4_1/fix_delivery_and_billing_status.py b/erpnext/patches/v4_1/fix_delivery_and_billing_status.py deleted file mode 100644 index 8cc6a4b0be..0000000000 --- a/erpnext/patches/v4_1/fix_delivery_and_billing_status.py +++ /dev/null @@ -1,12 +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 frappe - -def execute(): - frappe.db.sql("""update `tabSales Order` set delivery_status = 'Not Delivered' - where delivery_status = 'Delivered' and ifnull(per_delivered, 0) = 0 and ifnull(docstatus, 0) in (0, 1)""") - - frappe.db.sql("""update `tabSales Order` set billing_status = 'Not Billed' - where billing_status = 'Billed' and ifnull(per_billed, 0) = 0 and ifnull(docstatus, 0) in (0, 1)""") diff --git a/erpnext/patches/v4_1/fix_jv_remarks.py b/erpnext/patches/v4_1/fix_jv_remarks.py deleted file mode 100644 index e07bf05f1a..0000000000 --- a/erpnext/patches/v4_1/fix_jv_remarks.py +++ /dev/null @@ -1,21 +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 frappe - -def execute(): - reference_date = guess_reference_date() - for name in frappe.db.sql_list("""select name from `tabJournal Entry` - where date(creation)>=%s""", reference_date): - jv = frappe.get_doc("Journal Entry", name) - try: - jv.create_remarks() - except frappe.MandatoryError: - pass - else: - frappe.db.set_value("Journal Entry", jv.name, "remark", jv.remark) - -def guess_reference_date(): - return (frappe.db.get_value("Patch Log", {"patch": "erpnext.patches.v4_0.validate_v3_patch"}, "creation") - or "2014-05-06") diff --git a/erpnext/patches/v4_1/fix_sales_order_delivered_status.py b/erpnext/patches/v4_1/fix_sales_order_delivered_status.py deleted file mode 100644 index 66037b8958..0000000000 --- a/erpnext/patches/v4_1/fix_sales_order_delivered_status.py +++ /dev/null @@ -1,15 +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 frappe - -def execute(): - for si in frappe.db.sql_list("""select name - from `tabSales Invoice` - where ifnull(update_stock,0) = 1 and docstatus = 1 and exists( - select name from `tabSales Invoice Item` where parent=`tabSales Invoice`.name and - ifnull(so_detail, "") != "")"""): - - invoice = frappe.get_doc("Sales Invoice", si) - invoice.update_qty() diff --git a/erpnext/patches/v4_1/set_outgoing_email_footer.py b/erpnext/patches/v4_1/set_outgoing_email_footer.py deleted file mode 100644 index 54d016bf5f..0000000000 --- a/erpnext/patches/v4_1/set_outgoing_email_footer.py +++ /dev/null @@ -1,12 +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 frappe -from erpnext.setup.install import default_mail_footer - -def execute(): - return - mail_footer = frappe.db.get_default('mail_footer') or '' - mail_footer += default_mail_footer - frappe.db.set_value("Outgoing Email Settings", "Outgoing Email Settings", "footer", mail_footer) diff --git a/erpnext/patches/v4_2/__init__.py b/erpnext/patches/v4_2/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v4_2/add_currency_turkish_lira.py b/erpnext/patches/v4_2/add_currency_turkish_lira.py deleted file mode 100644 index 1a46089f94..0000000000 --- a/erpnext/patches/v4_2/add_currency_turkish_lira.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - return - # country = get_country_info(country="Turkey") - # add_country_and_currency("Turkey", country) diff --git a/erpnext/patches/v4_2/default_website_style.py b/erpnext/patches/v4_2/default_website_style.py deleted file mode 100644 index e8f9502ea6..0000000000 --- a/erpnext/patches/v4_2/default_website_style.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - return - # frappe.reload_doc('website', 'doctype', 'style_settings') - # style_settings = frappe.get_doc("Style Settings", "Style Settings") - # if not style_settings.apply_style: - # style_settings.update(default_properties) - # style_settings.apply_style = 1 - # style_settings.save() diff --git a/erpnext/patches/v4_2/delete_gl_entries_for_cancelled_invoices.py b/erpnext/patches/v4_2/delete_gl_entries_for_cancelled_invoices.py deleted file mode 100644 index 169b1e2927..0000000000 --- a/erpnext/patches/v4_2/delete_gl_entries_for_cancelled_invoices.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - cancelled_invoices = frappe.db.sql_list("""select name from `tabSales Invoice` - where docstatus = 2 and ifnull(update_stock, 0) = 1""") - - if cancelled_invoices: - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type = 'Sales Invoice' and voucher_no in (%s)""" - % (', '.join(['%s']*len(cancelled_invoices))), tuple(cancelled_invoices)) \ No newline at end of file diff --git a/erpnext/patches/v4_2/delete_old_print_formats.py b/erpnext/patches/v4_2/delete_old_print_formats.py deleted file mode 100644 index cacdb85ce0..0000000000 --- a/erpnext/patches/v4_2/delete_old_print_formats.py +++ /dev/null @@ -1,23 +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 frappe - -def execute(): - old_formats = ("Sales Invoice", "Sales Invoice Spartan", "Sales Invoice Modern", - "Sales Invoice Classic", - "Sales Order Spartan", "Sales Order Modern", "Sales Order Classic", - "Purchase Order Spartan", "Purchase Order Modern", "Purchase Order Classic", - "Quotation Spartan", "Quotation Modern", "Quotation Classic", - "Delivery Note Spartan", "Delivery Note Modern", "Delivery Note Classic") - - for fmt in old_formats: - # update property setter - for ps in frappe.db.sql_list("""select name from `tabProperty Setter` - where property='default_print_format' and value=%s""", fmt): - ps = frappe.get_doc("Property Setter", ps) - ps.value = "Standard" - ps.save(ignore_permissions = True) - - frappe.delete_doc_if_exists("Print Format", fmt) diff --git a/erpnext/patches/v4_2/discount_amount.py b/erpnext/patches/v4_2/discount_amount.py deleted file mode 100644 index 7ab61bdbea..0000000000 --- a/erpnext/patches/v4_2/discount_amount.py +++ /dev/null @@ -1,12 +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 frappe -from frappe.modules import scrub, get_doctype_module - -def execute(): - for dt in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: - frappe.reload_doc(get_doctype_module(dt), "doctype", scrub(dt)) - frappe.db.sql("""update `tab{0}` set base_discount_amount=discount_amount, - discount_amount=discount_amount/conversion_rate""".format(dt)) diff --git a/erpnext/patches/v4_2/fix_account_master_type.py b/erpnext/patches/v4_2/fix_account_master_type.py deleted file mode 100644 index 99444ce83b..0000000000 --- a/erpnext/patches/v4_2/fix_account_master_type.py +++ /dev/null @@ -1,12 +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 frappe - -def execute(): - for d in frappe.db.sql("""select name from `tabAccount` - where ifnull(master_type, '') not in ('Customer', 'Supplier', 'Employee', '') and docstatus=0"""): - ac = frappe.get_doc("Account", d[0]) - ac.master_type = None - ac.save() diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py deleted file mode 100644 index c6c94d4179..0000000000 --- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe -from frappe.utils import flt - -def execute(): - from erpnext.stock.stock_balance import repost - repost(allow_zero_rate=True, only_actual=True) - - frappe.reload_doctype("Account") - - warehouse_account = frappe.db.sql("""select name, master_name from tabAccount - where ifnull(account_type, '') = 'Warehouse'""") - if warehouse_account: - warehouses = [d[1] for d in warehouse_account] - accounts = [d[0] for d in warehouse_account] - - stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no - from `tabStock Ledger Entry` sle - where sle.warehouse in (%s) - order by sle.posting_date""" % - ', '.join(['%s']*len(warehouses)), tuple(warehouses)) - - rejected = [] - for voucher_type, voucher_no in stock_vouchers: - stock_bal = frappe.db.sql("""select sum(stock_value_difference) from `tabStock Ledger Entry` - where voucher_type=%s and voucher_no =%s and warehouse in (%s)""" % - ('%s', '%s', ', '.join(['%s']*len(warehouses))), tuple([voucher_type, voucher_no] + warehouses)) - - account_bal = frappe.db.sql("""select ifnull(sum(ifnull(debit, 0) - ifnull(credit, 0)), 0) - from `tabGL Entry` - where voucher_type=%s and voucher_no =%s and account in (%s) - group by voucher_type, voucher_no""" % - ('%s', '%s', ', '.join(['%s']*len(accounts))), tuple([voucher_type, voucher_no] + accounts)) - - if stock_bal and account_bal and abs(flt(stock_bal[0][0]) - flt(account_bal[0][0])) > 0.1: - try: - print(voucher_type, voucher_no, stock_bal[0][0], account_bal[0][0]) - - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) - - voucher = frappe.get_doc(voucher_type, voucher_no) - voucher.make_gl_entries() - frappe.db.commit() - except Exception as e: - print(frappe.get_traceback()) - rejected.append([voucher_type, voucher_no]) - frappe.db.rollback() - - print("Failed to repost: ") - print(rejected) diff --git a/erpnext/patches/v4_2/fix_recurring_orders.py b/erpnext/patches/v4_2/fix_recurring_orders.py deleted file mode 100644 index ea1724a040..0000000000 --- a/erpnext/patches/v4_2/fix_recurring_orders.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - sales_orders = frappe.db.sql("""select name from `tabSales Order` - where docstatus = 1 and ifnull(is_recurring, 0) = 1 - and (per_delivered > 0 or per_billed > 0)""", as_dict=1) - - for so in sales_orders: - if not frappe.db.exists("Delivery Note Item", {"against_sales_order": so.name, "docstatus": 1}): - frappe.db.sql("""update `tabSales Order` set per_delivered = 0, - delivery_status = 'Not Delivered' where name = %s""", so.name) - frappe.db.sql("""update `tabSales Order Item` set delivered_qty = 0 - where parent = %s""", so.name) - - if not frappe.db.exists("Sales Invoice Item", {"sales_order": so.name, "docstatus": 1}): - frappe.db.sql("""update `tabSales Order` set per_billed = 0, - billing_status = 'Not Billed' where name = %s""", so.name) - frappe.db.sql("""update `tabSales Order Item` set billed_amt = 0 - where parent = %s""", so.name) - - purchase_orders = frappe.db.sql("""select name from `tabPurchase Order` - where docstatus = 1 and ifnull(is_recurring, 0) = 1 - and (per_received > 0 or per_billed > 0)""", as_dict=1) - - for po in purchase_orders: - if not frappe.db.exists("Purchase Receipt Item", {"prevdoc_doctype": "Purchase Order", - "prevdoc_docname": po.name, "docstatus": 1}): - frappe.db.sql("""update `tabPurchase Order` set per_received = 0 - where name = %s""", po.name) - frappe.db.sql("""update `tabPurchase Order Item` set received_qty = 0 - where parent = %s""", po.name) - - if not frappe.db.exists("Purchase Invoice Item", {"purchase_order": po.name, "docstatus": 1}): - frappe.db.sql("""update `tabPurchase Order` set per_billed = 0 - where name = %s""", po.name) - frappe.db.sql("""update `tabPurchase Order Item` set billed_amt = 0 - where parent = %s""", po.name) \ No newline at end of file diff --git a/erpnext/patches/v4_2/party_model.py b/erpnext/patches/v4_2/party_model.py deleted file mode 100644 index 46d7fffee1..0000000000 --- a/erpnext/patches/v4_2/party_model.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - frappe.reload_doc("accounts", "doctype", "account") - frappe.reload_doc("setup", "doctype", "company") - frappe.reload_doc("accounts", "doctype", "gl_entry") - frappe.reload_doc("accounts", "doctype", "journal_entry_account") - receivable_payable_accounts = create_receivable_payable_account() - if receivable_payable_accounts: - set_party_in_jv_and_gl_entry(receivable_payable_accounts) - delete_individual_party_account() - remove_customer_supplier_account_report() - -def create_receivable_payable_account(): - receivable_payable_accounts = frappe._dict() - - def _create_account(args): - if args["parent_account"] and frappe.db.exists("Account", args["parent_account"]): - account_id = frappe.db.get_value("Account", - {"account_name": args["account_name"], "company": args["company"]}) - if not account_id: - account = frappe.new_doc("Account") - account.is_group = 0 - account.update(args) - account.insert() - - account_id = account.name - - frappe.db.set_value("Company", args["company"], ("default_receivable_account" - if args["account_type"]=="Receivable" else "default_payable_account"), account_id) - - receivable_payable_accounts.setdefault(args["company"], {}).setdefault(args["account_type"], account_id) - - for company in frappe.db.sql_list("select name from tabCompany"): - _create_account({ - "account_name": "Debtors", - "account_type": "Receivable", - "company": company, - "parent_account": get_parent_account(company, "Customer") - }) - - _create_account({ - "account_name": "Creditors", - "account_type": "Payable", - "company": company, - "parent_account": get_parent_account(company, "Supplier") - }) - - return receivable_payable_accounts - -def get_parent_account(company, master_type): - parent_account = None - - if "receivables_group" in frappe.db.get_table_columns("Company"): - parent_account = frappe.get_cached_value('Company', company, - "receivables_group" if master_type=="Customer" else "payables_group") - if not parent_account: - parent_account = frappe.db.get_value("Account", {"company": company, - "account_name": "Accounts Receivable" if master_type=="Customer" else "Accounts Payable"}) - - if not parent_account: - parent_account = frappe.db.sql_list("""select parent_account from tabAccount - where company=%s and ifnull(master_type, '')=%s and ifnull(master_name, '')!='' limit 1""", - (company, master_type)) - parent_account = parent_account[0][0] if parent_account else None - - return parent_account - -def set_party_in_jv_and_gl_entry(receivable_payable_accounts): - accounts = frappe.db.sql("""select name, master_type, master_name, company from `tabAccount` - where ifnull(master_type, '') in ('Customer', 'Supplier') and ifnull(master_name, '') != ''""", as_dict=1) - - account_map = frappe._dict() - for d in accounts: - account_map.setdefault(d.name, d) - - if not account_map: - return - - for dt in ["Journal Entry Account", "GL Entry"]: - records = frappe.db.sql("""select name, account from `tab%s` - where account in (%s) and ifnull(party, '') = '' and docstatus < 2""" % - (dt, ", ".join(['%s']*len(account_map))), tuple(account_map.keys()), as_dict=1) - for i, d in enumerate(records): - account_details = account_map.get(d.account, {}) - account_type = "Receivable" if account_details.get("master_type")=="Customer" else "Payable" - new_account = receivable_payable_accounts[account_details.get("company")][account_type] - - frappe.db.sql("update `tab{0}` set account=%s, party_type=%s, party=%s where name=%s".format(dt), - (new_account, account_details.get("master_type"), account_details.get("master_name"), d.name)) - - if i%500 == 0: - frappe.db.commit() - -def delete_individual_party_account(): - frappe.db.sql("""delete from `tabAccount` - where ifnull(master_type, '') in ('Customer', 'Supplier') - and ifnull(master_name, '') != '' - and not exists(select gle.name from `tabGL Entry` gle - where gle.account = tabAccount.name)""") - - accounts_not_deleted = frappe.db.sql_list("""select tabAccount.name from `tabAccount` - where ifnull(master_type, '') in ('Customer', 'Supplier') - and ifnull(master_name, '') != '' - and exists(select gle.name from `tabGL Entry` gle where gle.account = tabAccount.name)""") - - if accounts_not_deleted: - print("Accounts not deleted: " + "\n".join(accounts_not_deleted)) - - -def remove_customer_supplier_account_report(): - for d in ["Customer Account Head", "Supplier Account Head"]: - frappe.delete_doc("Report", d) diff --git a/erpnext/patches/v4_2/recalculate_bom_cost.py b/erpnext/patches/v4_2/recalculate_bom_cost.py deleted file mode 100644 index eee89fce96..0000000000 --- a/erpnext/patches/v4_2/recalculate_bom_cost.py +++ /dev/null @@ -1,16 +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 frappe - -def execute(): - for d in frappe.db.sql("select name from `tabBOM` where docstatus < 2"): - try: - document = frappe.get_doc('BOM', d[0]) - if document.docstatus == 1: - document.flags.ignore_validate_update_after_submit = True - document.calculate_cost() - document.save() - except: - pass diff --git a/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py b/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py deleted file mode 100644 index 1356129dc0..0000000000 --- a/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe -from erpnext.stock.stock_ledger import NegativeStockError - -def execute(): - si_list = frappe.db.sql("""select distinct si.name - from `tabSales Invoice Item` si_item, `tabSales Invoice` si - where si.name = si_item.parent and si.modified > '2015-02-16' and si.docstatus=1 - and ifnull(si_item.warehouse, '') = '' and ifnull(si.update_stock, 0) = 1 - order by posting_date, posting_time""", as_dict=1) - - failed_list = [] - for si in si_list: - try: - si_doc = frappe.get_doc("Sales Invoice", si.name) - si_doc.docstatus = 2 - si_doc.on_cancel() - - si_doc.docstatus = 1 - si_doc.set_missing_item_details() - si_doc.on_submit() - frappe.db.commit() - except: - failed_list.append(si.name) - frappe.local.stockledger_exceptions = None - frappe.db.rollback() - - print("Failed to repost: ", failed_list) - - - \ No newline at end of file diff --git a/erpnext/patches/v4_2/repost_stock_reconciliation.py b/erpnext/patches/v4_2/repost_stock_reconciliation.py deleted file mode 100644 index ad20ebbae4..0000000000 --- a/erpnext/patches/v4_2/repost_stock_reconciliation.py +++ /dev/null @@ -1,31 +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 frappe -import json - -def execute(): - existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) - - head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"] - stock_reco_to_be_reposted = [] - for d in frappe.db.sql("""select name, reconciliation_json from `tabStock Reconciliation` - where docstatus=1 and creation > '2014-03-01'""", as_dict=1): - data = json.loads(d.reconciliation_json) - for row in data[data.index(head_row)+1:]: - if row[3] in ["", None]: - stock_reco_to_be_reposted.append(d.name) - break - - for dn in stock_reco_to_be_reposted: - reco = frappe.get_doc("Stock Reconciliation", dn) - reco.docstatus = 2 - reco.on_cancel() - - reco.docstatus = 1 - reco.validate() - reco.on_submit() - - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock) diff --git a/erpnext/patches/v4_2/reset_bom_costs.py b/erpnext/patches/v4_2/reset_bom_costs.py deleted file mode 100644 index 42ca759467..0000000000 --- a/erpnext/patches/v4_2/reset_bom_costs.py +++ /dev/null @@ -1,17 +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 frappe - -def execute(): - frappe.reload_doc('manufacturing', 'doctype', 'bom_operation') - for d in frappe.db.sql("""select name from `tabBOM` where docstatus < 2""", as_dict=1): - try: - bom = frappe.get_doc('BOM', d.name) - bom.flags.ignore_validate_update_after_submit = True - bom.calculate_cost() - bom.save() - frappe.db.commit() - except: - frappe.db.rollback() diff --git a/erpnext/patches/v4_2/seprate_manufacture_and_repack.py b/erpnext/patches/v4_2/seprate_manufacture_and_repack.py deleted file mode 100644 index 2c935436a2..0000000000 --- a/erpnext/patches/v4_2/seprate_manufacture_and_repack.py +++ /dev/null @@ -1,9 +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 frappe - -def execute(): - frappe.db.sql("""update `tabStock Entry` set purpose='Manufacture' where purpose='Manufacture/Repack' and ifnull(work_order,"")!="" """) - frappe.db.sql("""update `tabStock Entry` set purpose='Repack' where purpose='Manufacture/Repack' and ifnull(work_order,"")="" """) \ No newline at end of file diff --git a/erpnext/patches/v4_2/set_company_country.py b/erpnext/patches/v4_2/set_company_country.py deleted file mode 100644 index 89f07f2873..0000000000 --- a/erpnext/patches/v4_2/set_company_country.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - country = frappe.db.get_single_value("Global Defaults", "country") - if not country: - print("Country not specified in Global Defaults") - return - - for company in frappe.db.sql_list("""select name from `tabCompany` - where ifnull(country, '')=''"""): - frappe.db.set_value("Company", company, "country", country) diff --git a/erpnext/patches/v4_2/set_item_has_batch.py b/erpnext/patches/v4_2/set_item_has_batch.py deleted file mode 100644 index 7e52d2def0..0000000000 --- a/erpnext/patches/v4_2/set_item_has_batch.py +++ /dev/null @@ -1,65 +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 frappe - -def execute(): - frappe.db.sql("update tabItem set has_batch_no = 0 where ifnull(has_batch_no, '') = ''") - frappe.db.sql("update tabItem set has_serial_no = 0 where ifnull(has_serial_no, '') = ''") - - item_list = frappe.db.sql("""select name, has_batch_no, has_serial_no from tabItem - where is_stock_item = 1""", as_dict=1) - - sle_count = get_sle_count() - sle_with_batch = get_sle_with_batch() - sle_with_serial = get_sle_with_serial() - - batch_items = get_items_with_batch() - serialized_items = get_items_with_serial() - - for d in item_list: - if d.has_batch_no == 1: - if d.name not in batch_items and sle_count.get(d.name) and sle_count.get(d.name) != sle_with_batch.get(d.name): - frappe.db.set_value("Item", d.name, "has_batch_no", 0) - else: - if d.name in batch_items or (sle_count.get(d.name) and sle_count.get(d.name) == sle_with_batch.get(d.name)): - frappe.db.set_value("Item", d.name, "has_batch_no", 1) - - if d.has_serial_no == 1: - if d.name not in serialized_items and sle_count.get(d.name) and sle_count.get(d.name) != sle_with_serial.get(d.name): - frappe.db.set_value("Item", d.name, "has_serial_no", 0) - else: - if d.name in serialized_items or (sle_count.get(d.name) and sle_count.get(d.name) == sle_with_serial.get(d.name)): - frappe.db.set_value("Item", d.name, "has_serial_no", 1) - - -def get_sle_count(): - sle_count = {} - for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry` group by item_code""", as_dict=1): - sle_count.setdefault(d.item_code, d.cnt) - - return sle_count - -def get_sle_with_batch(): - sle_with_batch = {} - for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry` - where ifnull(batch_no, '') != '' group by item_code""", as_dict=1): - sle_with_batch.setdefault(d.item_code, d.cnt) - - return sle_with_batch - - -def get_sle_with_serial(): - sle_with_serial = {} - for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry` - where ifnull(serial_no, '') != '' group by item_code""", as_dict=1): - sle_with_serial.setdefault(d.item_code, d.cnt) - - return sle_with_serial - -def get_items_with_batch(): - return frappe.db.sql_list("select item from tabBatch") - -def get_items_with_serial(): - return frappe.db.sql_list("select item_code from `tabSerial No`") diff --git a/erpnext/patches/v4_2/toggle_rounded_total.py b/erpnext/patches/v4_2/toggle_rounded_total.py deleted file mode 100644 index e571208eb6..0000000000 --- a/erpnext/patches/v4_2/toggle_rounded_total.py +++ /dev/null @@ -1,9 +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 frappe - -def execute(): - global_defaults = frappe.get_doc("Global Defaults", "Global Defaults") - global_defaults.toggle_rounded_total() diff --git a/erpnext/patches/v4_2/update_landed_cost_voucher.py b/erpnext/patches/v4_2/update_landed_cost_voucher.py deleted file mode 100644 index ec0029671e..0000000000 --- a/erpnext/patches/v4_2/update_landed_cost_voucher.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - frappe.reload_doc("stock", "doctype", "landed_cost_voucher") - frappe.db.sql("""update `tabLanded Cost Voucher` set distribute_charges_based_on = 'Amount' - where docstatus=1""") diff --git a/erpnext/patches/v4_2/update_project_milestones.py b/erpnext/patches/v4_2/update_project_milestones.py deleted file mode 100644 index e704116d05..0000000000 --- a/erpnext/patches/v4_2/update_project_milestones.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for project in frappe.db.sql_list("select name from tabProject"): - frappe.reload_doc("projects", "doctype", "project") - p = frappe.get_doc("Project", project) - p.update_milestones_completed() - p.db_set("percent_milestones_completed", p.percent_milestones_completed) diff --git a/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py b/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py deleted file mode 100644 index 28dd5c0d4e..0000000000 --- a/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'sales_invoice') - frappe.db.sql("""update `tabSales Invoice` set from_date = invoice_period_from_date, - to_date = invoice_period_to_date, is_recurring = convert_into_recurring_invoice""") diff --git a/erpnext/patches/v4_2/update_stock_uom_for_dn_in_sle.py b/erpnext/patches/v4_2/update_stock_uom_for_dn_in_sle.py deleted file mode 100644 index 89bf795534..0000000000 --- a/erpnext/patches/v4_2/update_stock_uom_for_dn_in_sle.py +++ /dev/null @@ -1,11 +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 frappe - -def execute(): - frappe.db.sql("""update `tabStock Ledger Entry` sle, tabItem item - set sle.stock_uom = item.stock_uom - where sle.voucher_type="Delivery Note" and item.name = sle.item_code - and sle.stock_uom != item.stock_uom""") diff --git a/erpnext/patches/v4_4/__init__.py b/erpnext/patches/v4_4/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v4_4/make_email_accounts.py b/erpnext/patches/v4_4/make_email_accounts.py deleted file mode 100644 index 57df1ae491..0000000000 --- a/erpnext/patches/v4_4/make_email_accounts.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model import default_fields - -from six import iteritems - -def execute(): - frappe.reload_doc("email", "doctype", "email_account") - - # outgoing - outgoing = dict(frappe.db.sql("select field, value from tabSingles where doctype='Outgoing Email Settings'")) - if outgoing and outgoing['mail_server'] and outgoing['mail_login']: - account = frappe.new_doc("Email Account") - mapping = { - "login_id_is_different": 1, - "email_id": "auto_email_id", - "login_id": "mail_login", - "password": "mail_password", - "footer": "footer", - "smtp_server": "mail_server", - "smtp_port": "mail_port", - "use_tls": "use_ssl" - } - - for target_fieldname, source_fieldname in iteritems(mapping): - account.set(target_fieldname, outgoing.get(source_fieldname)) - - account.enable_outgoing = 1 - account.enable_incoming = 0 - - account.insert() - - # support - support = dict(frappe.db.sql("select field, value from tabSingles where doctype='Support Email Settings'")) - if support and support['mail_server'] and support['mail_login']: - account = frappe.new_doc("Email Account") - mapping = { - "enable_incoming": "sync_support_mails", - "email_id": "mail_login", - "password": "mail_password", - "email_server": "mail_server", - "use_ssl": "use_ssl", - "signature": "support_signature", - "enable_auto_reply": "send_autoreply", - "auto_reply_message": "support_autoreply" - } - - for target_fieldname, source_fieldname in iteritems(mapping): - account.set(target_fieldname, support.get(source_fieldname)) - - account.enable_outgoing = 0 - account.append_to = "Issue" - - insert_or_update(account) - - # sales, jobs - for doctype in ("Sales Email Settings", "Jobs Email Settings"): - source = dict(frappe.db.sql("select field, value from tabSingles where doctype=%s", doctype)) - if source and source.get('host') and source.get('username'): - account = frappe.new_doc("Email Account") - mapping = { - "enable_incoming": "extract_emails", - "email_id": "username", - "password": "password", - "email_server": "host", - "use_ssl": "use_ssl", - } - - for target_fieldname, source_fieldname in iteritems(mapping): - account.set(target_fieldname, source.get(source_fieldname)) - - account.enable_outgoing = 0 - account.append_to = "Lead" if doctype=="Sales Email Settings" else "Job Applicant" - - insert_or_update(account) - - for doctype in ("Outgoing Email Settings", "Support Email Settings", - "Sales Email Settings", "Jobs Email Settings"): - frappe.delete_doc("DocType", doctype) - -def insert_or_update(account): - try: - account.insert() - except frappe.NameError as e: - if e.args[0]=="Email Account": - existing_account = frappe.get_doc("Email Account", e.args[1]) - for key, value in account.as_dict().items(): - if not existing_account.get(key) and value \ - and key not in default_fields \ - and key != "__islocal": - existing_account.set(key, value) - - existing_account.save() - else: - raise - diff --git a/erpnext/patches/v5_0/__init__.py b/erpnext/patches/v5_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v5_0/convert_stock_reconciliation.py b/erpnext/patches/v5_0/convert_stock_reconciliation.py deleted file mode 100644 index 75d1da752f..0000000000 --- a/erpnext/patches/v5_0/convert_stock_reconciliation.py +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import unicode_literals -import frappe, json - -def execute(): - # stock reco now amendable - frappe.db.sql("""update tabDocPerm set `amend` = 1 where parent='Stock Reconciliation' and submit = 1""") - - frappe.reload_doc("stock", "doctype", "stock_reconciliation_item") - frappe.reload_doctype("Stock Reconciliation") - - if frappe.db.has_column("Stock Reconciliation", "reconciliation_json"): - for sr in frappe.db.get_all("Stock Reconciliation", ["name"], - {"reconciliation_json": ["!=", ""]}): - start = False - sr = frappe.get_doc("Stock Reconciliation", sr.name) - for row in json.loads(sr.reconciliation_json): - if start: - sr.append("items", { - "item_code": row[0], - "warehouse": row[1], - "qty": row[2] if len(row) > 2 else None, - "valuation_rate": row[3] if len(row) > 3 else None - }) - - elif row[0]=="Item Code": - start = True - - - for item in sr.items: - item.db_update() - diff --git a/erpnext/patches/v5_0/execute_on_doctype_update.py b/erpnext/patches/v5_0/execute_on_doctype_update.py deleted file mode 100644 index 70b1d8ded6..0000000000 --- a/erpnext/patches/v5_0/execute_on_doctype_update.py +++ /dev/null @@ -1,9 +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 frappe - -def execute(): - for dt in ("Stock Ledger Entry", "Communication", "DefaultValue", "DocShare", "File", "ToDo"): - frappe.get_doc("DocType", dt).run_module_method("on_doctype_update") diff --git a/erpnext/patches/v5_0/fix_taxes_and_totals_in_party_currency.py b/erpnext/patches/v5_0/fix_taxes_and_totals_in_party_currency.py deleted file mode 100644 index 30dc0f8db4..0000000000 --- a/erpnext/patches/v5_0/fix_taxes_and_totals_in_party_currency.py +++ /dev/null @@ -1,66 +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 frappe -from frappe.model.meta import get_field_precision - -def execute(): - if not frappe.db.sql("""select name from `tabPatch Log` - where patch = 'erpnext.patches.v5_0.taxes_and_totals_in_party_currency'"""): - return - selling_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"] - buying_doctypes = ["Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"] - - for dt in selling_doctypes: - update_values(dt, "Sales Taxes and Charges") - - for dt in buying_doctypes: - update_values(dt, "Purchase Taxes and Charges") - -def update_values(dt, tax_table): - rate_field_precision = get_field_precision(frappe.get_meta(dt + " Item").get_field("rate")) - tax_amount_precision = get_field_precision(frappe.get_meta(tax_table).get_field("tax_amount")) - - # update net_total, discount_on - frappe.db.sql(""" - UPDATE - `tab{0}` - SET - total_taxes_and_charges = round(base_total_taxes_and_charges / conversion_rate, {1}) - WHERE - docstatus < 2 - and ifnull(base_total_taxes_and_charges, 0) != 0 - and ifnull(total_taxes_and_charges, 0) = 0 - """.format(dt, tax_amount_precision)) - - # update net_amount - frappe.db.sql(""" - UPDATE - `tab{0}` par, `tab{1}` item - SET - item.net_amount = round(item.base_net_amount / par.conversion_rate, {2}), - item.net_rate = round(item.base_net_rate / par.conversion_rate, {2}) - WHERE - par.name = item.parent - and par.docstatus < 2 - and ((ifnull(item.base_net_amount, 0) != 0 and ifnull(item.net_amount, 0) = 0) - or (ifnull(item.base_net_rate, 0) != 0 and ifnull(item.net_rate, 0) = 0)) - """.format(dt, dt + " Item", rate_field_precision)) - - # update tax in party currency - frappe.db.sql(""" - UPDATE - `tab{0}` par, `tab{1}` tax - SET - tax.tax_amount = round(tax.base_tax_amount / par.conversion_rate, {2}), - tax.total = round(tax.base_total / conversion_rate, {2}), - tax.tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount / conversion_rate, {2}) - WHERE - par.name = tax.parent - and par.docstatus < 2 - and ((ifnull(tax.base_tax_amount, 0) != 0 and ifnull(tax.tax_amount, 0) = 0) - or (ifnull(tax.base_total, 0) != 0 and ifnull(tax.total, 0) = 0) - or (ifnull(tax.base_tax_amount_after_discount_amount, 0) != 0 and - ifnull(tax.tax_amount_after_discount_amount, 0) = 0)) - """.format(dt, tax_table, tax_amount_precision)) \ No newline at end of file diff --git a/erpnext/patches/v5_0/index_on_account_and_gl_entry.py b/erpnext/patches/v5_0/index_on_account_and_gl_entry.py deleted file mode 100644 index 2920e9293d..0000000000 --- a/erpnext/patches/v5_0/index_on_account_and_gl_entry.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def execute(): - index_map = { - "Account": ["parent_account", "lft", "rgt"], - "GL Entry": ["posting_date", "account", 'party', "voucher_no"], - "Sales Invoice": ["posting_date", "debit_to", "customer"], - "Purchase Invoice": ["posting_date", "credit_to", "supplier"] - } - - for dt, indexes in index_map.items(): - existing_indexes = [(d.Key_name, d.Column_name) for d in frappe.db.sql("""show index from `tab{0}` - where Column_name != 'name'""".format(dt), as_dict=1)] - - for old, column in existing_indexes: - if column in ("parent", "group_or_ledger", "is_group", "is_pl_account", "debit_or_credit", - "account_name", "company", "project", "voucher_date", "due_date", "bill_no", - "bill_date", "is_opening", "fiscal_year", "outstanding_amount"): - frappe.db.sql("alter table `tab{0}` drop index {1}".format(dt, old)) - - existing_indexes = [(d.Key_name, d.Column_name) for d in frappe.db.sql("""show index from `tab{0}` - where Column_name != 'name'""".format(dt), as_dict=1)] - - existing_indexed_columns = list(set([x[1] for x in existing_indexes])) - - for new in indexes: - if new not in existing_indexed_columns: - frappe.db.sql("alter table `tab{0}` add index ({1})".format(dt, new)) \ No newline at end of file diff --git a/erpnext/patches/v5_0/is_group.py b/erpnext/patches/v5_0/is_group.py deleted file mode 100644 index 4e3f760bed..0000000000 --- a/erpnext/patches/v5_0/is_group.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def execute(): - frappe.reload_doctype("Account") - frappe.reload_doctype("Cost Center") - frappe.db.sql("update tabAccount set is_group = if(group_or_ledger='Group', 1, 0)") - frappe.db.sql("update `tabCost Center` set is_group = if(group_or_ledger='Group', 1, 0)") diff --git a/erpnext/patches/v5_0/item_patches.py b/erpnext/patches/v5_0/item_patches.py deleted file mode 100644 index e223e09f5b..0000000000 --- a/erpnext/patches/v5_0/item_patches.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("update `tabItem` set end_of_life='2099-12-31' where ifnull(end_of_life, '0000-00-00')='0000-00-00'") - frappe.db.sql("update `tabItem` set website_image = image where ifnull(website_image, '') = 'attach_files:'") diff --git a/erpnext/patches/v5_0/link_warehouse_with_account.py b/erpnext/patches/v5_0/link_warehouse_with_account.py deleted file mode 100644 index 338fd7ad7f..0000000000 --- a/erpnext/patches/v5_0/link_warehouse_with_account.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - if "master_name" in frappe.db.get_table_columns("Account"): - frappe.db.sql("""update tabAccount set warehouse=master_name - where ifnull(account_type, '') = 'Warehouse' and ifnull(master_name, '') != ''""") \ No newline at end of file diff --git a/erpnext/patches/v5_0/new_crm_module.py b/erpnext/patches/v5_0/new_crm_module.py deleted file mode 100644 index f5dda1f273..0000000000 --- a/erpnext/patches/v5_0/new_crm_module.py +++ /dev/null @@ -1,24 +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 - -def execute(): - frappe.reload_doc('crm', 'doctype', 'lead') - frappe.reload_doc('crm', 'doctype', 'opportunity') - - add_crm_to_user_desktop_items() - -def add_crm_to_user_desktop_items(): - key = "_user_desktop_items" - for user in frappe.get_all("User", filters={"enabled": 1, "user_type": "System User"}): - user = user.name - user_desktop_items = frappe.db.get_defaults(key, parent=user) - if user_desktop_items: - user_desktop_items = json.loads(user_desktop_items) - if "CRM" not in user_desktop_items: - user_desktop_items.append("CRM") - frappe.db.set_default(key, json.dumps(user_desktop_items), parent=user) - diff --git a/erpnext/patches/v5_0/newsletter.py b/erpnext/patches/v5_0/newsletter.py deleted file mode 100644 index 63e3312413..0000000000 --- a/erpnext/patches/v5_0/newsletter.py +++ /dev/null @@ -1,38 +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 frappe -import frappe.permissions - -def execute(): - frappe.reload_doc("core", "doctype", "block_module") - frappe.reload_doctype("User") - frappe.reload_doctype("Lead") - frappe.reload_doctype("Contact") - - frappe.reload_doc('email', 'doctype', 'email_group') - frappe.reload_doc('email', 'doctype', 'email_group_member') - frappe.reload_doc('email', 'doctype', 'newsletter') - - frappe.permissions.reset_perms("Newsletter") - - if not frappe.db.exists("Role", "Newsletter Manager"): - frappe.get_doc({"doctype": "Role", "role": "Newsletter Manager"}).insert() - - for userrole in frappe.get_all("Has Role", "parent", {"role": "Sales Manager", "parenttype": "User"}): - if frappe.db.exists("User", userrole.parent): - user = frappe.get_doc("User", userrole.parent) - user.append("roles", { - "doctype": "Has Role", - "role": "Newsletter Manager" - }) - user.flags.ignore_mandatory = True - user.save() - - # create default lists - general = frappe.new_doc("Email Group") - general.title = "General" - general.insert() - general.import_from("Lead") - general.import_from("Contact") diff --git a/erpnext/patches/v5_0/opportunity_not_submittable.py b/erpnext/patches/v5_0/opportunity_not_submittable.py deleted file mode 100644 index e29d66f284..0000000000 --- a/erpnext/patches/v5_0/opportunity_not_submittable.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - frappe.reload_doctype("Opportunity") - frappe.db.sql("update tabDocPerm set submit=0, cancel=0, amend=0 where parent='Opportunity'") - frappe.db.sql("update tabOpportunity set docstatus=0 where docstatus=1") diff --git a/erpnext/patches/v5_0/party_model_patch_fix.py b/erpnext/patches/v5_0/party_model_patch_fix.py deleted file mode 100644 index d9b6709792..0000000000 --- a/erpnext/patches/v5_0/party_model_patch_fix.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for company in frappe.get_all("Company", - ["name", "default_receivable_account", "default_payable_account"]): - - if company.default_receivable_account: - frappe.db.sql("""update `tabSales Invoice` invoice set `debit_to`=%(account)s - where company=%(company)s - and not exists (select name from `tabAccount` account where account.name=invoice.debit_to)""", - {"company": company.name, "account": company.default_receivable_account}) - - if company.default_payable_account: - frappe.db.sql("""update `tabPurchase Invoice` invoice set `credit_to`=%(account)s - where company=%(company)s - and not exists (select name from `tabAccount` account where account.name=invoice.credit_to)""", - {"company": company.name, "account": company.default_payable_account}) diff --git a/erpnext/patches/v5_0/portal_fixes.py b/erpnext/patches/v5_0/portal_fixes.py deleted file mode 100644 index 1fefd99167..0000000000 --- a/erpnext/patches/v5_0/portal_fixes.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe -import erpnext.setup.install - -def execute(): - frappe.reload_doc("website", "doctype", "web_form_field", force=True, reset_permissions=True) - #erpnext.setup.install.add_web_forms() diff --git a/erpnext/patches/v5_0/project_costing.py b/erpnext/patches/v5_0/project_costing.py deleted file mode 100644 index e2d65d0594..0000000000 --- a/erpnext/patches/v5_0/project_costing.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Project") - frappe.db.sql("update `tabProject` set expected_start_date = project_start_date, \ - expected_end_date = completion_date, actual_end_date = act_completion_date, \ - estimated_costing = project_value, gross_margin = gross_margin_value") \ No newline at end of file diff --git a/erpnext/patches/v5_0/recalculate_total_amount_in_jv.py b/erpnext/patches/v5_0/recalculate_total_amount_in_jv.py deleted file mode 100644 index d5af43c541..0000000000 --- a/erpnext/patches/v5_0/recalculate_total_amount_in_jv.py +++ /dev/null @@ -1,26 +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 frappe -from frappe.utils import money_in_words - -def execute(): - company_currency = dict(frappe.db.sql("select name, default_currency from `tabCompany`")) - bank_or_cash_accounts = frappe.db.sql_list("""select name from `tabAccount` - where account_type in ('Bank', 'Cash') and docstatus < 2""") - - for je in frappe.db.sql_list("""select name from `tabJournal Entry` where docstatus < 2"""): - total_amount = 0 - total_amount_in_words = "" - - je_doc = frappe.get_doc('Journal Entry', je) - for d in je_doc.get("accounts"): - if (d.party_type and d.party) or d.account in bank_or_cash_accounts: - total_amount = d.debit or d.credit - if total_amount: - total_amount_in_words = money_in_words(total_amount, company_currency.get(je_doc.company)) - - if total_amount: - frappe.db.sql("""update `tabJournal Entry` set total_amount=%s, total_amount_in_words=%s - where name = %s""", (total_amount, total_amount_in_words, je)) diff --git a/erpnext/patches/v5_0/reclculate_planned_operating_cost_in_production_order.py b/erpnext/patches/v5_0/reclculate_planned_operating_cost_in_production_order.py deleted file mode 100644 index 6d392831b4..0000000000 --- a/erpnext/patches/v5_0/reclculate_planned_operating_cost_in_production_order.py +++ /dev/null @@ -1,13 +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 frappe - -def execute(): - for wo in frappe.db.sql("""select name from `tabWork Order` where docstatus < 2""", as_dict=1): - work_order = frappe.get_doc("Work Order", wo.name) - if work_order.operations: - work_order.flags.ignore_validate_update_after_submit = True - work_order.calculate_time() - work_order.save() \ No newline at end of file diff --git a/erpnext/patches/v5_0/remove_birthday_events.py b/erpnext/patches/v5_0/remove_birthday_events.py deleted file mode 100644 index 3ead8669b8..0000000000 --- a/erpnext/patches/v5_0/remove_birthday_events.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for e in frappe.db.sql_list("""select name from tabEvent where - repeat_on='Every Year' and ref_type='Employee'"""): - frappe.delete_doc("Event", e, force=True) diff --git a/erpnext/patches/v5_0/rename_customer_issue.py b/erpnext/patches/v5_0/rename_customer_issue.py deleted file mode 100644 index 1bd69ceec1..0000000000 --- a/erpnext/patches/v5_0/rename_customer_issue.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.table_exists("Customer Issue"): - frappe.rename_doc("DocType", "Customer Issue", "Warranty Claim") diff --git a/erpnext/patches/v5_0/rename_pos_setting.py b/erpnext/patches/v5_0/rename_pos_setting.py deleted file mode 100644 index bf10333564..0000000000 --- a/erpnext/patches/v5_0/rename_pos_setting.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.table_exists("POS Setting"): - frappe.rename_doc("DocType", "POS Setting", "POS Profile") diff --git a/erpnext/patches/v5_0/rename_table_fieldnames.py b/erpnext/patches/v5_0/rename_table_fieldnames.py deleted file mode 100644 index aefb0a2037..0000000000 --- a/erpnext/patches/v5_0/rename_table_fieldnames.py +++ /dev/null @@ -1,243 +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 frappe -from frappe.model.utils.rename_field import rename_field -from frappe.modules import scrub, get_doctype_module - -rename_map = { - "Opportunity": [ - ["enquiry_details", "items"] - ], - "Quotation": [ - ["quotation_details", "items"], - ["other_charges", "taxes"] - ], - "Sales Order": [ - ["sales_order_details", "items"], - ["other_charges", "taxes"], - ["packing_details", "packed_items"] - ], - "Delivery Note": [ - ["delivery_note_details", "items"], - ["other_charges", "taxes"], - ["packing_details", "packed_items"] - ], - "Sales Invoice": [ - ["entries", "items"], - ["other_charges", "taxes"], - ["packing_details", "packed_items"], - ["advance_adjustment_details", "advances"] - ], - "Material Request": [ - ["indent_details", "items"] - ], - "Supplier Quotation": [ - ["quotation_items", "items"], - ["other_charges", "taxes"] - ], - "Purchase Order": [ - ["po_details", "items"], - ["other_charges", "taxes"], - ["po_raw_material_details", "supplied_items"] - ], - "Purchase Receipt": [ - ["purchase_receipt_details", "items"], - ["other_charges", "taxes"], - ["pr_raw_material_details", "supplied_items"] - ], - "Purchase Invoice": [ - ["entries", "items"], - ["other_charges", "taxes"], - ["advance_allocation_details", "advances"] - ], - "Work Order": [ - ["production_order_operations", "operations"] - ], - "BOM": [ - ["bom_operations", "operations"], - ["bom_materials", "items"], - ["flat_bom_details", "exploded_items"] - ], - "Payment Reconciliation": [ - ["payment_reconciliation_payments", "payments"], - ["payment_reconciliation_invoices", "invoices"] - ], - "Sales Taxes and Charges Template": [ - ["other_charges", "taxes"], - ["valid_for_territories", "territories"] - ], - "Purchase Taxes and Charges Template": [ - ["other_charges", "taxes"] - ], - "Shipping Rule": [ - ["shipping_rule_conditions", "conditions"], - ["valid_for_territories", "territories"] - ], - "Price List": [ - ["valid_for_territories", "territories"] - ], - "Appraisal": [ - ["appraisal_details", "goals"] - ], - "Appraisal Template": [ - ["kra_sheet", "goals"] - ], - "Bank Reconciliation": [ - ["entries", "journal_entries"] - ], - "C-Form": [ - ["invoice_details", "invoices"] - ], - "Employee": [ - ["employee_leave_approvers", "leave_approvers"], - ["educational_qualification_details", "education"], - ["previous_experience_details", "external_work_history"], - ["experience_in_company_details", "internal_work_history"] - ], - "Expense Claim": [ - ["expense_voucher_details", "expenses"] - ], - "Fiscal Year": [ - ["fiscal_year_companies", "companies"] - ], - "Holiday List": [ - ["holiday_list_details", "holidays"] - ], - "Installation Note": [ - ["installed_item_details", "items"] - ], - "Item": [ - ["item_reorder", "reorder_levels"], - ["uom_conversion_details", "uoms"], - ["item_supplier_details", "supplier_items"], - ["item_customer_details", "customer_items"], - ["item_tax", "taxes"], - ["item_specification_details", "quality_parameters"], - ["item_website_specifications", "website_specifications"] - ], - "Item Group": [ - ["item_website_specifications", "website_specifications"] - ], - "Landed Cost Voucher": [ - ["landed_cost_purchase_receipts", "purchase_receipts"], - ["landed_cost_items", "items"], - ["landed_cost_taxes_and_charges", "taxes"] - ], - "Maintenance Schedule": [ - ["item_maintenance_detail", "items"], - ["maintenance_schedule_detail", "schedules"] - ], - "Maintenance Visit": [ - ["maintenance_visit_details", "purposes"] - ], - "Packing Slip": [ - ["item_details", "items"] - ], - "Customer": [ - ["party_accounts", "accounts"] - ], - "Customer Group": [ - ["party_accounts", "accounts"] - ], - "Supplier": [ - ["party_accounts", "accounts"] - ], - "Supplier Type": [ - ["party_accounts", "accounts"] - ], - "Payment Tool": [ - ["payment_tool_details", "vouchers"] - ], - "Production Planning Tool": [ - ["pp_so_details", "sales_orders"], - ["pp_details", "items"] - ], - "Quality Inspection": [ - ["qa_specification_details", "readings"] - ], - "Salary Slip": [ - ["earning_details", "earnings"], - ["deduction_details", "deductions"] - ], - "Salary Structure": [ - ["earning_details", "earnings"], - ["deduction_details", "deductions"] - ], - "Product Bundle": [ - ["sales_bom_items", "items"] - ], - "SMS Settings": [ - ["static_parameter_details", "parameters"] - ], - "Stock Entry": [ - ["mtn_details", "items"] - ], - "Sales Partner": [ - ["partner_target_details", "targets"] - ], - "Sales Person": [ - ["target_details", "targets"] - ], - "Territory": [ - ["target_details", "targets"] - ], - "Time Sheet": [ - ["time_sheet_details", "time_logs"] - ], - "Workstation": [ - ["workstation_operation_hours", "working_hours"] - ], - "Payment Reconciliation Payment": [ - ["journal_voucher", "journal_entry"], - ], - "Purchase Invoice Advance": [ - ["journal_voucher", "journal_entry"], - ], - "Sales Invoice Advance": [ - ["journal_voucher", "journal_entry"], - ], - "Journal Entry": [ - ["entries", "accounts"] - ], - "Monthly Distribution": [ - ["budget_distribution_details", "percentages"] - ] -} - -def execute(): - # rename doctypes - tables = frappe.db.sql_list("show tables") - for old_dt, new_dt in [["Journal Voucher Detail", "Journal Entry Account"], - ["Journal Voucher", "Journal Entry"], - ["Budget Distribution Detail", "Monthly Distribution Percentage"], - ["Budget Distribution", "Monthly Distribution"]]: - if "tab"+new_dt not in tables: - frappe.rename_doc("DocType", old_dt, new_dt, force=True) - - # reload new child doctypes - frappe.reload_doc("manufacturing", "doctype", "work_order_operation") - frappe.reload_doc("manufacturing", "doctype", "workstation_working_hour") - frappe.reload_doc("stock", "doctype", "item_variant") - frappe.reload_doc("Payroll", "doctype", "salary_detail") - frappe.reload_doc("accounts", "doctype", "party_account") - frappe.reload_doc("accounts", "doctype", "fiscal_year_company") - - #rename table fieldnames - for dn in rename_map: - if not frappe.db.exists("DocType", dn): - continue - frappe.reload_doc(get_doctype_module(dn), "doctype", scrub(dn)) - - for dt, field_list in rename_map.items(): - if not frappe.db.exists("DocType", dt): - continue - for field in field_list: - rename_field(dt, field[0], field[1]) - - # update voucher type - for old, new in [["Bank Voucher", "Bank Entry"], ["Cash Voucher", "Cash Entry"], - ["Credit Card Voucher", "Credit Card Entry"], ["Contra Voucher", "Contra Entry"], - ["Write Off Voucher", "Write Off Entry"], ["Excise Voucher", "Excise Entry"]]: - frappe.db.sql("update `tabJournal Entry` set voucher_type=%s where voucher_type=%s", (new, old)) diff --git a/erpnext/patches/v5_0/rename_taxes_and_charges_master.py b/erpnext/patches/v5_0/rename_taxes_and_charges_master.py deleted file mode 100644 index e26f48cda1..0000000000 --- a/erpnext/patches/v5_0/rename_taxes_and_charges_master.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - - -def execute(): - if frappe.db.table_exists("Sales Taxes and Charges Master"): - frappe.rename_doc("DocType", "Sales Taxes and Charges Master", - "Sales Taxes and Charges Template") - frappe.delete_doc("DocType", "Sales Taxes and Charges Master") - - if frappe.db.table_exists("Purchase Taxes and Charges Master"): - frappe.rename_doc("DocType", "Purchase Taxes and Charges Master", - "Purchase Taxes and Charges Template") - frappe.delete_doc("DocType", "Purchase Taxes and Charges Master") diff --git a/erpnext/patches/v5_0/rename_total_fields.py b/erpnext/patches/v5_0/rename_total_fields.py deleted file mode 100644 index 6657dd843e..0000000000 --- a/erpnext/patches/v5_0/rename_total_fields.py +++ /dev/null @@ -1,55 +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 frappe -from frappe.model.utils.rename_field import rename_field -from frappe.modules import scrub, get_doctype_module - -selling_doctypes = ("Quotation", "Sales Order", "Delivery Note", "Sales Invoice") - -buying_doctypes = ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice") - -selling_renamed_fields = ( - ("net_total", "base_net_total"), - ("net_total_export", "net_total"), - ("other_charges_total", "base_total_taxes_and_charges"), - ("other_charges_total_export", "total_taxes_and_charges"), - ("grand_total", "base_grand_total"), - ("grand_total_export", "grand_total"), - ("rounded_total", "base_rounded_total"), - ("rounded_total_export", "rounded_total"), - ("in_words", "base_in_words"), - ("in_words_export", "in_words") -) - -buying_renamed_fields = ( - ("net_total", "base_net_total"), - ("net_total_import", "net_total"), - ("grand_total", "base_grand_total"), - ("grand_total_import", "grand_total"), - ("rounded_total", "base_rounded_total"), - ("in_words", "base_in_words"), - ("in_words_import", "in_words"), - ("other_charges_added", "base_taxes_and_charges_added"), - ("other_charges_added_import", "taxes_and_charges_added"), - ("other_charges_deducted", "base_taxes_and_charges_deducted"), - ("other_charges_deducted_import", "taxes_and_charges_deducted"), - ("total_tax", "base_total_taxes_and_charges") -) - -def execute(): - for doctypes, fields in [[selling_doctypes, selling_renamed_fields], [buying_doctypes, buying_renamed_fields]]: - for dt in doctypes: - frappe.reload_doc(get_doctype_module(dt), "doctype", scrub(dt)) - table_columns = frappe.db.get_table_columns(dt) - base_net_total = frappe.db.sql("select sum(ifnull({0}, 0)) from `tab{1}`".format(fields[0][1], dt))[0][0] - if not base_net_total: - for f in fields: - if f[0] in table_columns: - rename_field(dt, f[0], f[1]) - - # Added new field "total_taxes_and_charges" in buying cycle, updating value - if dt in ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"): - frappe.db.sql("""update `tab{0}` set total_taxes_and_charges = - round(base_total_taxes_and_charges/conversion_rate, 2)""".format(dt)) diff --git a/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py b/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py deleted file mode 100644 index c564f8b02a..0000000000 --- a/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py +++ /dev/null @@ -1,65 +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 frappe -import re - -def execute(): - # NOTE: sequence is important - renamed_fields = get_all_renamed_fields() - - for dt, script_field, ref_dt_field in (("Client Script", "script", "dt"), ("Print Format", "html", "doc_type")): - - cond1 = " or ".join("""{0} like "%%{1}%%" """.format(script_field, d[0].replace("_", "\\_")) for d in renamed_fields) - cond2 = " and standard = 'No'" if dt == "Print Format" else "" - - for name, script, ref_dt in frappe.db.sql("select name, {0} as script, {1} as ref_dt from `tab{2}` where ({3}) {4}".format(script_field, ref_dt_field, dt, cond1, cond2)): - update_script(dt, name, ref_dt, script_field, script, renamed_fields) - -def get_all_renamed_fields(): - from erpnext.patches.v5_0.rename_table_fieldnames import rename_map - - renamed_fields = ( - ("base_amount", "base_net_amount"), - ("net_total", "base_net_total"), - ("net_total_export", "total"), - ("net_total_import", "total"), - ("other_charges_total", "base_total_taxes_and_charges"), - ("other_charges_total_export", "total_taxes_and_charges"), - ("other_charges_added", "base_taxes_and_charges_added"), - ("other_charges_added_import", "taxes_and_charges_added"), - ("other_charges_deducted", "base_taxes_and_charges_deducted"), - ("other_charges_deducted_import", "taxes_and_charges_deducted"), - ("total_tax", "base_total_taxes_and_charges"), - ("grand_total", "base_grand_total"), - ("grand_total_export", "grand_total"), - ("grand_total_import", "grand_total"), - ("rounded_total", "base_rounded_total"), - ("rounded_total_export", "rounded_total"), - ("rounded_total_import", "rounded_total"), - ("in_words", "base_in_words"), - ("in_words_export", "in_words"), - ("in_words_import", "in_words"), - ("tax_amount", "base_tax_amount"), - ("tax_amount_after_discount_amount", "base_tax_amount_after_discount_amount"), - ) - - for fields in rename_map.values(): - renamed_fields += tuple(fields) - - return renamed_fields - -def update_script(dt, name, ref_dt, script_field, script, renamed_fields): - for from_field, to_field in renamed_fields: - if from_field != "entries": - script = re.sub(r"\b{}\b".format(from_field), to_field, script) - - if ref_dt == "Journal Entry": - script = re.sub(r"\bentries\b", "accounts", script) - elif ref_dt == "Bank Reconciliation": - script = re.sub(r"\bentries\b", "journal_entries", script) - elif ref_dt in ("Sales Invoice", "Purchase Invoice"): - script = re.sub(r"\bentries\b", "items", script) - - frappe.db.set_value(dt, name, script_field, script) \ No newline at end of file diff --git a/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py b/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py deleted file mode 100644 index 76efdcc7c6..0000000000 --- a/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - je_list = frappe.db.sql_list(""" - select par.name from `tabJournal Entry` par - where par.docstatus=1 and par.creation > '2015-03-01' - and (select count(distinct child.party) from `tabJournal Entry Account` child - where par.name=child.parent and ifnull(child.party, '') != '') > 1 - """) - - for d in je_list: - # delete existing gle - frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d) - - # repost gl entries - je = frappe.get_doc("Journal Entry", d) - je.make_gl_entries() - - if je_list: - print(je_list) - - \ No newline at end of file diff --git a/erpnext/patches/v5_0/repost_requested_qty.py b/erpnext/patches/v5_0/repost_requested_qty.py deleted file mode 100644 index 6af71f3fc4..0000000000 --- a/erpnext/patches/v5_0/repost_requested_qty.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty - - count=0 - for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse - from `tabMaterial Request Item` where docstatus = 1"""): - try: - count += 1 - update_bin_qty(item_code, warehouse, { - "indented_qty": get_indented_qty(item_code, warehouse), - }) - if count % 200 == 0: - frappe.db.commit() - except: - frappe.db.rollback() diff --git a/erpnext/patches/v5_0/reset_values_in_tools.py b/erpnext/patches/v5_0/reset_values_in_tools.py deleted file mode 100644 index fd970ba1b0..0000000000 --- a/erpnext/patches/v5_0/reset_values_in_tools.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for dt in ["Payment Tool", "Bank Reconciliation", "Payment Reconciliation", "Leave Control Panel", - "Salary Manager", "Upload Attenadance", "Production Planning Tool", "BOM Update Tool", "Customize Form", - "Employee Attendance Tool", "Rename Tool", "BOM Update Tool", "Process Payroll", "Naming Series"]: - frappe.db.sql("delete from `tabSingles` where doctype=%s", dt) - \ No newline at end of file diff --git a/erpnext/patches/v5_0/set_appraisal_remarks.py b/erpnext/patches/v5_0/set_appraisal_remarks.py deleted file mode 100644 index 8652c32cf0..0000000000 --- a/erpnext/patches/v5_0/set_appraisal_remarks.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Appraisal") - frappe.db.sql("update `tabAppraisal` set remarks = comments") \ No newline at end of file diff --git a/erpnext/patches/v5_0/set_default_company_in_bom.py b/erpnext/patches/v5_0/set_default_company_in_bom.py deleted file mode 100644 index a5cd761119..0000000000 --- a/erpnext/patches/v5_0/set_default_company_in_bom.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - frappe.reload_doc("manufacturing", "doctype", "bom") - company = frappe.db.get_value("Global Defaults", None, "default_company") - frappe.db.sql("""update `tabBOM` set company = %s""",company) diff --git a/erpnext/patches/v5_0/set_footer_address.py b/erpnext/patches/v5_0/set_footer_address.py deleted file mode 100644 index 8120d834e1..0000000000 --- a/erpnext/patches/v5_0/set_footer_address.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("System Settings") - ss = frappe.get_doc("System Settings", "System Settings") - ss.email_footer_address = frappe.db.get_default("company") - ss.flags.ignore_mandatory = True - ss.save() diff --git a/erpnext/patches/v5_0/stock_entry_update_value.py b/erpnext/patches/v5_0/stock_entry_update_value.py deleted file mode 100644 index ba1af310f5..0000000000 --- a/erpnext/patches/v5_0/stock_entry_update_value.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for d in frappe.db.get_all("Stock Entry"): - se = frappe.get_doc("Stock Entry", d.name) - se.set_total_incoming_outgoing_value() - se.db_update() diff --git a/erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py b/erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py deleted file mode 100644 index 76d10820b5..0000000000 --- a/erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py +++ /dev/null @@ -1,80 +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 frappe -from frappe.model.meta import get_field_precision -from frappe.custom.doctype.property_setter.property_setter import make_property_setter - -def execute(): - selling_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"] - buying_doctypes = ["Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"] - - for dt in selling_doctypes: - update_values(dt, "Sales Taxes and Charges") - - for dt in buying_doctypes: - update_values(dt, "Purchase Taxes and Charges") - -def update_values(dt, tax_table): - frappe.reload_doctype(dt) - frappe.reload_doctype(dt + " Item") - frappe.reload_doctype(tax_table) - - net_total_precision = get_field_precision(frappe.get_meta(dt).get_field("net_total")) - for field in ("total", "base_total", "base_net_total"): - make_property_setter(dt, field, "precision", net_total_precision, "Select") - - rate_field_precision = get_field_precision(frappe.get_meta(dt + " Item").get_field("rate")) - for field in ("net_rate", "base_net_rate", "net_amount", "base_net_amount", "base_rate", "base_amount"): - make_property_setter(dt + " Item", field, "precision", rate_field_precision, "Select") - - tax_amount_precision = get_field_precision(frappe.get_meta(tax_table).get_field("tax_amount")) - for field in ("base_tax_amount", "total", "base_total", "tax_amount_after_discount_amount", - "base_tax_amount_after_discount_amount"): - make_property_setter(tax_table, field, "precision", tax_amount_precision, "Select") - - # update net_total, discount_on - frappe.db.sql(""" - UPDATE - `tab{0}` - SET - total = round(net_total, {1}), - base_total = round(net_total*conversion_rate, {1}), - net_total = round(base_net_total / conversion_rate, {1}), - apply_discount_on = "Grand Total" - WHERE - docstatus < 2 - """.format(dt, net_total_precision)) - - # update net_amount - frappe.db.sql(""" - UPDATE - `tab{0}` par, `tab{1}` item - SET - item.base_net_amount = round(item.base_amount, {2}), - item.base_net_rate = round(item.base_rate, {2}), - item.net_amount = round(item.base_amount / par.conversion_rate, {2}), - item.net_rate = round(item.base_rate / par.conversion_rate, {2}), - item.base_amount = round(item.amount * par.conversion_rate, {2}), - item.base_rate = round(item.rate * par.conversion_rate, {2}) - WHERE - par.name = item.parent - and par.docstatus < 2 - """.format(dt, dt + " Item", rate_field_precision)) - - # update tax in party currency - frappe.db.sql(""" - UPDATE - `tab{0}` par, `tab{1}` tax - SET - tax.base_tax_amount = round(tax.tax_amount, {2}), - tax.tax_amount = round(tax.tax_amount / par.conversion_rate, {2}), - tax.base_total = round(tax.total, {2}), - tax.total = round(tax.total / conversion_rate, {2}), - tax.base_tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, {2}), - tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount / conversion_rate, {2}) - WHERE - par.name = tax.parent - and par.docstatus < 2 - """.format(dt, tax_table, tax_amount_precision)) diff --git a/erpnext/patches/v5_0/update_account_types.py b/erpnext/patches/v5_0/update_account_types.py deleted file mode 100644 index 424743efaa..0000000000 --- a/erpnext/patches/v5_0/update_account_types.py +++ /dev/null @@ -1,20 +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 frappe - -def execute(): - for company in frappe.db.get_all("Company"): - company = frappe.get_doc("Company", company.name) - - match_types = ("Stock Received But Not Billed", "Stock Adjustment", "Expenses Included In Valuation", - "Cost of Goods Sold") - - for account_type in match_types: - account_name = "{0} - {1}".format(account_type, company.abbr) - current_account_type = frappe.db.get_value("Account", account_name, "account_type") - if current_account_type != account_type: - frappe.db.set_value("Account", account_name, "account_type", account_type) - - company.set_default_accounts() diff --git a/erpnext/patches/v5_0/update_advance_paid.py b/erpnext/patches/v5_0/update_advance_paid.py deleted file mode 100644 index 74e71e84c8..0000000000 --- a/erpnext/patches/v5_0/update_advance_paid.py +++ /dev/null @@ -1,13 +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 frappe - -def execute(): - for dt in ("Sales Order", "Purchase Order"): - orders_with_advance = frappe.db.sql("""select name from `tab{0}` - where docstatus < 2 and ifnull(advance_paid, 0) != 0""".format(dt), as_dict=1) - - for order in orders_with_advance: - frappe.get_doc(dt, order.name).set_total_advance_paid() \ No newline at end of file diff --git a/erpnext/patches/v5_0/update_companywise_payment_account.py b/erpnext/patches/v5_0/update_companywise_payment_account.py deleted file mode 100644 index fb4b919c85..0000000000 --- a/erpnext/patches/v5_0/update_companywise_payment_account.py +++ /dev/null @@ -1,21 +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 frappe - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'mode_of_payment') - frappe.reload_doc('accounts', 'doctype', 'mode_of_payment_account') - - mode_of_payment_list = frappe.db.sql("""select name, default_account - from `tabMode of Payment`""", as_dict=1) - - for d in mode_of_payment_list: - if d.get("default_account"): - parent_doc = frappe.get_doc("Mode of Payment", d.get("name")) - - parent_doc.set("accounts", - [{"company": frappe.db.get_value("Account", d.get("default_account"), "company"), - "default_account": d.get("default_account")}]) - parent_doc.save() diff --git a/erpnext/patches/v5_0/update_dn_against_doc_fields.py b/erpnext/patches/v5_0/update_dn_against_doc_fields.py deleted file mode 100644 index 56f4f484b1..0000000000 --- a/erpnext/patches/v5_0/update_dn_against_doc_fields.py +++ /dev/null @@ -1,14 +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 frappe - -def execute(): - frappe.reload_doc('stock', 'doctype', 'delivery_note_item') - - frappe.db.sql("""update `tabDelivery Note Item` set so_detail = prevdoc_detail_docname - where ifnull(against_sales_order, '') != ''""") - - frappe.db.sql("""update `tabDelivery Note Item` set si_detail = prevdoc_detail_docname - where ifnull(against_sales_invoice, '') != ''""") diff --git a/erpnext/patches/v5_0/update_from_bom.py b/erpnext/patches/v5_0/update_from_bom.py deleted file mode 100644 index 4b3e62d7a5..0000000000 --- a/erpnext/patches/v5_0/update_from_bom.py +++ /dev/null @@ -1,9 +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 frappe - -def execute(): - frappe.reload_doctype("Stock Entry") - frappe.db.sql("update `tabStock Entry` set from_bom = if(ifnull(bom_no, '')='', 0, 1)") diff --git a/erpnext/patches/v5_0/update_frozen_accounts_permission_role.py b/erpnext/patches/v5_0/update_frozen_accounts_permission_role.py deleted file mode 100644 index b52785ae60..0000000000 --- a/erpnext/patches/v5_0/update_frozen_accounts_permission_role.py +++ /dev/null @@ -1,13 +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 frappe - -def execute(): - account_settings = frappe.get_doc("Accounts Settings") - - if not account_settings.frozen_accounts_modifier and account_settings.bde_auth_role: - frappe.db.set_value("Accounts Settings", None, - "frozen_accounts_modifier", account_settings.bde_auth_role) - diff --git a/erpnext/patches/v5_0/update_item_and_description_again.py b/erpnext/patches/v5_0/update_item_and_description_again.py deleted file mode 100644 index 35dedcc072..0000000000 --- a/erpnext/patches/v5_0/update_item_and_description_again.py +++ /dev/null @@ -1,50 +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 frappe -from frappe.utils import cstr -import re - -def execute(): - item_details = frappe._dict() - for d in frappe.db.sql("select name, description from `tabItem`", as_dict=1): - description = cstr(d.description).strip() - new_desc = extract_description(description) - - item_details.setdefault(d.name, frappe._dict({ - "old_description": description, - "new_description": new_desc - })) - - - dt_list= ["Purchase Order Item","Supplier Quotation Item", "BOM", "BOM Explosion Item" , \ - "BOM Item", "Opportunity Item" , "Quotation Item" , "Sales Order Item" , "Delivery Note Item" , \ - "Material Request Item" , "Purchase Receipt Item" , "Stock Entry Detail"] - for dt in dt_list: - frappe.reload_doctype(dt) - records = frappe.db.sql("""select name, `{0}` as item_code, description from `tab{1}` - where description is not null and description like '%%]*\>".format(tag), "", desc) - - return desc diff --git a/erpnext/patches/v5_0/update_item_desc_in_invoice.py b/erpnext/patches/v5_0/update_item_desc_in_invoice.py deleted file mode 100644 index dba35d5693..0000000000 --- a/erpnext/patches/v5_0/update_item_desc_in_invoice.py +++ /dev/null @@ -1,52 +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 frappe -from frappe.website.utils import find_first_image -from frappe.utils import cstr -import re - -def execute(): - item_details = frappe._dict() - for d in frappe.db.sql("select name, description, image from `tabItem`", as_dict=1): - description = cstr(d.description).strip() - item_details.setdefault(d.name, frappe._dict({ - "description": description, - "image": d.image - })) - - - dt_list= ["Sales Invoice Item","Purchase Invoice Item"] - for dt in dt_list: - frappe.reload_doctype(dt) - records = frappe.db.sql("""select name, item_code, description from `tab{0}` - where ifnull(item_code, '') != '' and description is not null """.format(dt), as_dict=1) - - count = 1 - for d in records: - if item_details.get(d.item_code) and cstr(d.description) == item_details.get(d.item_code).description: - desc = item_details.get(d.item_code).description - image = item_details.get(d.item_code).image - else: - desc, image = extract_image_and_description(cstr(d.description)) - - if not image: - item_detail = item_details.get(d.item_code) - if item_detail: - image = item_detail.image - - frappe.db.sql("""update `tab{0}` set description = %s, image = %s - where name = %s """.format(dt), (desc, image, d.name)) - - count += 1 - if count % 500 == 0: - frappe.db.commit() - - -def extract_image_and_description(data): - image_url = find_first_image(data) - desc = data - for tag in ("img", "table", "tr", "td"): - desc = re.sub("\]*\>".format(tag), "", desc) - return desc, image_url \ No newline at end of file diff --git a/erpnext/patches/v5_0/update_item_description_and_image.py b/erpnext/patches/v5_0/update_item_description_and_image.py deleted file mode 100644 index 75df39ee39..0000000000 --- a/erpnext/patches/v5_0/update_item_description_and_image.py +++ /dev/null @@ -1,54 +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 frappe -from frappe.website.utils import find_first_image -from frappe.utils import cstr -import re - -def execute(): - item_details = frappe._dict() - for d in frappe.db.sql("select name, description_html, description from `tabItem`", as_dict=1): - description = cstr(d.description_html).strip() or cstr(d.description).strip() - image_url, new_desc = extract_image_and_description(description) - - item_details.setdefault(d.name, frappe._dict({ - "old_description": description, - "new_description": new_desc, - "image_url": image_url - })) - - - dt_list= ["Purchase Order Item","Supplier Quotation Item", "BOM", "BOM Explosion Item" , \ - "BOM Item", "Opportunity Item" , "Quotation Item" , "Sales Order Item" , "Delivery Note Item" , \ - "Material Request Item" , "Purchase Receipt Item" , "Stock Entry Detail"] - for dt in dt_list: - frappe.reload_doctype(dt) - records = frappe.db.sql("""select name, `{0}` as item_code, description from `tab{1}` - where description is not null and image is null and description like '%%]+\>", "", data) - - return image_url, desc diff --git a/erpnext/patches/v5_0/update_item_name_in_bom.py b/erpnext/patches/v5_0/update_item_name_in_bom.py deleted file mode 100644 index 5781542a2a..0000000000 --- a/erpnext/patches/v5_0/update_item_name_in_bom.py +++ /dev/null @@ -1,18 +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 frappe - -def execute(): - frappe.reload_doc("manufacturing", "doctype", "bom") - frappe.reload_doc("manufacturing", "doctype", "bom_item") - frappe.reload_doc("manufacturing", "doctype", "bom_explosion_item") - frappe.reload_doc("manufacturing", "doctype", "bom_operation") - - frappe.db.sql("""update `tabBOM` as bom set bom.item_name = \ - ( select item.item_name from `tabItem` as item where item.name = bom.item)""") - frappe.db.sql("""update `tabBOM Item` as bomItem set bomItem.item_name = ( select item.item_name \ - from `tabItem` as item where item.name = bomItem.item_code)""") - frappe.db.sql("""update `tabBOM Explosion Item` as explosionItem set explosionItem.item_name = \ - ( select item.item_name from `tabItem` as item where item.name = explosionItem.item_code)""") diff --git a/erpnext/patches/v5_0/update_journal_entry_title.py b/erpnext/patches/v5_0/update_journal_entry_title.py deleted file mode 100644 index eaa2be054f..0000000000 --- a/erpnext/patches/v5_0/update_journal_entry_title.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Journal Entry") - frappe.db.sql("""update `tabJournal Entry` set title = - if(ifnull(pay_to_recd_from, "")!="", pay_to_recd_from, - (select account from `tabJournal Entry Account` - where parent=`tabJournal Entry`.name and idx=1 limit 1))""") diff --git a/erpnext/patches/v5_0/update_material_transfer_for_manufacture.py b/erpnext/patches/v5_0/update_material_transfer_for_manufacture.py deleted file mode 100644 index f31c9fed4d..0000000000 --- a/erpnext/patches/v5_0/update_material_transfer_for_manufacture.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""update `tabStock Entry` set purpose='Material Transfer for Manufacture' - where ifnull(work_order, '')!='' and purpose='Material Transfer'""") diff --git a/erpnext/patches/v5_0/update_material_transferred_for_manufacturing.py b/erpnext/patches/v5_0/update_material_transferred_for_manufacturing.py deleted file mode 100644 index 2a09aa29af..0000000000 --- a/erpnext/patches/v5_0/update_material_transferred_for_manufacturing.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Work Order") - frappe.db.sql("""update `tabWork Order` set material_transferred_for_manufacturing= - (select sum(fg_completed_qty) from `tabStock Entry` - where docstatus=1 - and work_order=`tabWork Order`.name - and purpose = "Material Transfer for Manufacture")""") diff --git a/erpnext/patches/v5_0/update_material_transferred_for_manufacturing_again.py b/erpnext/patches/v5_0/update_material_transferred_for_manufacturing_again.py deleted file mode 100644 index 5847c83d38..0000000000 --- a/erpnext/patches/v5_0/update_material_transferred_for_manufacturing_again.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - wo_order_qty_transferred = frappe._dict() - for se in frappe.db.sql("""select work_order, sum(fg_completed_qty) as transferred_qty - from `tabStock Entry` - where docstatus=1 and ifnull(work_order, '') != '' - and purpose = 'Material Transfer for Manufacture' - group by work_order""", as_dict=1): - wo_order_qty_transferred.setdefault(se.work_order, se.transferred_qty) - - for d in frappe.get_all("Work Order", filters={"docstatus": 1}, fields=["name", "qty"]): - if d.name in wo_order_qty_transferred: - material_transferred_for_manufacturing = wo_order_qty_transferred.get(d.name) \ - if wo_order_qty_transferred.get(d.name) <= d.qty else d.qty - - frappe.db.sql("""update `tabWork Order` set material_transferred_for_manufacturing=%s - where name=%s""", (material_transferred_for_manufacturing, d.name)) \ No newline at end of file diff --git a/erpnext/patches/v5_0/update_operation_description.py b/erpnext/patches/v5_0/update_operation_description.py deleted file mode 100644 index 4ce32f35f1..0000000000 --- a/erpnext/patches/v5_0/update_operation_description.py +++ /dev/null @@ -1,11 +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 frappe -import frappe.permissions - -def execute(): - if "opn_description" in frappe.db.get_table_columns("BOM Operation"): - frappe.db.sql("""update `tabBOM Operation` set description = opn_description - where ifnull(description, '') = ''""") \ No newline at end of file diff --git a/erpnext/patches/v5_0/update_opportunity.py b/erpnext/patches/v5_0/update_opportunity.py deleted file mode 100644 index 8eb45c48e7..0000000000 --- a/erpnext/patches/v5_0/update_opportunity.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('crm', 'doctype', 'opportunity') - frappe.reload_doc('crm', 'doctype', 'opportunity_item') - - # all existing opportunities were with items - frappe.db.sql("update `tabDocType` set module = 'CRM' where name='Opportunity Item'") - frappe.db.sql("update tabOpportunity set with_items=1, title=customer_name") - frappe.db.sql("update `tabEmail Account` set append_to='Opportunity' where append_to='Lead'") diff --git a/erpnext/patches/v5_0/update_projects.py b/erpnext/patches/v5_0/update_projects.py deleted file mode 100644 index 68e03c9bdb..0000000000 --- a/erpnext/patches/v5_0/update_projects.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -import frappe - -def execute(): - # convert milestones to tasks - frappe.reload_doctype("Project") - frappe.reload_doc("projects", "doctype", "project_task") - frappe.reload_doctype("Task") - frappe.reload_doc("projects", "doctype", "task_depends_on") - frappe.reload_doc("projects", "doctype", "time_log") - - for m in frappe.get_all("Project Milestone", "*"): - if (m.milestone and m.milestone_date - and frappe.db.exists("Project", m.parent)): - subject = (m.milestone[:139] + "…") if (len(m.milestone) > 140) else m.milestone - description = m.milestone - task = frappe.get_doc({ - "doctype": "Task", - "subject": subject, - "description": description if description!=subject else None, - "expected_start_date": m.milestone_date, - "status": "Open" if m.status=="Pending" else "Closed", - "project": m.parent, - }) - task.flags.ignore_mandatory = True - task.insert(ignore_permissions=True) - - # remove project milestone - frappe.delete_doc("DocType", "Project Milestone") - - # remove calendar events for milestone - for e in frappe.get_all("Event", ["name"], {"ref_type": "Project"}): - frappe.delete_doc("Event", e.name) diff --git a/erpnext/patches/v5_0/update_sms_sender.py b/erpnext/patches/v5_0/update_sms_sender.py deleted file mode 100644 index 7ffc703c43..0000000000 --- a/erpnext/patches/v5_0/update_sms_sender.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.set_value("SMS Settings", "SMS Settings", "sms_sender_name", - frappe.db.get_single_value("Global Defaults", "sms_sender_name")) diff --git a/erpnext/patches/v5_0/update_tax_amount_after_discount_in_purchase_cycle.py b/erpnext/patches/v5_0/update_tax_amount_after_discount_in_purchase_cycle.py deleted file mode 100644 index 53df9422b3..0000000000 --- a/erpnext/patches/v5_0/update_tax_amount_after_discount_in_purchase_cycle.py +++ /dev/null @@ -1,17 +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 frappe - -def execute(): - frappe.db.sql(""" - update - `tabPurchase Taxes and Charges` - set - tax_amount_after_discount_amount = tax_amount, - base_tax_amount_after_discount_amount = base_tax_amount - where - ifnull(tax_amount_after_discount_amount, 0) = 0 - and ifnull(base_tax_amount_after_discount_amount, 0) = 0 - """) \ No newline at end of file diff --git a/erpnext/patches/v5_0/update_temporary_account.py b/erpnext/patches/v5_0/update_temporary_account.py deleted file mode 100644 index 078c8714ff..0000000000 --- a/erpnext/patches/v5_0/update_temporary_account.py +++ /dev/null @@ -1,9 +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 frappe - -def execute(): - frappe.db.sql("""Update `tabAccount` set account_type = 'Temporary' - where account_name in ('Temporary Assets', 'Temporary Liabilities', 'Temporary Opening')""") \ No newline at end of file diff --git a/erpnext/patches/v5_0/update_time_log_title.py b/erpnext/patches/v5_0/update_time_log_title.py deleted file mode 100644 index 8263be0007..0000000000 --- a/erpnext/patches/v5_0/update_time_log_title.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Time Log") - for d in frappe.get_all("Time Log"): - time_log = frappe.get_doc("Time Log", d.name) - time_log.set_title() - frappe.db.set_value("Time Log", time_log.name, "title", time_log.title) diff --git a/erpnext/patches/v5_1/__init__.py b/erpnext/patches/v5_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v5_1/default_bom.py b/erpnext/patches/v5_1/default_bom.py deleted file mode 100644 index 6484edd603..0000000000 --- a/erpnext/patches/v5_1/default_bom.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def execute(): - frappe.db.sql("""Update `tabItem` as item set default_bom = NULL where - not exists(select name from `tabBOM` as bom where item.default_bom = bom.name and bom.docstatus =1 )""") \ No newline at end of file diff --git a/erpnext/patches/v5_1/fix_against_account.py b/erpnext/patches/v5_1/fix_against_account.py deleted file mode 100644 index a62c15b7d1..0000000000 --- a/erpnext/patches/v5_1/fix_against_account.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -from erpnext.accounts.doctype.gl_entry.gl_entry import update_against_account - -def execute(): - from_date = "2015-05-01" - - for doc in frappe.get_all("Journal Entry", - filters={"creation": (">", from_date), "docstatus": "1"}): - - # update in gl_entry - update_against_account("Journal Entry", doc.name) - - # update in jv - doc = frappe.get_doc("Journal Entry", doc.name) - doc.set_against_account() - doc.db_update() - - for doc in frappe.get_all("Sales Invoice", - filters={"creation": (">", from_date), "docstatus": "1"}, - fields=["name", "customer"]): - - frappe.db.sql("""update `tabGL Entry` set against=%s - where voucher_type='Sales Invoice' and voucher_no=%s - and credit > 0 and ifnull(party, '')=''""", - (doc.customer, doc.name)) - - for doc in frappe.get_all("Purchase Invoice", - filters={"creation": (">", from_date), "docstatus": "1"}, - fields=["name", "supplier"]): - - frappe.db.sql("""update `tabGL Entry` set against=%s - where voucher_type='Purchase Invoice' and voucher_no=%s - and debit > 0 and ifnull(party, '')=''""", - (doc.supplier, doc.name)) diff --git a/erpnext/patches/v5_1/rename_roles.py b/erpnext/patches/v5_1/rename_roles.py deleted file mode 100644 index e19c22a614..0000000000 --- a/erpnext/patches/v5_1/rename_roles.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if not frappe.db.exists("Role", "Stock User"): - frappe.rename_doc("Role", "Material User", "Stock User") - if not frappe.db.exists("Role", "Stock Manager"): - frappe.rename_doc("Role", "Material Manager", "Stock Manager") - if not frappe.db.exists("Role", "Item Manager"): - frappe.rename_doc("Role", "Material Master Manager", "Item Manager") diff --git a/erpnext/patches/v5_1/sales_bom_rename.py b/erpnext/patches/v5_1/sales_bom_rename.py deleted file mode 100644 index e06012f3e4..0000000000 --- a/erpnext/patches/v5_1/sales_bom_rename.py +++ /dev/null @@ -1,12 +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 frappe - -def execute(): - tables = frappe.db.sql_list("show tables") - for old_dt, new_dt in [["Sales BOM Item", "Product Bundle Item"], - ["Sales BOM", "Product Bundle"]]: - if "tab"+new_dt not in tables: - frappe.rename_doc("DocType", old_dt, new_dt, force=True) diff --git a/erpnext/patches/v5_2/__init__.py b/erpnext/patches/v5_2/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v5_2/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v5_2/change_item_selects_to_checks.py b/erpnext/patches/v5_2/change_item_selects_to_checks.py deleted file mode 100644 index 1ee8f6caa5..0000000000 --- a/erpnext/patches/v5_2/change_item_selects_to_checks.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def execute(): - fields = ("is_stock_item", "is_asset_item", "has_batch_no", "has_serial_no", - "is_sales_item", "is_purchase_item", "inspection_required", "is_sub_contracted_item") - - # convert to 1 or 0 - update_str = ", ".join(["`{0}`=if(`{0}`='Yes',1,0)".format(f) for f in fields]) - frappe.db.sql("update tabItem set {0}".format(update_str)) - - frappe.db.commit() - - # alter fields to int - for f in fields: - frappe.db.sql("alter table tabItem change {0} {0} int(1) default '0'".format(f, f)) - - frappe.reload_doctype("Item") diff --git a/erpnext/patches/v5_4/__init__.py b/erpnext/patches/v5_4/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v5_4/cleanup_journal_entry.py b/erpnext/patches/v5_4/cleanup_journal_entry.py deleted file mode 100644 index 6860e6ad09..0000000000 --- a/erpnext/patches/v5_4/cleanup_journal_entry.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -import frappe -from pymysql import InternalError - -def execute(): - frappe.reload_doctype("Journal Entry Account") - for doctype, fieldname in ( - ("Sales Order", "against_sales_order"), - ("Purchase Order", "against_purchase_order"), - ("Sales Invoice", "against_invoice"), - ("Purchase Invoice", "against_voucher"), - ("Journal Entry", "against_jv"), - ("Expense Claim", "against_expense_claim"), - ): - try: - frappe.db.sql("""update `tabJournal Entry Account` - set reference_type=%s, reference_name={0} where ifnull({0}, '') != '' - """.format(fieldname), doctype) - except InternalError: - # column not found - pass diff --git a/erpnext/patches/v5_4/fix_invoice_outstanding.py b/erpnext/patches/v5_4/fix_invoice_outstanding.py deleted file mode 100644 index 54a1da69ef..0000000000 --- a/erpnext/patches/v5_4/fix_invoice_outstanding.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt - -def execute(): - frappe.reload_doctype("Sales Invoice") - return_entries = frappe.get_list("Sales Invoice", filters={"is_return": 1, "docstatus": 1}, - fields=["debit_to", "customer", "return_against"]) - for d in return_entries: - update_outstanding_amt(d.debit_to, "Customer", d.customer, "Sales Invoice", d.return_against) diff --git a/erpnext/patches/v5_4/fix_missing_item_images.py b/erpnext/patches/v5_4/fix_missing_item_images.py deleted file mode 100644 index c6fe57896f..0000000000 --- a/erpnext/patches/v5_4/fix_missing_item_images.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import print_function, unicode_literals -import frappe -import os -from frappe.utils import get_files_path -from frappe.core.doctype.file.file import get_content_hash - -def execute(): - files_path = get_files_path() - - # get files that don't have attached_to_name but exist - unlinked_files = get_unlinked_files(files_path) - if not unlinked_files: - return - - fixed_files = fix_files_for_item(files_path, unlinked_files) - - # fix remaining files - for key, file_data in unlinked_files.items(): - if key not in fixed_files: - rename_and_set_content_hash(files_path, unlinked_files, key) - frappe.db.commit() - -def fix_files_for_item(files_path, unlinked_files): - fixed_files = [] - - # make a list of files/something and /files/something to check in child table's image column - file_urls = [key for key in unlinked_files.keys()] + ["/" + key for key in unlinked_files.keys()] - file_item_code = get_file_item_code(file_urls) - - for (file_url, item_code), children in file_item_code.items(): - new_file_url = "/files/{0}".format(unlinked_files[file_url]["file_name"]) - - for row in children: - # print file_url, new_file_url, item_code, row.doctype, row.name - - # replace image in these rows with the new file url - frappe.db.set_value(row.doctype, row.name, "image", new_file_url, update_modified=False) - - # set it as attachment of this item code - file_data = frappe.get_doc("File", unlinked_files[file_url]["file"]) - file_data.attached_to_doctype = "Item" - file_data.attached_to_name = item_code - file_data.flags.ignore_folder_validate = True - - try: - file_data.save() - except IOError: - print("File {0} does not exist".format(new_file_url)) - - # marking fix to prevent further errors - fixed_files.append(file_url) - - continue - - # set it as image in Item - if not frappe.db.get_value("Item", item_code, "image"): - frappe.db.set_value("Item", item_code, "image", new_file_url, update_modified=False) - - rename_and_set_content_hash(files_path, unlinked_files, file_url) - - fixed_files.append(file_url) - - # commit - frappe.db.commit() - - return fixed_files - -def rename_and_set_content_hash(files_path, unlinked_files, file_url): - # rename this file - old_filename = os.path.join(files_path, unlinked_files[file_url]["file"]) - new_filename = os.path.join(files_path, unlinked_files[file_url]["file_name"]) - - if not os.path.exists(new_filename): - os.rename(old_filename, new_filename) - - # set content hash if missing - file_data_name = unlinked_files[file_url]["file"] - if not frappe.db.get_value("File", file_data_name, "content_hash"): - with open(new_filename, "r") as f: - content_hash = get_content_hash(f.read()) - frappe.db.set_value("File", file_data_name, "content_hash", content_hash) - -def get_unlinked_files(files_path): - # find files that have the same name as a File doc - # and the file_name mentioned in that File doc doesn't exist - # and it isn't already attached to a doc - unlinked_files = {} - files = os.listdir(files_path) - for file in files: - if not frappe.db.exists("File", {"file_name": file}): - file_data = frappe.db.get_value("File", {"name": file}, - ["file_name", "attached_to_doctype", "attached_to_name"], as_dict=True) - - if (file_data - and file_data.file_name - and file_data.file_name not in files - and not file_data.attached_to_doctype - and not file_data.attached_to_name): - - file_data["file"] = file - unlinked_files["files/{0}".format(file)] = file_data - - return unlinked_files - -def get_file_item_code(file_urls): - # get a map of file_url, item_code and list of documents where file_url will need to be changed in image field - file_item_code = {} - - doctypes = frappe.db.sql_list("""select name from `tabDocType` dt - where istable=1 - and exists (select name from `tabDocField` df where df.parent=dt.name and df.fieldname='item_code') - and exists (select name from `tabDocField` df where df.parent=dt.name and df.fieldname='image')""") - - for doctype in doctypes: - result = frappe.db.sql("""select name, image, item_code, '{0}' as doctype from `tab{0}` - where image in ({1})""".format(doctype, ", ".join(["%s"]*len(file_urls))), - file_urls, as_dict=True) - - for r in result: - key = (r.image, r.item_code) - if key not in file_item_code: - file_item_code[key] = [] - - file_item_code[key].append(r) - - return file_item_code diff --git a/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py b/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py deleted file mode 100644 index 6eb3994c7c..0000000000 --- a/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.stock.stock_balance import repost_actual_qty - -def execute(): - cancelled_invoices = frappe.db.sql_list("""select name from `tabSales Invoice` - where docstatus = 2 and ifnull(update_stock, 0) = 1""") - - if cancelled_invoices: - repost_for = frappe.db.sql("""select distinct item_code, warehouse from `tabStock Ledger Entry` - where voucher_type = 'Sales Invoice' and voucher_no in (%s)""" - % (', '.join(['%s']*len(cancelled_invoices))), tuple(cancelled_invoices)) - - frappe.db.sql("""delete from `tabStock Ledger Entry` - where voucher_type = 'Sales Invoice' and voucher_no in (%s)""" - % (', '.join(['%s']*len(cancelled_invoices))), tuple(cancelled_invoices)) - - for item_code, warehouse in repost_for: - repost_actual_qty(item_code, warehouse) \ No newline at end of file diff --git a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py b/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py deleted file mode 100644 index ba311225bb..0000000000 --- a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe -from frappe.email import sendmail_to_system_managers -from frappe.utils import get_link_to_form - -def execute(): - wrong_records = [] - for dt in ("Quotation", "Sales Order", "Delivery Note", "Sales Invoice", - "Purchase Order", "Purchase Receipt", "Purchase Invoice"): - records = frappe.db.sql_list("""select name from `tab{0}` - where apply_discount_on = 'Net Total' and ifnull(discount_amount, 0) != 0 - and modified >= '2015-02-17' and docstatus=1""".format(dt)) - - if records: - records = [get_link_to_form(dt, d) for d in records] - wrong_records.append([dt, records]) - - if wrong_records: - content = """Dear System Manager, - -Due to an error related to Discount Amount on Net Total, tax calculation might be wrong in the following records. We did not fix the tax amount automatically because it can corrupt the entries, so we request you to check these records and amend if you found the calculation wrong. - -Please check following Entries: - -%s - - -Regards, - -Administrator""" % "\n".join([(d[0] + ": " + ", ".join(d[1])) for d in wrong_records]) - try: - sendmail_to_system_managers("[Important] [ERPNext] Tax calculation might be wrong, please check.", content) - except: - pass - - print("="*50) - print(content) - print("="*50) \ No newline at end of file diff --git a/erpnext/patches/v5_4/set_root_and_report_type.py b/erpnext/patches/v5_4/set_root_and_report_type.py deleted file mode 100644 index 9147644da2..0000000000 --- a/erpnext/patches/v5_4/set_root_and_report_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - roots = frappe.db.sql("""select lft, rgt, report_type, root_type - from `tabAccount` where ifnull(parent_account, '')=''""", as_dict=1) - for d in roots: - frappe.db.sql("update `tabAccount` set report_type=%s, root_type=%s where lft > %s and rgt < %s", - (d.report_type, d.root_type, d.lft, d.rgt)) \ No newline at end of file diff --git a/erpnext/patches/v5_4/stock_entry_additional_costs.py b/erpnext/patches/v5_4/stock_entry_additional_costs.py deleted file mode 100644 index 3a98deb918..0000000000 --- a/erpnext/patches/v5_4/stock_entry_additional_costs.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import flt - -def execute(): - frappe.reload_doctype("Stock Entry") - frappe.reload_doctype("Stock Entry Detail") - frappe.reload_doctype("Landed Cost Taxes and Charges") - - stock_entry_db_columns = frappe.db.get_table_columns("Stock Entry") - if "additional_operating_cost" in stock_entry_db_columns: - operating_cost_fieldname = "additional_operating_cost" - elif "total_fixed_cost" in stock_entry_db_columns: - operating_cost_fieldname = "total_fixed_cost" - else: - return - - - frappe.db.sql("""update `tabStock Entry Detail` sed, `tabStock Entry` se - set sed.valuation_rate=sed.incoming_rate, sed.basic_rate=sed.incoming_rate, sed.basic_amount=sed.amount - where sed.parent = se.name - and (se.purpose not in ('Manufacture', 'Repack') or ifnull({0}, 0)=0) - """.format(operating_cost_fieldname)) - - - stock_entries = frappe.db.sql_list("""select name from `tabStock Entry` - where purpose in ('Manufacture', 'Repack') and ifnull({0}, 0)!=0 - and docstatus < 2""".format(operating_cost_fieldname)) - - for d in stock_entries: - stock_entry = frappe.get_doc("Stock Entry", d) - stock_entry.append("additional_costs", { - "description": "Additional Operating Cost", - "amount": stock_entry.get(operating_cost_fieldname) - }) - - number_of_fg_items = len([t.t_warehouse for t in stock_entry.get("items") if t.t_warehouse]) - - for d in stock_entry.get("items"): - d.valuation_rate = d.incoming_rate - - if d.bom_no or (d.t_warehouse and number_of_fg_items == 1): - d.additional_cost = stock_entry.get(operating_cost_fieldname) - - d.basic_rate = flt(d.valuation_rate) - flt(d.additional_cost) - d.basic_amount = flt(flt(d.basic_rate) *flt(d.transfer_qty), d.precision("basic_amount")) - - stock_entry.flags.ignore_validate = True - stock_entry.flags.ignore_validate_update_after_submit = True - stock_entry.save() diff --git a/erpnext/patches/v5_4/update_purchase_cost_against_project.py b/erpnext/patches/v5_4/update_purchase_cost_against_project.py deleted file mode 100644 index 1b917c83c4..0000000000 --- a/erpnext/patches/v5_4/update_purchase_cost_against_project.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for p in frappe.get_all("Project"): - purchase_cost = frappe.db.sql("""select sum(ifnull(base_net_amount, 0)) - from `tabPurchase Invoice Item` where project = %s and docstatus=1""", p.name) - purchase_cost = purchase_cost and purchase_cost[0][0] or 0 - - frappe.db.set_value("Project", p.name, "total_purchase_cost", purchase_cost) \ No newline at end of file diff --git a/erpnext/patches/v5_7/__init__.py b/erpnext/patches/v5_7/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v5_7/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v5_7/item_template_attributes.py b/erpnext/patches/v5_7/item_template_attributes.py deleted file mode 100644 index 6aa81f79b2..0000000000 --- a/erpnext/patches/v5_7/item_template_attributes.py +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe -from frappe.exceptions import SQLError - -def execute(): - """ - Structure History: - 1. Item and Item Variant - 2. Item, Variant Attribute, Manage Variants and Manage Variant Items - 3. Item, Item Variant Attribute, Item Attribute and Item Attribute Type (latest) - """ - rename_and_reload_doctypes() - - variant_templates = frappe.get_all("Item", filters={"has_variants": 1}, limit_page_length=1) - if not variant_templates: - # database does not have items that have variants - # so no point in running the patch - return - - variant_attributes = frappe.get_all("Item Variant Attribute", fields=["*"], limit_page_length=1) - - if variant_attributes: - # manage variant patch is already applied - migrate_manage_variants() - - else: - # old structure based on "Item Variant" table - try: - migrate_item_variants() - - except SQLError: - print("`tabItem Variant` not found") - -def rename_and_reload_doctypes(): - if "tabVariant Attribute" in frappe.db.get_tables(): - frappe.rename_doc("DocType", "Variant Attribute", "Item Variant Attribute") - - frappe.reload_doctype("Item") - frappe.reload_doc("Stock", "DocType", "Item Variant Attribute") - frappe.reload_doc("Stock", "DocType", "Item Attribute Value") - frappe.reload_doc("Stock", "DocType", "Item Attribute") - -def migrate_manage_variants(): - item_attribute = {} - for d in frappe.db.sql("""select DISTINCT va.attribute, i.variant_of - from `tabItem Variant Attribute` va, `tabItem` i - where va.parent = i.name and ifnull(i.variant_of, '')!=''""", as_dict=1): - item_attribute.setdefault(d.variant_of, []).append({"attribute": d.attribute}) - - for item, attributes in item_attribute.items(): - template = frappe.get_doc("Item", item) - template.set('attributes', attributes) - template.save() - -# patch old style -def migrate_item_variants(): - for item in frappe.get_all("Item", filters={"has_variants": 1}): - all_variants = frappe.get_all("Item", filters={"variant_of": item.name}, fields=["name", "description"]) - item_attributes = frappe.db.sql("""select distinct item_attribute, item_attribute_value - from `tabItem Variant` where parent=%s""", item.name) - - if not item_attributes and not all_variants: - item = frappe.get_doc("Item", item.name) - item.has_variants = 0 - item.save() - continue - - attribute_value_options = {} - for attribute, value in item_attributes: - attribute_value_options.setdefault(attribute, []).append(value) - - possible_combinations = get_possible_combinations(attribute_value_options) - - for variant in all_variants: - for combination in possible_combinations: - match = True - for attribute, value in combination.items(): - if "{0}: {1}".format(attribute, value) not in variant.description: - match = False - break - - if match: - # found the right variant - save_attributes_in_variant(variant, combination) - break - - save_attributes_in_template(item, attribute_value_options) - - frappe.delete_doc("DocType", "Item Variant") - -def save_attributes_in_template(item, attribute_value_options): - # store attribute in Item Variant Attribute table for template - template = frappe.get_doc("Item", item) - template.set("attributes", [{"attribute": attribute} for attribute in attribute_value_options.keys()]) - template.save() - -def get_possible_combinations(attribute_value_options): - possible_combinations = [] - - for attribute, values in attribute_value_options.items(): - if not possible_combinations: - for v in values: - possible_combinations.append({attribute: v}) - - else: - for v in values: - for combination in possible_combinations: - combination[attribute] = v - - return possible_combinations - -def save_attributes_in_variant(variant, combination): - # add data into attributes table - variant_item = frappe.get_doc("Item", variant.name) - variant_item.set("attributes", []) - for attribute, value in combination.items(): - variant_item.append("attributes", { - "attribute": attribute, - "attribute_value": value - }) - variant_item.save() diff --git a/erpnext/patches/v5_8/__init__.py b/erpnext/patches/v5_8/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v5_8/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v5_8/add_credit_note_print_heading.py b/erpnext/patches/v5_8/add_credit_note_print_heading.py deleted file mode 100644 index 476cbc8956..0000000000 --- a/erpnext/patches/v5_8/add_credit_note_print_heading.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ - -def execute(): - for print_heading in (_("Credit Note"), _("Debit Note")): - if not frappe.db.exists("Print Heading", print_heading): - frappe.get_doc({ - "doctype": "Print Heading", - "print_heading": print_heading - }).insert() diff --git a/erpnext/patches/v5_8/tax_rule.py b/erpnext/patches/v5_8/tax_rule.py deleted file mode 100644 index 8da28ba4c9..0000000000 --- a/erpnext/patches/v5_8/tax_rule.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. 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", "tax_rule") - - customers = frappe.db.sql("""select name, default_taxes_and_charges from tabCustomer where - ifnull(default_taxes_and_charges, '') != '' """, as_dict=1) - - for d in customers: - if not frappe.db.sql("select name from `tabTax Rule` where customer=%s", d.name): - tr = frappe.new_doc("Tax Rule") - tr.tax_type = "Sales" - tr.customer = d.name - tr.sales_tax_template = d.default_taxes_and_charges - tr.save() - - - suppliers = frappe.db.sql("""select name, default_taxes_and_charges from tabSupplier where - ifnull(default_taxes_and_charges, '') != '' """, as_dict=1) - - for d in suppliers: - if not frappe.db.sql("select name from `tabTax Rule` where supplier=%s", d.name): - tr = frappe.new_doc("Tax Rule") - tr.tax_type = "Purchase" - tr.supplier = d.name - tr.purchase_tax_template = d.default_taxes_and_charges - tr.save() \ No newline at end of file diff --git a/erpnext/patches/v5_8/update_order_reference_in_return_entries.py b/erpnext/patches/v5_8/update_order_reference_in_return_entries.py deleted file mode 100644 index 503263834c..0000000000 --- a/erpnext/patches/v5_8/update_order_reference_in_return_entries.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Delivery Note") - frappe.reload_doctype("Sales Invoice") - frappe.reload_doctype("Purchase Receipt") - frappe.reload_doctype("Sales Order Item") - frappe.reload_doctype("Purchase Order Item") - frappe.reload_doctype("Purchase Order Item Supplied") - - # sales return - return_entries = list(frappe.db.sql(""" - select dn.name as name, dn_item.name as row_id, dn.return_against, - dn_item.item_code, "Delivery Note" as doctype - from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn - where dn_item.parent=dn.name and dn.is_return=1 and dn.docstatus < 2 - """, as_dict=1)) - - return_entries += list(frappe.db.sql(""" - select si.name as name, si_item.name as row_id, si.return_against, - si_item.item_code, "Sales Invoice" as doctype, update_stock - from `tabSales Invoice Item` si_item, `tabSales Invoice` si - where si_item.parent=si.name and si.is_return=1 and si.docstatus < 2 - """, as_dict=1)) - - for d in return_entries: - ref_field = "against_sales_order" if d.doctype == "Delivery Note" else "sales_order" - order_details = frappe.db.sql(""" - select {ref_field} as sales_order, so_detail, - (select transaction_date from `tabSales Order` where name=item.{ref_field}) as sales_order_date - from `tab{doctype} Item` item - where - parent=%s - and item_code=%s - and ifnull(so_detail, '') !='' - order by sales_order_date DESC limit 1 - """.format(ref_field=ref_field, doctype=d.doctype), (d.return_against, d.item_code), as_dict=1) - - if order_details: - frappe.db.sql(""" - update `tab{doctype} Item` - set {ref_field}=%s, so_detail=%s - where name=%s - """.format(doctype=d.doctype, ref_field=ref_field), - (order_details[0].sales_order, order_details[0].so_detail, d.row_id)) - - if (d.doctype=="Sales Invoice" and d.update_stock) or d.doctype=="Delivery Note": - doc = frappe.get_doc(d.doctype, d.name) - doc.update_reserved_qty() - - if d.doctype=="Sales Invoice": - doc.status_updater = [] - doc.update_status_updater_args() - - doc.update_prevdoc_status() - - #-------------------------- - # purchase return - return_entries = frappe.db.sql(""" - select pr.name as name, pr_item.name as row_id, pr.return_against, pr_item.item_code - from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr - where pr_item.parent=pr.name and pr.is_return=1 and pr.docstatus < 2 - """, as_dict=1) - - for d in return_entries: - order_details = frappe.db.sql(""" - select prevdoc_docname as purchase_order, prevdoc_detail_docname as po_detail, - (select transaction_date from `tabPurchase Order` where name=item.prevdoc_detail_docname) as purchase_order_date - from `tabPurchase Receipt Item` item - where - parent=%s - and item_code=%s - and ifnull(prevdoc_detail_docname, '') !='' - and ifnull(prevdoc_doctype, '') = 'Purchase Order' and ifnull(prevdoc_detail_docname, '') != '' - order by purchase_order_date DESC limit 1 - """, (d.return_against, d.item_code), as_dict=1) - - if order_details: - frappe.db.sql(""" - update `tabPurchase Receipt Item` - set prevdoc_doctype='Purchase Order', prevdoc_docname=%s, prevdoc_detail_docname=%s - where name=%s - """, (order_details[0].purchase_order, order_details[0].po_detail, d.row_id)) - - pr = frappe.get_doc("Purchase Receipt", d.name) - pr.update_ordered_and_reserved_qty() - pr.update_prevdoc_status() - diff --git a/erpnext/patches/v6_0/__init__.py b/erpnext/patches/v6_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v6_0/default_activity_rate.py b/erpnext/patches/v6_0/default_activity_rate.py deleted file mode 100644 index cfbfb723bc..0000000000 --- a/erpnext/patches/v6_0/default_activity_rate.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("projects", "doctype", "activity_cost") - - for cost in frappe.db.get_list("Activity Cost", filters = {"employee": ""}, - fields = ("name", "activity_type", "costing_rate", "billing_rate")): - activity_type = frappe.get_doc("Activity Type", cost.activity_type) - activity_type.costing_rate = cost.costing_rate - activity_type.billing_rate = cost.billing_rate - activity_type.save() - - frappe.delete_doc("Activity Cost", cost.name) diff --git a/erpnext/patches/v6_0/fix_outstanding_amount.py b/erpnext/patches/v6_0/fix_outstanding_amount.py deleted file mode 100644 index 0de689074f..0000000000 --- a/erpnext/patches/v6_0/fix_outstanding_amount.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt - -def execute(): - for dt, party_field, account_field in (("Sales Invoice", "customer", "debit_to"), - ("Purchase Invoice", "supplier", "credit_to")): - - wrong_invoices = frappe.db.sql("""select name, {0} as account from `tab{1}` - where docstatus=1 and ifnull({2}, '')=''""".format(account_field, dt, party_field)) - - for invoice, account in wrong_invoices: - update_outstanding_amt(account, party_field.title(), None, dt, invoice) \ No newline at end of file diff --git a/erpnext/patches/v6_0/fix_planned_qty.py b/erpnext/patches/v6_0/fix_planned_qty.py deleted file mode 100644 index cf7b429249..0000000000 --- a/erpnext/patches/v6_0/fix_planned_qty.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty - -def execute(): - for item_code, warehouse in frappe.db.sql("""select distinct production_item, fg_warehouse - from `tabWork Order`"""): - if frappe.db.exists("Item", item_code) and frappe.db.exists("Warehouse", warehouse): - update_bin_qty(item_code, warehouse, { - "planned_qty": get_planned_qty(item_code, warehouse) - }) diff --git a/erpnext/patches/v6_0/multi_currency.py b/erpnext/patches/v6_0/multi_currency.py deleted file mode 100644 index b4c37fc253..0000000000 --- a/erpnext/patches/v6_0/multi_currency.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - # Reload doctype - for dt in ("Account", "GL Entry", "Journal Entry", - "Journal Entry Account", "Sales Invoice", "Purchase Invoice", "Customer", "Supplier"): - frappe.reload_doctype(dt) - - company_list = frappe.get_all("Company", fields=["name", "default_currency", "default_receivable_account"]) - for company in company_list: - - # update currency in account and gl entry as per company currency - frappe.db.sql("""update `tabAccount` set account_currency = %s - where ifnull(account_currency, '') = '' and company=%s""", (company.default_currency, company.name)) - - # update newly introduced field's value in sales / purchase invoice - frappe.db.sql(""" - update - `tabSales Invoice` - set - base_paid_amount=paid_amount, - base_write_off_amount=write_off_amount, - party_account_currency=%s - where company=%s - """, (company.default_currency, company.name)) - - frappe.db.sql(""" - update - `tabPurchase Invoice` - set - base_write_off_amount=write_off_amount, - party_account_currency=%s - where company=%s - """, (company.default_currency, company.name)) - - # update exchange rate, debit/credit in account currency in Journal Entry - frappe.db.sql(""" - update `tabJournal Entry Account` jea - set exchange_rate=1, - debit_in_account_currency=debit, - credit_in_account_currency=credit, - account_type=(select account_type from `tabAccount` where name=jea.account) - """) - - frappe.db.sql(""" - update `tabJournal Entry Account` jea, `tabJournal Entry` je - set account_currency=%s - where jea.parent = je.name and je.company=%s - """, (company.default_currency, company.name)) - - # update debit/credit in account currency in GL Entry - frappe.db.sql(""" - update - `tabGL Entry` - set - debit_in_account_currency=debit, - credit_in_account_currency=credit, - account_currency=%s - where - company=%s - """, (company.default_currency, company.name)) diff --git a/erpnext/patches/v6_0/set_default_title.py b/erpnext/patches/v6_0/set_default_title.py deleted file mode 100644 index cceff3f480..0000000000 --- a/erpnext/patches/v6_0/set_default_title.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Quotation") - frappe.db.sql("""update tabQuotation set title = customer_name""") - - frappe.reload_doctype("Sales Order") - frappe.db.sql("""update `tabSales Order` set title = customer_name""") - - frappe.reload_doctype("Delivery Note") - frappe.db.sql("""update `tabDelivery Note` set title = customer_name""") - - frappe.reload_doctype("Material Request") - frappe.db.sql("""update `tabMaterial Request` set title = material_request_type""") - - frappe.reload_doctype("Supplier Quotation") - frappe.db.sql("""update `tabSupplier Quotation` set title = supplier_name""") - - frappe.reload_doctype("Purchase Order") - frappe.db.sql("""update `tabPurchase Order` set title = supplier_name""") - - frappe.reload_doctype("Purchase Receipt") - frappe.db.sql("""update `tabPurchase Receipt` set title = supplier_name""") - - frappe.reload_doctype("Purchase Invoice") - frappe.db.sql("""update `tabPurchase Invoice` set title = supplier_name""") - - frappe.reload_doctype("Stock Entry") - frappe.db.sql("""update `tabStock Entry` set title = purpose""") - - frappe.reload_doctype("Sales Invoice") - frappe.db.sql("""update `tabSales Invoice` set title = customer_name""") - - frappe.reload_doctype("Expense Claim") - frappe.db.sql("""update `tabExpense Claim` set title = employee_name""") diff --git a/erpnext/patches/v6_10/__init__.py b/erpnext/patches/v6_10/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_10/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_10/email_digest_default_quote.py b/erpnext/patches/v6_10/email_digest_default_quote.py deleted file mode 100644 index 6139f1a88a..0000000000 --- a/erpnext/patches/v6_10/email_digest_default_quote.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Email Digest") - frappe.db.sql("update `tabEmail Digest` set add_quote = 1") diff --git a/erpnext/patches/v6_10/fix_billed_amount_in_drop_ship_po.py b/erpnext/patches/v6_10/fix_billed_amount_in_drop_ship_po.py deleted file mode 100644 index d7f72b5880..0000000000 --- a/erpnext/patches/v6_10/fix_billed_amount_in_drop_ship_po.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""update `tabPurchase Order Item` set billed_amt = 0 - where delivered_by_supplier=1 and docstatus=1""") - - drop_ship_pos = frappe.db.sql("""select distinct parent from `tabPurchase Order Item` - where delivered_by_supplier=1 and docstatus=1""") - - for po in drop_ship_pos: - invoices = frappe.db.sql("""select distinct parent from `tabPurchase Invoice Item` - where purchase_order=%s and docstatus=1""", po[0]) - if invoices: - for inv in invoices: - frappe.get_doc("Purchase Invoice", inv[0]).update_qty(update_modified=False) - else: - frappe.db.sql("""update `tabPurchase Order` set per_billed=0 where name=%s""", po[0]) \ No newline at end of file diff --git a/erpnext/patches/v6_10/fix_delivery_status_of_drop_ship_item.py b/erpnext/patches/v6_10/fix_delivery_status_of_drop_ship_item.py deleted file mode 100644 index 9a53b7f5d3..0000000000 --- a/erpnext/patches/v6_10/fix_delivery_status_of_drop_ship_item.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Sales Order Item") - for so_name in frappe.db.sql("""select distinct parent from `tabSales Order Item` - where delivered_by_supplier=1 and docstatus=1"""): - so = frappe.get_doc("Sales Order", so_name[0]) - so.update_delivery_status() - so.set_status(update=True, update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v6_10/fix_jv_total_amount.py b/erpnext/patches/v6_10/fix_jv_total_amount.py deleted file mode 100644 index 42cb9e9e15..0000000000 --- a/erpnext/patches/v6_10/fix_jv_total_amount.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -# patch all for-print field (total amount) in Journal Entry in 2015 -def execute(): - for je in frappe.get_all("Journal Entry", filters={"creation": (">", "2015-01-01")}): - je = frappe.get_doc("Journal Entry", je.name) - original = je.total_amount - - je.set_print_format_fields() - - if je.total_amount != original: - je.db_set("total_amount", je.total_amount, update_modified=False) - je.db_set("total_amount_in_words", je.total_amount_in_words, update_modified=False) diff --git a/erpnext/patches/v6_10/fix_ordered_received_billed.py b/erpnext/patches/v6_10/fix_ordered_received_billed.py deleted file mode 100644 index c81a20ec54..0000000000 --- a/erpnext/patches/v6_10/fix_ordered_received_billed.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - not_null_patch_date = frappe.db.sql("""select date(creation) from `tabPatch Log` where patch='frappe.patches.v6_9.int_float_not_null'""") - if not not_null_patch_date: - return - - not_null_patch_date = not_null_patch_date[0][0] - - for doctype in ("Purchase Invoice", "Sales Invoice", "Purchase Order", "Delivery Note", "Installation Note", "Delivery Note", "Purchase Receipt"): - for name in frappe.db.sql_list("""select name from `tab{doctype}` - where docstatus > 0 and (date(creation) >= %(patch_date)s or date(modified) >= %(patch_date)s)""".format(doctype=doctype), - {"patch_date": not_null_patch_date}): - - doc = frappe.get_doc(doctype, name) - doc.update_qty(update_modified=False) diff --git a/erpnext/patches/v6_12/__init__.py b/erpnext/patches/v6_12/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py b/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py deleted file mode 100644 index fb5eab4e05..0000000000 --- a/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -""" -This patch is written to fix Stock Ledger Entries and GL Entries -against Delivery Notes and Sales Invoice where Target Warehouse has been set wrongly -due to User Permissions on Warehouse. - -This cannot be run automatically because we can't take a call that -Target Warehouse has been set purposefully or by mistake. -Thats why we left it to the users to take the call, and manually run the patch. - -This patch has 2 main functions, `check()` and `repost()`. -- Run `check` function, to list out all the Sales Orders, Delivery Notes - and Sales Invoice with Target Warehouse. -- Run `repost` function to remove the Target Warehouse value and repost SLE and GLE again. - -To execute this patch run following commands from frappe-bench directory: -``` - bench --site [your-site-name] execute erpnext.patches.v6_12.repost_entries_with_target_warehouse.check - bench --site [your-site-name] backup - bench --site [your-site-name] execute erpnext.patches.v6_12.repost_entries_with_target_warehouse.repost -``` - -Exception Handling: -While reposting, if you get any exception, it will printed on screen. -Mostly it can be due to negative stock issue. If that is the case, follow these steps - - Ensure that stock is available for those items in the mentioned warehouse on the date mentioned in the error - - Execute `repost` funciton again -""" - -def check(): - so_list = get_affected_sales_order() - dn_list = get_affected_delivery_notes() - si_list = get_affected_sales_invoice() - - if so_list or dn_list or si_list: - print("Entries with Target Warehouse:") - - if so_list: - print("Sales Order") - print(so_list) - - if dn_list: - print("Delivery Notes") - print([d.name for d in dn_list]) - - if si_list: - print("Sales Invoice") - print([d.name for d in si_list]) - - -def repost(): - dn_failed_list, si_failed_list = [], [] - repost_dn(dn_failed_list) - repost_si(si_failed_list) - repost_so() - frappe.db.commit() - - if dn_failed_list: - print("-"*40) - print("Delivery Note Failed to Repost") - print(dn_failed_list) - - if si_failed_list: - print("-"*40) - print("Sales Invoice Failed to Repost") - print(si_failed_list) - print() - - print(""" -If above Delivery Notes / Sales Invoice failed due to negative stock, follow these steps: - - Ensure that stock is available for those items in the mentioned warehouse on the date mentioned in the error - - Run this patch again -""") - -def repost_dn(dn_failed_list): - dn_list = get_affected_delivery_notes() - - if dn_list: - print("-"*40) - print("Reposting Delivery Notes") - - for dn in dn_list: - if dn.docstatus == 0: - continue - - print(dn.name) - - try: - dn_doc = frappe.get_doc("Delivery Note", dn.name) - dn_doc.docstatus = 2 - dn_doc.update_prevdoc_status() - dn_doc.update_stock_ledger() - dn_doc.cancel_packing_slips() - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type='Delivery Note' and voucher_no=%s""", dn.name) - - frappe.db.sql("update `tabDelivery Note Item` set target_warehouse='' where parent=%s", dn.name) - dn_doc = frappe.get_doc("Delivery Note", dn.name) - dn_doc.docstatus = 1 - dn_doc.on_submit() - frappe.db.commit() - except Exception: - dn_failed_list.append(dn.name) - frappe.local.stockledger_exceptions = None - print(frappe.get_traceback()) - frappe.db.rollback() - - frappe.db.sql("update `tabDelivery Note Item` set target_warehouse='' where docstatus=0") - -def repost_si(si_failed_list): - si_list = get_affected_sales_invoice() - - if si_list: - print("-"*40) - print("Reposting Sales Invoice") - - for si in si_list: - if si.docstatus == 0: - continue - - print(si.name) - - try: - si_doc = frappe.get_doc("Sales Invoice", si.name) - si_doc.docstatus = 2 - si_doc.update_stock_ledger() - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - - frappe.db.sql("update `tabSales Invoice Item` set target_warehouse='' where parent=%s", si.name) - si_doc = frappe.get_doc("Sales Invoice", si.name) - si_doc.docstatus = 1 - si_doc.update_stock_ledger() - si_doc.make_gl_entries() - frappe.db.commit() - except Exception: - si_failed_list.append(si.name) - frappe.local.stockledger_exceptions = None - print(frappe.get_traceback()) - frappe.db.rollback() - - frappe.db.sql("update `tabSales Invoice Item` set target_warehouse='' where docstatus=0") - -def repost_so(): - so_list = get_affected_sales_order() - - frappe.db.sql("update `tabSales Order Item` set target_warehouse=''") - - if so_list: - print("-"*40) - print("Sales Order reposted") - - -def get_affected_delivery_notes(): - return frappe.db.sql("""select distinct dn.name, dn.docstatus - from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item - where dn.name=dn_item.parent and dn.docstatus < 2 - and dn_item.target_warehouse is not null and dn_item.target_warehouse != '' - order by dn.posting_date asc""", as_dict=1) - -def get_affected_sales_invoice(): - return frappe.db.sql("""select distinct si.name, si.docstatus - from `tabSales Invoice` si, `tabSales Invoice Item` si_item - where si.name=si_item.parent and si.docstatus < 2 and si.update_stock=1 - and si_item.target_warehouse is not null and si_item.target_warehouse != '' - order by si.posting_date asc""", as_dict=1) - -def get_affected_sales_order(): - return frappe.db.sql_list("""select distinct parent from `tabSales Order Item` - where target_warehouse is not null and target_warehouse != '' and docstatus <2""") \ No newline at end of file diff --git a/erpnext/patches/v6_12/set_overdue_tasks.py b/erpnext/patches/v6_12/set_overdue_tasks.py deleted file mode 100644 index 7dbb8ba8b6..0000000000 --- a/erpnext/patches/v6_12/set_overdue_tasks.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Task") - - from erpnext.projects.doctype.task.task import set_tasks_as_overdue - set_tasks_as_overdue() diff --git a/erpnext/patches/v6_16/__init__.py b/erpnext/patches/v6_16/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_16/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_16/create_manufacturer_records.py b/erpnext/patches/v6_16/create_manufacturer_records.py deleted file mode 100644 index 5ae65f0660..0000000000 --- a/erpnext/patches/v6_16/create_manufacturer_records.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cstr - -def execute(): - frappe.reload_doc("stock", "doctype", "manufacturer") - frappe.reload_doctype("Item") - - for d in frappe.db.sql("""select distinct manufacturer from tabItem - where ifnull(manufacturer, '') != '' and disabled=0"""): - manufacturer_name = cstr(d[0]).strip() - if manufacturer_name and not frappe.db.exists("Manufacturer", manufacturer_name): - man = frappe.new_doc("Manufacturer") - man.short_name = manufacturer_name - man.full_name = manufacturer_name - man.save() diff --git a/erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py b/erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py deleted file mode 100644 index 481f13005b..0000000000 --- a/erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for dt in ("Delivery Note", "Purchase Receipt"): - frappe.reload_doctype(dt) - frappe.reload_doctype(dt + " Item") - - # Update billed_amt in DN and PR which are not against any order - for d in frappe.db.sql("""select name from `tabDelivery Note Item` item - where (so_detail is null or so_detail = '') and docstatus=1""", as_dict=1): - - billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item` - where dn_detail=%s and docstatus=1""", d.name) - billed_amt = billed_amt and billed_amt[0][0] or 0 - frappe.db.set_value("Delivery Note Item", d.name, "billed_amt", billed_amt, update_modified=False) - - frappe.db.commit() - - # Update billed_amt in DN and PR which are not against any order - for d in frappe.db.sql("""select name from `tabPurchase Receipt Item` item - where (purchase_order_item is null or purchase_order_item = '') and docstatus=1""", as_dict=1): - - billed_amt = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item` - where pr_detail=%s and docstatus=1""", d.name) - billed_amt = billed_amt and billed_amt[0][0] or 0 - frappe.db.set_value("Purchase Receipt Item", d.name, "billed_amt", billed_amt, update_modified=False) - - frappe.db.commit() - - for dt in ("Delivery Note", "Purchase Receipt"): - # Update billed amt which are against order or invoice - # Update billing status for all - for d in frappe.db.sql("select name from `tab{0}` where docstatus=1".format(dt), as_dict=1): - doc = frappe.get_doc(dt, d.name) - doc.update_billing_status(update_modified=False) - doc.set_status(update=True, update_modified=False) - - frappe.db.commit() diff --git a/erpnext/patches/v6_19/__init__.py b/erpnext/patches/v6_19/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_19/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_19/comment_feed_communication.py b/erpnext/patches/v6_19/comment_feed_communication.py deleted file mode 100644 index bc41c2d8ff..0000000000 --- a/erpnext/patches/v6_19/comment_feed_communication.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.patches.v6_19.comment_feed_communication import update_timeline_doc_for - -def execute(): - for doctype in ("Customer", "Supplier", "Employee", "Project"): - update_timeline_doc_for(doctype) diff --git a/erpnext/patches/v6_2/__init__.py b/erpnext/patches/v6_2/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_2/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_2/fix_missing_default_taxes_and_lead.py b/erpnext/patches/v6_2/fix_missing_default_taxes_and_lead.py deleted file mode 100644 index b0cfc3d3bf..0000000000 --- a/erpnext/patches/v6_2/fix_missing_default_taxes_and_lead.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - # remove missing lead - for customer in frappe.db.sql_list("""select name from `tabCustomer` - where ifnull(lead_name, '')!='' and not exists (select name from `tabLead` where name=`tabCustomer`.lead_name)"""): - frappe.db.set_value("Customer", customer, "lead_name", None) - - # remove missing default taxes - for customer in frappe.db.sql_list("""select name from `tabCustomer` - where ifnull(default_taxes_and_charges, '')!='' and not exists ( - select name from `tabSales Taxes and Charges Template` where name=`tabCustomer`.default_taxes_and_charges - )"""): - c = frappe.get_doc("Customer", customer) - c.default_taxes_and_charges = None - c.save() - - for supplier in frappe.db.sql_list("""select name from `tabSupplier` - where ifnull(default_taxes_and_charges, '')!='' and not exists ( - select name from `tabPurchase Taxes and Charges Template` where name=`tabSupplier`.default_taxes_and_charges - )"""): - c = frappe.get_doc("Supplier", supplier) - c.default_taxes_and_charges = None - c.save() diff --git a/erpnext/patches/v6_2/remove_newsletter_duplicates.py b/erpnext/patches/v6_2/remove_newsletter_duplicates.py deleted file mode 100644 index f9d15475d1..0000000000 --- a/erpnext/patches/v6_2/remove_newsletter_duplicates.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - duplicates = frappe.db.sql("""select email_group, email, count(name) - from `tabEmail Group Member` - group by email_group, email - having count(name) > 1""") - - # delete all duplicates except 1 - for email_group, email, count in duplicates: - frappe.db.sql("""delete from `tabEmail Group Member` - where email_group=%s and email=%s limit %s""", (email_group, email, count-1)) diff --git a/erpnext/patches/v6_20/__init__.py b/erpnext/patches/v6_20/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v6_20/set_party_account_currency_in_orders.py b/erpnext/patches/v6_20/set_party_account_currency_in_orders.py deleted file mode 100644 index ae7ad9592d..0000000000 --- a/erpnext/patches/v6_20/set_party_account_currency_in_orders.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype in ("Sales Order", "Purchase Order"): - frappe.reload_doctype(doctype) - - for order in frappe.db.sql("""select name, {0} as party from `tab{1}` - where advance_paid > 0 and docstatus=1""" - .format(("customer" if doctype=="Sales Order" else "supplier"), doctype), as_dict=1): - - party_account_currency = frappe.db.get_value("Journal Entry Account", { - "reference_type": doctype, - "reference_name": order.name, - "party": order.party, - "docstatus": 1, - "is_advance": "Yes" - }, "account_currency") - - frappe.db.set_value(doctype, order.name, "party_account_currency", party_account_currency) - \ No newline at end of file diff --git a/erpnext/patches/v6_20x/__init__.py b/erpnext/patches/v6_20x/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_20x/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_20x/remove_customer_supplier_roles.py b/erpnext/patches/v6_20x/remove_customer_supplier_roles.py deleted file mode 100644 index a651576887..0000000000 --- a/erpnext/patches/v6_20x/remove_customer_supplier_roles.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("buying", "doctype", "request_for_quotation_supplier") - frappe.reload_doc("buying", "doctype", "request_for_quotation_item") - frappe.reload_doc("buying", "doctype", "request_for_quotation") - frappe.reload_doc("projects", "doctype", "timesheet") - - for role in ('Customer', 'Supplier'): - frappe.db.sql('''delete from `tabHas Role` - where role=%s and parent in ("Administrator", "Guest")''', role) - - if not frappe.db.sql('select name from `tabHas Role` where role=%s', role): - - # delete DocPerm - for doctype in frappe.db.sql('select parent from tabDocPerm where role=%s', role): - d = frappe.get_doc("DocType", doctype[0]) - d.permissions = [p for p in d.permissions if p.role != role] - d.save() - - # delete Role - frappe.delete_doc_if_exists('Role', role) diff --git a/erpnext/patches/v6_20x/remove_fiscal_year_from_holiday_list.py b/erpnext/patches/v6_20x/remove_fiscal_year_from_holiday_list.py deleted file mode 100644 index d440c6859e..0000000000 --- a/erpnext/patches/v6_20x/remove_fiscal_year_from_holiday_list.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Holiday List") - - default_holiday_list = frappe.db.get_value("Holiday List", {"is_default": 1}) - if default_holiday_list: - for company in frappe.get_all("Company", fields=["name", "default_holiday_list"]): - if not company.default_holiday_list: - frappe.db.set_value("Company", company.name, "default_holiday_list", default_holiday_list) - - - fiscal_years = frappe._dict((fy.name, fy) for fy in frappe.get_all("Fiscal Year", fields=["name", "year_start_date", "year_end_date"])) - - for holiday_list in frappe.get_all("Holiday List", fields=["name", "fiscal_year"]): - fy = fiscal_years[holiday_list.fiscal_year] - frappe.db.set_value("Holiday List", holiday_list.name, "from_date", fy.year_start_date) - frappe.db.set_value("Holiday List", holiday_list.name, "to_date", fy.year_end_date) diff --git a/erpnext/patches/v6_20x/rename_project_name_to_project.py b/erpnext/patches/v6_20x/rename_project_name_to_project.py deleted file mode 100644 index 49ec9d22bc..0000000000 --- a/erpnext/patches/v6_20x/rename_project_name_to_project.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - - doc_list = ["Work Order", "BOM", "Purchase Invoice Item", "Sales Invoice", - "Purchase Order Item", "Stock Entry", "Delivery Note", "Sales Order", - "Purchase Receipt Item", "Supplier Quotation Item"] - - for doctype in doc_list: - frappe.reload_doctype(doctype) - rename_field(doctype, "project_name", "project") - \ No newline at end of file diff --git a/erpnext/patches/v6_20x/repost_valuation_rate_for_negative_inventory.py b/erpnext/patches/v6_20x/repost_valuation_rate_for_negative_inventory.py deleted file mode 100644 index 8369fea317..0000000000 --- a/erpnext/patches/v6_20x/repost_valuation_rate_for_negative_inventory.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cint -from erpnext.stock.stock_balance import repost - -def execute(): - if cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")): - repost(only_actual=True) \ No newline at end of file diff --git a/erpnext/patches/v6_20x/set_compact_print.py b/erpnext/patches/v6_20x/set_compact_print.py deleted file mode 100644 index 495407f0e0..0000000000 --- a/erpnext/patches/v6_20x/set_compact_print.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from erpnext.setup.install import create_compact_item_print_custom_field - -def execute(): - create_compact_item_print_custom_field() - frappe.db.set_value("Print Settings", None, "compact_item_print", 1) diff --git a/erpnext/patches/v6_20x/update_product_bundle_description.py b/erpnext/patches/v6_20x/update_product_bundle_description.py deleted file mode 100644 index 1fac44b001..0000000000 --- a/erpnext/patches/v6_20x/update_product_bundle_description.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import sanitize_html - -def execute(): - for product_bundle in frappe.get_all('Product Bundle'): - doc = frappe.get_doc('Product Bundle', product_bundle.name) - for item in doc.items: - if item.description: - description = sanitize_html(item.description) - item.db_set('description', description, update_modified=False) diff --git a/erpnext/patches/v6_21/__init__.py b/erpnext/patches/v6_21/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_21/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_21/fix_reorder_level.py b/erpnext/patches/v6_21/fix_reorder_level.py deleted file mode 100644 index 82a35ebab1..0000000000 --- a/erpnext/patches/v6_21/fix_reorder_level.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from erpnext.stock.doctype.item.item import DuplicateReorderRows - -def execute(): - if frappe.db.has_column("Item", "re_order_level"): - for item in frappe.db.sql("""select name, default_warehouse, re_order_level, re_order_qty - from tabItem - where ifnull(re_order_level, 0) != 0 - and ifnull(re_order_qty, 0) != 0""", as_dict=1): - - item_doc = frappe.get_doc("Item", item.name) - item_doc.append("reorder_levels", { - "warehouse": item.default_warehouse, - "warehouse_reorder_level": item.re_order_level, - "warehouse_reorder_qty": item.re_order_qty, - "material_request_type": "Purchase" - }) - - try: - item_doc.save() - except DuplicateReorderRows: - pass diff --git a/erpnext/patches/v6_21/rename_material_request_fields.py b/erpnext/patches/v6_21/rename_material_request_fields.py deleted file mode 100644 index 07be27a5d6..0000000000 --- a/erpnext/patches/v6_21/rename_material_request_fields.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - frappe.reload_doc('stock', 'doctype', 'material_request_item') - rename_field("Material Request Item", "sales_order_no", "sales_order") - - frappe.reload_doc('support', 'doctype', 'maintenance_schedule_item') - rename_field("Maintenance Schedule Item", "prevdoc_docname", "sales_order") - \ No newline at end of file diff --git a/erpnext/patches/v6_23/__init__.py b/erpnext/patches/v6_23/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v6_23/update_stopped_status_to_closed.py b/erpnext/patches/v6_23/update_stopped_status_to_closed.py deleted file mode 100644 index 79d1e0ac30..0000000000 --- a/erpnext/patches/v6_23/update_stopped_status_to_closed.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for dt in ("Sales Order", "Purchase Order"): - frappe.db.sql("update `tab{0}` set status='Closed' where status='Stopped'".format(dt)) \ No newline at end of file diff --git a/erpnext/patches/v6_24/__init__.py b/erpnext/patches/v6_24/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v6_24/map_customer_address_to_shipping_address_on_po.py b/erpnext/patches/v6_24/map_customer_address_to_shipping_address_on_po.py deleted file mode 100644 index 1dd8083c7c..0000000000 --- a/erpnext/patches/v6_24/map_customer_address_to_shipping_address_on_po.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Purchase Order") - - if not frappe.db.has_column("Purchase Order", "shipping_address"): - return - - if not frappe.db.has_column("Purchase Order", "customer_address"): - return - - frappe.db.sql("""update `tabPurchase Order` set shipping_address=customer_address, - shipping_address_display=customer_address_display""") - - frappe.db.commit() \ No newline at end of file diff --git a/erpnext/patches/v6_24/set_recurring_id.py b/erpnext/patches/v6_24/set_recurring_id.py deleted file mode 100644 index 527a2fd3d9..0000000000 --- a/erpnext/patches/v6_24/set_recurring_id.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype in ('Sales Order', 'Purchase Order', 'Sales Invoice', - 'Purchase Invoice'): - frappe.reload_doctype(doctype) - frappe.db.sql('''update `tab{0}` set submit_on_creation=1, notify_by_email=1 - where is_recurring=1'''.format(doctype)) - frappe.db.sql('''update `tab{0}` set notify_by_email=1 - where is_recurring=1'''.format(doctype)) - frappe.db.sql('''update `tab{0}` set recurring_id = name - where is_recurring=1 and ifnull(recurring_id, '') = "" '''.format(doctype)) diff --git a/erpnext/patches/v6_27/__init__.py b/erpnext/patches/v6_27/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_27/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_27/fix_recurring_order_status.py b/erpnext/patches/v6_27/fix_recurring_order_status.py deleted file mode 100644 index 5843c9fbe5..0000000000 --- a/erpnext/patches/v6_27/fix_recurring_order_status.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for doc in ( - { - "doctype": "Sales Order", - "stock_doctype": "Delivery Note", - "invoice_doctype": "Sales Invoice", - "stock_doctype_ref_field": "against_sales_order", - "invoice_ref_field": "sales_order", - "qty_field": "delivered_qty" - }, - { - "doctype": "Purchase Order", - "stock_doctype": "Purchase Receipt", - "invoice_doctype": "Purchase Invoice", - "stock_doctype_ref_field": "prevdoc_docname", - "invoice_ref_field": "purchase_order", - "qty_field": "received_qty" - }): - - order_list = frappe.db.sql("""select name from `tab{0}` - where docstatus=1 and is_recurring=1 - and ifnull(recurring_id, '') != name and creation >= '2016-01-25'""" - .format(doc["doctype"]), as_dict=1) - - for order in order_list: - frappe.db.sql("""update `tab{0} Item` - set {1}=0, billed_amt=0 where parent=%s""".format(doc["doctype"], - doc["qty_field"]), order.name) - - # Check against Delivery Note and Purchase Receipt - stock_doc_list = frappe.db.sql("""select distinct parent from `tab{0} Item` - where docstatus=1 and ifnull({1}, '')=%s""" - .format(doc["stock_doctype"], doc["stock_doctype_ref_field"]), order.name) - - if stock_doc_list: - for dn in stock_doc_list: - frappe.get_doc(doc["stock_doctype"], dn[0]).update_qty(update_modified=False) - - # Check against Invoice - invoice_list = frappe.db.sql("""select distinct parent from `tab{0} Item` - where docstatus=1 and ifnull({1}, '')=%s""" - .format(doc["invoice_doctype"], doc["invoice_ref_field"]), order.name) - - if invoice_list: - for dn in invoice_list: - frappe.get_doc(doc["invoice_doctype"], dn[0]).update_qty(update_modified=False) - - frappe.get_doc(doc["doctype"], order.name).set_status(update=True, update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v6_3/__init__.py b/erpnext/patches/v6_3/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v6_3/convert_applicable_territory.py b/erpnext/patches/v6_3/convert_applicable_territory.py deleted file mode 100644 index 231a483ea2..0000000000 --- a/erpnext/patches/v6_3/convert_applicable_territory.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("stock", "doctype", "price_list_country") - frappe.reload_doc("accounts", "doctype", "shipping_rule_country") - frappe.reload_doctype("Price List") - frappe.reload_doctype("Shipping Rule") - frappe.reload_doctype("shopping_cart", "doctype", "shopping_cart_settings") - - # for price list - countries = frappe.db.sql_list("select name from tabCountry") - - for doctype in ("Price List", "Shipping Rule"): - for at in frappe.db.sql("""select name, parent, territory from `tabApplicable Territory` where - parenttype = %s """, doctype, as_dict=True): - if at.territory in countries: - parent = frappe.get_doc(doctype, at.parent) - if not parent.countries: - parent.append("countries", {"country": at.territory}) - parent.save() - - - frappe.delete_doc("DocType", "Applicable Territory") diff --git a/erpnext/patches/v6_4/__init__.py b/erpnext/patches/v6_4/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_4/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_4/email_digest_update.py b/erpnext/patches/v6_4/email_digest_update.py deleted file mode 100644 index 8342b7fce6..0000000000 --- a/erpnext/patches/v6_4/email_digest_update.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Email Digest") - frappe.db.sql("""update `tabEmail Digest` set expense_year_to_date = - income_year_to_date""") - - if frappe.db.exists("Email Digest", "Scheduler Errors"): - frappe.delete_doc("Email Digest", "Scheduler Errors") diff --git a/erpnext/patches/v6_4/fix_duplicate_bins.py b/erpnext/patches/v6_4/fix_duplicate_bins.py deleted file mode 100644 index 77d05273e8..0000000000 --- a/erpnext/patches/v6_4/fix_duplicate_bins.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.stock.stock_balance import repost_stock - -def execute(): - bins = frappe.db.sql("""select item_code, warehouse, count(*) from `tabBin` - group by item_code, warehouse having count(*) > 1""", as_dict=True) - - for d in bins: - try: - frappe.db.sql("delete from tabBin where item_code=%s and warehouse=%s", (d.item_code, d.warehouse)) - - repost_stock(d.item_code, d.warehouse, allow_zero_rate=True, only_actual=False, only_bin=True) - - frappe.db.commit() - except: - frappe.db.rollback() \ No newline at end of file diff --git a/erpnext/patches/v6_4/fix_expense_included_in_valuation.py b/erpnext/patches/v6_4/fix_expense_included_in_valuation.py deleted file mode 100644 index 7ed15ab010..0000000000 --- a/erpnext/patches/v6_4/fix_expense_included_in_valuation.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe -from frappe.utils import cstr - -def execute(): - for company in frappe.db.sql("select name, expenses_included_in_valuation from tabCompany", as_dict=1): - frozen_date = get_frozen_date(company.name, company.expenses_included_in_valuation) - - # Purchase Invoices after frozen date - # which are not against Receipt, but valuation related tax is there - pi_list = frappe.db.sql(""" - select distinct pi.name - from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item - where - pi.name = pi_item.parent - and pi.company = %s - and pi.posting_date > %s - and pi.docstatus = 1 - and pi.is_opening = 'No' - and (pi_item.item_tax_amount is not null and pi_item.item_tax_amount > 0) - and (pi_item.purchase_receipt is null or pi_item.purchase_receipt = '') - and (pi_item.item_code is not null and pi_item.item_code != '') - and exists(select name from `tabItem` where name=pi_item.item_code and is_stock_item=1) - """, (company.name, frozen_date), as_dict=1) - - for pi in pi_list: - # Check whether gle exists for Expenses Included in Valuation account against the PI - gle_for_expenses_included_in_valuation = frappe.db.sql("""select name from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no=%s and account=%s""", - (pi.name, company.expenses_included_in_valuation)) - - if gle_for_expenses_included_in_valuation: - print(pi.name) - - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no=%s""", pi.name) - - purchase_invoice = frappe.get_doc("Purchase Invoice", pi.name) - - # some old entries have missing expense accounts - if purchase_invoice.against_expense_account: - expense_account = purchase_invoice.against_expense_account.split(",") - if len(expense_account) == 1: - expense_account = expense_account[0] - for item in purchase_invoice.items: - if not item.expense_account: - item.db_set("expense_account", expense_account, update_modified=False) - - purchase_invoice.make_gl_entries() - -def get_frozen_date(company, account): - # Accounting frozen upto - accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto") - - # Last adjustment entry to correct Expenses Included in Valuation account balance - last_adjustment_entry = frappe.db.sql("""select posting_date from `tabGL Entry` - where account=%s and company=%s and voucher_type = 'Journal Entry' - order by posting_date desc limit 1""", (account, company)) - - last_adjustment_date = cstr(last_adjustment_entry[0][0]) if last_adjustment_entry else None - - # Last period closing voucher - last_closing_entry = frappe.db.sql("""select posting_date from `tabGL Entry` - where company=%s and voucher_type = 'Period Closing Voucher' - order by posting_date desc limit 1""", company) - - last_closing_date = cstr(last_closing_entry[0][0]) if last_closing_entry else None - - frozen_date = max([accounts_frozen_upto, last_adjustment_date, last_closing_date]) - - return frozen_date or '1900-01-01' diff --git a/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py b/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py deleted file mode 100644 index b53412d7eb..0000000000 --- a/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Sales Invoice Advance") - frappe.reload_doctype("Purchase Invoice Advance") - - je_rows = frappe.db.sql(""" - select name, parent, reference_type, reference_name, debit, credit - from `tabJournal Entry Account` - where docstatus=1 and date(modified) >= '2015-09-17' - and ((ifnull(debit_in_account_currency, 0)*exchange_rate != ifnull(debit, 0)) - or (ifnull(credit_in_account_currency, 0)*exchange_rate != ifnull(credit, 0))) - order by parent - """, as_dict=True) - - journal_entries = [] - - for d in je_rows: - if d.parent not in journal_entries: - journal_entries.append(d.parent) - - is_advance_entry=None - if d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name: - is_advance_entry = frappe.db.sql("""select name from `tab{0}` - where reference_name=%s and reference_row=%s - and ifnull(allocated_amount, 0) > 0 and docstatus=1""" - .format(d.reference_type + " Advance"), (d.parent, d.name)) - - if is_advance_entry or not (d.debit or d.credit): - frappe.db.sql(""" - update `tabJournal Entry Account` - set debit=debit_in_account_currency*exchange_rate, - credit=credit_in_account_currency*exchange_rate - where name=%s""", d.name) - else: - frappe.db.sql(""" - update `tabJournal Entry Account` - set debit_in_account_currency=debit/exchange_rate, - credit_in_account_currency=credit/exchange_rate - where name=%s""", d.name) - - for d in journal_entries: - print(d) - # delete existing gle - frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d) - - # repost gl entries - je = frappe.get_doc("Journal Entry", d) - je.make_gl_entries() \ No newline at end of file diff --git a/erpnext/patches/v6_4/fix_modified_in_sales_order_and_purchase_order.py b/erpnext/patches/v6_4/fix_modified_in_sales_order_and_purchase_order.py deleted file mode 100644 index f27489e7b0..0000000000 --- a/erpnext/patches/v6_4/fix_modified_in_sales_order_and_purchase_order.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype in ("Sales Order", "Purchase Order"): - data = frappe.db.sql("""select parent, modified_by, modified - from `tab{doctype} Item` where docstatus=1 group by parent""".format(doctype=doctype), as_dict=True) - for item in data: - frappe.db.sql("""update `tab{doctype}` set modified_by=%(modified_by)s, modified=%(modified)s - where name=%(parent)s""".format(doctype=doctype), item) diff --git a/erpnext/patches/v6_4/fix_sales_order_maintenance_status.py b/erpnext/patches/v6_4/fix_sales_order_maintenance_status.py deleted file mode 100644 index 50aa9e542e..0000000000 --- a/erpnext/patches/v6_4/fix_sales_order_maintenance_status.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for doc in frappe.get_all("Sales Order", filters={"docstatus": 1, - "order_type": "Maintenance"}): - doc = frappe.get_doc("Sales Order", doc.name) - doc.set_status(update=True) diff --git a/erpnext/patches/v6_4/fix_status_in_sales_and_purchase_order.py b/erpnext/patches/v6_4/fix_status_in_sales_and_purchase_order.py deleted file mode 100644 index 746a99004a..0000000000 --- a/erpnext/patches/v6_4/fix_status_in_sales_and_purchase_order.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype in ("Sales Order", "Purchase Order"): - for doc in frappe.get_all(doctype, filters={"docstatus": 1}): - doc = frappe.get_doc(doctype, doc.name) - doc.set_status(update=True) diff --git a/erpnext/patches/v6_4/make_image_thumbnail.py b/erpnext/patches/v6_4/make_image_thumbnail.py deleted file mode 100644 index 2c86e8af86..0000000000 --- a/erpnext/patches/v6_4/make_image_thumbnail.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("File") - frappe.reload_doctype("Item") - for item in frappe.get_all("Item", fields=("name", "website_image", "thumbnail")): - if item.website_image and not item.thumbnail: - item_doc = frappe.get_doc("Item", item.name) - try: - item_doc.make_thumbnail() - if item_doc.thumbnail: - item_doc.db_set("thumbnail", item_doc.thumbnail, update_modified=False) - except Exception: - print("Unable to make thumbnail for {0}".format(item.website_image.encode("utf-8"))) diff --git a/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py b/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py deleted file mode 100644 index 1319b53558..0000000000 --- a/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - je_list = frappe.db.sql_list("""select distinct parent from `tabJournal Entry Account` je - where docstatus=1 and ifnull(reference_name, '') !='' and creation > '2015-03-01' - and not exists(select name from `tabGL Entry` - where voucher_type='Journal Entry' and voucher_no=je.parent - and against_voucher_type=je.reference_type - and against_voucher=je.reference_name)""") - - for d in je_list: - print(d) - - # delete existing gle - frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d) - - # repost gl entries - je = frappe.get_doc("Journal Entry", d) - je.make_gl_entries() \ No newline at end of file diff --git a/erpnext/patches/v6_4/round_status_updater_percentages.py b/erpnext/patches/v6_4/round_status_updater_percentages.py deleted file mode 100644 index 900e906b7b..0000000000 --- a/erpnext/patches/v6_4/round_status_updater_percentages.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype, fieldname in ( - ("Sales Order", "per_billed"), - ("Sales Order", "per_delivered"), - ("Delivery Note", "per_installed"), - ("Purchase Order", "per_billed"), - ("Purchase Order", "per_received"), - ("Material Request", "per_ordered"), - ): - frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=round(`{fieldname}`, 2)""".format( - doctype=doctype, fieldname=fieldname)) diff --git a/erpnext/patches/v6_4/set_user_in_contact.py b/erpnext/patches/v6_4/set_user_in_contact.py deleted file mode 100644 index 7e8a6eecd5..0000000000 --- a/erpnext/patches/v6_4/set_user_in_contact.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Contact") - frappe.db.sql("""update tabContact, tabUser set tabContact.user = tabUser.name - where tabContact.email_id = tabUser.email""") diff --git a/erpnext/patches/v6_5/__init__.py b/erpnext/patches/v6_5/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_5/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_5/show_in_website_for_template_item.py b/erpnext/patches/v6_5/show_in_website_for_template_item.py deleted file mode 100644 index af6e8304d6..0000000000 --- a/erpnext/patches/v6_5/show_in_website_for_template_item.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe -import frappe.website.render - -def execute(): - for item_code in frappe.db.sql_list("""select distinct variant_of from `tabItem` - where variant_of is not null and variant_of !='' and show_in_website=1"""): - - item = frappe.get_doc("Item", item_code) - item.db_set("show_in_website", 1, update_modified=False) - - item.make_route() - item.db_set("route", item.route, update_modified=False) - - frappe.website.render.clear_cache() diff --git a/erpnext/patches/v6_6/__init__.py b/erpnext/patches/v6_6/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v6_6/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v6_6/fix_website_image.py b/erpnext/patches/v6_6/fix_website_image.py deleted file mode 100644 index cc3e2d852c..0000000000 --- a/erpnext/patches/v6_6/fix_website_image.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import print_function, unicode_literals -import frappe -from frappe.utils import encode - -def execute(): - """Fix the File records created via item.py even if the website_image file didn't exist""" - for item in frappe.db.sql_list("""select name from `tabItem` - where website_image is not null and website_image != '' - and website_image like '/files/%' - and exists ( - select name from `tabFile` - where attached_to_doctype='Item' - and attached_to_name=`tabItem`.name - and file_url=`tabItem`.website_image - and (file_name is null or file_name = '') - )"""): - - item = frappe.get_doc("Item", item) - file = frappe.get_doc("File", { - "attached_to_doctype": "Item", - "attached_to_name": item.name, - "file_url": item.website_image - }) - - try: - file.validate_file() - except IOError: - print(encode(item.website_image), "does not exist") - file.delete() - item.db_set("website_image", None, update_modified=False) - - diff --git a/erpnext/patches/v6_6/remove_fiscal_year_from_leave_allocation.py b/erpnext/patches/v6_6/remove_fiscal_year_from_leave_allocation.py deleted file mode 100644 index 11c582fc49..0000000000 --- a/erpnext/patches/v6_6/remove_fiscal_year_from_leave_allocation.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Leave Allocation") - if frappe.db.has_column("Leave Allocation", "fiscal_year"): - for leave_allocation in frappe.db.sql("select name, fiscal_year from `tabLeave Allocation`", as_dict=True): - dates = frappe.db.get_value("Fiscal Year", leave_allocation["fiscal_year"], - ["year_start_date", "year_end_date"]) - - if dates: - year_start_date, year_end_date = dates - - frappe.db.sql("""update `tabLeave Allocation` - set from_date=%s, to_date=%s where name=%s""", - (year_start_date, year_end_date, leave_allocation["name"])) - diff --git a/erpnext/patches/v6_8/__init__.py b/erpnext/patches/v6_8/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v6_8/make_webform_standard.py b/erpnext/patches/v6_8/make_webform_standard.py deleted file mode 100644 index 2cc16a286f..0000000000 --- a/erpnext/patches/v6_8/make_webform_standard.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - pass - - # done via fixtures - - # frappe.reload_doctype("Web Form") - # frappe.delete_doc("Web Form", "Issues") - # frappe.delete_doc("Web Form", "Addresses") - - # from erpnext.setup.install import add_web_forms - # add_web_forms() diff --git a/erpnext/patches/v6_8/move_drop_ship_to_po_items.py b/erpnext/patches/v6_8/move_drop_ship_to_po_items.py deleted file mode 100644 index 7184deeccc..0000000000 --- a/erpnext/patches/v6_8/move_drop_ship_to_po_items.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Purchase Order") - frappe.reload_doctype("Purchase Order Item") - - if not frappe.db.has_column("Purchase Order", "delivered_by_supplier"): - return - - for po in frappe.get_all("Purchase Order", filters={"delivered_by_supplier": 1}, fields=["name"]): - purchase_order = frappe.get_doc("Purchase Order", po) - - for item in purchase_order.items: - if item.prevdoc_doctype == "Sales Order": - delivered_by_supplier = frappe.get_value("Sales Order Item", item.prevdoc_detail_docname, - "delivered_by_supplier") - - if delivered_by_supplier: - frappe.db.sql("""update `tabPurchase Order Item` - set delivered_by_supplier=1, billed_amt=amount, received_qty=qty - where name=%s """, item.name) - - update_per_received(purchase_order) - update_per_billed(purchase_order) - -def update_per_received(po): - frappe.db.sql(""" update `tabPurchase Order` - set per_received = round((select sum(if(qty > ifnull(received_qty, 0), - ifnull(received_qty, 0), qty)) / sum(qty) *100 - from `tabPurchase Order Item` - where parent = %(name)s), 2) - where name = %(name)s """, {"name": po.name}) - -def update_per_billed(po): - frappe.db.sql(""" update `tabPurchase Order` - set per_billed = round((select sum( if(amount > ifnull(billed_amt, 0), - ifnull(billed_amt, 0), amount)) / sum(amount) *100 - from `tabPurchase Order Item` - where parent = %(name)s), 2) - where name = %(name)s """, {"name": po.name}) - - diff --git a/erpnext/patches/v7_0/__init__.py b/erpnext/patches/v7_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v7_0/calculate_total_costing_amount.py b/erpnext/patches/v7_0/calculate_total_costing_amount.py deleted file mode 100644 index 8ed60a2955..0000000000 --- a/erpnext/patches/v7_0/calculate_total_costing_amount.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import flt - -def execute(): - frappe.reload_doc('projects', 'doctype', 'timesheet') - - for data in frappe.get_all('Timesheet', fields=["name, total_costing_amount"], - filters = [["docstatus", "<", "2"]]): - if flt(data.total_costing_amount) == 0.0: - ts = frappe.get_doc('Timesheet', data.name) - ts.update_cost() - ts.calculate_total_amounts() - ts.flags.ignore_validate = True - ts.flags.ignore_mandatory = True - ts.flags.ignore_validate_update_after_submit = True - ts.flags.ignore_links = True - ts.save() diff --git a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py deleted file mode 100644 index 8c60b5b71e..0000000000 --- a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('projects', 'doctype', 'task') - frappe.reload_doc('projects', 'doctype', 'timesheet') - if not frappe.db.table_exists("Time Log"): - return - - from erpnext.manufacturing.doctype.work_order.work_order \ - import make_timesheet, add_timesheet_detail - - for data in frappe.db.sql("select * from `tabTime Log`", as_dict=1): - if data.task: - company = frappe.db.get_value("Task", data.task, "company") - elif data.work_order: - company = frappe.db.get_value("Work Order", data.work_order, "company") - else: - company = frappe.db.get_single_value('Global Defaults', 'default_company') - - time_sheet = make_timesheet(data.work_order, company) - args = get_timelog_data(data) - add_timesheet_detail(time_sheet, args) - if data.docstatus == 2: - time_sheet.docstatus = 0 - else: - time_sheet.docstatus = data.docstatus - time_sheet.employee = data.employee - time_sheet.note = data.note - time_sheet.company = company - - time_sheet.set_status() - time_sheet.set_dates() - time_sheet.update_cost() - time_sheet.calculate_total_amounts() - time_sheet.flags.ignore_validate = True - time_sheet.flags.ignore_links = True - time_sheet.save(ignore_permissions=True) - - # To ignore validate_mandatory_fields function - if data.docstatus == 1: - time_sheet.db_set("docstatus", 1) - for d in time_sheet.get("time_logs"): - d.db_set("docstatus", 1) - time_sheet.update_work_order(time_sheet.name) - time_sheet.update_task_and_project() - if data.docstatus == 2: - time_sheet.db_set("docstatus", 2) - for d in time_sheet.get("time_logs"): - d.db_set("docstatus", 2) - -def get_timelog_data(data): - return { - 'is_billable': data.billable, - 'from_time': data.from_time, - 'hours': data.hours, - 'to_time': data.to_time, - 'project': data.project, - 'task': data.task, - 'activity_type': data.activity_type, - 'operation': data.operation, - 'operation_id': data.operation_id, - 'workstation': data.workstation, - 'completed_qty': data.completed_qty, - 'billing_rate': data.billing_rate, - 'billing_amount': data.billing_amount, - 'costing_rate': data.costing_rate, - 'costing_amount': data.costing_amount - } diff --git a/erpnext/patches/v7_0/convert_timelogbatch_to_timesheet.py b/erpnext/patches/v7_0/convert_timelogbatch_to_timesheet.py deleted file mode 100644 index e78f163e07..0000000000 --- a/erpnext/patches/v7_0/convert_timelogbatch_to_timesheet.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import cint - -def execute(): - if not frappe.db.exists("DocType", "Time Log Batch"): - return - - from erpnext.manufacturing.doctype.work_order.work_order import add_timesheet_detail - - for tlb in frappe.get_all('Time Log Batch', fields=["*"], - filters = [["docstatus", "<", "2"]]): - time_sheet = frappe.new_doc('Timesheet') - time_sheet.employee= "" - time_sheet.company = frappe.db.get_single_value('Global Defaults', 'default_company') - time_sheet.sales_invoice = tlb.sales_invoice - - for data in frappe.get_all('Time Log Batch Detail', fields=["*"], - filters = {'parent': tlb.name}): - args = get_timesheet_data(data) - add_timesheet_detail(time_sheet, args) - - time_sheet.docstatus = tlb.docstatus - time_sheet.flags.ignore_links = True - time_sheet.save(ignore_permissions=True) - -def get_timesheet_data(data): - from erpnext.patches.v7_0.convert_timelog_to_timesheet import get_timelog_data - - time_log = frappe.get_all('Time Log', fields=["*"], filters = {'name': data.time_log}) - if time_log: - return get_timelog_data(time_log[0]) \ No newline at end of file diff --git a/erpnext/patches/v7_0/create_budget_record.py b/erpnext/patches/v7_0/create_budget_record.py deleted file mode 100644 index fd8bec9f32..0000000000 --- a/erpnext/patches/v7_0/create_budget_record.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from erpnext.accounts.doctype.budget.budget import DuplicateBudgetError - -def execute(): - frappe.reload_doc("accounts", "doctype", "budget") - frappe.reload_doc("accounts", "doctype", "budget_account") - - existing_budgets = frappe.db.sql(""" - select - cc.name, cc.company, cc.distribution_id, - budget.account, budget.budget_allocated, budget.fiscal_year - from - `tabCost Center` cc, `tabBudget Detail` budget - where - cc.name=budget.parent - """, as_dict=1) - - actions = {} - for d in frappe.db.sql("select name, yearly_bgt_flag, monthly_bgt_flag from tabCompany", as_dict=1): - actions.setdefault(d.name, d) - - budget_records = [] - for d in existing_budgets: - budget = frappe.db.get_value("Budget", - {"cost_center": d.name, "fiscal_year": d.fiscal_year, "company": d.company}) - - if not budget: - budget = frappe.new_doc("Budget") - budget.cost_center = d.name - budget.fiscal_year = d.fiscal_year - budget.monthly_distribution = d.distribution_id - budget.company = d.company - if actions[d.company]["yearly_bgt_flag"]: - budget.action_if_annual_budget_exceeded = actions[d.company]["yearly_bgt_flag"] - if actions[d.company]["monthly_bgt_flag"]: - budget.action_if_accumulated_monthly_budget_exceeded = actions[d.company]["monthly_bgt_flag"] - else: - budget = frappe.get_doc("Budget", budget) - - budget.append("accounts", { - "account": d.account, - "budget_amount": d.budget_allocated - }) - - try: - budget.insert() - budget_records.append(budget) - except DuplicateBudgetError: - pass - - for budget in budget_records: - budget.submit() - - if frappe.db.get_value("DocType", "Budget Detail"): - frappe.delete_doc("DocType", "Budget Detail") \ No newline at end of file diff --git a/erpnext/patches/v7_0/create_warehouse_nestedset.py b/erpnext/patches/v7_0/create_warehouse_nestedset.py deleted file mode 100644 index 1c9fc32142..0000000000 --- a/erpnext/patches/v7_0/create_warehouse_nestedset.py +++ /dev/null @@ -1,128 +0,0 @@ - -from __future__ import unicode_literals -import frappe, erpnext -from frappe import _ -from frappe.utils import cint -from frappe.utils.nestedset import rebuild_tree - -def execute(): - """ - Patch Reference: - 1. check whether warehouse is associated to company or not - 2. if warehouse is associated with company - a. create warehouse group for company - b. set warehouse group as parent to other warehouses and set is_group as 0 - 3. if warehouses is not associated with company - a. get distinct companies from stock ledger entries - b. if sle have only company, - i. set default company to all warehouse - ii. repeat 2.a and 2.b - c. if have multiple companies, - i. create group warehouse without company - ii. repeat 2.b - """ - - frappe.reload_doc("stock", "doctype", "warehouse") - - if check_is_warehouse_associated_with_company(): - for company in frappe.get_all("Company", fields=["name", "abbr"]): - make_warehouse_nestedset(company) - else: - sle_against_companies = frappe.db.sql_list("""select distinct company from `tabStock Ledger Entry`""") - - if len(sle_against_companies) == 1: - company = frappe.get_cached_value('Company', sle_against_companies[0], - fieldname=["name", "abbr"], as_dict=1) - set_company_to_warehouse(company.name) - make_warehouse_nestedset(company) - - elif len(sle_against_companies) > 1: - make_warehouse_nestedset() - -def check_is_warehouse_associated_with_company(): - warehouse_associcated_with_company = False - - for warehouse in frappe.get_all("Warehouse", fields=["name", "company"]): - if warehouse.company: - warehouse_associcated_with_company = True - - return warehouse_associcated_with_company - -def make_warehouse_nestedset(company=None): - validate_parent_account_for_warehouse(company) - stock_account_group = get_stock_account_group(company.name) - enable_perpetual_inventory = cint(erpnext.is_perpetual_inventory_enabled(company.name)) or 0 - if not stock_account_group and enable_perpetual_inventory: - return - - if company: - warehouse_group = "{0} - {1}".format(_("All Warehouses"), company.abbr) - ignore_mandatory = False - else: - warehouse_group = _("All Warehouses") - ignore_mandatory = True - - if not frappe.db.get_value("Warehouse", warehouse_group): - create_default_warehouse_group(company, stock_account_group, ignore_mandatory) - - set_parent_to_warehouse(warehouse_group, company) - if enable_perpetual_inventory: - set_parent_to_warehouse_account(company) - -def validate_parent_account_for_warehouse(company=None): - if not company: - return - - if cint(erpnext.is_perpetual_inventory_enabled(company.name)): - parent_account = frappe.db.sql("""select name from tabAccount - where account_type='Stock' and company=%s and is_group=1 - and (warehouse is null or warehouse = '')""", company.name) - - if not parent_account: - current_parent_accounts_for_warehouse = frappe.db.sql("""select parent_account from tabAccount - where account_type='Warehouse' and (warehouse is not null or warehouse != '') """) - - if current_parent_accounts_for_warehouse: - frappe.db.set_value("Account", current_parent_accounts_for_warehouse[0][0], "account_type", "Stock") - -def create_default_warehouse_group(company=None, stock_account_group=None, ignore_mandatory=False): - wh = frappe.get_doc({ - "doctype": "Warehouse", - "warehouse_name": _("All Warehouses"), - "is_group": 1, - "company": company.name if company else "", - "parent_warehouse": "" - }) - - if ignore_mandatory: - wh.flags.ignore_mandatory = ignore_mandatory - - wh.insert(ignore_permissions=True) - -def set_parent_to_warehouse(warehouse_group, company=None): - frappe.db.sql(""" update tabWarehouse set parent_warehouse = %s, is_group = 0 - where (is_group = 0 or is_group is null or is_group = '') and ifnull(company, '') = %s - """,(warehouse_group, company.name if company else "")) - - rebuild_tree("Warehouse", "parent_warehouse") - -def set_parent_to_warehouse_account(company): - frappe.db.sql(""" update tabAccount set parent_account = %s - where is_group = 0 and account_type = "Warehouse" - and (warehouse is not null or warehouse != '') and company = %s - """,("{0} - {1}".format(_("All Warehouses"), company.abbr), company.name)) - - rebuild_tree("Account", "parent_account") - -def set_company_to_warehouse(company): - frappe.db.sql("update tabWahouse set company=%s", company) - -def get_stock_account_group(company): - stock_account_group = frappe.db.get_all('Account', filters = {'company': company, 'is_group': 1, - 'account_type': 'Stock', 'root_type': 'Asset'}, limit=1) - - if not stock_account_group: - stock_account_group = frappe.db.get_all('Account', filters = {'company': company, 'is_group': 1, - 'parent_account': '', 'root_type': 'Asset'}, limit=1) - - return stock_account_group[0].name if stock_account_group else None \ No newline at end of file diff --git a/erpnext/patches/v7_0/fix_duplicate_icons.py b/erpnext/patches/v7_0/fix_duplicate_icons.py deleted file mode 100644 index 9f442029b5..0000000000 --- a/erpnext/patches/v7_0/fix_duplicate_icons.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.desk.doctype.desktop_icon.desktop_icon import (sync_desktop_icons, - get_desktop_icons, set_hidden) -from erpnext.patches.v7_0.migrate_schools_to_erpnext import reload_doctypes_for_schools_icons - -def execute(): - '''hide new style icons if old ones are set''' - frappe.reload_doc('desk', 'doctype', 'desktop_icon') - - reload_doctypes_for_schools_icons() - - sync_desktop_icons() - - for user in frappe.get_all('User', filters={'user_type': 'System User'}): - desktop_icons = get_desktop_icons(user.name) - icons_dict = {} - for d in desktop_icons: - if not d.hidden: - icons_dict[d.module_name] = d - - for key in (('Selling', 'Customer'), ('Stock', 'Item'), ('Buying', 'Supplier'), - ('HR', 'Employee'), ('CRM', 'Lead'), ('Support', 'Issue'), ('Projects', 'Project')): - if key[0] in icons_dict and key[1] in icons_dict: - set_hidden(key[1], user.name, 1) - diff --git a/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py b/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py deleted file mode 100644 index 2bc09714d8..0000000000 --- a/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import print_function, unicode_literals -import frappe, erpnext - -def execute(): - frappe.reload_doctype("Account") - - warehouses = frappe.db.sql("""select name, company from tabAccount - where account_type = 'Stock' and is_group = 0 - and (warehouse is null or warehouse = '')""", as_dict=1) - warehouses = [d.name for d in warehouses if erpnext.is_perpetual_inventory_enabled(d.company)] - - if len(warehouses) > 0: - warehouses = set_warehouse_for_stock_account(warehouses) - if not warehouses: - return - - stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no - from `tabStock Ledger Entry` sle - where sle.warehouse in (%s) and creation > '2016-05-01' - and not exists(select name from `tabGL Entry` - where account=sle.warehouse and voucher_type=sle.voucher_type and voucher_no=sle.voucher_no) - order by sle.posting_date""" % - ', '.join(['%s']*len(warehouses)), tuple(warehouses)) - - rejected = [] - for voucher_type, voucher_no in stock_vouchers: - try: - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) - - voucher = frappe.get_doc(voucher_type, voucher_no) - voucher.make_gl_entries() - frappe.db.commit() - except Exception as e: - print(frappe.get_traceback()) - rejected.append([voucher_type, voucher_no]) - frappe.db.rollback() - - print(rejected) - -def set_warehouse_for_stock_account(warehouse_account): - for account in warehouse_account: - if frappe.db.exists('Warehouse', account): - frappe.db.set_value("Account", account, "warehouse", account) - else: - warehouse_account.remove(account) - - return warehouse_account diff --git a/erpnext/patches/v7_0/make_guardian.py b/erpnext/patches/v7_0/make_guardian.py deleted file mode 100644 index 519969b38d..0000000000 --- a/erpnext/patches/v7_0/make_guardian.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.exists("DocType", "Student"): - student_table_cols = frappe.db.get_table_columns("Student") - if "father_name" in student_table_cols: - - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", "student") - # frappe.reload_doc("schools", "doctype", "guardian") - # frappe.reload_doc("schools", "doctype", "guardian_interest") - - frappe.reload_doc("education", "doctype", "student") - frappe.reload_doc("education", "doctype", "guardian") - frappe.reload_doc("education", "doctype", "guardian_interest") - frappe.reload_doc("hr", "doctype", "interest") - - fields = ["name", "father_name", "mother_name"] - - if "father_email_id" in student_table_cols: - fields += ["father_email_id", "mother_email_id"] - - students = frappe.get_all("Student", fields) - for stud in students: - if stud.father_name: - make_guardian(stud.father_name, stud.name, stud.father_email_id) - if stud.mother_name: - make_guardian(stud.mother_name, stud.name, stud.mother_email_id) - -def make_guardian(name, student, email=None): - frappe.get_doc({ - 'doctype': 'Guardian', - 'guardian_name': name, - 'email': email, - 'student': student - }).insert() diff --git a/erpnext/patches/v7_0/make_is_group_fieldtype_as_check.py b/erpnext/patches/v7_0/make_is_group_fieldtype_as_check.py deleted file mode 100644 index ba82e869fa..0000000000 --- a/erpnext/patches/v7_0/make_is_group_fieldtype_as_check.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype in ["Sales Person", "Customer Group", "Item Group", "Territory"]: - - # convert to 1 or 0 - frappe.db.sql("update `tab{doctype}` set is_group = if(is_group='Yes',1,0) " - .format(doctype=doctype)) - - frappe.db.commit() - - # alter fields to int - - frappe.db.sql("alter table `tab{doctype}` change is_group is_group int(1) default '0'" - .format(doctype=doctype)) - - frappe.reload_doctype(doctype) diff --git a/erpnext/patches/v7_0/merge_account_type_stock_and_warehouse_to_stock.py b/erpnext/patches/v7_0/merge_account_type_stock_and_warehouse_to_stock.py deleted file mode 100644 index 02808a742f..0000000000 --- a/erpnext/patches/v7_0/merge_account_type_stock_and_warehouse_to_stock.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("accounts", "doctype", "account") - - frappe.db.sql(""" update tabAccount set account_type = "Stock" - where account_type = "Warehouse" """) - - frappe.db.commit() \ No newline at end of file diff --git a/erpnext/patches/v7_0/migrate_mode_of_payments_v6_to_v7.py b/erpnext/patches/v7_0/migrate_mode_of_payments_v6_to_v7.py deleted file mode 100644 index e0e3f7075a..0000000000 --- a/erpnext/patches/v7_0/migrate_mode_of_payments_v6_to_v7.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_timesheet') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_payment') - frappe.reload_doc('accounts', 'doctype', 'mode_of_payment') - - count = 0 - for data in frappe.db.sql("""select name, mode_of_payment, cash_bank_account, paid_amount, company - from `tabSales Invoice` si - where si.is_pos = 1 and si.docstatus < 2 - and si.cash_bank_account is not null and si.cash_bank_account != '' - and not exists(select name from `tabSales Invoice Payment` where parent=si.name)""", as_dict=1): - - if not data.mode_of_payment and not frappe.db.exists("Mode of Payment", "Cash"): - mop = frappe.new_doc("Mode of Payment") - mop.mode_of_payment = "Cash" - mop.type = "Cash" - mop.save() - - si_doc = frappe.get_doc('Sales Invoice', data.name) - row = si_doc.append('payments', { - 'mode_of_payment': data.mode_of_payment or 'Cash', - 'account': data.cash_bank_account, - 'type': frappe.db.get_value('Mode of Payment', data.mode_of_payment, 'type') or 'Cash', - 'amount': data.paid_amount - }) - row.db_update() - - si_doc.set_paid_amount() - si_doc.db_set("paid_amount", si_doc.paid_amount, update_modified = False) - si_doc.db_set("base_paid_amount", si_doc.base_paid_amount, update_modified = False) - - count +=1 - - if count % 200 == 0: - frappe.db.commit() \ No newline at end of file diff --git a/erpnext/patches/v7_0/migrate_schools_to_erpnext.py b/erpnext/patches/v7_0/migrate_schools_to_erpnext.py deleted file mode 100644 index b72bc137b6..0000000000 --- a/erpnext/patches/v7_0/migrate_schools_to_erpnext.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import unicode_literals -import frappe, os -from frappe.installer import remove_from_installed_apps - -def execute(): - reload_doctypes_for_schools_icons() - - frappe.reload_doc('website', 'doctype', 'portal_settings') - frappe.reload_doc('website', 'doctype', 'portal_menu_item') - frappe.reload_doc('buying', 'doctype', 'request_for_quotation') - - if 'schools' in frappe.get_installed_apps(): - if not frappe.db.exists('Module Def', 'Schools') and frappe.db.exists('Module Def', 'Academics'): - - # 'Schools' module changed to the 'Education' - # frappe.rename_doc("Module Def", "Academics", "Schools") - - frappe.rename_doc("Module Def", "Academics", "Education") - - remove_from_installed_apps("schools") - -def reload_doctypes_for_schools_icons(): - # 'Schools' module changed to the 'Education' - # base_path = frappe.get_app_path('erpnext', 'schools', 'doctype') - - base_path = frappe.get_app_path('erpnext', 'education', 'doctype') - for doctype in os.listdir(base_path): - if os.path.exists(os.path.join(base_path, doctype, doctype + '.json')) \ - and doctype not in ("fee_component", "assessment", "assessment_result"): - frappe.reload_doc('education', 'doctype', doctype) \ No newline at end of file diff --git a/erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py b/erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py deleted file mode 100644 index 998c4b674b..0000000000 --- a/erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'sales_invoice') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_payment') - for time_sheet in frappe.db.sql(""" select sales_invoice, name, total_billable_amount from `tabTimesheet` - where sales_invoice is not null and docstatus < 2""", as_dict=True): - if not frappe.db.exists('Sales Invoice', time_sheet.sales_invoice): - continue - si_doc = frappe.get_doc('Sales Invoice', time_sheet.sales_invoice) - ts = si_doc.append('timesheets',{}) - ts.time_sheet = time_sheet.name - ts.billing_amount = time_sheet.total_billable_amount - ts.db_update() - si_doc.calculate_billing_amount_from_timesheet() - si_doc.db_set("total_billing_amount", si_doc.total_billing_amount, update_modified = False) \ No newline at end of file diff --git a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py deleted file mode 100644 index 910814fd22..0000000000 --- a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py +++ /dev/null @@ -1,40 +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 frappe - -def execute(): - parent_list = [] - count = 0 - - frappe.reload_doc('stock', 'doctype', 'purchase_receipt') - frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item') - - for data in frappe.db.sql(""" - select - `tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name, - `tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx, - `tabPurchase Receipt Item`.parent - from - `tabPurchase Receipt Item`, `tabPurchase Receipt` - where - `tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and - `tabPurchase Receipt Item`.purchase_order_item is null and - `tabPurchase Receipt Item`.purchase_order is not null and - `tabPurchase Receipt`.is_return = 1""", as_dict=1): - name = frappe.db.get_value('Purchase Order Item', - {'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name') - - if name: - frappe.db.set_value('Purchase Receipt Item', data.name, 'purchase_order_item', name, update_modified=False) - parent_list.append(data.parent) - - count +=1 - if count % 200 == 0: - frappe.db.commit() - - if len(parent_list) > 0: - for parent in set(parent_list): - doc = frappe.get_doc('Purchase Receipt', parent) - doc.update_qty(update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v7_0/re_route.py b/erpnext/patches/v7_0/re_route.py deleted file mode 100644 index 3cec6f39b2..0000000000 --- a/erpnext/patches/v7_0/re_route.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -from frappe.patches.v7_0.re_route import update_routes - -def execute(): - update_routes(['Item', 'Item Group', 'Sales Partner', 'Job Opening']) \ No newline at end of file diff --git a/erpnext/patches/v7_0/remove_administrator_role_in_doctypes.py b/erpnext/patches/v7_0/remove_administrator_role_in_doctypes.py deleted file mode 100644 index 8c87c4e3d3..0000000000 --- a/erpnext/patches/v7_0/remove_administrator_role_in_doctypes.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""delete from tabDocPerm where role="Administrator" and parent in - ("Payment Gateway", "Payment Gateway Account", "Payment Request", "Academic Term", "Academic Year", "Course", - "Course Schedule", "Examination", "Fee Category", "Fee Structure", "Fees", "Instructor", "Program", "Program Enrollment Tool", - "Room", "Scheduling Tool", "Student", "Student Applicant", "Student Attendance", "Student Group", "Student Group Creation Tool") - """) \ No newline at end of file diff --git a/erpnext/patches/v7_0/remove_doctypes_and_reports.py b/erpnext/patches/v7_0/remove_doctypes_and_reports.py deleted file mode 100644 index 2356e2f6ee..0000000000 --- a/erpnext/patches/v7_0/remove_doctypes_and_reports.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.table_exists("Time Log"): - frappe.db.sql("""delete from `tabDocType` - where name in('Time Log Batch', 'Time Log Batch Detail', 'Time Log')""") - - frappe.db.sql("""delete from `tabDocField` where parent in ('Time Log', 'Time Log Batch')""") - frappe.db.sql("""update `tabClient Script` set dt = 'Timesheet' where dt = 'Time Log'""") - - for data in frappe.db.sql(""" select label, fieldname from `tabCustom Field` where dt = 'Time Log'""", as_dict=1): - custom_field = frappe.get_doc({ - 'doctype': 'Custom Field', - 'label': data.label, - 'dt': 'Timesheet Detail', - 'fieldname': data.fieldname, - 'fieldtype': data.fieldtype or "Data" - }).insert(ignore_permissions=True) - - frappe.db.sql("""delete from `tabCustom Field` where dt = 'Time Log'""") - frappe.reload_doc('projects', 'doctype', 'timesheet') - frappe.reload_doc('projects', 'doctype', 'timesheet_detail') - - report = "Daily Time Log Summary" - if frappe.db.exists("Report", report): - frappe.delete_doc('Report', report) diff --git a/erpnext/patches/v7_0/remove_features_setup.py b/erpnext/patches/v7_0/remove_features_setup.py deleted file mode 100644 index 49393cc248..0000000000 --- a/erpnext/patches/v7_0/remove_features_setup.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from erpnext.setup.install import create_compact_item_print_custom_field -from frappe.utils import cint - -def execute(): - frappe.reload_doctype('Stock Settings') - stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings') - stock_settings.show_barcode_field = cint(frappe.db.get_value("Features Setup", None, "fs_item_barcode")) - if not frappe.db.exists("UOM", stock_settings.stock_uom): - stock_settings.stock_uom = None - stock_settings.save() - - create_compact_item_print_custom_field() - - compact_item_print = frappe.db.get_value("Features Setup", None, "compact_item_print") - frappe.db.set_value("Print Settings", None, "compact_item_print", compact_item_print) - - # remove defaults - frappe.db.sql("""delete from tabDefaultValue where defkey in ('fs_item_serial_nos', - 'fs_item_batch_nos', 'fs_brands', 'fs_item_barcode', - 'fs_item_advanced', 'fs_packing_details', 'fs_item_group_in_details', - 'fs_exports', 'fs_imports', 'fs_discounts', 'fs_purchase_discounts', - 'fs_after_sales_installations', 'fs_projects', 'fs_sales_extras', - 'fs_recurring_invoice', 'fs_pos', 'fs_manufacturing', 'fs_quality', - 'fs_page_break', 'fs_more_info', 'fs_pos_view', 'compact_item_print')""") - - frappe.delete_doc('DocType', 'Features Setup') diff --git a/erpnext/patches/v7_0/remove_old_earning_deduction_doctypes.py b/erpnext/patches/v7_0/remove_old_earning_deduction_doctypes.py deleted file mode 100644 index 05a2c49461..0000000000 --- a/erpnext/patches/v7_0/remove_old_earning_deduction_doctypes.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.exists("DocType", "Salary Component"): - for dt in ("Salary Structure Earning", "Salary Structure Deduction", "Salary Slip Earning", - "Salary Slip Deduction", "Earning Type", "Deduction Type"): - frappe.delete_doc("DocType", dt) - - - for d in frappe.db.sql("""select name from `tabCustom Field` - where dt in ('Salary Detail', 'Salary Component')"""): - frappe.get_doc("Custom Field", d[0]).save() \ No newline at end of file diff --git a/erpnext/patches/v7_0/rename_advance_table_fields.py b/erpnext/patches/v7_0/rename_advance_table_fields.py deleted file mode 100644 index 34d81343e2..0000000000 --- a/erpnext/patches/v7_0/rename_advance_table_fields.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - for dt in ("Sales Invoice Advance", "Purchase Invoice Advance"): - frappe.reload_doctype(dt) - - frappe.db.sql("update `tab{0}` set reference_type = 'Journal Entry'".format(dt)) - - if frappe.get_meta(dt).has_field('journal_entry'): - rename_field(dt, "journal_entry", "reference_name") - - if frappe.get_meta(dt).has_field('jv_detail_no'): - rename_field(dt, "jv_detail_no", "reference_row") \ No newline at end of file diff --git a/erpnext/patches/v7_0/rename_examination_to_assessment.py b/erpnext/patches/v7_0/rename_examination_to_assessment.py deleted file mode 100644 index dc248de4fa..0000000000 --- a/erpnext/patches/v7_0/rename_examination_to_assessment.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -from frappe.model.utils.rename_field import rename_field - -def execute(): - if frappe.db.exists("DocType", "Examination"): - frappe.rename_doc("DocType", "Examination", "Assessment") - frappe.rename_doc("DocType", "Examination Result", "Assessment Result") - - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", "assessment") - # frappe.reload_doc("schools", "doctype", "assessment_result") - - frappe.reload_doc("education", "doctype", "assessment") - frappe.reload_doc("education", "doctype", "assessment_result") - - rename_field("Assessment", "exam_name", "assessment_name") - rename_field("Assessment", "exam_code", "assessment_code") - - frappe.db.sql("delete from `tabPortal Menu Item` where route = '/examination'") \ No newline at end of file diff --git a/erpnext/patches/v7_0/rename_fee_amount_to_fee_component.py b/erpnext/patches/v7_0/rename_fee_amount_to_fee_component.py deleted file mode 100644 index 5cb6a3b7c4..0000000000 --- a/erpnext/patches/v7_0/rename_fee_amount_to_fee_component.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -from frappe.model.utils.rename_field import rename_field - -def execute(): - if frappe.db.exists("DocType", "Fee Amount"): - frappe.rename_doc("DocType", "Fee Amount", "Fee Component") - for dt in ("Fees", "Fee Structure"): - frappe.reload_doctype(dt) - rename_field(dt, "amount", "components") - - \ No newline at end of file diff --git a/erpnext/patches/v7_0/rename_prevdoc_fields.py b/erpnext/patches/v7_0/rename_prevdoc_fields.py deleted file mode 100644 index ded4ad4aae..0000000000 --- a/erpnext/patches/v7_0/rename_prevdoc_fields.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import unicode_literals -import frappe -import json -from frappe.model.utils.rename_field import update_reports, rename_field, update_property_setters -from frappe.custom.doctype.property_setter.property_setter import make_property_setter - -def execute(): - frappe.reload_doctype('Purchase Order Item') - frappe.reload_doctype('Purchase Receipt Item') - update_po_fields() - update_prop_setters_reports_print_format_for_po() - set_sales_order_field() - rename_pr_fields() - -def update_po_fields(): - for data in frappe.db.sql(""" select prevdoc_docname, prevdoc_detail_docname, name, prevdoc_doctype - from `tabPurchase Order Item` where prevdoc_doctype is not null""", as_dict=True): - if data.prevdoc_doctype == 'Material Request': - frappe.db.set_value("Purchase Order Item", data.name, "material_request", data.prevdoc_docname, update_modified=False) - frappe.db.set_value("Purchase Order Item", data.name, "material_request_item", data.prevdoc_detail_docname, update_modified=False) - elif data.prevdoc_doctype == 'Sales Order': - frappe.db.set_value("Purchase Order Item", data.name, "sales_order", data.prevdoc_docname, update_modified=False) - frappe.db.set_value("Purchase Order Item", data.name, "sales_order_item", data.prevdoc_detail_docname, update_modified=False) - -def get_columns(): - return { - 'prevdoc_docname': 'material_request', - 'prevdoc_detail_docname': 'material_request_item' - } - -def update_prop_setters_reports_print_format_for_po(): - for key, val in get_columns().items(): - update_property_setters('Purchase Order Item', key, val) - update_reports('Purchase Order Item', key, val) - update_print_format_for_po(key, val, 'Purchase Order') - -def update_print_format_for_po(old_fieldname, new_fieldname, doc_type): - column_mapper = get_columns() - - for data in frappe.db.sql(""" select name, format_data from `tabPrint Format` where - format_data like %(old_fieldname)s and doc_type = %(doc_type)s""", - {'old_fieldname': '%%%s%%'%(old_fieldname), 'doc_type': doc_type}, as_dict=True): - - update_print_format_fields(old_fieldname, new_fieldname, data) - -def update_print_format_fields(old_fieldname, new_fieldname, args): - report_dict = json.loads(args.format_data) - update = False - - for col in report_dict: - if col.get('fieldname') and col.get('fieldname') == old_fieldname: - col['fieldname'] = new_fieldname - update = True - - if col.get('visible_columns'): - for key in col.get('visible_columns'): - if key.get('fieldname') == old_fieldname: - key['fieldname'] = new_fieldname - update = True - - if update: - val = json.dumps(report_dict) - frappe.db.sql("""update `tabPrint Format` set `format_data`=%s where name=%s""", (val, args.name)) - -def set_sales_order_field(): - for data in frappe.db.sql("""select doc_type, field_name, property, value, property_type - from `tabProperty Setter` where doc_type = 'Purchase Order Item' - and field_name in('material_request', 'material_request_item')""", as_dict=True): - if data.field_name == 'material_request': - make_property_setter(data.doc_type, 'sales_order', data.property, data.value, data.property_type) - else: - make_property_setter(data.doc_type, 'sales_order_item', data.property, data.value, data.property_type) - -def rename_pr_fields(): - rename_field("Purchase Receipt Item", "prevdoc_docname", "purchase_order") - rename_field("Purchase Receipt Item", "prevdoc_detail_docname", "purchase_order_item") diff --git a/erpnext/patches/v7_0/rename_salary_components.py b/erpnext/patches/v7_0/rename_salary_components.py deleted file mode 100644 index 1693f3bdf1..0000000000 --- a/erpnext/patches/v7_0/rename_salary_components.py +++ /dev/null @@ -1,149 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import update_property_setters - -def execute(): - if not frappe.db.exists("DocType", "Salary Structure Earning"): - return - - frappe.reload_doc("Payroll", "doctype", "salary_detail") - frappe.reload_doc("Payroll", "doctype", "salary_component") - - standard_cols = ["name", "creation", "modified", "owner", "modified_by", "parent", "parenttype", "parentfield", "idx"] - - dt_cols = { - "Salary Structure Deduction": ["d_type", "d_modified_amt", "depend_on_lwp"], - "Salary Structure Earning": ["e_type", "modified_value", "depend_on_lwp"], - "Salary Slip Earning": ["e_type", "e_modified_amount", "e_depends_on_lwp", "e_amount"], - "Salary Slip Deduction": ["d_type", "d_modified_amount", "d_depends_on_lwp", "d_amount"], - } - - earning_type_exists = True if "earning_type" in frappe.db.get_table_columns("Salary Slip Earning") else False - e_type_exists = True if "e_type" in frappe.db.get_table_columns("Salary Slip Earning") else False - - - if e_type_exists and earning_type_exists: - frappe.db.sql("""update `tabSalary Slip Earning` - set e_type = earning_type, e_modified_amount = earning_amount - where e_type is null and earning_type is not null""") - - frappe.db.sql("""update `tabSalary Structure Earning` set e_type = earning_type - where e_type is null and earning_type is not null""") - - frappe.db.sql("""update `tabSalary Slip Deduction` set - d_type = deduction_type, d_modified_amount = deduction_amount - where d_type is null and deduction_type is not null""") - - frappe.db.sql("""update `tabSalary Structure Deduction` set d_type = deduction_type - where d_type is null and deduction_type is not null""") - - if earning_type_exists and not e_type_exists: - for val in dt_cols.values(): - if val[0] == "e_type": - val[0] = "earning_type" - - if val[0] == "d_type": - val[0] = "deduction_type" - - if val[1] == "e_modified_amount": - val[1] ="earning_amount" - - if val[1] == "d_modified_amount": - val[1] ="deduction_amount" - - - - target_cols = standard_cols + ["salary_component", "amount", "depends_on_payment_days", "default_amount"] - target_cols = "`" + "`, `".join(target_cols) + "`" - - for doctype, cols in dt_cols.items(): - source_cols = "`" + "`, `".join(standard_cols + cols) + "`" - if len(cols) == 3: - source_cols += ", 0" - - - frappe.db.sql("""INSERT INTO `tabSalary Detail` ({0}) SELECT {1} FROM `tab{2}`""" - .format(target_cols, source_cols, doctype)) - - - dt_cols_de = { - "Deduction Type": ["deduction_name", "description"], - "Earning Type": ["earning_name", "description"], - } - - standard_cols_de = standard_cols - - - target_cols = standard_cols_de + ["salary_component", "description"] - target_cols = "`" + "`, `".join(target_cols) + "`" - - for doctype, cols in dt_cols_de.items(): - source_cols = "`" + "`, `".join(standard_cols_de + cols) + "`" - try: - frappe.db.sql("""INSERT INTO `tabSalary Component` ({0}) SELECT {1} FROM `tab{2}`""" - .format(target_cols, source_cols, doctype)) - except Exception as e: - if e.args[0]==1062: - pass - - update_customizations() - - for doctype in ["Salary Structure Deduction", "Salary Structure Earning", "Salary Slip Earning", - "Salary Slip Deduction", "Deduction Type", "Earning Type"] : - frappe.delete_doc("DocType", doctype) - - -def update_customizations(): - dt_cols = { - "Salary Structure Deduction": { - "d_type": "salary_component", - "deduction_type": "salary_component", - "d_modified_amt": "amount", - "depend_on_lwp": "depends_on_payment_days" - }, - "Salary Structure Earning": { - "e_type": "salary_component", - "earning_type": "salary_component", - "modified_value": "amount", - "depend_on_lwp": "depends_on_payment_days" - }, - "Salary Slip Earning": { - "e_type": "salary_component", - "earning_type": "salary_component", - "e_modified_amount": "amount", - "e_amount" : "default_amount", - "e_depends_on_lwp": "depends_on_payment_days" - }, - "Salary Slip Deduction": { - "d_type": "salary_component", - "deduction_type": "salary_component", - "d_modified_amount": "amount", - "d_amount" : "default_amount", - "d_depends_on_lwp": "depends_on_payment_days" - } - } - - update_property_setters_and_custom_fields("Salary Detail", dt_cols) - - dt_cols = { - "Earning Type": { - "earning_name": "salary_component" - }, - "Deduction Type": { - "deduction_name": "salary_component" - } - } - - update_property_setters_and_custom_fields("Salary Component", dt_cols) - - - - -def update_property_setters_and_custom_fields(new_dt, dt_cols): - for doctype, cols in dt_cols.items(): - frappe.db.sql("update `tabProperty Setter` set doc_type = %s where doc_type=%s", (new_dt, doctype)) - frappe.db.sql("update `tabCustom Field` set dt = %s where dt=%s", (new_dt, doctype)) - - - for old_fieldname, new_fieldname in cols.items(): - update_property_setters(new_dt, old_fieldname, new_fieldname) diff --git a/erpnext/patches/v7_0/rename_time_sheet_doctype.py b/erpnext/patches/v7_0/rename_time_sheet_doctype.py deleted file mode 100644 index f80a8301d7..0000000000 --- a/erpnext/patches/v7_0/rename_time_sheet_doctype.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.table_exists("Time Sheet") and not frappe.db.table_exists("Timesheet"): - frappe.rename_doc("DocType", "Time Sheet", "Timesheet") - frappe.rename_doc("DocType", "Time Sheet Detail", "Timesheet Detail") - - for doctype in ['Time Sheet', 'Time Sheet Detail']: - frappe.delete_doc('DocType', doctype) - - report = "Daily Time Sheet Summary" - if frappe.db.exists("Report", report): - frappe.delete_doc('Report', report) diff --git a/erpnext/patches/v7_0/repost_bin_qty_and_item_projected_qty.py b/erpnext/patches/v7_0/repost_bin_qty_and_item_projected_qty.py deleted file mode 100644 index a5cf22cf01..0000000000 --- a/erpnext/patches/v7_0/repost_bin_qty_and_item_projected_qty.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - repost_bin_qty() - -def repost_bin_qty(): - for bin in frappe.db.sql(""" select name from `tabBin` - where (actual_qty + ordered_qty + indented_qty + planned_qty - reserved_qty - reserved_qty_for_production - reserved_qty_for_sub_contract) != projected_qty """, as_dict=1): - bin_doc = frappe.get_doc('Bin', bin.name) - bin_doc.set_projected_qty() - bin_doc.db_set("projected_qty", bin_doc.projected_qty, update_modified = False) diff --git a/erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py b/erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py deleted file mode 100644 index b864e597b8..0000000000 --- a/erpnext/patches/v7_0/repost_gle_for_pi_with_update_stock.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cint - -def execute(): - frappe.reload_doctype("Purchase Invoice") - - for pi in frappe.db.sql("""select name from `tabPurchase Invoice` - where company in(select name from tabCompany where enable_perpetual_inventory = 1) and - update_stock=1 and docstatus=1 order by posting_date asc""", as_dict=1): - - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name) - - pi_doc = frappe.get_doc("Purchase Invoice", pi.name) - pi_doc.make_gl_entries() - frappe.db.commit() \ No newline at end of file diff --git a/erpnext/patches/v7_0/repost_gle_for_pos_sales_return.py b/erpnext/patches/v7_0/repost_gle_for_pos_sales_return.py deleted file mode 100644 index 77ecafd6f1..0000000000 --- a/erpnext/patches/v7_0/repost_gle_for_pos_sales_return.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cint, flt - -def execute(): - frappe.reload_doctype("Sales Invoice") - frappe.reload_doctype("Sales Invoice Item") - - for si in frappe.get_all("Sales Invoice", fields = ["name"], - filters={"docstatus": 1, "is_pos": 1, "is_return": 1}): - si_doc = frappe.get_doc("Sales Invoice", si.name) - if len(si_doc.payments) > 0: - si_doc.set_paid_amount() - si_doc.flags.ignore_validate_update_after_submit = True - si_doc.save() - if si_doc.grand_total <= si_doc.paid_amount and si_doc.paid_amount < 0: - delete_gle_for_voucher(si_doc.name) - si_doc.run_method("make_gl_entries") - -def delete_gle_for_voucher(voucher_no): - frappe.db.sql("""delete from `tabGL Entry` where voucher_no = %(voucher_no)s""", - {'voucher_no': voucher_no}) diff --git a/erpnext/patches/v7_0/set_base_amount_in_invoice_payment_table.py b/erpnext/patches/v7_0/set_base_amount_in_invoice_payment_table.py deleted file mode 100644 index 5dd61a06cc..0000000000 --- a/erpnext/patches/v7_0/set_base_amount_in_invoice_payment_table.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import flt - -def execute(): - si_list = frappe.db.sql(""" - select distinct parent - from `tabSales Invoice Payment` - where docstatus!=2 and parenttype = 'Sales Invoice' - and amount != 0 and base_amount = 0 - """) - - count = 0 - for d in si_list: - si = frappe.get_doc("Sales Invoice", d[0]) - for p in si.get("payments"): - if p.amount and not p.base_amount: - base_amount = flt(p.amount*si.conversion_rate, si.precision("base_paid_amount")) - frappe.db.set_value("Sales Invoice Payment", p.name, "base_amount", base_amount, update_modified=False) - - count +=1 - - if count % 200 == 0: - frappe.db.commit() diff --git a/erpnext/patches/v7_0/set_is_group_for_warehouse.py b/erpnext/patches/v7_0/set_is_group_for_warehouse.py deleted file mode 100644 index 3e69616b80..0000000000 --- a/erpnext/patches/v7_0/set_is_group_for_warehouse.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("stock", "doctype", "warehouse") - frappe.db.sql("""update tabWarehouse - set is_group = if ((ifnull(is_group, "No") = "Yes" or ifnull(is_group, 0) = 1), 1, 0)""") \ No newline at end of file diff --git a/erpnext/patches/v7_0/set_material_request_type_in_item.py b/erpnext/patches/v7_0/set_material_request_type_in_item.py deleted file mode 100644 index 5fb14adbc8..0000000000 --- a/erpnext/patches/v7_0/set_material_request_type_in_item.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Item") - if "default_bom" in frappe.db.get_table_columns("Item"): - frappe.db.sql("""update `tabItem` - set default_material_request_type = ( - case - when (default_bom is not null and default_bom != '') - then 'Manufacture' - else 'Purchase' - end )""") - - else: - frappe.db.sql("update tabItem set default_material_request_type='Purchase'") \ No newline at end of file diff --git a/erpnext/patches/v7_0/set_naming_series_for_timesheet.py b/erpnext/patches/v7_0/set_naming_series_for_timesheet.py deleted file mode 100644 index d4d1a69d21..0000000000 --- a/erpnext/patches/v7_0/set_naming_series_for_timesheet.py +++ /dev/null @@ -1,15 +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 frappe -from frappe.custom.doctype.property_setter.property_setter import make_property_setter - -def execute(): - frappe.reload_doc('projects', 'doctype', 'timesheet') - frappe.reload_doc('projects', 'doctype', 'timesheet_detail') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_timesheet') - - make_property_setter('Timesheet', "naming_series", "options", 'TS-', "Text") - make_property_setter('Timesheet', "naming_series", "default", 'TS-', "Text") \ No newline at end of file diff --git a/erpnext/patches/v7_0/set_party_name_in_payment_entry.py b/erpnext/patches/v7_0/set_party_name_in_payment_entry.py deleted file mode 100644 index bbdcf5cf3c..0000000000 --- a/erpnext/patches/v7_0/set_party_name_in_payment_entry.py +++ /dev/null @@ -1,20 +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 frappe - -def execute(): - customers = frappe._dict(frappe.db.sql("select name, customer_name from tabCustomer")) - suppliers = frappe._dict(frappe.db.sql("select name, supplier_name from tabSupplier")) - - frappe.reload_doc('accounts', 'doctype', 'payment_entry') - - pe_list = frappe.db.sql("""select name, party_type, party from `tabPayment Entry` - where party is not null and party != ''""", as_dict=1) - for pe in pe_list: - party_name = customers.get(pe.party) if pe.party_type=="Customer" else suppliers.get(pe.party) - - frappe.db.set_value("Payment Entry", pe.name, "party_name", party_name, update_modified=False) - diff --git a/erpnext/patches/v7_0/set_portal_settings.py b/erpnext/patches/v7_0/set_portal_settings.py deleted file mode 100644 index 5259d4fbd4..0000000000 --- a/erpnext/patches/v7_0/set_portal_settings.py +++ /dev/null @@ -1,28 +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 frappe - -def execute(): - frappe.reload_doctype('Role') - for dt in ("assessment", "course", "fees"): - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", dt) - frappe.reload_doc("education", "doctype", dt) - - for dt in ("domain", "has_domain", "domain_settings"): - frappe.reload_doc("core", "doctype", dt) - - frappe.reload_doc('website', 'doctype', 'portal_menu_item') - - frappe.get_doc('Portal Settings').sync_menu() - - if 'schools' in frappe.get_installed_apps(): - domain = frappe.get_doc('Domain', 'Education') - domain.setup_domain() - else: - domain = frappe.get_doc('Domain', 'Manufacturing') - domain.setup_data() - domain.setup_sidebar_items() diff --git a/erpnext/patches/v7_0/setup_account_table_for_expense_claim_type_if_exists.py b/erpnext/patches/v7_0/setup_account_table_for_expense_claim_type_if_exists.py deleted file mode 100644 index c5657079b3..0000000000 --- a/erpnext/patches/v7_0/setup_account_table_for_expense_claim_type_if_exists.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("hr", "doctype", "expense_claim_type") - frappe.reload_doc("hr", "doctype", "expense_claim_account") - - if not frappe.db.has_column('Expense Claim Type', 'default_account'): - return - - for expense_claim_type in frappe.get_all("Expense Claim Type", fields=["name", "default_account"]): - if expense_claim_type.default_account \ - and frappe.db.exists("Account", expense_claim_type.default_account): - doc = frappe.get_doc("Expense Claim Type", expense_claim_type.name) - doc.append("accounts", { - "company": frappe.db.get_value("Account", expense_claim_type.default_account, "company"), - "default_account": expense_claim_type.default_account, - }) - doc.flags.ignore_mandatory = True - doc.save(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/patches/v7_0/system_settings_setup_complete.py b/erpnext/patches/v7_0/system_settings_setup_complete.py deleted file mode 100644 index 0feeee981e..0000000000 --- a/erpnext/patches/v7_0/system_settings_setup_complete.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('System Settings') - companies = frappe.db.sql("""select name, country - from tabCompany order by creation asc""", as_dict=True) - if companies: - frappe.db.set_value('System Settings', 'System Settings', 'setup_complete', 1) - - for company in companies: - if company.country: - frappe.db.set_value('System Settings', 'System Settings', 'country', company.country) - break - - diff --git a/erpnext/patches/v7_0/update_autoname_field.py b/erpnext/patches/v7_0/update_autoname_field.py deleted file mode 100644 index bfa9b281df..0000000000 --- a/erpnext/patches/v7_0/update_autoname_field.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - doctypes = frappe.db.sql(""" select name, autoname from `tabDocType` - where autoname like 'field:%' and allow_rename = 1""", as_dict=1) - - for doctype in doctypes: - fieldname = doctype.autoname.split(":")[1] - if fieldname: - frappe.db.sql(""" update `tab%s` set %s = name """%(doctype.name, fieldname)) \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_change_amount_account.py b/erpnext/patches/v7_0/update_change_amount_account.py deleted file mode 100644 index 1741095ea9..0000000000 --- a/erpnext/patches/v7_0/update_change_amount_account.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'sales_invoice') - - for company in frappe.db.sql("""select company from `tabSales Invoice` - where change_amount <> 0 and account_for_change_amount is null group by company""", as_list = 1): - cash_account = get_default_bank_cash_account(company[0], 'Cash').get('account') - if not cash_account: - bank_account = get_default_bank_cash_account(company[0], 'Bank').get('account') - cash_account = bank_account - - if cash_account: - frappe.db.sql("""update `tabSales Invoice` - set account_for_change_amount = %(cash_account)s where change_amount <> 0 - and company = %(company)s and account_for_change_amount is null""", - {'cash_account': cash_account, 'company': company[0]}) diff --git a/erpnext/patches/v7_0/update_conversion_factor_in_supplier_quotation_item.py b/erpnext/patches/v7_0/update_conversion_factor_in_supplier_quotation_item.py deleted file mode 100644 index 24da4b1aeb..0000000000 --- a/erpnext/patches/v7_0/update_conversion_factor_in_supplier_quotation_item.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('buying', 'doctype', 'supplier_quotation_item') - - frappe.db.sql("""update - `tabSupplier Quotation Item` as sqi_t, - (select sqi.item_code as item_code, sqi.uom as uom, ucd.conversion_factor as conversion_factor - from `tabSupplier Quotation Item` sqi left join `tabUOM Conversion Detail` ucd - on ucd.uom = sqi.uom and sqi.item_code = ucd.parent) as conversion_data, - `tabItem` as item - set - sqi_t.conversion_factor= ifnull(conversion_data.conversion_factor, 1), - sqi_t.stock_qty = (ifnull(conversion_data.conversion_factor, 1) * sqi_t.qty), - sqi_t.stock_uom = item.stock_uom - where - sqi_t.item_code = conversion_data.item_code and - sqi_t.uom = conversion_data.uom and sqi_t.item_code = item.name""") \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_home_page.py b/erpnext/patches/v7_0/update_home_page.py deleted file mode 100644 index 909825c572..0000000000 --- a/erpnext/patches/v7_0/update_home_page.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals -import frappe -import erpnext - -def execute(): - frappe.reload_doc('portal', 'doctype', 'homepage_featured_product') - frappe.reload_doc('portal', 'doctype', 'homepage') - frappe.reload_doc('portal', 'doctype', 'products_settings') - frappe.reload_doctype('Item') - frappe.reload_doctype('Item Group') - - website_settings = frappe.get_doc('Website Settings', 'Website Settings') - if frappe.db.exists('Web Page', website_settings.home_page): - header = frappe.db.get_value('Web Page', website_settings.home_page, 'header') - if header and header.startswith("
    "): - homepage = frappe.get_doc('Homepage', 'Homepage') - homepage.company = erpnext.get_default_company() or frappe.get_all("Company")[0].name - if '

    ' in header: - homepage.tag_line = header.split('

    ')[1].split('

    ')[0] or 'Default Website' - else: - homepage.tag_line = 'Default Website' - homepage.setup_items() - homepage.save() - - website_settings.home_page = 'home' - website_settings.save() - diff --git a/erpnext/patches/v7_0/update_maintenance_module_in_doctype.py b/erpnext/patches/v7_0/update_maintenance_module_in_doctype.py deleted file mode 100644 index 4c0c6a9313..0000000000 --- a/erpnext/patches/v7_0/update_maintenance_module_in_doctype.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.set_value("DocType", "Maintenance Schedule", "module", "Maintenance") - frappe.db.set_value("DocType", "Maintenance Schedule Detail", "module", "Maintenance") - frappe.db.set_value("DocType", "Maintenance Schedule Item", "module", "Maintenance") - frappe.db.set_value("DocType", "Maintenance Visit", "module", "Maintenance") - frappe.db.set_value("DocType", "Maintenance Visit Purpose", "module", "Maintenance") \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_mins_to_first_response.py b/erpnext/patches/v7_0/update_mins_to_first_response.py deleted file mode 100644 index 16681357e6..0000000000 --- a/erpnext/patches/v7_0/update_mins_to_first_response.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.core.doctype.communication.communication import update_mins_to_first_communication - -def execute(): - frappe.reload_doctype('Issue') - frappe.reload_doctype('Opportunity') - - for doctype in ('Issue', 'Opportunity'): - frappe.db.sql('update tab{0} set mins_to_first_response=0'.format(doctype)) - for parent in frappe.get_all(doctype, order_by='creation desc', limit=500): - parent_doc = frappe.get_doc(doctype, parent.name) - for communication in frappe.get_all('Communication', - filters={'reference_doctype': doctype, 'reference_name': parent.name, - 'communication_medium': 'Email'}, - order_by = 'creation asc', limit=2): - - communication_doc = frappe.get_doc('Communication', communication.name) - - update_mins_to_first_communication(parent_doc, communication_doc) - - if parent_doc.mins_to_first_response: - continue \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_missing_employee_in_timesheet.py b/erpnext/patches/v7_0/update_missing_employee_in_timesheet.py deleted file mode 100644 index 54d492b265..0000000000 --- a/erpnext/patches/v7_0/update_missing_employee_in_timesheet.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.table_exists("Time Log") and "employee" in frappe.db.get_table_columns("Time Log"): - timesheet = frappe.db.sql("""select tl.employee as employee, ts.name as name, - tl.modified as modified, tl.modified_by as modified_by, tl.creation as creation, tl.owner as owner - from - `tabTimesheet` ts, `tabTimesheet Detail` tsd, `tabTime Log` tl - where - tsd.parent = ts.name and tl.from_time = tsd.from_time and tl.to_time = tsd.to_time - and tl.hours = tsd.hours and tl.billing_rate = tsd.billing_rate and tsd.idx=1 - and tl.docstatus < 2 and (ts.employee = '' or ts.employee is null)""", as_dict=1) - - for data in timesheet: - ts_doc = frappe.get_doc('Timesheet', data.name) - if len(ts_doc.time_logs) == 1: - frappe.db.sql(""" update `tabTimesheet` set creation = %(creation)s, - owner = %(owner)s, modified = %(modified)s, modified_by = %(modified_by)s, - employee = %(employee)s where name = %(name)s""", data) diff --git a/erpnext/patches/v7_0/update_mode_of_payment_type.py b/erpnext/patches/v7_0/update_mode_of_payment_type.py deleted file mode 100644 index 9292a1be1b..0000000000 --- a/erpnext/patches/v7_0/update_mode_of_payment_type.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import flt - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'mode_of_payment') - - frappe.db.sql(""" update `tabMode of Payment` set type = 'Cash' where (type is null or type = '') and name = 'Cash'""") - - for data in frappe.db.sql("""select name from `tabSales Invoice` where is_pos=1 and docstatus<2 and - (ifnull(paid_amount, 0) - ifnull(change_amount, 0)) > ifnull(grand_total, 0) and modified > '2016-05-01'""", as_dict=1): - if data.name: - si_doc = frappe.get_doc("Sales Invoice", data.name) - remove_payment = [] - mode_of_payment = [d.mode_of_payment for d in si_doc.payments if flt(d.amount) > 0] - if mode_of_payment != set(mode_of_payment): - for payment_data in si_doc.payments: - if payment_data.idx != 1 and payment_data.amount == si_doc.grand_total: - remove_payment.append(payment_data) - frappe.db.sql(""" delete from `tabSales Invoice Payment` - where name = %(name)s""", {'name': payment_data.name}) - - if len(remove_payment) > 0: - for d in remove_payment: - si_doc.remove(d) - - si_doc.set_paid_amount() - si_doc.db_set("paid_amount", si_doc.paid_amount, update_modified = False) - si_doc.db_set("base_paid_amount", si_doc.base_paid_amount, update_modified = False) \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_party_status.py b/erpnext/patches/v7_0/update_party_status.py deleted file mode 100644 index 0c6b4ea598..0000000000 --- a/erpnext/patches/v7_0/update_party_status.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - return - # for party_type in ('Customer', 'Supplier'): - # frappe.reload_doctype(party_type) - # - # # set all as default status - # frappe.db.sql('update `tab{0}` set status=%s'.format(party_type), default_status[party_type]) - # - # for doctype in status_depends_on[party_type]: - # filters = get_filters_for(doctype) - # parties = frappe.get_all(doctype, fields="{0} as party".format(party_type.lower()), - # filters=filters, limit_page_length=1) - # - # parties = filter(None, [p.party for p in parties]) - # - # if parties: - # frappe.db.sql('update `tab{0}` set status="Open" where name in ({1})'.format(party_type, - # ', '.join(len(parties) * ['%s'])), parties) \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_prevdoc_values_for_supplier_quotation_item.py b/erpnext/patches/v7_0/update_prevdoc_values_for_supplier_quotation_item.py deleted file mode 100644 index e90de50c1e..0000000000 --- a/erpnext/patches/v7_0/update_prevdoc_values_for_supplier_quotation_item.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Supplier Quotation Item') - for data in frappe.db.sql(""" select prevdoc_docname, prevdoc_detail_docname, name - from `tabSupplier Quotation Item` where prevdoc_docname is not null""", as_dict=True): - frappe.db.set_value("Supplier Quotation Item", data.name, "material_request", data.prevdoc_docname) - frappe.db.set_value("Supplier Quotation Item", data.name, "material_request_item", data.prevdoc_detail_docname) \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_project_in_gl_entry.py b/erpnext/patches/v7_0/update_project_in_gl_entry.py deleted file mode 100644 index d99e9a41e3..0000000000 --- a/erpnext/patches/v7_0/update_project_in_gl_entry.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("GL Entry") - - for doctype in ("Delivery Note", "Sales Invoice", "Stock Entry"): - frappe.db.sql(""" - update `tabGL Entry` gle, `tab{0}` dt - set gle.project = dt.project - where gle.voucher_type=%s and gle.voucher_no = dt.name - and ifnull(gle.cost_center, '') != '' and ifnull(dt.project, '') != '' - """.format(doctype), doctype) - - for doctype in ("Purchase Receipt", "Purchase Invoice"): - frappe.db.sql(""" - update `tabGL Entry` gle, `tab{0} Item` dt - set gle.project = dt.project - where gle.voucher_type=%s and gle.voucher_no = dt.parent and gle.cost_center=dt.cost_center - and ifnull(gle.cost_center, '') != '' and ifnull(dt.project, '') != '' - """.format(doctype), doctype) \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_refdoc_in_landed_cost_voucher.py b/erpnext/patches/v7_0/update_refdoc_in_landed_cost_voucher.py deleted file mode 100644 index 2d562bb40e..0000000000 --- a/erpnext/patches/v7_0/update_refdoc_in_landed_cost_voucher.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if "purchase_receipt" not in frappe.db.get_table_columns("Landed Cost Purchase Receipt"): - return - - frappe.reload_doctype("Landed Cost Purchase Receipt") - - frappe.db.sql(""" - update `tabLanded Cost Purchase Receipt` - set receipt_document_type = 'Purchase Receipt', receipt_document = purchase_receipt - where (receipt_document is null or receipt_document = '') - and (purchase_receipt is not null and purchase_receipt != '') - """) \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_status_for_timesheet.py b/erpnext/patches/v7_0/update_status_for_timesheet.py deleted file mode 100644 index 117c40c59f..0000000000 --- a/erpnext/patches/v7_0/update_status_for_timesheet.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""update - `tabTimesheet` as ts, - ( - select min(from_time)as from_time, max(to_time) as to_time, parent from `tabTimesheet Detail` group by parent - ) as tsd - set ts.status = 'Submitted', ts.start_date = tsd.from_time, ts.end_date = tsd.to_time - where tsd.parent = ts.name and ts.status = 'Draft' and ts.docstatus =1""") \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_status_of_po_so.py b/erpnext/patches/v7_0/update_status_of_po_so.py deleted file mode 100644 index d630e8f0f2..0000000000 --- a/erpnext/patches/v7_0/update_status_of_po_so.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cint, flt - -def execute(): - update_po_per_received_per_billed() - update_so_per_delivered_per_billed() - update_status() - -def update_po_per_received_per_billed(): - frappe.db.sql(""" - update - `tabPurchase Order` - set - `tabPurchase Order`.per_received = round((select sum(if(qty > ifnull(received_qty, 0), - ifnull(received_qty, 0), qty)) / sum(qty) *100 from `tabPurchase Order Item` - where parent = `tabPurchase Order`.name), 2), - `tabPurchase Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0), - ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabPurchase Order Item` - where parent = `tabPurchase Order`.name), 2), 0) - where - net_total > 0 - """) - -def update_so_per_delivered_per_billed(): - frappe.db.sql(""" - update - `tabSales Order` - set - `tabSales Order`.per_delivered = round((select sum( if(qty > ifnull(delivered_qty, 0), - ifnull(delivered_qty, 0), qty)) / sum(qty) *100 from `tabSales Order Item` - where parent = `tabSales Order`.name), 2), - `tabSales Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0), - ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabSales Order Item` - where parent = `tabSales Order`.name), 2), 0) - where - net_total > 0 - """) - -def update_status(): - frappe.db.sql(""" - update - `tabSales Order` - set status = (Case when status = 'Closed' then 'Closed' - When per_delivered < 100 and per_billed < 100 and docstatus = 1 then 'To Deliver and Bill' - when per_delivered = 100 and per_billed < 100 and docstatus = 1 then 'To Bill' - when per_delivered < 100 and per_billed = 100 and docstatus = 1 then 'To Deliver' - when per_delivered = 100 and per_billed = 100 and docstatus = 1 then 'Completed' - when order_type = 'Maintenance' and per_billed = 100 and docstatus = 1 then 'Completed' - when docstatus = 2 then 'Cancelled' - else 'Draft' - End)""") - - frappe.db.sql(""" - update - `tabPurchase Order` - set status = (Case when status = 'Closed' then 'Closed' - when status = 'Delivered' then 'Delivered' - When per_received < 100 and per_billed < 100 and docstatus = 1 then 'To Receive and Bill' - when per_received = 100 and per_billed < 100 and docstatus = 1 then 'To Bill' - when per_received < 100 and per_billed = 100 and docstatus = 1 then 'To Receive' - when per_received = 100 and per_billed = 100 and docstatus = 1 then 'Completed' - when docstatus = 2 then 'Cancelled' - else 'Draft' - End)""") \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_status_of_zero_amount_sales_order.py b/erpnext/patches/v7_0/update_status_of_zero_amount_sales_order.py deleted file mode 100644 index 9b2b24785a..0000000000 --- a/erpnext/patches/v7_0/update_status_of_zero_amount_sales_order.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for data in frappe.get_all('Sales Order', fields = ["name"], filters = [["docstatus", "=", "1"], ["grand_total", "=", "0"]]): - sales_order = frappe.get_doc('Sales Order', data.name) - sales_order.set_status(update=True, update_modified = False) \ No newline at end of file diff --git a/erpnext/patches/v7_0/update_timesheet_communications.py b/erpnext/patches/v7_0/update_timesheet_communications.py deleted file mode 100644 index 203471ea8f..0000000000 --- a/erpnext/patches/v7_0/update_timesheet_communications.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.table_exists("Time Log"): - timesheet = frappe.db.sql("""SELECT ts.name AS name, tl.name AS timelogname, - tl.modified AS modified, tl.modified_by AS modified_by, tl.creation AS creation, tl.owner AS owner - FROM - `tabTimesheet` ts, `tabTimesheet Detail` tsd, `tabTime Log` tl - WHERE - tsd.parent = ts.name AND tl.from_time = tsd.from_time AND tl.to_time = tsd.to_time - AND tl.hours = tsd.hours AND tl.billing_rate = tsd.billing_rate AND tsd.idx=1 - AND tl.docstatus < 2""", as_dict=1) - - for data in timesheet: - frappe.db.sql(""" update `tabTimesheet` set creation = %(creation)s, - owner = %(owner)s, modified = %(modified)s, modified_by = %(modified_by)s - where name = %(name)s""", data) - - frappe.db.sql(""" - update - tabCommunication - set - reference_doctype = "Timesheet", reference_name = %(timesheet)s - where - reference_doctype = "Time Log" and reference_name = %(timelog)s - """, {'timesheet': data.name, 'timelog': data.timelogname}, auto_commit=1) diff --git a/erpnext/patches/v7_1/__init__.py b/erpnext/patches/v7_1/__init__.py deleted file mode 100644 index 519ff49eac..0000000000 --- a/erpnext/patches/v7_1/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals \ No newline at end of file diff --git a/erpnext/patches/v7_1/add_account_user_role_for_timesheet.py b/erpnext/patches/v7_1/add_account_user_role_for_timesheet.py deleted file mode 100644 index 7372b0cc5f..0000000000 --- a/erpnext/patches/v7_1/add_account_user_role_for_timesheet.py +++ /dev/null @@ -1,31 +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 frappe - -def execute(): - if not frappe.db.get_value('DocPerm', {'parent': 'Timesheet', 'role': 'Accounts User', 'permlevel': 1}): - doc = frappe.get_doc('DocType', 'Timesheet') - doc.append('permissions', { - 'role': "Accounts User", - 'permlevel': 0, - 'read': 1, - 'write': 1, - 'create': 1, - 'delete': 1, - 'submit': 1, - 'cancel': 1, - 'amend': 1, - 'report': 1, - 'email': 1 - }) - - doc.append('permissions', { - 'role': "Accounts User", - 'permlevel': 1, - 'read': 1, - 'write': 1 - }) - - doc.save(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/patches/v7_1/add_field_for_task_dependent.py b/erpnext/patches/v7_1/add_field_for_task_dependent.py deleted file mode 100644 index 65b1c74e87..0000000000 --- a/erpnext/patches/v7_1/add_field_for_task_dependent.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Task') - for t in frappe.get_all('Task', fields=['name']): - task = frappe.get_doc('Task', t.name) - task.update_depends_on() - if task.depends_on_tasks: - task.db_set('depends_on_tasks', task.depends_on_tasks, update_modified=False) diff --git a/erpnext/patches/v7_1/fix_link_for_customer_from_lead.py b/erpnext/patches/v7_1/fix_link_for_customer_from_lead.py deleted file mode 100644 index 33f809fe37..0000000000 --- a/erpnext/patches/v7_1/fix_link_for_customer_from_lead.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for c in frappe.db.sql('select name from tabCustomer where ifnull(lead_name,"")!=""'): - customer = frappe.get_doc('Customer', c[0]) - customer.update_lead_status() \ No newline at end of file diff --git a/erpnext/patches/v7_1/move_sales_invoice_from_parent_to_child_timesheet.py b/erpnext/patches/v7_1/move_sales_invoice_from_parent_to_child_timesheet.py deleted file mode 100644 index d1ec7c697e..0000000000 --- a/erpnext/patches/v7_1/move_sales_invoice_from_parent_to_child_timesheet.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('projects', 'doctype', 'timesheet_detail') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_timesheet') - - frappe.db.sql(""" update - `tabTimesheet` as ts, - (select - sum(billing_amount) as billing_amount, sum(billing_hours) as billing_hours, time_sheet - from `tabSales Invoice Timesheet` where docstatus = 1 group by time_sheet - ) as sit - set - ts.total_billed_amount = sit.billing_amount, ts.total_billed_hours = sit.billing_hours, - ts.per_billed = ((sit.billing_amount * 100)/ts.total_billable_amount) - where ts.name = sit.time_sheet and ts.docstatus = 1""") - - frappe.db.sql(""" update `tabTimesheet Detail` tsd, `tabTimesheet` ts set tsd.sales_invoice = ts.sales_invoice - where tsd.parent = ts.name and ts.sales_invoice is not null""") \ No newline at end of file diff --git a/erpnext/patches/v7_1/rename_field_timesheet.py b/erpnext/patches/v7_1/rename_field_timesheet.py deleted file mode 100644 index 3690a2e79d..0000000000 --- a/erpnext/patches/v7_1/rename_field_timesheet.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - doctype = 'Timesheet' - fields_dict = {'total_billing_amount': 'total_billable_amount', 'total_billing_hours': 'total_billable_hours'} - - for old_fieldname, new_fieldname in fields_dict.items(): - if old_fieldname in frappe.db.get_table_columns(doctype): - rename_field(doctype, old_fieldname, new_fieldname) diff --git a/erpnext/patches/v7_1/rename_quality_inspection_field.py b/erpnext/patches/v7_1/rename_quality_inspection_field.py deleted file mode 100644 index 3b5a7d95eb..0000000000 --- a/erpnext/patches/v7_1/rename_quality_inspection_field.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import * - -def execute(): - for doctype in ("Purchase Receipt Item", "Delivery Note Item"): - frappe.reload_doctype(doctype) - - table_columns = frappe.db.get_table_columns(doctype) - if "qa_no" in table_columns: - rename_field(doctype, "qa_no", "quality_inspection") - - frappe.reload_doctype("Item") - rename_field("Item", "inspection_required", "inspection_required_before_purchase") - - frappe.reload_doc('stock', 'doctype', 'quality_inspection') - frappe.db.sql(""" - update - `tabQuality Inspection` - set - reference_type = 'Purchase Receipt', reference_name = purchase_receipt_no - where - ifnull(purchase_receipt_no, '') != '' and inspection_type = 'Incoming' - """) - - frappe.db.sql(""" - update - `tabQuality Inspection` - set - reference_type = 'Delivery Note', reference_name = delivery_note_no - where - ifnull(delivery_note_no, '') != '' and inspection_type = 'Outgoing' - """) - - for old_fieldname in ["purchase_receipt_no", "delivery_note_no"]: - update_reports("Quality Inspection", old_fieldname, "reference_name") - update_users_report_view_settings("Quality Inspection", old_fieldname, "reference_name") - update_property_setters("Quality Inspection", old_fieldname, "reference_name") diff --git a/erpnext/patches/v7_1/repost_stock_for_deleted_bins_for_merging_items.py b/erpnext/patches/v7_1/repost_stock_for_deleted_bins_for_merging_items.py deleted file mode 100644 index aca21085cc..0000000000 --- a/erpnext/patches/v7_1/repost_stock_for_deleted_bins_for_merging_items.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.stock.stock_balance import repost_stock - -def execute(): - frappe.reload_doc('manufacturing', 'doctype', 'work_order_item') - frappe.reload_doc('manufacturing', 'doctype', 'work_order') - - modified_items = frappe.db.sql_list(""" - select name from `tabItem` - where is_stock_item=1 and modified >= '2016-10-31' - """) - - if not modified_items: - return - - item_warehouses_with_transactions = [] - transactions = ("Sales Order Item", "Material Request Item", "Purchase Order Item", - "Stock Ledger Entry", "Packed Item") - - for doctype in transactions: - item_warehouses_with_transactions += list(frappe.db.sql(""" - select distinct item_code, warehouse - from `tab{0}` where docstatus=1 and item_code in ({1})""" - .format(doctype, ', '.join(['%s']*len(modified_items))), tuple(modified_items))) - - item_warehouses_with_transactions += list(frappe.db.sql(""" - select distinct production_item, fg_warehouse - from `tabWork Order` where docstatus=1 and production_item in ({0})""" - .format(', '.join(['%s']*len(modified_items))), tuple(modified_items))) - - item_warehouses_with_transactions += list(frappe.db.sql(""" - select distinct pr_item.item_code, pr_item.source_warehouse - from `tabWork Order` pr, `tabWork Order Item` pr_item - where pr_item.parent and pr.name and pr.docstatus=1 and pr_item.item_code in ({0})""" - .format(', '.join(['%s']*len(modified_items))), tuple(modified_items))) - - item_warehouses_with_bin = list(frappe.db.sql("select distinct item_code, warehouse from `tabBin`")) - - item_warehouses_with_missing_bin = list( - set(item_warehouses_with_transactions) - set(item_warehouses_with_bin)) - - for item_code, warehouse in item_warehouses_with_missing_bin: - repost_stock(item_code, warehouse) diff --git a/erpnext/patches/v7_1/save_stock_settings.py b/erpnext/patches/v7_1/save_stock_settings.py deleted file mode 100644 index d3f0263c10..0000000000 --- a/erpnext/patches/v7_1/save_stock_settings.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - stock_settings = frappe.get_doc('Stock Settings') - - if stock_settings.default_warehouse \ - and not frappe.db.exists("Warehouse", stock_settings.default_warehouse): - stock_settings.default_warehouse = None - - if stock_settings.stock_uom and not frappe.db.exists("UOM", stock_settings.stock_uom): - stock_settings.stock_uom = None - - stock_settings.flags.ignore_mandatory = True - stock_settings.save() diff --git a/erpnext/patches/v7_1/set_budget_against_as_cost_center.py b/erpnext/patches/v7_1/set_budget_against_as_cost_center.py deleted file mode 100644 index dd9a432cf0..0000000000 --- a/erpnext/patches/v7_1/set_budget_against_as_cost_center.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("accounts", "doctype", "budget") - frappe.db.sql(""" - update - `tabBudget` - set - budget_against = 'Cost Center' - """) diff --git a/erpnext/patches/v7_1/set_currency_exchange_date.py b/erpnext/patches/v7_1/set_currency_exchange_date.py deleted file mode 100644 index 2a2d420f21..0000000000 --- a/erpnext/patches/v7_1/set_currency_exchange_date.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Currency Exchange") - frappe.db.sql(""" - update `tabCurrency Exchange` - set `date` = '2010-01-01' - where date is null or date = '0000-00-00' - """) \ No newline at end of file diff --git a/erpnext/patches/v7_1/set_prefered_contact_email.py b/erpnext/patches/v7_1/set_prefered_contact_email.py deleted file mode 100644 index 3b68e22269..0000000000 --- a/erpnext/patches/v7_1/set_prefered_contact_email.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('User') - for d in frappe.get_all("Employee"): - employee = frappe.get_doc("Employee", d.name) - if employee.company_email: - employee.prefered_contact_email = "Company Email" - employee.prefered_email = employee.company_email - elif employee.personal_email: - employee.prefered_contact_email = "Personal Email" - employee.prefered_email = employee.personal_email - elif employee.user_id: - employee.prefered_contact_email = "User ID" - employee.prefered_email = employee.user_id - employee.db_update() diff --git a/erpnext/patches/v7_1/set_sales_person_status.py b/erpnext/patches/v7_1/set_sales_person_status.py deleted file mode 100644 index 929beac27f..0000000000 --- a/erpnext/patches/v7_1/set_sales_person_status.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('setup','doctype','sales_person') - frappe.db.sql("""update `tabSales Person` set enabled=1 - where (employee is null or employee = '' - or employee IN (select employee from tabEmployee where status != "Left"))""") diff --git a/erpnext/patches/v7_1/set_student_guardian.py b/erpnext/patches/v7_1/set_student_guardian.py deleted file mode 100644 index 093c0bf6d9..0000000000 --- a/erpnext/patches/v7_1/set_student_guardian.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.exists("DocType", "Guardian"): - - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", "student") - # frappe.reload_doc("schools", "doctype", "student_guardian") - # frappe.reload_doc("schools", "doctype", "student_sibling") - - frappe.reload_doc("education", "doctype", "student") - frappe.reload_doc("education", "doctype", "student_guardian") - frappe.reload_doc("education", "doctype", "student_sibling") - if "student" not in frappe.db.get_table_columns("Guardian"): - return - guardian = frappe.get_all("Guardian", fields=["name", "student"]) - for d in guardian: - if d.student: - student = frappe.get_doc("Student", d.student) - if student: - student.append("guardians", {"guardian": d.name}) - student.save() diff --git a/erpnext/patches/v7_1/set_total_amount_currency_in_je.py b/erpnext/patches/v7_1/set_total_amount_currency_in_je.py deleted file mode 100644 index 8426ddcd7d..0000000000 --- a/erpnext/patches/v7_1/set_total_amount_currency_in_je.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext import get_default_currency - -def execute(): - frappe.reload_doc("accounts", "doctype", "journal_entry") - - frappe.db.sql(""" update `tabJournal Entry` set total_amount_currency = %s - where ifnull(multi_currency, 0) = 0 - and (pay_to_recd_from is not null or pay_to_recd_from != "") """, get_default_currency()) - - for je in frappe.db.sql(""" select name from `tabJournal Entry` where multi_currency = 1 - and (pay_to_recd_from is not null or pay_to_recd_from != "")""", as_dict=1): - - doc = frappe.get_doc("Journal Entry", je.name) - for d in doc.get('accounts'): - if d.party_type and d.party: - total_amount_currency = d.account_currency - - elif frappe.db.get_value("Account", d.account, "account_type") in ["Bank", "Cash"]: - total_amount_currency = d.account_currency - - frappe.db.set_value("Journal Entry", je.name, "total_amount_currency", - total_amount_currency, update_modified=False) diff --git a/erpnext/patches/v7_1/update_bom_base_currency.py b/erpnext/patches/v7_1/update_bom_base_currency.py deleted file mode 100644 index 9a59209ea5..0000000000 --- a/erpnext/patches/v7_1/update_bom_base_currency.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext import get_default_currency - -def execute(): - frappe.reload_doc("manufacturing", "doctype", "bom") - frappe.reload_doc("manufacturing", "doctype", "bom_item") - frappe.reload_doc("manufacturing", "doctype", "bom_explosion_item") - frappe.reload_doc("manufacturing", "doctype", "bom_operation") - frappe.reload_doc("manufacturing", "doctype", "bom_scrap_item") - - frappe.db.sql(""" update `tabBOM Operation` set base_hour_rate = hour_rate, - base_operating_cost = operating_cost """) - - frappe.db.sql(""" update `tabBOM Item` set base_rate = rate, base_amount = amount """) - frappe.db.sql(""" update `tabBOM Scrap Item` set base_rate = rate, base_amount = amount """) - - frappe.db.sql(""" update `tabBOM` set `tabBOM`.base_operating_cost = `tabBOM`.operating_cost, - `tabBOM`.base_raw_material_cost = `tabBOM`.raw_material_cost, - `tabBOM`.currency = (select default_currency from `tabCompany` where name = `tabBOM`.company)""") diff --git a/erpnext/patches/v7_1/update_component_type.py b/erpnext/patches/v7_1/update_component_type.py deleted file mode 100644 index 24ca0570e0..0000000000 --- a/erpnext/patches/v7_1/update_component_type.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import flt - -def execute(): - frappe.reload_doc('Payroll', 'doctype', 'salary_component') - sal_components = frappe.db.sql(""" - select DISTINCT salary_component, parentfield from `tabSalary Detail`""", as_dict=True) - - if sal_components: - for sal_component in sal_components: - if sal_component.parentfield == "earnings": - frappe.db.sql("""update `tabSalary Component` set type='Earning' where salary_component=%(sal_comp)s""",{"sal_comp": sal_component.salary_component}) - else: - frappe.db.sql("""update `tabSalary Component` set type='Deduction' where salary_component=%(sal_comp)s""",{"sal_comp": sal_component.salary_component}) \ No newline at end of file diff --git a/erpnext/patches/v7_1/update_invoice_status.py b/erpnext/patches/v7_1/update_invoice_status.py deleted file mode 100644 index 851af80f7a..0000000000 --- a/erpnext/patches/v7_1/update_invoice_status.py +++ /dev/null @@ -1,34 +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 frappe - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'sales_invoice') - frappe.reload_doc('accounts', 'doctype', 'purchase_invoice') - - frappe.db.sql(""" - update - `tabPurchase Invoice` - set - status = (Case When outstanding_amount = 0 and docstatus = 1 and is_return = 0 then 'Paid' - when due_date < CURDATE() and outstanding_amount > 0 and docstatus =1 then 'Overdue' - when due_date >= CURDATE() and outstanding_amount > 0 and docstatus =1 then 'Unpaid' - when outstanding_amount < 0 and docstatus =1 then 'Debit Note Issued' - when is_return = 1 and docstatus =1 then 'Return' - when docstatus = 2 then 'Cancelled' - else 'Draft' - End)""") - - frappe.db.sql(""" - update - `tabSales Invoice` - set status = (Case When outstanding_amount = 0 and docstatus = 1 and is_return = 0 then 'Paid' - when due_date < CURDATE() and outstanding_amount > 0 and docstatus =1 then 'Overdue' - when due_date >= CURDATE() and outstanding_amount > 0 and docstatus =1 then 'Unpaid' - when outstanding_amount < 0 and docstatus =1 then 'Credit Note Issued' - when is_return = 1 and docstatus =1 then 'Return' - when docstatus = 2 then 'Cancelled' - else 'Draft' - End)""") \ No newline at end of file diff --git a/erpnext/patches/v7_1/update_lead_source.py b/erpnext/patches/v7_1/update_lead_source.py deleted file mode 100644 index a2a48a62e1..0000000000 --- a/erpnext/patches/v7_1/update_lead_source.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ - -def execute(): - from erpnext.setup.setup_wizard.operations.install_fixtures import default_lead_sources - - frappe.reload_doc('crm', 'doctype', 'lead_source') - - frappe.local.lang = frappe.db.get_default("lang") or 'en' - - for s in default_lead_sources: - insert_lead_source(_(s)) - - # get lead sources in existing forms (customized) - # and create a document if not created - for d in ['Lead', 'Opportunity', 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']: - sources = frappe.db.sql_list('select distinct source from `tab{0}`'.format(d)) - for s in sources: - if s and s not in default_lead_sources: - insert_lead_source(s) - - # remove customization for source - for p in frappe.get_all('Property Setter', {'doc_type':d, 'field_name':'source', 'property':'options'}): - frappe.delete_doc('Property Setter', p.name) - -def insert_lead_source(s): - if not frappe.db.exists('Lead Source', s): - frappe.get_doc(dict(doctype='Lead Source', source_name=s)).insert() diff --git a/erpnext/patches/v7_1/update_missing_salary_component_type.py b/erpnext/patches/v7_1/update_missing_salary_component_type.py deleted file mode 100644 index 824f2b881f..0000000000 --- a/erpnext/patches/v7_1/update_missing_salary_component_type.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe.utils import cstr - -''' -Some components do not have type set, try and guess whether they turn up in -earnings or deductions in existing salary slips -''' - -def execute(): - frappe.reload_doc("accounts", "doctype", "salary_component_account") - frappe.reload_doc("Payroll", "doctype", "salary_component") - frappe.reload_doc("Payroll", "doctype", "taxable_salary_slab") - - for s in frappe.db.sql('''select name, type, salary_component_abbr from `tabSalary Component` - where ifnull(type, "")="" or ifnull(salary_component_abbr, "") = ""''', as_dict=1): - - component = frappe.get_doc('Salary Component', s.name) - - # guess - if not s.type: - guess = frappe.db.sql('''select - parentfield from `tabSalary Detail` - where salary_component=%s limit 1''', s.name) - - if guess: - component.type = 'Earning' if guess[0][0]=='earnings' else 'Deduction' - - else: - component.type = 'Deduction' - - if not s.salary_component_abbr: - abbr = ''.join([c[0] for c in component.salary_component.split()]).upper() - - abbr_count = frappe.db.sql(""" - select - count(name) - from - `tabSalary Component` - where - salary_component_abbr = %s or salary_component_abbr like %s - """, (abbr, abbr + "-%%")) - - if abbr_count and abbr_count[0][0] > 0: - abbr = abbr + "-" + cstr(abbr_count[0][0]) - - component.salary_component_abbr = abbr - - component.save() diff --git a/erpnext/patches/v7_1/update_portal_roles.py b/erpnext/patches/v7_1/update_portal_roles.py deleted file mode 100644 index 482586b8ef..0000000000 --- a/erpnext/patches/v7_1/update_portal_roles.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Role') - frappe.reload_doctype('User') - for role_name in ('Customer', 'Supplier', 'Student'): - if frappe.db.exists('Role', role_name): - frappe.db.set_value('Role', role_name, 'desk_access', 0) - else: - frappe.get_doc(dict(doctype='Role', role_name=role_name, desk_access=0)).insert() - - - # set customer, supplier roles - for c in frappe.get_all('Contact', fields=['user'], filters={'ifnull(user, "")': ('!=', '')}): - user = frappe.get_doc('User', c.user) - user.flags.ignore_validate = True - user.flags.ignore_mandatory = True - user.save() - - diff --git a/erpnext/patches/v7_1/update_total_billing_hours.py b/erpnext/patches/v7_1/update_total_billing_hours.py deleted file mode 100644 index b9c96028f5..0000000000 --- a/erpnext/patches/v7_1/update_total_billing_hours.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('projects', 'doctype', 'timesheet_detail') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_timesheet') - - frappe.db.sql("""update tabTimesheet set total_billable_hours=total_hours - where total_billable_amount>0 and docstatus = 1""") - - frappe.db.sql("""update `tabTimesheet Detail` set billing_hours=hours where docstatus < 2""") - - frappe.db.sql(""" update `tabSales Invoice Timesheet` set billing_hours = (select total_billable_hours from `tabTimesheet` - where name = time_sheet) where time_sheet is not null""") \ No newline at end of file diff --git a/erpnext/patches/v7_2/__init__.py b/erpnext/patches/v7_2/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v7_2/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v7_2/arrear_leave_encashment_as_salary_component.py b/erpnext/patches/v7_2/arrear_leave_encashment_as_salary_component.py deleted file mode 100644 index d2583b9422..0000000000 --- a/erpnext/patches/v7_2/arrear_leave_encashment_as_salary_component.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - # frappe.reload_doctype('Salary Slip', 'Salary Component') - frappe.reload_doc("Payroll", "doctype", "Salary Slip") - frappe.reload_doc("Payroll", "doctype", "Salary Component") - salary_components = [['Arrear', "ARR"], ['Leave Encashment', 'LENC']] - for salary_component, salary_abbr in salary_components: - if not frappe.db.exists('Salary Component', salary_component): - sal_comp = frappe.get_doc({ - "doctype": "Salary Component", - "salary_component": salary_component, - "type": "Earning", - "salary_component_abbr": salary_abbr - }).insert() - - salary_slips = frappe.db.sql("""select name, arrear_amount, leave_encashment_amount from `tabSalary Slip` - where docstatus !=2 and (arrear_amount > 0 or leave_encashment_amount > 0)""", as_dict=True) - - for salary_slip in salary_slips: - doc = frappe.get_doc('Salary Slip', salary_slip.name) - - if salary_slip.get("arrear_amount") > 0: - r = doc.append('earnings', { - 'salary_component': 'Arrear', - 'amount': salary_slip.arrear_amount - }) - r.db_update() - - if salary_slip.get("leave_encashment_amount") > 0: - r = doc.append('earnings', { - 'salary_component': 'Leave Encashment', - 'amount': salary_slip.leave_encashment_amount - }) - r.db_update() \ No newline at end of file diff --git a/erpnext/patches/v7_2/contact_address_links.py b/erpnext/patches/v7_2/contact_address_links.py deleted file mode 100644 index 200434c208..0000000000 --- a/erpnext/patches/v7_2/contact_address_links.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links -from frappe.utils import update_progress_bar - -def execute(): - frappe.reload_doc('core', 'doctype', 'dynamic_link') - frappe.reload_doc('contacts', 'doctype', 'contact') - frappe.reload_doc('contacts', 'doctype', 'address') - map_fields = ( - ('Customer', 'customer'), - ('Supplier', 'supplier'), - ('Lead', 'lead'), - ('Sales Partner', 'sales_partner') - ) - for doctype in ('Contact', 'Address'): - if frappe.db.has_column(doctype, 'customer'): - items = frappe.get_all(doctype) - for i, doc in enumerate(items): - doc = frappe.get_doc(doctype, doc.name) - dirty = False - for field in map_fields: - if doc.get(field[1]): - doc.append('links', dict(link_doctype=field[0], link_name=doc.get(field[1]))) - dirty = True - - if dirty: - deduplicate_dynamic_links(doc) - doc.update_children() - - update_progress_bar('Updating {0}'.format(doctype), i, len(items)) - print \ No newline at end of file diff --git a/erpnext/patches/v7_2/delete_fleet_management_module_def.py b/erpnext/patches/v7_2/delete_fleet_management_module_def.py deleted file mode 100644 index 542ac11e3f..0000000000 --- a/erpnext/patches/v7_2/delete_fleet_management_module_def.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - if frappe.db.exists('Module Def', 'Fleet Management'): - frappe.db.sql("""delete from `tabModule Def` - where module_name = 'Fleet Management'""") \ No newline at end of file diff --git a/erpnext/patches/v7_2/empty_supplied_items_for_non_subcontracted.py b/erpnext/patches/v7_2/empty_supplied_items_for_non_subcontracted.py deleted file mode 100644 index ec6f8afc3a..0000000000 --- a/erpnext/patches/v7_2/empty_supplied_items_for_non_subcontracted.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]: - child_table = 'Purchase Receipt Item Supplied' if doctype != 'Purchase Order' else 'Purchase Order Item Supplied' - for data in frappe.db.sql(""" select distinct `tab{doctype}`.name from `tab{doctype}` , `tab{child_table}` - where `tab{doctype}`.name = `tab{child_table}`.parent and `tab{doctype}`.docstatus != 2 - and `tab{doctype}`.is_subcontracted = 'No' """.format(doctype = doctype, child_table = child_table), as_dict=1): - frappe.db.sql(""" delete from `tab{child_table}` - where parent = %s and parenttype = %s""".format(child_table= child_table), (data.name, doctype)) \ No newline at end of file diff --git a/erpnext/patches/v7_2/make_all_assessment_group.py b/erpnext/patches/v7_2/make_all_assessment_group.py deleted file mode 100644 index f3ec628374..0000000000 --- a/erpnext/patches/v7_2/make_all_assessment_group.py +++ /dev/null @@ -1,14 +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 frappe - -def execute(): - if not frappe.db.exists({"doctype": "Assessment Group","assessment_group_name": "All Assessment Groups"}): - frappe.reload_doc("education", "doctype", "assessment_group") - doc = frappe.new_doc("Assessment Group") - doc.assessment_group_name = "All Assessment Groups" - doc.is_group = 1 - doc.flags.ignore_mandatory = True - doc.save() \ No newline at end of file diff --git a/erpnext/patches/v7_2/mark_students_active.py b/erpnext/patches/v7_2/mark_students_active.py deleted file mode 100644 index 7289e4a915..0000000000 --- a/erpnext/patches/v7_2/mark_students_active.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - # 'Schools' module changed to the 'Education' - # frappe.reload_doc('schools', 'doctype', 'student_group_student') - - frappe.reload_doc('education', 'doctype', 'student_group_student') - frappe.db.sql("update `tabStudent Group Student` set active=1") diff --git a/erpnext/patches/v7_2/rename_att_date_attendance.py b/erpnext/patches/v7_2/rename_att_date_attendance.py deleted file mode 100644 index 7f06d8f123..0000000000 --- a/erpnext/patches/v7_2/rename_att_date_attendance.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import update_reports, update_users_report_view_settings, update_property_setters - -def execute(): - if "att_date" not in frappe.db.get_table_columns("Attendance"): - return - frappe.reload_doc("hr", "doctype", "attendance") - frappe.db.sql("""update `tabAttendance` - set attendance_date = att_date - where attendance_date is null or attendance_date = '0000-00-00'""") - - update_reports("Attendance", "att_date", "attendance_date") - update_users_report_view_settings("Attendance", "att_date", "attendance_date") - update_property_setters("Attendance", "att_date", "attendance_date") diff --git a/erpnext/patches/v7_2/rename_evaluation_criteria.py b/erpnext/patches/v7_2/rename_evaluation_criteria.py deleted file mode 100644 index c6520b1b72..0000000000 --- a/erpnext/patches/v7_2/rename_evaluation_criteria.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - # 'Schools' module changed to the 'Education' - - - frappe.rename_doc("DocType", "Evaluation Criteria", "Assessment Criteria", force=True) - # frappe.reload_doc("schools", "doctype", "assessment_criteria") - frappe.reload_doc("education", "doctype", "assessment_criteria") - if 'evaluation_criteria' in frappe.db.get_table_columns('Assessment Criteria'): - rename_field("Assessment Criteria", "evaluation_criteria", "assessment_criteria") - - frappe.rename_doc("DocType", "Assessment Evaluation Criteria", "Assessment Plan Criteria", force=True) - # frappe.reload_doc("schools", "doctype", "assessment_plan_criteria") - frappe.reload_doc("education", "doctype", "assessment_plan_criteria") - if 'evaluation_criteria' in frappe.db.get_table_columns('Assessment Plan'): - rename_field("Assessment Plan Criteria", "evaluation_criteria", "assessment_criteria") - - # frappe.reload_doc("schools", "doctype", "assessment_plan") - frappe.reload_doc("education", "doctype", "assessment_plan") - rename_field("Assessment Plan", "evaluation_criterias", "assessment_criteria") - - # frappe.reload_doc("schools", "doctype", "assessment_result_detail") - frappe.reload_doc("education", "doctype", "assessment_result_detail") - if 'evaluation_criteria' in frappe.db.get_table_columns('Assessment Result Detail'): - rename_field("Assessment Result Detail", "evaluation_criteria", "assessment_criteria") - - frappe.rename_doc("DocType", "Course Evaluation Criteria", "Course Assessment Criteria", force=True) - # frappe.reload_doc("schools", "doctype", "course_assessment_criteria") - frappe.reload_doc("education", "doctype", "course_assessment_criteria") - if 'evaluation_criteria' in frappe.db.get_table_columns('Course Assessment Criteria'): - rename_field("Course Assessment Criteria", "evaluation_criteria", "assessment_criteria") - - # frappe.reload_doc("schools", "doctype", "course") - frappe.reload_doc("education", "doctype", "course") - if 'evaluation_criteria' in frappe.db.get_table_columns('Course'): - rename_field("Course", "evaluation_criterias", "assessment_criteria") diff --git a/erpnext/patches/v7_2/set_null_value_to_fields.py b/erpnext/patches/v7_2/set_null_value_to_fields.py deleted file mode 100644 index 6388be438d..0000000000 --- a/erpnext/patches/v7_2/set_null_value_to_fields.py +++ /dev/null @@ -1,11 +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 frappe - -def execute(): - fields = {"Cost Center": "project", "Project": "cost_center"} - for budget_against, field in fields.items(): - frappe.db.sql(""" update `tabBudget` set {field} = null - where budget_against = %s """.format(field = field), budget_against) diff --git a/erpnext/patches/v7_2/setup_auto_close_settings.py b/erpnext/patches/v7_2/setup_auto_close_settings.py deleted file mode 100644 index 4eef2b9c8a..0000000000 --- a/erpnext/patches/v7_2/setup_auto_close_settings.py +++ /dev/null @@ -1,18 +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 frappe - -def execute(): - # update the selling settings and set the close_opportunity_after_days - frappe.reload_doc("selling", "doctype", "selling_settings") - frappe.db.set_value("Selling Settings", "Selling Settings", "close_opportunity_after_days", 15) - - # Auto close Replied opportunity - frappe.db.sql("""update `tabOpportunity` set status='Closed' where status='Replied' - and date_sub(curdate(), interval 15 Day)>modified""") - - # create Support Settings doctype and update close_issue_after_days - frappe.reload_doc("support", "doctype", "support_settings") - frappe.db.set_value("Support Settings", "Support Settings", "close_issue_after_days", 7) \ No newline at end of file diff --git a/erpnext/patches/v7_2/stock_uom_in_selling.py b/erpnext/patches/v7_2/stock_uom_in_selling.py deleted file mode 100644 index d029555747..0000000000 --- a/erpnext/patches/v7_2/stock_uom_in_selling.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Sales Order') - frappe.reload_doctype('Sales Invoice') - frappe.reload_doctype('Quotation') - frappe.reload_doctype('Delivery Note') - - doctype_list = ['Sales Order Item', 'Delivery Note Item', 'Quotation Item', 'Sales Invoice Item'] - - for doctype in doctype_list: - frappe.reload_doctype(doctype) - frappe.db.sql("""update `tab{doctype}` - set uom = stock_uom, conversion_factor = 1, stock_qty = qty""".format(doctype=doctype)) \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_abbr_in_salary_slips.py b/erpnext/patches/v7_2/update_abbr_in_salary_slips.py deleted file mode 100644 index 57432fe986..0000000000 --- a/erpnext/patches/v7_2/update_abbr_in_salary_slips.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('Payroll', 'doctype', 'Salary Slip') - if not frappe.db.has_column('Salary Detail', 'abbr'): - return - - salary_details = frappe.db.sql("""select abbr, salary_component, name from `tabSalary Detail` - where abbr is null or abbr = ''""", as_dict=True) - - for salary_detail in salary_details: - salary_component_abbr = frappe.get_value("Salary Component", salary_detail.salary_component, "salary_component_abbr") - frappe.db.sql("""update `tabSalary Detail` set abbr = %s where name = %s""",(salary_component_abbr, salary_detail.name)) \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_assessment_modules.py b/erpnext/patches/v7_2/update_assessment_modules.py deleted file mode 100644 index 2b5e774d46..0000000000 --- a/erpnext/patches/v7_2/update_assessment_modules.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - #Rename Grading Structure to Grading Scale - if not frappe.db.exists("DocType", "Grading Scale"): - frappe.rename_doc("DocType", "Grading Structure", "Grading Scale", force=True) - if not frappe.db.exists("DocType", "Grading Scale Interval"): - frappe.rename_doc("DocType", "Grade Interval", "Grading Scale Interval", force=True) - - # frappe.reload_doc("schools", "doctype", "grading_scale_interval") - frappe.reload_doc("education", "doctype", "grading_scale_interval") - if "to_score" in frappe.db.get_table_columns("Grading Scale Interval"): - rename_field("Grading Scale Interval", "to_score", "threshold") - - if not frappe.db.exists("DocType", "Assessment Plan"): - frappe.rename_doc("DocType", "Assessment", "Assessment Plan", force=True) - - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", "assessment_plan") - - #Rename Assessment Results - frappe.reload_doc("education", "doctype", "assessment_plan") - if "grading_structure" in frappe.db.get_table_columns("Assessment Plan"): - rename_field("Assessment Plan", "grading_structure", "grading_scale") - - # frappe.reload_doc("schools", "doctype", "assessment_result") - # frappe.reload_doc("schools", "doctype", "assessment_result_detail") - # frappe.reload_doc("schools", "doctype", "assessment_criteria") - frappe.reload_doc("education", "doctype", "assessment_result") - frappe.reload_doc("education", "doctype", "assessment_result_detail") - frappe.reload_doc("education", "doctype", "assessment_criteria") - - - for assessment in frappe.get_all("Assessment Plan", - fields=["name", "grading_scale"], filters = [["docstatus", "!=", 2 ]]): - for stud_result in frappe.db.sql("select * from `tabAssessment Result` where parent= %s", - assessment.name, as_dict=True): - if stud_result.result: - assessment_result = frappe.new_doc("Assessment Result") - assessment_result.student = stud_result.student - assessment_result.student_name = stud_result.student_name - assessment_result.assessment_plan = assessment.name - assessment_result.grading_scale = assessment.grading_scale - assessment_result.total_score = stud_result.result - assessment_result.flags.ignore_validate = True - assessment_result.flags.ignore_mandatory = True - assessment_result.save() - - frappe.db.sql("""delete from `tabAssessment Result` where parent != '' or parent is not null""") \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_attendance_docstatus.py b/erpnext/patches/v7_2/update_attendance_docstatus.py deleted file mode 100644 index a69052657d..0000000000 --- a/erpnext/patches/v7_2/update_attendance_docstatus.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("education", "doctype", "student_attendance") - frappe.db.sql(''' - update `tabStudent Attendance` set - docstatus=0 - where - docstatus=1''') \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_doctype_status.py b/erpnext/patches/v7_2/update_doctype_status.py deleted file mode 100644 index c66f3f2e73..0000000000 --- a/erpnext/patches/v7_2/update_doctype_status.py +++ /dev/null @@ -1,11 +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 frappe - -def execute(): - doctypes = ["Opportunity", "Quotation", "Sales Order", "Sales Invoice", "Purchase Invoice", "Purchase Order", "Delivery Note", "Purchase Receipt"] - for doctype in doctypes: - frappe.db.sql(""" update `tab{doctype}` set status = 'Draft' - where status = 'Cancelled' and docstatus = 0 """.format(doctype = doctype)) \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_guardian_name_in_student_master.py b/erpnext/patches/v7_2/update_guardian_name_in_student_master.py deleted file mode 100644 index 9f589ef00e..0000000000 --- a/erpnext/patches/v7_2/update_guardian_name_in_student_master.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", "student_guardian") - frappe.reload_doc("education", "doctype", "student_guardian") - - student_guardians = frappe.get_all("Student Guardian", fields=["guardian"]) - for student_guardian in student_guardians: - guardian_name = frappe.db.get_value("Guardian", student_guardian.guardian, "guardian_name") - frappe.db.sql("update `tabStudent Guardian` set guardian_name = %s where guardian= %s", - (guardian_name, student_guardian.guardian)) \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_party_type.py b/erpnext/patches/v7_2/update_party_type.py deleted file mode 100644 index 147f5a3643..0000000000 --- a/erpnext/patches/v7_2/update_party_type.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('setup', 'doctype', 'party_type') - make_party_type() - -def make_party_type(): - for party_type in ["Customer", "Supplier", "Employee"]: - if not frappe.db.get_value("Party Type", party_type): - doc = frappe.new_doc("Party Type") - doc.party_type = party_type - doc.save(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_salary_slips.py b/erpnext/patches/v7_2/update_salary_slips.py deleted file mode 100644 index 9fcce62d8f..0000000000 --- a/erpnext/patches/v7_2/update_salary_slips.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details -from frappe.utils import cint - -def execute(): - frappe.reload_doc("Payroll", "doctype", "Salary Slip") - if not frappe.db.has_column('Salary Slip', 'fiscal_year'): - return - - salary_slips = frappe.db.sql("""select month, name, fiscal_year from `tabSalary Slip` - where (month is not null and month != '') and - start_date is null and end_date is null and docstatus != 2""", as_dict=True) - - for salary_slip in salary_slips: - if not cint(salary_slip.month): - continue - get_start_end_date = get_month_details(salary_slip.fiscal_year, cint(salary_slip.month)) - start_date = get_start_end_date['month_start_date'] - end_date = get_start_end_date['month_end_date'] - frappe.db.sql("""update `tabSalary Slip` set start_date = %s, end_date = %s where name = %s""", - (start_date, end_date, salary_slip.name)) \ No newline at end of file diff --git a/erpnext/patches/v7_2/update_website_for_variant.py b/erpnext/patches/v7_2/update_website_for_variant.py deleted file mode 100644 index e8eef6e7da..0000000000 --- a/erpnext/patches/v7_2/update_website_for_variant.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - # variant must have show_in_website = 0 - frappe.reload_doctype('Item') - frappe.db.sql(''' - update tabItem set - show_variant_in_website = 1, - show_in_website = 0 - where - show_in_website=1 - and ifnull(variant_of, "")!=""''') \ No newline at end of file diff --git a/erpnext/patches/v8_0/__init__.py b/erpnext/patches/v8_0/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v8_0/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v8_0/addresses_linked_to_lead.py b/erpnext/patches/v8_0/addresses_linked_to_lead.py deleted file mode 100644 index b5f2234228..0000000000 --- a/erpnext/patches/v8_0/addresses_linked_to_lead.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""UPDATE `tabDynamic Link` SET link_doctype = 'Lead' WHERE link_doctype = 'Load'""") diff --git a/erpnext/patches/v8_0/change_in_words_varchar_length.py b/erpnext/patches/v8_0/change_in_words_varchar_length.py deleted file mode 100644 index 68ff95b5ed..0000000000 --- a/erpnext/patches/v8_0/change_in_words_varchar_length.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - doctypes = frappe.db.sql_list("""select parent from tabDocField where fieldname = 'in_words'""") - - for dt in doctypes: - for fieldname in ("in_words", "base_in_words"): - frappe.db.sql("alter table `tab{0}` change column `{1}` `{2}` varchar(255)" - .format(dt, fieldname, fieldname)) - - frappe.db.sql("""alter table `tabJournal Entry` - change column `total_amount_in_words` `total_amount_in_words` varchar(255)""") diff --git a/erpnext/patches/v8_0/create_address_doc_from_address_field_in_company.py b/erpnext/patches/v8_0/create_address_doc_from_address_field_in_company.py deleted file mode 100644 index cf1bc5af05..0000000000 --- a/erpnext/patches/v8_0/create_address_doc_from_address_field_in_company.py +++ /dev/null @@ -1,33 +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(): - # new field address_html is created in place of address field for the company's address in PR #8754 (without patch) - # so here is the patch for moving the address details in the address doc - company_list = [] - if 'address' in frappe.db.get_table_columns('Company'): - company_list = frappe.db.sql('''select name, address from `tabCompany` - where address is not null and address != ""''', as_dict=1) - - for company in company_list: - add_list = company.address.split(" ") - if ',' in company.address: - add_list = company.address.rpartition(',') - elif ' ' in company.address: - add_list = company.address.rpartition(' ') - else: - add_list = [company.address, None, company.address] - - doc = frappe.get_doc({ - "doctype":"Address", - "address_line1": add_list[0], - "city": add_list[2], - "links": [{ - "link_doctype": "Company", - "link_name": company.name - }] - }) - doc.save() diff --git a/erpnext/patches/v8_0/create_domain_docs.py b/erpnext/patches/v8_0/create_domain_docs.py deleted file mode 100644 index 3ef4f3c1bb..0000000000 --- a/erpnext/patches/v8_0/create_domain_docs.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -import erpnext - -def execute(): - """Create domain documents""" - frappe.reload_doc("core", "doctype", "domain") - frappe.reload_doc("core", "doctype", "domain_settings") - frappe.reload_doc("core", "doctype", "has_domain") - frappe.reload_doc("core", "doctype", "role") - - for domain in ("Distribution", "Manufacturing", "Retail", "Services", "Education"): - if not frappe.db.exists({"doctype": "Domain", "domain": domain}): - create_domain(domain) - - # set domain in domain settings based on company domain - - domains = [] - condition = "" - company = erpnext.get_default_company() - if company: - condition = " and name={0}".format(frappe.db.escape(company)) - - domains = frappe.db.sql_list("select distinct domain from `tabCompany` where domain != 'Other' {0}".format(condition)) - - if not domains: - return - - domain_settings = frappe.get_doc("Domain Settings", "Domain Settings") - checked_domains = [row.domain for row in domain_settings.active_domains] - - for domain in domains: - # check and ignore if the domains is already checked in domain settings - if domain in checked_domains: - continue - - if not frappe.db.get_value("Domain", domain): - # user added custom domain in companies domain field - create_domain(domain) - - row = domain_settings.append("active_domains", dict(domain=domain)) - - domain_settings.save(ignore_permissions=True) - -def create_domain(domain): - # create new domain - - doc = frappe.new_doc("Domain") - doc.domain = domain - doc.db_update() \ No newline at end of file diff --git a/erpnext/patches/v8_0/delete_bin_indexes.py b/erpnext/patches/v8_0/delete_bin_indexes.py deleted file mode 100644 index 12cacdb952..0000000000 --- a/erpnext/patches/v8_0/delete_bin_indexes.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals -import frappe - -def execute(): - # delete bin indexes - unwanted_indexes = ["item_code", "warehouse"] - - for k in unwanted_indexes: - try: - frappe.db.sql("drop index {0} on `tabBin`".format(k)) - except: - pass \ No newline at end of file diff --git a/erpnext/patches/v8_0/delete_schools_depricated_doctypes.py b/erpnext/patches/v8_0/delete_schools_depricated_doctypes.py deleted file mode 100644 index 09a78ed3ca..0000000000 --- a/erpnext/patches/v8_0/delete_schools_depricated_doctypes.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - """ delete doctypes """ - - if frappe.db.exists("DocType", "Grading Structure"): - frappe.delete_doc("DocType", "Grading Structure", force=1) - - if frappe.db.exists("DocType", "Grade Interval"): - frappe.delete_doc("DocType", "Grade Interval", force=1) \ No newline at end of file diff --git a/erpnext/patches/v8_0/disable_instructor_role.py b/erpnext/patches/v8_0/disable_instructor_role.py deleted file mode 100644 index 4ba78d172c..0000000000 --- a/erpnext/patches/v8_0/disable_instructor_role.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - """ - disable the instructor role for companies with domain other than - Education. - """ - - domains = frappe.db.sql_list("select domain from tabCompany") - if "Education" not in domains: - if frappe.db.exists("Role", "Instructor"): - role = frappe.get_doc("Role", "Instructor") - role.disabled = 1 - role.save(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/patches/v8_0/enable_booking_asset_depreciation_automatically.py b/erpnext/patches/v8_0/enable_booking_asset_depreciation_automatically.py deleted file mode 100644 index 1088d702dd..0000000000 --- a/erpnext/patches/v8_0/enable_booking_asset_depreciation_automatically.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.set_value("Accounts Settings", None, - "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file diff --git a/erpnext/patches/v8_0/fix_status_for_invoices_with_negative_outstanding.py b/erpnext/patches/v8_0/fix_status_for_invoices_with_negative_outstanding.py deleted file mode 100644 index 2e7f360c97..0000000000 --- a/erpnext/patches/v8_0/fix_status_for_invoices_with_negative_outstanding.py +++ /dev/null @@ -1,23 +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(): - for dt, status in [["Sales Invoice", "Credit Note Issued"], ["Purchase Invoice", "Debit Note Issued"]]: - invoices = frappe.db.sql(""" - select name - from `tab{0}` - where - status = %s - and outstanding_amount < 0 - and docstatus=1 - and is_return=0 - """.format(dt), status) - - for inv in invoices: - return_inv = frappe.db.sql("""select name from `tab{0}` - where is_return=1 and return_against=%s and docstatus=1""".format(dt), inv[0]) - if not return_inv: - frappe.db.sql("update `tab{0}` set status='Paid' where name = %s".format(dt), inv[0]) \ No newline at end of file diff --git a/erpnext/patches/v8_0/make_payments_table_blank_for_non_pos_invoice.py b/erpnext/patches/v8_0/make_payments_table_blank_for_non_pos_invoice.py deleted file mode 100644 index 9750fb7222..0000000000 --- a/erpnext/patches/v8_0/make_payments_table_blank_for_non_pos_invoice.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Sales Invoice') - - frappe.db.sql(""" - delete from - `tabSales Invoice Payment` - where - parent in (select name from `tabSales Invoice` where is_pos = 0) - """) \ No newline at end of file diff --git a/erpnext/patches/v8_0/merge_student_batch_and_student_group.py b/erpnext/patches/v8_0/merge_student_batch_and_student_group.py deleted file mode 100644 index fb9021fd68..0000000000 --- a/erpnext/patches/v8_0/merge_student_batch_and_student_group.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import * -from frappe.model.mapper import get_mapped_doc - - -def execute(): - # for converting student batch into student group - for doctype in ["Student Group", "Student Group Student", 'Program Enrollment', - "Student Group Instructor", "Student Attendance", "Student", "Student Batch Name"]: - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", frappe.scrub(doctype)) - - frappe.reload_doc("education", "doctype", frappe.scrub(doctype)) - - if frappe.db.table_exists("Student Batch"): - student_batches = frappe.db.sql('''select name as student_group_name, student_batch_name as batch, - program, academic_year, academic_term from `tabStudent Batch`''', as_dict=1) - - for student_batch in student_batches: - # create student batch name if does not exists !! - if student_batch.get("batch") and not frappe.db.exists("Student Batch Name", student_batch.get("batch")): - frappe.get_doc({ - "doctype": "Student Batch Name", - "batch_name": student_batch.get("batch") - }).insert(ignore_permissions=True) - - student_batch.update({"doctype":"Student Group", "group_based_on": "Batch"}) - doc = frappe.get_doc(student_batch) - - if frappe.db.sql("SHOW COLUMNS FROM `tabStudent Batch Student` LIKE 'active'"): - cond = ", active" - else: - cond = " " - student_list = frappe.db.sql('''select student, student_name {cond} from `tabStudent Batch Student` - where parent=%s'''.format(cond=cond), (doc.student_group_name), as_dict=1) - - if student_list: - for i, student in enumerate(student_list): - student.update({"group_roll_number": i+1}) - doc.extend("students", student_list) - - instructor_list = None - if frappe.db.table_exists("Student Batch Instructor"): - instructor_list = frappe.db.sql('''select instructor, instructor_name from `tabStudent Batch Instructor` - where parent=%s''', (doc.student_group_name), as_dict=1) - if instructor_list: - doc.extend("instructors", instructor_list) - doc.save() - - # delete the student batch and child-table - if frappe.db.table_exists("Student Batch"): - frappe.delete_doc("DocType", "Student Batch", force=1) - if frappe.db.table_exists("Student Batch Student"): - frappe.delete_doc("DocType", "Student Batch Student", force=1) - if frappe.db.table_exists("Student Batch Instructor"): - frappe.delete_doc("DocType", "Student Batch Instructor", force=1) - - # delete the student batch creation tool - if frappe.db.table_exists("Student Batch Creation Tool"): - frappe.delete_doc("DocType", "Student Batch Creation Tool", force=1) - - # delete the student batch creation tool - if frappe.db.table_exists("Attendance Tool Student"): - frappe.delete_doc("DocType", "Attendance Tool Student", force=1) - - # change the student batch to student group in the student attendance - table_columns = frappe.db.get_table_columns("Student Attendance") - if "student_batch" in table_columns: - rename_field("Student Attendance", "student_batch", "student_group") diff --git a/erpnext/patches/v8_0/move_account_head_from_account_to_warehouse_for_inventory.py b/erpnext/patches/v8_0/move_account_head_from_account_to_warehouse_for_inventory.py deleted file mode 100644 index b59d81831f..0000000000 --- a/erpnext/patches/v8_0/move_account_head_from_account_to_warehouse_for_inventory.py +++ /dev/null @@ -1,15 +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_doctype("Warehouse") - frappe.db.sql(""" - update - `tabWarehouse` - set - account = (select name from `tabAccount` - where account_type = 'Stock' and - warehouse = `tabWarehouse`.name and is_group = 0 limit 1)""") \ No newline at end of file diff --git a/erpnext/patches/v8_0/move_perpetual_inventory_setting.py b/erpnext/patches/v8_0/move_perpetual_inventory_setting.py deleted file mode 100644 index 78322d4575..0000000000 --- a/erpnext/patches/v8_0/move_perpetual_inventory_setting.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Company') - enabled = frappe.db.get_single_value("Accounts Settings", "auto_accounting_for_stock") or 0 - for data in frappe.get_all('Company', fields = ["name"]): - doc = frappe.get_doc('Company', data.name) - doc.enable_perpetual_inventory = enabled - doc.db_update() \ No newline at end of file diff --git a/erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py b/erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py deleted file mode 100644 index e517df5fdb..0000000000 --- a/erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - - doc_list = ["Purchase Invoice Item", "Stock Entry Detail", "Delivery Note Item", - "Purchase Receipt Item", "Sales Invoice Item"] - - for doctype in doc_list: - frappe.reload_doctype(doctype) - if "is_sample_item" in frappe.db.get_table_columns(doctype): - rename_field(doctype, "is_sample_item", "allow_zero_valuation_rate") \ No newline at end of file diff --git a/erpnext/patches/v8_0/rename_items_in_status_field_of_material_request.py b/erpnext/patches/v8_0/rename_items_in_status_field_of_material_request.py deleted file mode 100644 index 5ad862a436..0000000000 --- a/erpnext/patches/v8_0/rename_items_in_status_field_of_material_request.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql( - """ - UPDATE `tabMaterial Request` - SET status = CASE - WHEN docstatus = 2 THEN 'Cancelled' - WHEN docstatus = 0 THEN 'Draft' - ELSE CASE - WHEN status = 'Stopped' THEN 'Stopped' - WHEN status != 'Stopped' AND per_ordered = 0 THEN 'Pending' - WHEN per_ordered < 100 AND per_ordered > 0 AND status != 'Stopped' - THEN 'Partially Ordered' - WHEN per_ordered = 100 AND material_request_type = 'Purchase' - AND status != 'Stopped' THEN 'Ordered' - WHEN per_ordered = 100 AND material_request_type = 'Material Transfer' - AND status != 'Stopped' THEN 'Transferred' - WHEN per_ordered = 100 AND material_request_type = 'Material Issue' - AND status != 'Stopped' THEN 'Issued' - END - END - """ - ) \ No newline at end of file diff --git a/erpnext/patches/v8_0/rename_total_margin_to_rate_with_margin.py b/erpnext/patches/v8_0/rename_total_margin_to_rate_with_margin.py deleted file mode 100644 index 4065438796..0000000000 --- a/erpnext/patches/v8_0/rename_total_margin_to_rate_with_margin.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.model.utils.rename_field import rename_field - -def execute(): - """ - Rename Total Margin field to Rate With Margin in - "Sales Order Item", "Sales Invoice Item", "Delivery Note Item", - "Quotation Item" - """ - - for d in ("Sales Order Item", "Sales Invoice Item", - "Delivery Note Item", "Quotation Item"): - frappe.reload_doctype(d) - rename_field_if_exists(d, "total_margin", "rate_with_margin") - - -def rename_field_if_exists(doctype, old_fieldname, new_fieldname): - try: - rename_field(doctype, old_fieldname, new_fieldname) - except Exception as e: - if e.args[0] != 1054: - raise diff --git a/erpnext/patches/v8_0/repost_reserved_qty_for_multiple_sales_uom.py b/erpnext/patches/v8_0/repost_reserved_qty_for_multiple_sales_uom.py deleted file mode 100644 index 3030b8e2f3..0000000000 --- a/erpnext/patches/v8_0/repost_reserved_qty_for_multiple_sales_uom.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty - -def execute(): - for doctype in ("Sales Order Item", "Bin"): - frappe.reload_doctype(doctype) - - repost_for = frappe.db.sql("""select distinct item_code, warehouse - from `tabSales Order Item` where docstatus=1 and uom != stock_uom and - exists(select name from tabItem where name=`tabSales Order Item`.item_code and ifnull(is_stock_item, 0)=1)""") - - for item_code, warehouse in repost_for: - update_bin_qty(item_code, warehouse, { - "reserved_qty": get_reserved_qty(item_code, warehouse) - }) \ No newline at end of file diff --git a/erpnext/patches/v8_0/revert_manufacturers_table_from_item.py b/erpnext/patches/v8_0/revert_manufacturers_table_from_item.py deleted file mode 100644 index 60cbb33b80..0000000000 --- a/erpnext/patches/v8_0/revert_manufacturers_table_from_item.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.exists("DocType", "Item Manufacturer"): - frappe.reload_doctype("Item") - item_manufacturers = frappe.db.sql(""" - select parent, manufacturer, manufacturer_part_no - from `tabItem Manufacturer` - """, as_dict=1) - - for im in item_manufacturers: - frappe.db.sql(""" - update tabItem - set manufacturer=%s, manufacturer_part_no=%s - where name=%s - """, (im.manufacturer, im.manufacturer_part_no, im.parent)) - - frappe.delete_doc("DocType", "Item Manufacturer") \ No newline at end of file diff --git a/erpnext/patches/v8_0/save_system_settings.py b/erpnext/patches/v8_0/save_system_settings.py deleted file mode 100644 index d479ece8a6..0000000000 --- a/erpnext/patches/v8_0/save_system_settings.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cint - -def execute(): - """ - save system settings document - """ - - frappe.reload_doc("core", "doctype", "system_settings") - doc = frappe.get_doc("System Settings") - doc.flags.ignore_mandatory = True - - if cint(doc.currency_precision) == 0: - doc.currency_precision = '' - - doc.save(ignore_permissions=True) diff --git a/erpnext/patches/v8_0/set_null_to_serial_nos_for_disabled_sales_invoices.py b/erpnext/patches/v8_0/set_null_to_serial_nos_for_disabled_sales_invoices.py deleted file mode 100644 index 197d6ded61..0000000000 --- a/erpnext/patches/v8_0/set_null_to_serial_nos_for_disabled_sales_invoices.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty - -def execute(): - frappe.db.sql(""" - update - `tabSales Invoice Item` - set serial_no = NULL - where - parent in (select name from `tabSales Invoice` where update_stock = 0 and docstatus = 1)""") \ No newline at end of file diff --git a/erpnext/patches/v8_0/set_project_copied_from.py b/erpnext/patches/v8_0/set_project_copied_from.py deleted file mode 100644 index d4287978cf..0000000000 --- a/erpnext/patches/v8_0/set_project_copied_from.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Project") - - frappe.db.sql(''' - UPDATE `tabProject` - SET copied_from=name - WHERE copied_from is NULL - ''') \ No newline at end of file diff --git a/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py b/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py deleted file mode 100644 index 8a4ef4086b..0000000000 --- a/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty - -def execute(): - """ Set the Serial Numbers in Sales Invoice Item from Delivery Note Item """ - - frappe.reload_doc("stock", "doctype", "serial_no") - - frappe.db.sql(""" update `tabSales Invoice Item` sii inner join - `tabDelivery Note Item` dni on sii.dn_detail=dni.name and sii.qty=dni.qty - set sii.serial_no=dni.serial_no where sii.parent IN (select si.name - from `tabSales Invoice` si where si.update_stock=0 and si.docstatus=1)""") - - items = frappe.db.sql(""" select sii.parent, sii.serial_no from `tabSales Invoice Item` sii - left join `tabSales Invoice` si on sii.parent=si.name - where si.docstatus=1 and si.update_stock=0""", as_dict=True) - - for item in items: - sales_invoice = item.get("parent", None) - serial_nos = item.get("serial_no", "") - - if not sales_invoice or not serial_nos: - continue - - serial_nos = ["{}".format(frappe.db.escape(no)) for no in serial_nos.split("\n")] - - frappe.db.sql(""" - UPDATE - `tabSerial No` - SET - sales_invoice={sales_invoice} - WHERE - name in ({serial_nos}) - """.format( - sales_invoice=frappe.db.escape(sales_invoice), - serial_nos=",".join(serial_nos) - ) - ) \ No newline at end of file diff --git a/erpnext/patches/v8_0/update_customer_pos_id.py b/erpnext/patches/v8_0/update_customer_pos_id.py deleted file mode 100644 index a772ae90c5..0000000000 --- a/erpnext/patches/v8_0/update_customer_pos_id.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Customer") - frappe.db.sql(""" update `tabCustomer` set customer_pos_id = name """) \ No newline at end of file diff --git a/erpnext/patches/v8_0/update_production_orders.py b/erpnext/patches/v8_0/update_production_orders.py deleted file mode 100644 index 8e993cc102..0000000000 --- a/erpnext/patches/v8_0/update_production_orders.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - # reload schema - for doctype in ("Work Order", "Work Order Item", "Work Order Operation", - "BOM Item", "BOM Explosion Item", "BOM"): - frappe.reload_doctype(doctype) - - frappe.reload_doc("stock", "doctype", "item") - frappe.reload_doc("stock", "doctype", "item_default") - - # fetch all draft and submitted work orders - fields = ["name"] - if "source_warehouse" in frappe.db.get_table_columns("Work Order"): - fields.append("source_warehouse") - - wo_orders = frappe.get_all("Work Order", filters={"docstatus": ["!=", 2]}, fields=fields) - - count = 0 - for p in wo_orders: - wo_order = frappe.get_doc("Work Order", p.name) - count += 1 - - # set required items table - wo_order.set_required_items() - - for item in wo_order.get("required_items"): - # set source warehouse based on parent - if not item.source_warehouse and "source_warehouse" in fields: - item.source_warehouse = wo_order.get("source_warehouse") - item.db_update() - - if wo_order.docstatus == 1: - # update transferred qty based on Stock Entry, it also updates db - wo_order.update_transaferred_qty_for_required_items() - - # Set status where it was 'Unstopped', as it is deprecated - if wo_order.status == "Unstopped": - status = wo_order.get_status() - wo_order.db_set("status", status) - elif wo_order.status == "Stopped": - wo_order.update_reserved_qty_for_production() - - if count % 200 == 0: - frappe.db.commit() \ No newline at end of file diff --git a/erpnext/patches/v8_0/update_sales_cost_in_project.py b/erpnext/patches/v8_0/update_sales_cost_in_project.py deleted file mode 100644 index 1a29fc4db4..0000000000 --- a/erpnext/patches/v8_0/update_sales_cost_in_project.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("projects", "doctype", "project") - - frappe.db.sql(""" - update `tabProject` p - set total_sales_amount = ifnull((select sum(base_grand_total) - from `tabSales Order` where project=p.name and docstatus=1), 0) - """) \ No newline at end of file diff --git a/erpnext/patches/v8_0/update_status_as_paid_for_completed_expense_claim.py b/erpnext/patches/v8_0/update_status_as_paid_for_completed_expense_claim.py deleted file mode 100644 index 19d27b206b..0000000000 --- a/erpnext/patches/v8_0/update_status_as_paid_for_completed_expense_claim.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - """ set status as Paid in Expense Claim if total_sactioned_amount - and total_amount_reimbursed is equal """ - - frappe.reload_doctype('Expense Claim') - - frappe.db.sql(""" - update - `tabExpense Claim` - set status = 'Paid' - where - total_sanctioned_amount = total_amount_reimbursed - """) diff --git a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py deleted file mode 100644 index 1f937bb8af..0000000000 --- a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py +++ /dev/null @@ -1,14 +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('manufacturing', 'doctype', 'bom_item') - frappe.reload_doc('manufacturing', 'doctype', 'bom_explosion_item') - frappe.reload_doc('manufacturing', 'doctype', 'bom_scrap_item') - frappe.db.sql("update `tabBOM Item` set stock_qty = qty, uom = stock_uom, conversion_factor = 1") - frappe.db.sql("update `tabBOM Explosion Item` set stock_qty = qty") - if "qty" in frappe.db.get_table_columns("BOM Scrap Item"): - frappe.db.sql("update `tabBOM Scrap Item` set stock_qty = qty") \ No newline at end of file diff --git a/erpnext/patches/v8_0/update_stock_qty_value_in_purchase_invoice.py b/erpnext/patches/v8_0/update_stock_qty_value_in_purchase_invoice.py deleted file mode 100644 index be5cf3aed7..0000000000 --- a/erpnext/patches/v8_0/update_stock_qty_value_in_purchase_invoice.py +++ /dev/null @@ -1,9 +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', 'purchase_invoice_item') - frappe.db.sql("update `tabPurchase Invoice Item` set stock_qty = qty, stock_uom = uom") \ No newline at end of file diff --git a/erpnext/patches/v8_0/update_student_groups_from_student_batches.py b/erpnext/patches/v8_0/update_student_groups_from_student_batches.py deleted file mode 100644 index ae24fe4a14..0000000000 --- a/erpnext/patches/v8_0/update_student_groups_from_student_batches.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import * -from frappe.model.mapper import get_mapped_doc - - -def execute(): - if frappe.db.table_exists("Student Batch"): - student_batches = frappe.db.sql('''select name from `tabStudent Batch`''', as_dict=1) - - for student_batch in student_batches: - if frappe.db.exists("Student Group", student_batch.get("name")): - student_group = frappe.get_doc("Student Group", student_batch.get("name")) - - if frappe.db.table_exists("Student Batch Student"): - current_student_list = frappe.db.sql_list('''select student from `tabStudent Group Student` - where parent=%s''', (student_group.name)) - batch_student_list = frappe.db.sql_list('''select student from `tabStudent Batch Student` - where parent=%s''', (student_group.name)) - - student_list = list(set(batch_student_list)-set(current_student_list)) - if student_list: - student_group.extend("students", [{"student":d} for d in student_list]) - - if frappe.db.table_exists("Student Batch Instructor"): - current_instructor_list = frappe.db.sql_list('''select instructor from `tabStudent Group Instructor` - where parent=%s''', (student_group.name)) - batch_instructor_list = frappe.db.sql_list('''select instructor from `tabStudent Batch Instructor` - where parent=%s''', (student_group.name)) - - instructor_list = list(set(batch_instructor_list)-set(current_instructor_list)) - if instructor_list: - student_group.extend("instructors", [{"instructor":d} for d in instructor_list]) - - student_group.save() diff --git a/erpnext/patches/v8_0/update_supplier_address_in_stock_entry.py b/erpnext/patches/v8_0/update_supplier_address_in_stock_entry.py deleted file mode 100644 index a2173048fd..0000000000 --- a/erpnext/patches/v8_0/update_supplier_address_in_stock_entry.py +++ /dev/null @@ -1,22 +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(): - # copy supplier_address to address_display, and set supplier_address to blank - - stock_entries = frappe.db.sql(""" select name, purchase_order, supplier_address from `tabStock Entry` - where ifnull(supplier_address, '') <> ''""", as_dict=True) - - frappe.reload_doc('stock', 'doctype', 'stock_entry') - - for stock_entry in stock_entries: - # move supplier address to address_display, and fetch the supplier address from purchase order - - se = frappe.get_doc("Stock Entry", stock_entry.get("name")) - se.address_display = stock_entry.get("supplier_address") - se.supplier_address = frappe.db.get_value("Purchase Order", stock_entry.get("purchase_order"),"supplier_address") or None - - se.db_update() diff --git a/erpnext/patches/v8_1/__init__.py b/erpnext/patches/v8_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v8_1/add_hsn_sac_codes.py b/erpnext/patches/v8_1/add_hsn_sac_codes.py deleted file mode 100644 index 0fce96a8d4..0000000000 --- a/erpnext/patches/v8_1/add_hsn_sac_codes.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.regional.india.setup import setup - -def execute(): - company = frappe.get_all('Company', filters = {'country': 'India'}) - if not company: - return - - # call setup for india - setup(patch=True) \ No newline at end of file diff --git a/erpnext/patches/v8_1/add_indexes_in_transaction_doctypes.py b/erpnext/patches/v8_1/add_indexes_in_transaction_doctypes.py deleted file mode 100644 index 4631602606..0000000000 --- a/erpnext/patches/v8_1/add_indexes_in_transaction_doctypes.py +++ /dev/null @@ -1,10 +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 frappe - -def execute(): - for dt in ("Sales Order Item", "Purchase Order Item", - "Material Request Item", "Work Order Item", "Packed Item"): - frappe.get_doc("DocType", dt).run_module_method("on_doctype_update") \ No newline at end of file diff --git a/erpnext/patches/v8_1/allow_invoice_copy_to_edit_after_submit.py b/erpnext/patches/v8_1/allow_invoice_copy_to_edit_after_submit.py deleted file mode 100644 index 4c606af424..0000000000 --- a/erpnext/patches/v8_1/allow_invoice_copy_to_edit_after_submit.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - inv_copy_options = "ORIGINAL FOR RECIPIENT\nDUPLICATE FOR TRANSPORTER\nDUPLICATE FOR SUPPLIER\nTRIPLICATE FOR SUPPLIER" - - frappe.db.sql("""update `tabCustom Field` set allow_on_submit=1, options=%s - where fieldname='invoice_copy' and dt = 'Sales Invoice' - """, inv_copy_options) - - frappe.db.sql("""update `tabCustom Field` set read_only=1 - where fieldname='gst_state_number' and dt = 'Address' - """) diff --git a/erpnext/patches/v8_1/delete_deprecated_reports.py b/erpnext/patches/v8_1/delete_deprecated_reports.py deleted file mode 100644 index 3e0fdee719..0000000000 --- a/erpnext/patches/v8_1/delete_deprecated_reports.py +++ /dev/null @@ -1,33 +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(): - """ delete deprecated reports """ - - reports = [ - "Monthly Salary Register", "Customer Addresses And Contacts", - "Supplier Addresses And Contacts" - ] - - for report in reports: - if frappe.db.exists("Report", report): - check_and_update_auto_email_report(report) - frappe.db.commit() - - frappe.delete_doc("Report", report, ignore_permissions=True) - -def check_and_update_auto_email_report(report): - """ delete or update auto email report for deprecated report """ - - auto_email_report = frappe.db.get_value("Auto Email Report", {"report": report}) - if not auto_email_report: - return - - if report == "Monthly Salary Register": - frappe.delete_doc("Auto Email Report", auto_email_report) - - elif report in ["Customer Addresses And Contacts", "Supplier Addresses And Contacts"]: - frappe.db.set_value("Auto Email Report", auto_email_report, "report", report) \ No newline at end of file diff --git a/erpnext/patches/v8_1/gst_fixes.py b/erpnext/patches/v8_1/gst_fixes.py deleted file mode 100644 index 34255eb0a4..0000000000 --- a/erpnext/patches/v8_1/gst_fixes.py +++ /dev/null @@ -1,62 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_field -from erpnext.regional.address_template.setup import set_up_address_templates - - -def execute(): - company = frappe.get_all('Company', filters = {'country': 'India'}) - if not company: - return - - update_existing_custom_fields() - add_custom_fields() - set_up_address_templates(default_country='India') - frappe.reload_doc("regional", "print_format", "gst_tax_invoice") - - -def update_existing_custom_fields(): - frappe.db.sql("""update `tabCustom Field` set label = 'HSN/SAC' - where fieldname='gst_hsn_code' and label='GST HSN Code' - """) - - frappe.db.sql("""update `tabCustom Field` set print_hide = 1 - where fieldname in ('customer_gstin', 'supplier_gstin', 'company_gstin') - """) - - frappe.db.sql("""update `tabCustom Field` set insert_after = 'address_display' - where fieldname in ('customer_gstin', 'supplier_gstin') - """) - - frappe.db.sql("""update `tabCustom Field` set insert_after = 'company_address_display' - where fieldname = 'company_gstin' - """) - - frappe.db.sql("""update `tabCustom Field` set insert_after = 'description' - where fieldname='gst_hsn_code' and dt in ('Sales Invoice Item', 'Purchase Invoice Item') - """) - - -def add_custom_fields(): - hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', - fieldtype='Data', options='item_code.gst_hsn_code', insert_after='description') - - custom_fields = { - 'Address': [ - dict(fieldname='gst_state_number', label='GST State Number', - fieldtype='Int', insert_after='gst_state'), - ], - 'Sales Invoice': [ - dict(fieldname='invoice_copy', label='Invoice Copy', - fieldtype='Select', insert_after='project', print_hide=1, allow_on_submit=1, - options='ORIGINAL FOR RECIPIENT\nDUPLICATE FOR TRANSPORTER\nTRIPLICATE FOR SUPPLIER'), - ], - 'Sales Order Item': [hsn_sac_field], - 'Delivery Note Item': [hsn_sac_field], - 'Purchase Order Item': [hsn_sac_field], - 'Purchase Receipt Item': [hsn_sac_field] - } - - for doctype, fields in custom_fields.items(): - for df in fields: - create_custom_field(doctype, df) diff --git a/erpnext/patches/v8_1/remove_sales_invoice_from_returned_serial_no.py b/erpnext/patches/v8_1/remove_sales_invoice_from_returned_serial_no.py deleted file mode 100644 index 3962f8f1f2..0000000000 --- a/erpnext/patches/v8_1/remove_sales_invoice_from_returned_serial_no.py +++ /dev/null @@ -1,18 +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_doctype("Serial No") - - frappe.db.sql(""" - update - `tabSerial No` - set - sales_invoice = NULL - where - sales_invoice in (select return_against from - `tabSales Invoice` where docstatus =1 and is_return=1) - and sales_invoice is not null and sales_invoice !='' """) \ No newline at end of file diff --git a/erpnext/patches/v8_1/removed_report_support_hours.py b/erpnext/patches/v8_1/removed_report_support_hours.py deleted file mode 100644 index 0936b2231b..0000000000 --- a/erpnext/patches/v8_1/removed_report_support_hours.py +++ /dev/null @@ -1,14 +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.db.sql(""" update `tabAuto Email Report` set report = %s - where name = %s""", ('Support Hour Distribution', 'Support Hours')) - - frappe.db.sql(""" update `tabCustom Role` set report = %s - where report = %s""", ('Support Hour Distribution', 'Support Hours')) - - frappe.delete_doc('Report', 'Support Hours') \ No newline at end of file diff --git a/erpnext/patches/v8_1/set_delivery_date_in_so_item.py b/erpnext/patches/v8_1/set_delivery_date_in_so_item.py deleted file mode 100644 index af2d28b857..0000000000 --- a/erpnext/patches/v8_1/set_delivery_date_in_so_item.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Sales Order") - frappe.reload_doctype("Sales Order Item") - - if "final_delivery_date" in frappe.db.get_table_columns("Sales Order"): - frappe.db.sql(""" - update `tabSales Order` - set delivery_date = final_delivery_date - where (delivery_date is null or delivery_date = '0000-00-00') - and order_type = 'Sales'""") - - frappe.db.sql(""" - update `tabSales Order` so, `tabSales Order Item` so_item - set so_item.delivery_date = so.delivery_date - where so.name = so_item.parent - and so.order_type = 'Sales' - and (so_item.delivery_date is null or so_item.delivery_date = '0000-00-00') - and (so.delivery_date is not null and so.delivery_date != '0000-00-00') - """) \ No newline at end of file diff --git a/erpnext/patches/v8_1/update_expense_claim_status.py b/erpnext/patches/v8_1/update_expense_claim_status.py deleted file mode 100644 index 4c1b85a13f..0000000000 --- a/erpnext/patches/v8_1/update_expense_claim_status.py +++ /dev/null @@ -1,23 +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_doctype('Expense Claim') - - for data in frappe.db.sql(""" select name from `tabExpense Claim` - where (docstatus=1 and total_sanctioned_amount=0 and status = 'Paid') or - (docstatus = 1 and approval_status = 'Rejected' and total_sanctioned_amount > 0)""", as_dict=1): - doc = frappe.get_doc('Expense Claim', data.name) - if doc.approval_status == 'Rejected': - for d in doc.expenses: - d.db_set("sanctioned_amount", 0, update_modified = False) - doc.db_set("total_sanctioned_amount", 0, update_modified = False) - - frappe.db.sql(""" delete from `tabGL Entry` where voucher_type = 'Expense Claim' - and voucher_no = %s""", (doc.name)) - - doc.set_status() - doc.db_set("status", doc.status, update_modified = False) \ No newline at end of file diff --git a/erpnext/patches/v8_1/update_gst_state.py b/erpnext/patches/v8_1/update_gst_state.py deleted file mode 100644 index 7aaf2d5ff3..0000000000 --- a/erpnext/patches/v8_1/update_gst_state.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.regional.india import states - -def execute(): - company = frappe.get_all('Company', filters = {'country': 'India'}) - if not company: - return - - if not frappe.db.get_value("Custom Field", filters={'fieldname':'gst_state'}): - return - - frappe.db.sql("update `tabCustom Field` set options=%s where fieldname='gst_state'", '\n'.join(states)) - frappe.db.sql("update `tabAddress` set gst_state='Chhattisgarh' where gst_state='Chattisgarh'") - frappe.db.sql("update `tabAddress` set gst_state_number='05' where gst_state='Uttarakhand'") diff --git a/erpnext/patches/v8_10/__init__.py b/erpnext/patches/v8_10/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v8_10/change_default_customer_credit_days.py b/erpnext/patches/v8_10/change_default_customer_credit_days.py deleted file mode 100644 index 992be17da0..0000000000 --- a/erpnext/patches/v8_10/change_default_customer_credit_days.py +++ /dev/null @@ -1,89 +0,0 @@ -from __future__ import unicode_literals -import frappe - - -def execute(): - frappe.reload_doc("selling", "doctype", "customer") - frappe.reload_doc("buying", "doctype", "supplier") - frappe.reload_doc("setup", "doctype", "supplier_type") - frappe.reload_doc("accounts", "doctype", "payment_term") - frappe.reload_doc("accounts", "doctype", "payment_terms_template_detail") - frappe.reload_doc("accounts", "doctype", "payment_terms_template") - - payment_terms = [] - records = [] - for doctype in ("Customer", "Supplier", "Supplier Type"): - credit_days = frappe.db.sql(""" - SELECT DISTINCT `credit_days`, `credit_days_based_on`, `name` - from `tab{0}` - where - ((credit_days_based_on='Fixed Days' or credit_days_based_on is null) - and credit_days is not null) - or credit_days_based_on='Last Day of the Next Month' - """.format(doctype)) - - credit_records = ((record[0], record[1], record[2]) for record in credit_days) - for days, based_on, party_name in credit_records: - if based_on == "Fixed Days": - pyt_template_name = 'Default Payment Term - N{0}'.format(days) - else: - pyt_template_name = 'Default Payment Term - EO2M' - - if not frappe.db.exists("Payment Terms Template", pyt_template_name): - payment_term = make_payment_term(days, based_on) - template = make_template(payment_term) - else: - template = frappe.get_doc("Payment Terms Template", pyt_template_name) - - payment_terms.append('WHEN `name`={0} THEN {1}'.format(frappe.db.escape(party_name), template.template_name)) - records.append(frappe.db.escape(party_name)) - - begin_query_str = "UPDATE `tab{0}` SET `payment_terms` = CASE ".format(doctype) - value_query_str = " ".join(payment_terms) - cond_query_str = " ELSE `payment_terms` END WHERE " - - if records: - frappe.db.sql( - begin_query_str + value_query_str + cond_query_str + '`name` IN %s', - (records,) - ) - - -def make_template(payment_term): - doc = frappe.new_doc('Payment Terms Template Detail') - doc.payment_term = payment_term.payment_term_name - doc.due_date_based_on = payment_term.due_date_based_on - doc.invoice_portion = payment_term.invoice_portion - doc.description = payment_term.description - doc.credit_days = payment_term.credit_days - doc.credit_months = payment_term.credit_months - - template = frappe.new_doc('Payment Terms Template') - template.template_name = 'Default Payment Term - {0}'.format(payment_term.payment_term_name) - template.append('terms', doc) - template.save() - - return template - - -def make_payment_term(days, based_on): - based_on_map = { - 'Fixed Days': 'Day(s) after invoice date', - 'Last Day of the Next Month': 'Month(s) after the end of the invoice month' - } - - doc = frappe.new_doc('Payment Term') - doc.due_date_based_on = based_on_map.get(based_on) - doc.invoice_portion = 100 - - if based_on == 'Fixed Days': - doc.credit_days = days - doc.description = 'Net payable within {0} days'.format(days) - doc.payment_term_name = 'N{0}'.format(days) - else: - doc.credit_months = 1 - doc.description = 'Net payable by the end of next month' - doc.payment_term_name = 'EO2M' - - doc.save() - return doc diff --git a/erpnext/patches/v8_3/__init__.py b/erpnext/patches/v8_3/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v8_3/set_restrict_to_domain_for_module_def.py b/erpnext/patches/v8_3/set_restrict_to_domain_for_module_def.py deleted file mode 100644 index 6c4c6d5bd8..0000000000 --- a/erpnext/patches/v8_3/set_restrict_to_domain_for_module_def.py +++ /dev/null @@ -1,9 +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(): - """ set the restrict to domain in module def """ - pass \ No newline at end of file diff --git a/erpnext/patches/v8_3/update_company_total_sales.py b/erpnext/patches/v8_3/update_company_total_sales.py deleted file mode 100644 index 78efecb387..0000000000 --- a/erpnext/patches/v8_3/update_company_total_sales.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.setup.doctype.company.company import update_company_current_month_sales, update_company_monthly_sales - -def execute(): - '''Update company monthly sales history based on sales invoices''' - frappe.reload_doctype("Company") - companies = [d['name'] for d in frappe.get_list("Company")] - - for company in companies: - update_company_current_month_sales(company) - update_company_monthly_sales(company) diff --git a/erpnext/patches/v8_4/__init__.py b/erpnext/patches/v8_4/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v8_4/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v8_4/make_scorecard_records.py b/erpnext/patches/v8_4/make_scorecard_records.py deleted file mode 100644 index 73afa277b4..0000000000 --- a/erpnext/patches/v8_4/make_scorecard_records.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records -def execute(): - frappe.reload_doc('buying', 'doctype', 'supplier_scorecard_variable') - frappe.reload_doc('buying', 'doctype', 'supplier_scorecard_standing') - make_default_records() \ No newline at end of file diff --git a/erpnext/patches/v8_5/__init__.py b/erpnext/patches/v8_5/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v8_5/fix_tax_breakup_for_non_invoice_docs.py b/erpnext/patches/v8_5/fix_tax_breakup_for_non_invoice_docs.py deleted file mode 100644 index 82beba3770..0000000000 --- a/erpnext/patches/v8_5/fix_tax_breakup_for_non_invoice_docs.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.regional.india.setup import make_custom_fields -from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_html - -def execute(): - companies = [d.name for d in frappe.get_all('Company', filters = {'country': 'India'})] - if not companies: - return - - make_custom_fields() - - # update invoice copy value - values = ["Original for Recipient", "Duplicate for Transporter", - "Duplicate for Supplier", "Triplicate for Supplier"] - for d in values: - frappe.db.sql("update `tabSales Invoice` set invoice_copy=%s where invoice_copy=%s", (d, d)) - - # update tax breakup in transactions made after 1st July 2017 - doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", - "Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"] - - for doctype in doctypes: - frappe.reload_doctype(doctype) - - date_field = "posting_date" - if doctype in ["Quotation", "Sales Order", "Supplier Quotation", "Purchase Order"]: - date_field = "transaction_date" - - records = [d.name for d in frappe.get_all(doctype, filters={ - "docstatus": ["!=", 2], - date_field: [">=", "2017-07-01"], - "company": ["in", companies], - "total_taxes_and_charges": [">", 0], - "other_charges_calculation": "" - })] - if records: - frappe.db.sql(""" - update `tab%s Item` dt_item - set gst_hsn_code = (select gst_hsn_code from tabItem where name=dt_item.item_code) - where parent in (%s) - and (gst_hsn_code is null or gst_hsn_code = '') - """ % (doctype, ', '.join(['%s']*len(records))), tuple(records)) - - for record in records: - doc = frappe.get_doc(doctype, record) - html = get_itemised_tax_breakup_html(doc) - doc.db_set("other_charges_calculation", html, update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v8_5/remove_project_type_property_setter.py b/erpnext/patches/v8_5/remove_project_type_property_setter.py deleted file mode 100644 index 70a08f5377..0000000000 --- a/erpnext/patches/v8_5/remove_project_type_property_setter.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - ps = frappe.db.get_value('Property Setter', dict(doc_type='Project', field_name='project_type', - property='options')) - if ps: - frappe.delete_doc('Property Setter', ps) - - project_types = frappe.db.sql_list('select distinct project_type from tabProject') - - for project_type in project_types: - if project_type and not frappe.db.exists("Project Type", project_type): - p_type = frappe.get_doc({ - "doctype": "Project Type", - "project_type": project_type - }) - p_type.insert() \ No newline at end of file diff --git a/erpnext/patches/v8_5/remove_quotations_route_in_sidebar.py b/erpnext/patches/v8_5/remove_quotations_route_in_sidebar.py deleted file mode 100644 index 2d7df4a179..0000000000 --- a/erpnext/patches/v8_5/remove_quotations_route_in_sidebar.py +++ /dev/null @@ -1,16 +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_doctype("Portal Settings") - - frappe.db.sql(""" - delete from - `tabPortal Menu Item` - where - (route = '/quotations' and title = 'Supplier Quotation') - or (route = '/quotation' and title = 'Quotations') - """) \ No newline at end of file diff --git a/erpnext/patches/v8_5/set_default_mode_of_payment.py b/erpnext/patches/v8_5/set_default_mode_of_payment.py deleted file mode 100644 index 34ecbb0a3c..0000000000 --- a/erpnext/patches/v8_5/set_default_mode_of_payment.py +++ /dev/null @@ -1,17 +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_doctype("POS Profile") - frappe.reload_doctype("Sales Invoice Payment") - - frappe.db.sql(""" - update - `tabSales Invoice Payment` - set `tabSales Invoice Payment`.default = 1 - where - `tabSales Invoice Payment`.parenttype = 'POS Profile' - and `tabSales Invoice Payment`.idx=1""") \ No newline at end of file diff --git a/erpnext/patches/v8_5/update_customer_group_in_POS_profile.py b/erpnext/patches/v8_5/update_customer_group_in_POS_profile.py deleted file mode 100644 index 2661914401..0000000000 --- a/erpnext/patches/v8_5/update_customer_group_in_POS_profile.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('POS Profile') - customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') - if customer_group: - frappe.db.sql(""" update `tabPOS Profile` - set customer_group = %s where customer_group is null """, (customer_group)) \ No newline at end of file diff --git a/erpnext/patches/v8_5/update_existing_data_in_project_type.py b/erpnext/patches/v8_5/update_existing_data_in_project_type.py deleted file mode 100644 index 497da0602e..0000000000 --- a/erpnext/patches/v8_5/update_existing_data_in_project_type.py +++ /dev/null @@ -1,19 +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("projects", "doctype", "project_type") - frappe.reload_doc("projects", "doctype", "project") - - project_types = ["Internal", "External", "Other"] - - for project_type in project_types: - if not frappe.db.exists("Project Type", project_type): - p_type = frappe.get_doc({ - "doctype": "Project Type", - "project_type": project_type - }) - p_type.insert() \ No newline at end of file diff --git a/erpnext/patches/v8_6/__init__.py b/erpnext/patches/v8_6/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v8_6/point_sms_doctype_module_to_frappe_core.py b/erpnext/patches/v8_6/point_sms_doctype_module_to_frappe_core.py deleted file mode 100644 index 014a74abe3..0000000000 --- a/erpnext/patches/v8_6/point_sms_doctype_module_to_frappe_core.py +++ /dev/null @@ -1,9 +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.db.sql('''UPDATE `tabDocType` SET module="Core" - WHERE name IN ("SMS Parameter", "SMS Settings");''') \ No newline at end of file diff --git a/erpnext/patches/v8_6/rename_bom_update_tool.py b/erpnext/patches/v8_6/rename_bom_update_tool.py deleted file mode 100644 index ef5f335e45..0000000000 --- a/erpnext/patches/v8_6/rename_bom_update_tool.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.delete_doc_if_exists("DocType", "BOM Replace Tool") - - frappe.reload_doctype("BOM") - frappe.db.sql("update tabBOM set conversion_rate=1 where conversion_rate is null or conversion_rate=0") - frappe.db.sql("update tabBOM set set_rate_of_sub_assembly_item_based_on_bom=1") \ No newline at end of file diff --git a/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py b/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py deleted file mode 100644 index db4f94748e..0000000000 --- a/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py +++ /dev/null @@ -1,11 +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(): - # Set write permission to permlevel 1 for sales manager role in Quotation doctype - frappe.db.sql(""" update `tabCustom DocPerm` set `tabCustom DocPerm`.write = 1 - where `tabCustom DocPerm`.parent = 'Quotation' and `tabCustom DocPerm`.role = 'Sales Manager' - and `tabCustom DocPerm`.permlevel = 1 """) \ No newline at end of file diff --git a/erpnext/patches/v8_6/update_timesheet_company_from_PO.py b/erpnext/patches/v8_6/update_timesheet_company_from_PO.py deleted file mode 100644 index 2d46bee7ca..0000000000 --- a/erpnext/patches/v8_6/update_timesheet_company_from_PO.py +++ /dev/null @@ -1,15 +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_doctype('Timesheet') - company = frappe.get_all('Company') - - #Check more than one company exists - if len(company) > 1: - frappe.db.sql(""" update `tabTimesheet` set `tabTimesheet`.company = - (select company from `tabWork Order` where name = `tabTimesheet`.work_order) - where workn_order is not null and work_order !=''""") \ No newline at end of file diff --git a/erpnext/patches/v8_7/__init__.py b/erpnext/patches/v8_7/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v8_7/fix_purchase_receipt_status.py b/erpnext/patches/v8_7/fix_purchase_receipt_status.py deleted file mode 100644 index 99ecb44214..0000000000 --- a/erpnext/patches/v8_7/fix_purchase_receipt_status.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - # there is no more status called "Submitted", there was an old issue that used - # to set it as Submitted, fixed in this commit - frappe.db.sql(""" - update - `tabPurchase Receipt` - set - status = 'To Bill' - where - status = 'Submitted'""") \ No newline at end of file diff --git a/erpnext/patches/v8_7/make_subscription_from_recurring_data.py b/erpnext/patches/v8_7/make_subscription_from_recurring_data.py deleted file mode 100644 index 2932749116..0000000000 --- a/erpnext/patches/v8_7/make_subscription_from_recurring_data.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import today - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'subscription') - frappe.reload_doc('selling', 'doctype', 'sales_order') - frappe.reload_doc('selling', 'doctype', 'quotation') - frappe.reload_doc('buying', 'doctype', 'purchase_order') - frappe.reload_doc('buying', 'doctype', 'supplier_quotation') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice') - frappe.reload_doc('accounts', 'doctype', 'purchase_invoice') - frappe.reload_doc('stock', 'doctype', 'purchase_receipt') - frappe.reload_doc('stock', 'doctype', 'delivery_note') - frappe.reload_doc('accounts', 'doctype', 'journal_entry') - frappe.reload_doc('accounts', 'doctype', 'payment_entry') - - for doctype in ['Sales Order', 'Sales Invoice', 'Purchase Order', 'Purchase Invoice']: - date_field = "transaction_date" - if doctype in ("Sales Invoice", "Purchase Invoice"): - date_field = "posting_date" - - for data in get_data(doctype, date_field): - make_subscription(doctype, data, date_field) - -def get_data(doctype, date_field): - return frappe.db.sql(""" select name, from_date, end_date, recurring_type, recurring_id, - next_date, notify_by_email, notification_email_address, recurring_print_format, - repeat_on_day_of_month, submit_on_creation, docstatus, {0} - from `tab{1}` where is_recurring = 1 and next_date >= %s and docstatus < 2 - order by next_date desc - """.format(date_field, doctype), today(), as_dict=1) - -def make_subscription(doctype, data, date_field): - if data.name == data.recurring_id: - start_date = data.get(date_field) - else: - start_date = frappe.db.get_value(doctype, data.recurring_id, date_field) - - doc = frappe.get_doc({ - 'doctype': 'Subscription', - 'reference_doctype': doctype, - 'reference_document': data.recurring_id, - 'start_date': start_date, - 'end_date': data.end_date, - 'frequency': data.recurring_type, - 'repeat_on_day': data.repeat_on_day_of_month, - 'notify_by_email': data.notify_by_email, - 'recipients': data.notification_email_address, - 'next_schedule_date': data.next_date, - 'submit_on_creation': data.submit_on_creation - }).insert(ignore_permissions=True) - - if data.docstatus == 1: - doc.submit() \ No newline at end of file diff --git a/erpnext/patches/v8_8/__init__.py b/erpnext/patches/v8_8/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v8_8/add_new_fields_in_accounts_settings.py b/erpnext/patches/v8_8/add_new_fields_in_accounts_settings.py deleted file mode 100644 index bd25f15d78..0000000000 --- a/erpnext/patches/v8_8/add_new_fields_in_accounts_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - - -def execute(): - frappe.db.sql( - "INSERT INTO `tabSingles` (`doctype`, `field`, `value`) VALUES ('Accounts Settings', 'allow_stale', '1'), " - "('Accounts Settings', 'stale_days', '1')" - ) diff --git a/erpnext/patches/v8_8/set_bom_rate_as_per_uom.py b/erpnext/patches/v8_8/set_bom_rate_as_per_uom.py deleted file mode 100644 index 5b169cdff2..0000000000 --- a/erpnext/patches/v8_8/set_bom_rate_as_per_uom.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.db.sql(""" - update `tabBOM Item` - set rate = rate * conversion_factor - where uom != stock_uom and docstatus < 2 - and conversion_factor not in (0, 1) - """) \ No newline at end of file diff --git a/erpnext/patches/v8_9/__init__.py b/erpnext/patches/v8_9/__init__.py deleted file mode 100644 index 8b13789179..0000000000 --- a/erpnext/patches/v8_9/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/erpnext/patches/v8_9/add_setup_progress_actions.py b/erpnext/patches/v8_9/add_setup_progress_actions.py deleted file mode 100644 index 77501073cf..0000000000 --- a/erpnext/patches/v8_9/add_setup_progress_actions.py +++ /dev/null @@ -1,47 +0,0 @@ - -from __future__ import unicode_literals -import frappe -from frappe import _ - -def execute(): - """Add setup progress actions""" - if not frappe.db.exists('DocType', 'Setup Progress') or not frappe.db.exists('DocType', 'Setup Progress Action'): - return - - frappe.reload_doc("setup", "doctype", "setup_progress") - frappe.reload_doc("setup", "doctype", "setup_progress_action") - - actions = [ - {"action_name": "Add Company", "action_doctype": "Company", "min_doc_count": 1, "is_completed": 1, - "domains": '[]' }, - {"action_name": "Set Sales Target", "action_doctype": "Company", "min_doc_count": 99, - "action_document": frappe.defaults.get_defaults().get("company") or '', - "action_field": "monthly_sales_target", "is_completed": 0, - "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, - {"action_name": "Add Customers", "action_doctype": "Customer", "min_doc_count": 1, "is_completed": 0, - "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, - {"action_name": "Add Suppliers", "action_doctype": "Supplier", "min_doc_count": 1, "is_completed": 0, - "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, - {"action_name": "Add Products", "action_doctype": "Item", "min_doc_count": 1, "is_completed": 0, - "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, - {"action_name": "Add Programs", "action_doctype": "Program", "min_doc_count": 1, "is_completed": 0, - "domains": '["Education"]' }, - {"action_name": "Add Instructors", "action_doctype": "Instructor", "min_doc_count": 1, "is_completed": 0, - "domains": '["Education"]' }, - {"action_name": "Add Courses", "action_doctype": "Course", "min_doc_count": 1, "is_completed": 0, - "domains": '["Education"]' }, - {"action_name": "Add Rooms", "action_doctype": "Room", "min_doc_count": 1, "is_completed": 0, - "domains": '["Education"]' }, - {"action_name": "Add Users", "action_doctype": "User", "min_doc_count": 4, "is_completed": 0, - "domains": '[]' }, - {"action_name": "Add Letterhead", "action_doctype": "Letter Head", "min_doc_count": 1, "is_completed": 0, - "domains": '[]' } - ] - - setup_progress = frappe.get_doc("Setup Progress", "Setup Progress") - setup_progress.actions = [] - for action in actions: - setup_progress.append("actions", action) - - setup_progress.save(ignore_permissions=True) - diff --git a/erpnext/patches/v8_9/delete_gst_doctypes_for_outside_india_accounts.py b/erpnext/patches/v8_9/delete_gst_doctypes_for_outside_india_accounts.py deleted file mode 100644 index f67af90555..0000000000 --- a/erpnext/patches/v8_9/delete_gst_doctypes_for_outside_india_accounts.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - company = frappe.get_all('Company', filters = {'country': 'India'}) - if not company: - if frappe.db.exists("DocType", "GST Settings"): - frappe.delete_doc("DocType", "GST Settings") - frappe.delete_doc("DocType", "GST HSN Code") - - for report_name in ('GST Sales Register', 'GST Purchase Register', - 'GST Itemised Sales Register', 'GST Itemised Purchase Register'): - - frappe.delete_doc('Report', report_name) \ No newline at end of file diff --git a/erpnext/patches/v8_9/remove_employee_from_salary_structure_parent.py b/erpnext/patches/v8_9/remove_employee_from_salary_structure_parent.py deleted file mode 100644 index 808ae6d527..0000000000 --- a/erpnext/patches/v8_9/remove_employee_from_salary_structure_parent.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if 'employee' in frappe.db.get_table_columns("Salary Structure"): - frappe.db.sql("alter table `tabSalary Structure` drop column employee") diff --git a/erpnext/patches/v8_9/rename_company_sales_target_field.py b/erpnext/patches/v8_9/rename_company_sales_target_field.py deleted file mode 100644 index 5433eb673e..0000000000 --- a/erpnext/patches/v8_9/rename_company_sales_target_field.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - frappe.reload_doc("setup", "doctype", "company") - if frappe.db.has_column('Company', 'sales_target'): - rename_field("Company", "sales_target", "monthly_sales_target") diff --git a/erpnext/patches/v8_9/set_default_customer_group.py b/erpnext/patches/v8_9/set_default_customer_group.py deleted file mode 100644 index cbbe09daf5..0000000000 --- a/erpnext/patches/v8_9/set_default_customer_group.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - selling_settings = frappe.get_single('Selling Settings') - selling_settings.set_default_customer_group_and_territory() - selling_settings.flags.ignore_mandatory = True - selling_settings.save() diff --git a/erpnext/patches/v8_9/set_default_fields_in_variant_settings.py b/erpnext/patches/v8_9/set_default_fields_in_variant_settings.py deleted file mode 100644 index a550d093fa..0000000000 --- a/erpnext/patches/v8_9/set_default_fields_in_variant_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('stock', 'doctype', 'item_variant_settings') - frappe.reload_doc('stock', 'doctype', 'variant_field') - - doc = frappe.get_doc('Item Variant Settings') - doc.set_default_fields() - doc.save() \ No newline at end of file diff --git a/erpnext/patches/v8_9/set_member_party_type.py b/erpnext/patches/v8_9/set_member_party_type.py deleted file mode 100644 index 33bbc11a93..0000000000 --- a/erpnext/patches/v8_9/set_member_party_type.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if not frappe.db.exists("Party Type", "Member"): - frappe.reload_doc("non_profit", "doctype", "member") - party = frappe.new_doc("Party Type") - party.party_type = "Member" - party.save() diff --git a/erpnext/patches/v8_9/set_print_zero_amount_taxes.py b/erpnext/patches/v8_9/set_print_zero_amount_taxes.py deleted file mode 100644 index 3c508eaa09..0000000000 --- a/erpnext/patches/v8_9/set_print_zero_amount_taxes.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from erpnext.setup.install import create_print_zero_amount_taxes_custom_field - -def execute(): - frappe.reload_doc('printing', 'doctype', 'print_style') - frappe.reload_doc('printing', 'doctype', 'print_settings') - create_print_zero_amount_taxes_custom_field() \ No newline at end of file diff --git a/erpnext/patches/v8_9/update_billing_gstin_for_indian_account.py b/erpnext/patches/v8_9/update_billing_gstin_for_indian_account.py deleted file mode 100644 index 24e20409c1..0000000000 --- a/erpnext/patches/v8_9/update_billing_gstin_for_indian_account.py +++ /dev/null @@ -1,15 +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(): - company = frappe.get_all('Company', filters = {'country': 'India'}) - - if company: - for doctype in ['Sales Invoice', 'Delivery Note']: - frappe.db.sql(""" update `tab{0}` - set billing_address_gstin = (select gstin from `tabAddress` - where name = customer_address) - where customer_address is not null and customer_address != ''""".format(doctype)) \ No newline at end of file diff --git a/erpnext/patches/v9_0/__init__.py b/erpnext/patches/v9_0/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/patches/v9_0/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/patches/v9_0/add_healthcare_domain.py b/erpnext/patches/v9_0/add_healthcare_domain.py deleted file mode 100644 index 3c0433b9d4..0000000000 --- a/erpnext/patches/v9_0/add_healthcare_domain.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(): - domain = 'Healthcare' - if not frappe.db.exists('Domain', domain): - frappe.get_doc({ - 'doctype': 'Domain', - 'domain': domain - }).insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py b/erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py deleted file mode 100644 index 8a8c8064dd..0000000000 --- a/erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py +++ /dev/null @@ -1,38 +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(): - if frappe.db.table_exists("POS Profile User"): - frappe.reload_doc('accounts', 'doctype', 'pos_profile_user') - - frappe.db.sql(""" update `tabPOS Profile User`, - (select `tabPOS Profile User`.name from `tabPOS Profile User`, `tabPOS Profile` - where `tabPOS Profile`.name = `tabPOS Profile User`.parent - group by `tabPOS Profile User`.user, `tabPOS Profile`.company) as pfu - set - `tabPOS Profile User`.default = 1 - where `tabPOS Profile User`.name = pfu.name""") - else: - doctype = 'POS Profile' - frappe.reload_doc('accounts', 'doctype', doctype) - frappe.reload_doc('accounts', 'doctype', 'pos_profile_user') - frappe.reload_doc('accounts', 'doctype', 'pos_item_group') - frappe.reload_doc('accounts', 'doctype', 'pos_customer_group') - - for doc in frappe.get_all(doctype): - _doc = frappe.get_doc(doctype, doc.name) - user = frappe.db.get_value(doctype, doc.name, 'user') - - if not user: continue - - _doc.append('applicable_for_users', { - 'user': user, - 'default': 1 - }) - - _doc.flags.ignore_validate = True - _doc.flags.ignore_mandatory = True - _doc.save() \ No newline at end of file diff --git a/erpnext/patches/v9_0/copy_old_fees_field_data.py b/erpnext/patches/v9_0/copy_old_fees_field_data.py deleted file mode 100644 index 14278209c7..0000000000 --- a/erpnext/patches/v9_0/copy_old_fees_field_data.py +++ /dev/null @@ -1,15 +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(): - # 'Schools' module changed to the 'Education' - # frappe.reload_doc("schools", "doctype", "fees") - frappe.reload_doc("education", "doctype", "fees") - - if "total_amount" not in frappe.db.get_table_columns("Fees"): - return - - frappe.db.sql("""update tabFees set grand_total=total_amount where grand_total = 0.0""") \ No newline at end of file diff --git a/erpnext/patches/v9_0/remove_non_existing_warehouse_from_stock_settings.py b/erpnext/patches/v9_0/remove_non_existing_warehouse_from_stock_settings.py deleted file mode 100644 index c685bbc681..0000000000 --- a/erpnext/patches/v9_0/remove_non_existing_warehouse_from_stock_settings.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - default_warehouse = frappe.db.get_value("Stock Settings", None, "default_warehouse") - if default_warehouse: - if not frappe.db.get_value("Warehouse", {"name": default_warehouse}): - frappe.db.set_value("Stock Settings", None, "default_warehouse", "") \ No newline at end of file diff --git a/erpnext/patches/v9_0/remove_subscription_module.py b/erpnext/patches/v9_0/remove_subscription_module.py deleted file mode 100644 index 493873f3e8..0000000000 --- a/erpnext/patches/v9_0/remove_subscription_module.py +++ /dev/null @@ -1,9 +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(): - if frappe.db.exists('Module Def', 'Subscription'): - frappe.db.sql(""" delete from `tabModule Def` where name = 'Subscription'""") \ No newline at end of file diff --git a/erpnext/patches/v9_0/revert_manufacturing_user_role.py b/erpnext/patches/v9_0/revert_manufacturing_user_role.py deleted file mode 100644 index f38b7f29ce..0000000000 --- a/erpnext/patches/v9_0/revert_manufacturing_user_role.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if 'Manufacturing' in frappe.get_active_domains(): return - - role = 'Manufacturing User' - frappe.db.set_value('Role', role, 'restrict_to_domain', '') - frappe.db.set_value('Role', role, 'disabled', 0) - - users = frappe.get_all('Has Role', filters = { - 'parenttype': 'User', - 'role': ('in', ['System Manager', 'Manufacturing Manager']) - }, fields=['parent'], as_list=1) - - for user in users: - _user = frappe.get_doc('User', user[0]) - _user.append('roles', { - 'role': role - }) - _user.flags.ignore_validate = True - _user.save() diff --git a/erpnext/patches/v9_0/set_pos_profile_name.py b/erpnext/patches/v9_0/set_pos_profile_name.py deleted file mode 100644 index a3a9735215..0000000000 --- a/erpnext/patches/v9_0/set_pos_profile_name.py +++ /dev/null @@ -1,24 +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(): - doctype = 'POS Profile' - frappe.reload_doctype(doctype) - - for pos in frappe.get_all(doctype, filters={'disabled': 0}): - doc = frappe.get_doc(doctype, pos.name) - - if not doc.user: continue - - try: - pos_profile_name = doc.user + ' - ' + doc.company - doc.flags.ignore_validate = True - doc.flags.ignore_mandatory = True - doc.save() - - frappe.rename_doc(doctype, doc.name, pos_profile_name, force=True) - except frappe.LinkValidationError: - frappe.db.set_value("POS Profile", doc.name, 'disabled', 1) diff --git a/erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py b/erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py deleted file mode 100644 index 3d012978fa..0000000000 --- a/erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py +++ /dev/null @@ -1,24 +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(): - for doctype in ("Material Request", "Purchase Order"): - frappe.reload_doctype(doctype) - frappe.reload_doctype(doctype + " Item") - - if not frappe.db.has_column(doctype, "schedule_date"): - continue - - #Update only submitted MR - for record in frappe.get_all(doctype, filters= [["docstatus", "=", 1]], fields=["name"]): - doc = frappe.get_doc(doctype, record) - if doc.items: - if not doc.schedule_date: - schedule_dates = [d.schedule_date for d in doc.items if d.schedule_date] - if len(schedule_dates) > 0: - min_schedule_date = min(schedule_dates) - frappe.db.set_value(doctype, record, - "schedule_date", min_schedule_date, update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v9_0/set_shipping_type_for_existing_shipping_rules.py b/erpnext/patches/v9_0/set_shipping_type_for_existing_shipping_rules.py deleted file mode 100644 index 5092695b7d..0000000000 --- a/erpnext/patches/v9_0/set_shipping_type_for_existing_shipping_rules.py +++ /dev/null @@ -1,18 +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_doctype("Shipping Rule") - - # default "calculate_based_on" - frappe.db.sql('''update `tabShipping Rule` - set calculate_based_on = "Net Weight" - where ifnull(calculate_based_on, '') = '' ''') - - # default "shipping_rule_type" - frappe.db.sql('''update `tabShipping Rule` - set shipping_rule_type = "Selling" - where ifnull(shipping_rule_type, '') = '' ''') diff --git a/erpnext/patches/v9_0/set_uoms_in_variant_field.py b/erpnext/patches/v9_0/set_uoms_in_variant_field.py deleted file mode 100644 index 9e783d99be..0000000000 --- a/erpnext/patches/v9_0/set_uoms_in_variant_field.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - - -def execute(): - doc = frappe.get_doc('Item Variant Settings') - variant_field_names = [vf.field_name for vf in doc.fields] - if 'uoms' not in variant_field_names: - doc.append( - 'fields', { - 'field_name': 'uoms' - } - ) - doc.save() diff --git a/erpnext/patches/v9_0/set_variant_item_description.py b/erpnext/patches/v9_0/set_variant_item_description.py deleted file mode 100644 index 82d6148508..0000000000 --- a/erpnext/patches/v9_0/set_variant_item_description.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import cstr - -def execute(): - ''' - Issue: - While copying data from template item to variant item, - the system appending description multiple times to the respective variant. - - Purpose: - Check variant description, - if variant have user defined description remove all system appended descriptions - else replace multiple system generated descriptions with single description - - Steps: - 1. Get all variant items - 2. Create system generated variant description - 3. If variant have user defined description, remove all system generated descriptions - 4. If variant description only contains system generated description, - replace multiple descriptions by new description. - ''' - for item in frappe.db.sql(""" select name from tabItem - where ifnull(variant_of, '') != '' """,as_dict=1): - variant = frappe.get_doc("Item", item.name) - temp_variant_description = '\n' - - if variant.attributes: - for d in variant.attributes: - temp_variant_description += "
    " + d.attribute + ": " + cstr(d.attribute_value) + "
    " - - variant_description = variant.description.replace(temp_variant_description, '').rstrip() - if variant_description: - splitted_desc = variant.description.strip().split(temp_variant_description) - - if len(splitted_desc) > 2: - if splitted_desc[0] == '': - variant_description = temp_variant_description + variant_description - elif splitted_desc[1] == '' or splitted_desc[1] == '\n': - variant_description += temp_variant_description - variant.db_set('description', variant_description, update_modified=False) - else: - variant.db_set('description', variant_description, update_modified=False) - - else: - variant.db_set('description', temp_variant_description, update_modified=False) \ No newline at end of file diff --git a/erpnext/patches/v9_0/student_admission_childtable_migrate.py b/erpnext/patches/v9_0/student_admission_childtable_migrate.py deleted file mode 100644 index a5712c76dc..0000000000 --- a/erpnext/patches/v9_0/student_admission_childtable_migrate.py +++ /dev/null @@ -1,31 +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(): - # 'Schools' module changed to the 'Education' - # frappe.reload_doc('schools', 'doctype', 'Student Admission Program') - # frappe.reload_doc('schools', 'doctype', 'student_admission') - frappe.reload_doc('education', 'doctype', 'Student Admission Program') - frappe.reload_doc('education', 'doctype', 'student_admission') - - if "program" not in frappe.db.get_table_columns("Student Admission"): - return - - student_admissions = frappe.get_all("Student Admission", fields=["name", "application_fee", \ - "naming_series_for_student_applicant", "program", "introduction", "eligibility"]) - for student_admission in student_admissions: - doc = frappe.get_doc("Student Admission", student_admission.name) - doc.append("program_details", { - "program": student_admission.get("program"), - "application_fee": student_admission.get("application_fee"), - "applicant_naming_series": student_admission.get("naming_series_for_student_applicant"), - }) - if student_admission.eligibility and student_admission.introduction: - doc.introduction = student_admission.introduction + "
    " + \ - student_admission.eligibility + "
    " - doc.flags.ignore_validate = True - doc.flags.ignore_mandatory = True - doc.save() diff --git a/erpnext/patches/v9_0/update_employee_loan_details.py b/erpnext/patches/v9_0/update_employee_loan_details.py deleted file mode 100644 index ef8d32855f..0000000000 --- a/erpnext/patches/v9_0/update_employee_loan_details.py +++ /dev/null @@ -1,24 +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('Payroll', 'doctype', 'salary_slip_loan') - frappe.reload_doc('Payroll', 'doctype', 'salary_slip') - - for data in frappe.db.sql(""" select name, - start_date, end_date, total_loan_repayment - from - `tabSalary Slip` - where - docstatus < 2 and ifnull(total_loan_repayment, 0) > 0""", as_dict=1): - salary_slip = frappe.get_doc('Salary Slip', data.name) - salary_slip.set_loan_repayment() - - if salary_slip.total_loan_repayment == data.total_loan_repayment: - for row in salary_slip.loans: - row.db_update() - - salary_slip.db_update() diff --git a/erpnext/patches/v9_0/update_multi_uom_fields_in_material_request.py b/erpnext/patches/v9_0/update_multi_uom_fields_in_material_request.py deleted file mode 100644 index 45610ed5a7..0000000000 --- a/erpnext/patches/v9_0/update_multi_uom_fields_in_material_request.py +++ /dev/null @@ -1,12 +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_doctype('Material Request') - frappe.reload_doctype('Material Request Item') - - frappe.db.sql(""" update `tabMaterial Request Item` - set stock_uom = uom, stock_qty = qty, conversion_factor = 1.0""") \ No newline at end of file diff --git a/erpnext/patches/v9_1/__init__.py b/erpnext/patches/v9_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v9_1/create_issue_opportunity_type.py b/erpnext/patches/v9_1/create_issue_opportunity_type.py deleted file mode 100644 index aa8bbd1e79..0000000000 --- a/erpnext/patches/v9_1/create_issue_opportunity_type.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ - -def execute(): - # delete custom field if exists - for doctype, fieldname in (('Issue', 'issue_type'), ('Opportunity', 'opportunity_type')): - custom_field = frappe.db.get_value("Custom Field", {"fieldname": fieldname, 'dt': doctype}) - if custom_field: - frappe.delete_doc("Custom Field", custom_field, ignore_permissions=True) - - frappe.reload_doc('support', 'doctype', 'issue_type') - frappe.reload_doc('support', 'doctype', 'issue') - frappe.reload_doc('crm', 'doctype', 'opportunity_type') - frappe.reload_doc('crm', 'doctype', 'opportunity') - - # rename enquiry_type -> opportunity_type - from frappe.model.utils.rename_field import rename_field - rename_field('Opportunity', 'enquiry_type', 'opportunity_type') - - # create values if already set - for opts in (('Issue', 'issue_type', 'Issue Type'), - ('Opportunity', 'opportunity_type', 'Opportunity Type')): - for d in frappe.db.sql('select distinct {0} from `tab{1}`'.format(opts[1], opts[0])): - if d[0] and not frappe.db.exists(opts[2], d[0]): - frappe.get_doc(dict(doctype = opts[2], name=d[0])).insert() - - # fixtures - for name in ('Hub', _('Sales'), _('Support'), _('Maintenance')): - if not frappe.db.exists('Opportunity Type', name): - frappe.get_doc(dict(doctype = 'Opportunity Type', name=name)).insert() diff --git a/erpnext/patches/v9_2/__init__.py b/erpnext/patches/v9_2/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v9_2/delete_healthcare_domain_default_items.py b/erpnext/patches/v9_2/delete_healthcare_domain_default_items.py deleted file mode 100644 index 54ae18b8e2..0000000000 --- a/erpnext/patches/v9_2/delete_healthcare_domain_default_items.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import getdate - -def execute(): - domain_settings = frappe.get_doc('Domain Settings') - active_domains = [d.domain for d in domain_settings.active_domains] - - if "Healthcare" not in active_domains: - items = ["TTT", "MCH", "LDL", "GTT", "HDL", "BILT", "BILD", "BP", "BS"] - for item_code in items: - try: - item = frappe.db.get_value("Item", {"item_code": item_code}, ["name", "creation"], as_dict=1) - if item and getdate(item.creation) >= getdate("2017-11-10"): - frappe.delete_doc("Item", item.name) - except: - pass \ No newline at end of file diff --git a/erpnext/patches/v9_2/delete_process_payroll.py b/erpnext/patches/v9_2/delete_process_payroll.py deleted file mode 100644 index 91c49f577f..0000000000 --- a/erpnext/patches/v9_2/delete_process_payroll.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.delete_doc("DocType", "Process Payroll") diff --git a/erpnext/patches/v9_2/remove_company_from_patient.py b/erpnext/patches/v9_2/remove_company_from_patient.py deleted file mode 100644 index 1a50088f23..0000000000 --- a/erpnext/patches/v9_2/remove_company_from_patient.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.exists("DocType", "Patient"): - if 'company' in frappe.db.get_table_columns("Patient"): - frappe.db.sql("alter table `tabPatient` drop column company") diff --git a/erpnext/patches/v9_2/rename_net_weight_in_item_master.py b/erpnext/patches/v9_2/rename_net_weight_in_item_master.py deleted file mode 100644 index cad979deab..0000000000 --- a/erpnext/patches/v9_2/rename_net_weight_in_item_master.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - frappe.reload_doc("stock", "doctype", "item") - if frappe.db.has_column('Item', 'net_weight'): - rename_field("Item", "net_weight", "weight_per_unit") diff --git a/erpnext/patches/v9_2/rename_translated_domains_in_en.py b/erpnext/patches/v9_2/rename_translated_domains_in_en.py deleted file mode 100644 index e5a9e2461f..0000000000 --- a/erpnext/patches/v9_2/rename_translated_domains_in_en.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.model.rename_doc import rename_doc - -def execute(): - frappe.reload_doc('stock', 'doctype', 'item') - language = frappe.get_single("System Settings").language - - if language and language.startswith('en'): return - - frappe.local.lang = language - - all_domains = frappe.get_hooks("domains") - - for domain in all_domains: - translated_domain = _(domain, lang=language) - if frappe.db.exists("Domain", translated_domain): - #if domain already exists merged translated_domain and domain - merge = False - if frappe.db.exists("Domain", domain): - merge=True - - rename_doc("Domain", translated_domain, domain, ignore_permissions=True, merge=merge) - - domain_settings = frappe.get_single("Domain Settings") - active_domains = [d.domain for d in domain_settings.active_domains] - - try: - for domain in active_domains: - domain = frappe.get_doc("Domain", domain) - domain.setup_domain() - - if int(frappe.db.get_single_value('System Settings', 'setup_complete')): - domain.setup_sidebar_items() - domain.setup_desktop_icons() - domain.set_default_portal_role() - except frappe.LinkValidationError: - pass \ No newline at end of file diff --git a/erpnext/patches/v9_2/repost_reserved_qty_for_production.py b/erpnext/patches/v9_2/repost_reserved_qty_for_production.py deleted file mode 100644 index 040e655bd8..0000000000 --- a/erpnext/patches/v9_2/repost_reserved_qty_for_production.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("stock", "doctype", "bin") - bins = frappe.db.sql("select name from `tabBin` where reserved_qty_for_production > 0") - for d in bins: - bin_doc = frappe.get_doc("Bin", d[0]) - bin_doc.update_reserved_qty_for_production() diff --git a/erpnext/patches/v9_2/set_item_name_in_production_order.py b/erpnext/patches/v9_2/set_item_name_in_production_order.py deleted file mode 100644 index 1f490e62c8..0000000000 --- a/erpnext/patches/v9_2/set_item_name_in_production_order.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - - frappe.db.sql(""" - update `tabBOM Item` bom, `tabWork Order Item` po_item - set po_item.item_name = bom.item_name, - po_item.description = bom.description - where po_item.item_code = bom.item_code - and (po_item.item_name is null or po_item.description is null) - """) From 3dfbfe87e9e67a3605cf6f92c05a63455fd1bff1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 4 Jun 2021 11:47:09 +0530 Subject: [PATCH 1002/1264] chore: Drop < v10 patches from list v7 backup was restored and upgraded to latest v10.x.x branch. The patches run uptil the upgrade are removed in this change. This means only existing v10 sites are allowed direct upgrade to v13 and newer There are older version patches still left since they're being used in later ERPNext versions too. --- erpnext/patches.txt | 495 -------------------------------------------- 1 file changed, 495 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 93689a0ef3..ed6fefdd87 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -1,494 +1,19 @@ -execute:import unidecode # new requirement -erpnext.patches.v8_0.move_perpetual_inventory_setting -erpnext.patches.v8_9.set_print_zero_amount_taxes erpnext.patches.v12_0.update_is_cancelled_field erpnext.patches.v11_0.rename_production_order_to_work_order erpnext.patches.v11_0.refactor_naming_series erpnext.patches.v11_0.refactor_autoname_naming -erpnext.patches.v10_0.rename_schools_to_education -erpnext.patches.v4_0.validate_v3_patch -erpnext.patches.v4_0.fix_employee_user_id -erpnext.patches.v4_0.remove_employee_role_if_no_employee -erpnext.patches.v4_0.update_user_properties -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 #2020-07-24 -execute:frappe.reload_doc('stock', 'doctype', 'warehouse') # 2017-04-24 -execute:frappe.reload_doc('accounts', 'doctype', 'sales_invoice') # 2016-08-31 -execute:frappe.reload_doc('selling', 'doctype', 'sales_order') # 2014-01-29 -execute:frappe.reload_doc('selling', 'doctype', 'quotation') # 2014-01-29 -execute:frappe.reload_doc('stock', 'doctype', 'delivery_note') # 2014-01-29 -erpnext.patches.v4_0.reload_sales_print_format -execute:frappe.reload_doc('accounts', 'doctype', 'purchase_invoice') # 2014-01-29 -execute:frappe.reload_doc('buying', 'doctype', 'purchase_order') # 2014-01-29 -execute:frappe.reload_doc('buying', 'doctype', 'supplier_quotation') # 2014-01-29 -execute:frappe.reload_doc('stock', 'doctype', 'purchase_receipt') # 2014-01-29 -execute:frappe.reload_doc('accounts', 'doctype', 'pos_setting') # 2014-01-29 -execute:frappe.reload_doc('selling', 'doctype', 'customer') # 2014-01-29 -execute:frappe.reload_doc('buying', 'doctype', 'supplier') # 2014-01-29 -execute:frappe.reload_doc('accounts', 'doctype', 'asset_category') -execute:frappe.reload_doc('accounts', 'doctype', 'pricing_rule') -erpnext.patches.v4_0.map_charge_to_taxes_and_charges -execute:frappe.reload_doc('support', 'doctype', 'newsletter') # 2014-01-31 -execute:frappe.reload_doc('hr', 'doctype', 'employee') # 2014-02-03 -execute:frappe.db.sql("update tabPage set module='Core' where name='Setup'") -erpnext.patches.v5_2.change_item_selects_to_checks -execute:frappe.reload_doctype('Item') -erpnext.patches.v4_0.fields_to_be_renamed -erpnext.patches.v4_0.rename_sitemap_to_route -erpnext.patches.v7_0.re_route #2016-06-27 -erpnext.patches.v4_0.fix_contact_address -erpnext.patches.v4_0.customer_discount_to_pricing_rule -execute:frappe.db.sql("""delete from `tabWebsite Item Group` where ifnull(item_group, '')=''""") -erpnext.patches.v4_0.remove_module_home_pages -erpnext.patches.v4_0.split_email_settings -erpnext.patches.v4_0.import_country_codes -erpnext.patches.v4_0.countrywise_coa -execute:frappe.delete_doc("DocType", "MIS Control") -execute:frappe.delete_doc("Page", "Financial Statements") -execute:frappe.delete_doc("DocType", "Stock Ledger") -execute:frappe.delete_doc("DocType", "Grade") -execute:frappe.db.sql("delete from `tabWebsite Item Group` where ifnull(item_group, '')=''") -execute:frappe.delete_doc("Print Format", "SalesInvoice") -execute:import frappe.defaults;frappe.defaults.clear_default("price_list_currency") -erpnext.patches.v4_0.update_account_root_type -execute:frappe.delete_doc("Report", "Purchase In Transit") -erpnext.patches.v4_0.new_address_template -execute:frappe.delete_doc("DocType", "SMS Control") -execute:frappe.delete_doc_if_exists("DocType", "Bulk SMS") #2015-08-18 -erpnext.patches.v4_0.fix_case_of_hr_module_def -erpnext.patches.v4_0.fix_address_template - -# WATCHOUT: This patch reload's documents -erpnext.patches.v4_0.reset_permissions_for_masters -erpnext.patches.v6_20x.rename_project_name_to_project #2016-03-14 - -erpnext.patches.v4_0.update_tax_amount_after_discount -execute:frappe.permissions.reset_perms("GL Entry") #2014-06-09 -execute:frappe.permissions.reset_perms("Stock Ledger Entry") #2014-06-09 -erpnext.patches.v4_0.create_custom_fields_for_india_specific_fields -erpnext.patches.v4_0.save_default_letterhead -erpnext.patches.v4_0.update_custom_print_formats_for_renamed_fields -erpnext.patches.v4_0.update_other_charges_in_custom_purchase_print_formats -erpnext.patches.v4_0.create_price_list_if_missing -execute:frappe.db.sql("update `tabItem` set end_of_life=null where end_of_life='0000-00-00'") #2014-06-16 -erpnext.patches.v4_0.update_users_report_view_settings -erpnext.patches.v4_0.set_pricing_rule_for_buying_or_selling -erpnext.patches.v4_1.set_outgoing_email_footer -erpnext.patches.v4_1.fix_sales_order_delivered_status -erpnext.patches.v4_1.fix_delivery_and_billing_status -execute:frappe.db.sql("update `tabAccount` set root_type='Liability' where root_type='Income' and report_type='Balance Sheet'") -execute:frappe.delete_doc("DocType", "Payment to Invoice Matching Tool") # 29-07-2014 -execute:frappe.delete_doc("DocType", "Payment to Invoice Matching Tool Detail") # 29-07-2014 -execute:frappe.delete_doc("Page", "trial-balance") #2014-07-22 -erpnext.patches.v4_2.delete_old_print_formats #2014-07-29 -erpnext.patches.v4_2.toggle_rounded_total #2014-07-30 -erpnext.patches.v4_2.fix_account_master_type -erpnext.patches.v4_2.update_project_milestones -erpnext.patches.v4_2.add_currency_turkish_lira #2014-08-08 -execute:frappe.delete_doc("DocType", "Landed Cost Wizard") -erpnext.patches.v4_2.default_website_style -erpnext.patches.v4_2.set_company_country -erpnext.patches.v4_2.update_sales_order_invoice_field_name -erpnext.patches.v4_2.seprate_manufacture_and_repack -execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance") -execute:frappe.delete_doc("DocType", "Purchase Request") -execute:frappe.delete_doc("DocType", "Purchase Request Item") -erpnext.patches.v4_2.recalculate_bom_cost -erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31 -execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True) -erpnext.patches.v4_4.make_email_accounts -execute:frappe.delete_doc("DocType", "Contact Control") -erpnext.patches.v4_2.discount_amount -erpnext.patches.v4_2.reset_bom_costs -erpnext.patches.v5_0.update_frozen_accounts_permission_role -erpnext.patches.v5_0.update_dn_against_doc_fields -execute:frappe.db.sql("update `tabMaterial Request` set material_request_type = 'Material Transfer' where material_request_type = 'Transfer'") -execute:frappe.reload_doc('stock', 'doctype', 'item') -erpnext.patches.v5_0.set_default_company_in_bom -execute:frappe.reload_doc('crm', 'doctype', 'lead') -execute:frappe.reload_doc('crm', 'doctype', 'opportunity') -erpnext.patches.v5_0.rename_taxes_and_charges_master -erpnext.patches.v5_1.sales_bom_rename -erpnext.patches.v5_0.rename_table_fieldnames -execute:frappe.db.sql("update `tabJournal Entry` set voucher_type='Journal Entry' where ifnull(voucher_type, '')=''") -erpnext.patches.v5_0.is_group -erpnext.patches.v4_2.party_model -erpnext.patches.v5_0.party_model_patch_fix -erpnext.patches.v4_1.fix_jv_remarks -erpnext.patches.v4_2.update_landed_cost_voucher -erpnext.patches.v4_2.set_item_has_batch -erpnext.patches.v4_2.update_stock_uom_for_dn_in_sle -erpnext.patches.v5_0.recalculate_total_amount_in_jv -erpnext.patches.v5_0.update_companywise_payment_account -erpnext.patches.v5_0.remove_birthday_events -erpnext.patches.v5_0.update_item_name_in_bom -erpnext.patches.v5_0.rename_customer_issue -erpnext.patches.v5_0.rename_total_fields -erpnext.patches.v5_0.new_crm_module -erpnext.patches.v5_0.rename_customer_issue -erpnext.patches.v5_0.update_material_transfer_for_manufacture -execute:frappe.reload_doc('crm', 'doctype', 'opportunity_item') -erpnext.patches.v5_0.update_item_description_and_image -erpnext.patches.v5_0.update_material_transferred_for_manufacturing -erpnext.patches.v5_0.stock_entry_update_value -erpnext.patches.v5_0.convert_stock_reconciliation -erpnext.patches.v5_0.update_projects -erpnext.patches.v5_0.item_patches -erpnext.patches.v5_0.update_journal_entry_title -erpnext.patches.v5_0.taxes_and_totals_in_party_currency -erpnext.patches.v5_0.replace_renamed_fields_in_custom_scripts_and_print_formats -erpnext.patches.v5_0.update_from_bom -erpnext.patches.v5_0.update_account_types -erpnext.patches.v5_0.update_sms_sender -erpnext.patches.v5_0.set_appraisal_remarks -erpnext.patches.v5_0.update_time_log_title -erpnext.patches.v7_0.create_warehouse_nestedset -erpnext.patches.v7_0.merge_account_type_stock_and_warehouse_to_stock -erpnext.patches.v7_0.set_is_group_for_warehouse -erpnext.patches.v7_2.stock_uom_in_selling -erpnext.patches.v4_2.repost_sle_for_si_with_no_warehouse -erpnext.patches.v5_0.newsletter -execute:frappe.delete_doc("DocType", "Chart of Accounts") -execute:frappe.delete_doc("DocType", "Style Settings") -erpnext.patches.v5_0.update_opportunity -erpnext.patches.v5_0.opportunity_not_submittable -execute:frappe.permissions.reset_perms("Purchase Taxes and Charges Template") #2014-06-09 -execute:frappe.permissions.reset_perms("Expense Claim Type") #2014-06-19 -erpnext.patches.v5_0.execute_on_doctype_update -erpnext.patches.v4_2.fix_recurring_orders -erpnext.patches.v4_2.delete_gl_entries_for_cancelled_invoices -erpnext.patches.v5_0.project_costing -erpnext.patches.v5_0.update_temporary_account -erpnext.patches.v5_0.update_advance_paid -erpnext.patches.v5_0.link_warehouse_with_account -execute:frappe.delete_doc("Page", "stock-ledger") -execute:frappe.delete_doc("Page","stock-level") -erpnext.patches.v5_0.reclculate_planned_operating_cost_in_production_order -erpnext.patches.v5_0.repost_requested_qty -erpnext.patches.v5_0.fix_taxes_and_totals_in_party_currency -erpnext.patches.v5_0.update_tax_amount_after_discount_in_purchase_cycle -erpnext.patches.v5_0.rename_pos_setting -erpnext.patches.v5_0.update_operation_description -erpnext.patches.v5_0.set_footer_address -execute:frappe.db.set_value("Backup Manager", None, "send_backups_to_dropbox", 1 if frappe.db.get_value("Backup Manager", None, "upload_backups_to_dropbox") in ("Daily", "Weekly") else 0) -execute:frappe.db.sql_list("delete from `tabDocPerm` where parent='Issue' and modified_by='Administrator' and role='Guest'") -erpnext.patches.v5_0.update_item_and_description_again -erpnext.patches.v6_0.multi_currency -erpnext.patches.v7_0.create_budget_record -erpnext.patches.v5_0.repost_gle_for_jv_with_multiple_party -erpnext.patches.v5_0.portal_fixes -erpnext.patches.v5_0.reset_values_in_tools # 02-05-2016 -execute:frappe.delete_doc("Page", "users") -erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again -erpnext.patches.v5_0.index_on_account_and_gl_entry -execute:frappe.db.sql("""delete from `tabProject Task`""") -erpnext.patches.v5_0.update_item_desc_in_invoice -erpnext.patches.v5_1.fix_against_account -execute:frappe.rename_doc("DocType", "Salary Manager", "Process Payroll", force=True) -erpnext.patches.v5_1.rename_roles -erpnext.patches.v5_1.default_bom -execute:frappe.delete_doc("DocType", "Party Type") -execute:frappe.delete_doc("Module Def", "Contacts") -erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items # 30-07-2015 -execute:frappe.reload_doctype("Leave Type") -execute:frappe.db.sql("update `tabLeave Type` set include_holiday=0") -erpnext.patches.v5_4.set_root_and_report_type -erpnext.patches.v5_4.notify_system_managers_regarding_wrong_tax_calculation -erpnext.patches.v5_4.fix_invoice_outstanding -execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' where voucher_type = 'Stock Reconciliation' and ifnull(qty_after_transaction, 0) = 0") -erpnext.patches.v5_4.fix_missing_item_images -erpnext.patches.v5_4.stock_entry_additional_costs -erpnext.patches.v5_4.cleanup_journal_entry #2015-08-14 erpnext.patches.v5_7.update_item_description_based_on_item_master -erpnext.patches.v5_7.item_template_attributes -execute:frappe.delete_doc_if_exists("DocType", "Manage Variants") -execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item") erpnext.patches.v4_2.repost_reserved_qty #2021-03-31 -erpnext.patches.v5_4.update_purchase_cost_against_project -erpnext.patches.v5_8.update_order_reference_in_return_entries -erpnext.patches.v5_8.add_credit_note_print_heading -execute:frappe.delete_doc_if_exists("Print Format", "Credit Note - Negative Invoice") - -# V6.0 -erpnext.patches.v6_0.set_default_title # 2015-09-03 -erpnext.patches.v6_0.default_activity_rate -execute:frappe.db.set_value("Stock Settings", None, "automatically_set_serial_nos_based_on_fifo", 1) -execute:frappe.db.sql("""update `tabProject` set percent_complete=round(percent_complete, 2) where percent_complete is not null""") -erpnext.patches.v6_0.fix_outstanding_amount -erpnext.patches.v6_0.fix_planned_qty -erpnext.patches.v6_2.remove_newsletter_duplicates -erpnext.patches.v6_2.fix_missing_default_taxes_and_lead -erpnext.patches.v6_3.convert_applicable_territory -erpnext.patches.v6_4.round_status_updater_percentages -erpnext.patches.v6_4.repost_gle_for_journal_entries_where_reference_name_missing -erpnext.patches.v6_4.fix_journal_entries_due_to_reconciliation -erpnext.patches.v6_4.fix_status_in_sales_and_purchase_order -erpnext.patches.v6_4.fix_modified_in_sales_order_and_purchase_order -erpnext.patches.v6_4.fix_duplicate_bins -erpnext.patches.v6_4.fix_sales_order_maintenance_status -erpnext.patches.v6_4.email_digest_update - -# delete shopping cart doctypes -execute:frappe.delete_doc_if_exists("DocType", "Applicable Territory") -execute:frappe.delete_doc_if_exists("DocType", "Shopping Cart Price List") -execute:frappe.delete_doc_if_exists("DocType", "Shopping Cart Taxes and Charges Master") - -erpnext.patches.v6_4.set_user_in_contact -erpnext.patches.v6_4.make_image_thumbnail #2015-10-20 -erpnext.patches.v6_5.show_in_website_for_template_item -erpnext.patches.v6_4.fix_expense_included_in_valuation -execute:frappe.delete_doc_if_exists("Report", "Item-wise Last Purchase Rate") -erpnext.patches.v6_6.fix_website_image -erpnext.patches.v6_6.remove_fiscal_year_from_leave_allocation -execute:frappe.delete_doc_if_exists("DocType", "Stock UOM Replace Utility") -erpnext.patches.v6_8.make_webform_standard #2015-11-23 -erpnext.patches.v6_8.move_drop_ship_to_po_items -erpnext.patches.v6_10.fix_ordered_received_billed -erpnext.patches.v6_10.fix_jv_total_amount #2015-11-30 -erpnext.patches.v6_10.email_digest_default_quote -erpnext.patches.v6_10.fix_billed_amount_in_drop_ship_po -erpnext.patches.v6_10.fix_delivery_status_of_drop_ship_item #2015-12-08 -erpnext.patches.v5_8.tax_rule #2015-12-08 -erpnext.patches.v6_12.set_overdue_tasks -erpnext.patches.v6_16.update_billing_status_in_dn_and_pr -erpnext.patches.v6_16.create_manufacturer_records -execute:frappe.db.sql("update `tabPricing Rule` set title=name where title='' or title is null") #2016-01-27 -erpnext.patches.v6_20.set_party_account_currency_in_orders -erpnext.patches.v6_19.comment_feed_communication -erpnext.patches.v6_21.fix_reorder_level -erpnext.patches.v6_21.rename_material_request_fields -erpnext.patches.v6_23.update_stopped_status_to_closed -erpnext.patches.v6_24.set_recurring_id -erpnext.patches.v6_20x.set_compact_print -execute:frappe.delete_doc_if_exists("Web Form", "contact") #2016-03-10 -erpnext.patches.v6_20x.remove_fiscal_year_from_holiday_list -erpnext.patches.v6_24.map_customer_address_to_shipping_address_on_po -erpnext.patches.v6_27.fix_recurring_order_status -erpnext.patches.v6_20x.update_product_bundle_description -erpnext.patches.v7_0.update_party_status #2016-09-22 -erpnext.patches.v7_0.remove_features_setup -erpnext.patches.v7_0.update_home_page -execute:frappe.delete_doc_if_exists("Page", "financial-analytics") -erpnext.patches.v7_0.update_project_in_gl_entry -execute:frappe.db.sql('update tabQuotation set status="Cancelled" where docstatus=2') -execute:frappe.rename_doc("DocType", "Payments", "Sales Invoice Payment", force=True) -erpnext.patches.v7_0.update_mins_to_first_response -erpnext.patches.v6_20x.repost_valuation_rate_for_negative_inventory -erpnext.patches.v7_0.migrate_mode_of_payments_v6_to_v7 -erpnext.patches.v7_0.system_settings_setup_complete -erpnext.patches.v7_0.set_naming_series_for_timesheet #2016-07-27 -execute:frappe.reload_doc('projects', 'doctype', 'project') -execute:frappe.reload_doc('projects', 'doctype', 'project_user') -erpnext.patches.v7_0.convert_timelogbatch_to_timesheet -erpnext.patches.v7_0.convert_timelog_to_timesheet -erpnext.patches.v7_0.move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet -erpnext.patches.v7_0.remove_doctypes_and_reports #2016-10-29 -erpnext.patches.v7_0.update_maintenance_module_in_doctype -erpnext.patches.v7_0.update_prevdoc_values_for_supplier_quotation_item -erpnext.patches.v7_0.rename_advance_table_fields -erpnext.patches.v7_0.rename_salary_components -erpnext.patches.v7_0.rename_prevdoc_fields -erpnext.patches.v7_0.rename_time_sheet_doctype -execute:frappe.delete_doc_if_exists("Report", "Customers Not Buying Since Long Time") -erpnext.patches.v7_0.make_is_group_fieldtype_as_check -execute:frappe.reload_doc('projects', 'doctype', 'timesheet') #2016-09-12 -erpnext.patches.v7_1.rename_field_timesheet -execute:frappe.delete_doc_if_exists("Report", "Employee Holiday Attendance") -execute:frappe.delete_doc_if_exists("DocType", "Payment Tool") -execute:frappe.delete_doc_if_exists("DocType", "Payment Tool Detail") -erpnext.patches.v7_0.setup_account_table_for_expense_claim_type_if_exists -erpnext.patches.v7_0.migrate_schools_to_erpnext -erpnext.patches.v7_1.update_lead_source -erpnext.patches.v6_20x.remove_customer_supplier_roles -erpnext.patches.v7_0.remove_administrator_role_in_doctypes -erpnext.patches.v7_0.rename_fee_amount_to_fee_component -erpnext.patches.v7_0.calculate_total_costing_amount -erpnext.patches.v7_0.fix_nonwarehouse_ledger_gl_entries_for_transactions -erpnext.patches.v7_0.remove_old_earning_deduction_doctypes -erpnext.patches.v7_0.make_guardian -erpnext.patches.v7_0.update_refdoc_in_landed_cost_voucher -erpnext.patches.v7_0.set_material_request_type_in_item -erpnext.patches.v7_0.rename_examination_to_assessment -erpnext.patches.v7_0.set_portal_settings -erpnext.patches.v7_0.update_change_amount_account -erpnext.patches.v7_0.fix_duplicate_icons -erpnext.patches.v7_0.repost_gle_for_pos_sales_return -erpnext.patches.v7_1.update_total_billing_hours -erpnext.patches.v7_1.update_component_type -erpnext.patches.v7_0.repost_gle_for_pos_sales_return -erpnext.patches.v7_0.update_missing_employee_in_timesheet -erpnext.patches.v7_0.update_status_for_timesheet -erpnext.patches.v7_0.set_party_name_in_payment_entry -erpnext.patches.v7_1.set_student_guardian -erpnext.patches.v7_0.update_conversion_factor_in_supplier_quotation_item -erpnext.patches.v7_1.move_sales_invoice_from_parent_to_child_timesheet -execute:frappe.db.sql("update `tabTimesheet` ts, `tabEmployee` emp set ts.employee_name = emp.employee_name where emp.name = ts.employee and ts.employee_name is null and ts.employee is not null") -erpnext.patches.v7_1.fix_link_for_customer_from_lead -execute:frappe.db.sql("delete from `tabTimesheet Detail` where NOT EXISTS (select name from `tabTimesheet` where name = `tabTimesheet Detail`.parent)") -erpnext.patches.v7_0.update_mode_of_payment_type - -execute:frappe.reload_doctype('Employee') #2016-10-18 -execute:frappe.db.sql("update `tabEmployee` set prefered_contact_email = IFNULL(prefered_contact_email,'') ") execute:frappe.reload_doc("Payroll", "doctype", "salary_slip") -execute:frappe.db.sql("update `tabSalary Slip` set posting_date=creation") -execute:frappe.reload_doc("stock", "doctype", "stock_settings") -erpnext.patches.v8_0.create_domain_docs #16-05-2017 -erpnext.patches.v7_1.update_portal_roles -erpnext.patches.v7_1.set_total_amount_currency_in_je -finally:erpnext.patches.v7_0.update_timesheet_communications -erpnext.patches.v7_0.update_status_of_zero_amount_sales_order -erpnext.patches.v7_1.add_field_for_task_dependent -erpnext.patches.v7_0.repost_bin_qty_and_item_projected_qty -erpnext.patches.v7_1.set_prefered_contact_email -execute:frappe.reload_doc('accounts', 'doctype', 'accounts_settings') -execute:frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancellation_of_invoice", 0) -execute:frappe.db.sql("update `tabStock Entry` set total_amount = 0 where purpose in('Repack', 'Manufacture')") -erpnext.patches.v7_1.save_stock_settings -erpnext.patches.v7_0.repost_gle_for_pi_with_update_stock #2016-11-01 -erpnext.patches.v7_1.add_account_user_role_for_timesheet -erpnext.patches.v7_0.set_base_amount_in_invoice_payment_table -erpnext.patches.v7_1.update_invoice_status -erpnext.patches.v7_0.po_status_issue_for_pr_return -erpnext.patches.v7_1.update_missing_salary_component_type -erpnext.patches.v7_1.rename_quality_inspection_field -erpnext.patches.v7_0.update_autoname_field -erpnext.patches.v7_1.update_bom_base_currency -erpnext.patches.v7_0.update_status_of_po_so -erpnext.patches.v7_1.set_budget_against_as_cost_center -erpnext.patches.v7_1.set_currency_exchange_date -erpnext.patches.v7_1.set_sales_person_status -erpnext.patches.v7_1.repost_stock_for_deleted_bins_for_merging_items -erpnext.patches.v7_2.update_website_for_variant -erpnext.patches.v7_2.update_assessment_modules -erpnext.patches.v7_2.update_doctype_status -erpnext.patches.v7_2.update_salary_slips -erpnext.patches.v7_2.delete_fleet_management_module_def -erpnext.patches.v7_2.contact_address_links -erpnext.patches.v7_2.mark_students_active -erpnext.patches.v7_2.set_null_value_to_fields -erpnext.patches.v7_2.update_guardian_name_in_student_master -erpnext.patches.v7_2.update_abbr_in_salary_slips -erpnext.patches.v7_2.rename_evaluation_criteria -erpnext.patches.v7_2.update_party_type -erpnext.patches.v7_2.setup_auto_close_settings -erpnext.patches.v7_2.empty_supplied_items_for_non_subcontracted -erpnext.patches.v7_2.arrear_leave_encashment_as_salary_component -erpnext.patches.v7_2.rename_att_date_attendance -erpnext.patches.v7_2.update_attendance_docstatus -erpnext.patches.v7_2.make_all_assessment_group -erpnext.patches.v8_0.repost_reserved_qty_for_multiple_sales_uom -erpnext.patches.v8_0.addresses_linked_to_lead -execute:frappe.delete_doc('DocType', 'Purchase Common') -erpnext.patches.v8_0.update_stock_qty_value_in_purchase_invoice -erpnext.patches.v8_0.update_supplier_address_in_stock_entry -erpnext.patches.v8_0.rename_is_sample_item_to_allow_zero_valuation_rate -erpnext.patches.v8_0.set_null_to_serial_nos_for_disabled_sales_invoices -erpnext.patches.v8_0.enable_booking_asset_depreciation_automatically -erpnext.patches.v8_0.set_project_copied_from -erpnext.patches.v8_0.update_status_as_paid_for_completed_expense_claim -erpnext.patches.v7_2.stock_uom_in_selling -erpnext.patches.v8_0.revert_manufacturers_table_from_item -erpnext.patches.v8_0.disable_instructor_role -erpnext.patches.v8_0.merge_student_batch_and_student_group -erpnext.patches.v8_0.rename_total_margin_to_rate_with_margin # 11-05-2017 -erpnext.patches.v8_0.fix_status_for_invoices_with_negative_outstanding -erpnext.patches.v8_0.make_payments_table_blank_for_non_pos_invoice -erpnext.patches.v8_0.set_sales_invoice_serial_number_from_delivery_note -erpnext.patches.v8_0.delete_schools_depricated_doctypes -erpnext.patches.v8_0.update_customer_pos_id -erpnext.patches.v8_0.rename_items_in_status_field_of_material_request -erpnext.patches.v8_0.delete_bin_indexes -erpnext.patches.v8_0.move_account_head_from_account_to_warehouse_for_inventory -erpnext.patches.v8_0.change_in_words_varchar_length -erpnext.patches.v8_0.update_stock_qty_value_in_bom_item -erpnext.patches.v8_0.update_sales_cost_in_project -erpnext.patches.v8_0.save_system_settings -erpnext.patches.v8_1.delete_deprecated_reports -erpnext.patches.v9_0.remove_subscription_module -erpnext.patches.v8_7.make_subscription_from_recurring_data erpnext.patches.v8_1.setup_gst_india #2017-06-27 -execute:frappe.reload_doc('regional', 'doctype', 'gst_hsn_code') erpnext.patches.v8_1.removed_roles_from_gst_report_non_indian_account #16-08-2018 -erpnext.patches.v8_1.gst_fixes #2017-07-06 -erpnext.patches.v8_0.update_production_orders -erpnext.patches.v8_1.remove_sales_invoice_from_returned_serial_no -erpnext.patches.v8_1.allow_invoice_copy_to_edit_after_submit -erpnext.patches.v8_1.add_hsn_sac_codes -erpnext.patches.v8_1.update_gst_state #17-07-2017 -erpnext.patches.v8_1.removed_report_support_hours -erpnext.patches.v8_1.add_indexes_in_transaction_doctypes -erpnext.patches.v8_3.set_restrict_to_domain_for_module_def -erpnext.patches.v8_1.update_expense_claim_status -erpnext.patches.v8_3.update_company_total_sales #2017-08-16 -erpnext.patches.v8_4.make_scorecard_records -erpnext.patches.v8_1.set_delivery_date_in_so_item #2017-07-28 -erpnext.patches.v8_5.fix_tax_breakup_for_non_invoice_docs -erpnext.patches.v8_5.remove_quotations_route_in_sidebar -erpnext.patches.v8_5.update_existing_data_in_project_type -erpnext.patches.v8_5.set_default_mode_of_payment -erpnext.patches.v8_5.update_customer_group_in_POS_profile -erpnext.patches.v8_6.update_timesheet_company_from_PO -erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager -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_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 -erpnext.patches.v8_8.add_new_fields_in_accounts_settings -erpnext.patches.v8_9.set_default_customer_group -erpnext.patches.v8_9.delete_gst_doctypes_for_outside_india_accounts -erpnext.patches.v8_9.set_default_fields_in_variant_settings -erpnext.patches.v8_9.update_billing_gstin_for_indian_account -erpnext.patches.v8_9.set_member_party_type -erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile -erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order -erpnext.patches.v9_0.student_admission_childtable_migrate -erpnext.patches.v9_0.add_healthcare_domain -erpnext.patches.v9_0.set_variant_item_description -erpnext.patches.v9_0.set_uoms_in_variant_field -erpnext.patches.v9_0.copy_old_fees_field_data -execute:frappe.delete_doc_if_exists("DocType", "Program Fee") -erpnext.patches.v9_0.set_pos_profile_name -erpnext.patches.v9_0.remove_non_existing_warehouse_from_stock_settings -execute:frappe.delete_doc_if_exists("DocType", "Program Fee") -erpnext.patches.v8_10.change_default_customer_credit_days -erpnext.patches.v9_0.update_employee_loan_details -erpnext.patches.v9_2.delete_healthcare_domain_default_items -erpnext.patches.v9_1.create_issue_opportunity_type -erpnext.patches.v9_2.rename_translated_domains_in_en -erpnext.patches.v9_0.set_shipping_type_for_existing_shipping_rules -erpnext.patches.v9_0.update_multi_uom_fields_in_material_request -erpnext.patches.v9_2.repost_reserved_qty_for_production -erpnext.patches.v9_2.remove_company_from_patient -erpnext.patches.v9_2.set_item_name_in_production_order -erpnext.patches.v10_0.update_lft_rgt_for_employee -erpnext.patches.v9_2.rename_net_weight_in_item_master -erpnext.patches.v9_2.delete_process_payroll -erpnext.patches.v10_0.add_agriculture_domain -erpnext.patches.v10_0.add_non_profit_domain -erpnext.patches.v10_0.setup_vat_for_uae_and_saudi_arabia #2017-12-28 -erpnext.patches.v10_0.set_primary_contact_for_customer -erpnext.patches.v10_0.copy_projects_renamed_fields -erpnext.patches.v10_0.enabled_regional_print_format_based_on_country -erpnext.patches.v10_0.update_asset_calculate_depreciation -erpnext.patches.v10_0.add_guardian_role_for_parent_portal -erpnext.patches.v10_0.set_numeric_ranges_in_template_if_blank -erpnext.patches.v10_0.update_reserved_qty_for_purchase_order erpnext.patches.v10_0.fichier_des_ecritures_comptables_for_france -erpnext.patches.v10_0.update_assessment_plan -erpnext.patches.v10_0.update_assessment_result -erpnext.patches.v10_0.set_default_payment_terms_based_on_company -erpnext.patches.v10_0.update_sales_order_link_to_purchase_order erpnext.patches.v10_0.rename_price_to_rate_in_pricing_rule erpnext.patches.v10_0.set_currency_in_pricing_rule -erpnext.patches.v10_0.set_b2c_limit erpnext.patches.v10_0.update_translatable_fields erpnext.patches.v10_0.rename_offer_letter_to_job_offer execute:frappe.delete_doc('DocType', 'Production Planning Tool', ignore_missing=True) @@ -496,16 +21,6 @@ erpnext.patches.v10_0.migrate_daily_work_summary_settings_to_daily_work_summary_ erpnext.patches.v10_0.add_default_cash_flow_mappers erpnext.patches.v11_0.rename_duplicate_item_code_values erpnext.patches.v11_0.make_quality_inspection_template -erpnext.patches.v10_0.update_status_for_multiple_source_in_po -erpnext.patches.v10_0.set_auto_created_serial_no_in_stock_entry -erpnext.patches.v10_0.update_territory_and_customer_group -erpnext.patches.v10_0.update_warehouse_address_details -erpnext.patches.v10_0.update_reserved_qty_for_purchase_order -erpnext.patches.v10_0.update_hub_connector_domain -erpnext.patches.v10_0.set_student_party_type -erpnext.patches.v10_0.update_project_in_sle -erpnext.patches.v10_0.fix_reserved_qty_for_sub_contract -erpnext.patches.v10_0.repost_requested_qty_for_non_stock_uom_items erpnext.patches.v11_0.merge_land_unit_with_location erpnext.patches.v11_0.add_index_on_nestedset_doctypes erpnext.patches.v11_0.remove_modules_setup_page @@ -514,7 +29,6 @@ erpnext.patches.v11_0.update_department_lft_rgt erpnext.patches.v11_0.add_default_email_template_for_leave erpnext.patches.v11_0.set_default_email_template_in_hr #08-06-2018 erpnext.patches.v11_0.uom_conversion_data #30-06-2018 -erpnext.patches.v10_0.taxes_issue_with_pos erpnext.patches.v11_0.update_account_type_in_party_type erpnext.patches.v11_0.rename_healthcare_doctype_and_fields erpnext.patches.v11_0.rename_supplier_type_to_supplier_group @@ -522,8 +36,6 @@ erpnext.patches.v10_1.transfer_subscription_to_auto_repeat erpnext.patches.v11_0.update_brand_in_item_price erpnext.patches.v11_0.create_default_success_action erpnext.patches.v11_0.add_healthcare_service_unit_tree_root -erpnext.patches.v10_0.set_qty_in_transactions_based_on_serial_no_input -erpnext.patches.v10_0.show_leaves_of_all_department_members_in_calendar erpnext.patches.v11_0.rename_field_max_days_allowed erpnext.patches.v11_0.create_salary_structure_assignments erpnext.patches.v11_0.rename_health_insurance @@ -536,7 +48,6 @@ erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07- erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07 erpnext.patches.v11_0.rename_overproduction_percent_field erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom -erpnext.patches.v10_0.update_status_in_purchase_receipt erpnext.patches.v11_0.inter_state_field_for_gst erpnext.patches.v11_0.rename_members_with_naming_series #04-06-2018 erpnext.patches.v11_0.set_update_field_and_value_in_workflow_state @@ -550,13 +61,10 @@ erpnext.patches.v11_0.skip_user_permission_check_for_department erpnext.patches.v11_0.set_department_for_doctypes erpnext.patches.v11_0.update_allow_transfer_for_manufacture erpnext.patches.v11_0.add_item_group_defaults -erpnext.patches.v10_0.update_address_template_for_india erpnext.patches.v11_0.add_expense_claim_default_account execute:frappe.delete_doc("Page", "hub") erpnext.patches.v11_0.reset_publish_in_hub_for_all_items erpnext.patches.v11_0.update_hub_url # 2018-08-31 # 2018-09-03 -erpnext.patches.v10_0.set_discount_amount -erpnext.patches.v10_0.recalculate_gross_margin_for_project erpnext.patches.v11_0.make_job_card erpnext.patches.v11_0.redesign_healthcare_billing_work_flow erpnext.patches.v10_0.delete_hub_documents # 12-08-2018 @@ -570,9 +78,6 @@ execute:frappe.delete_doc_if_exists("Page", "stock-analytics") execute:frappe.delete_doc_if_exists("Page", "production-analytics") erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09 #2019-04-01 #2019-04-26 #2019-05-03 erpnext.patches.v11_0.drop_column_max_days_allowed -erpnext.patches.v10_0.update_user_image_in_employee -erpnext.patches.v10_0.repost_gle_for_purchase_receipts_with_rejected_items -erpnext.patches.v10_0.allow_operators_in_supplier_scorecard erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019 erpnext.patches.v11_0.update_delivery_trip_status erpnext.patches.v11_0.set_missing_gst_hsn_code From 43a24f2aa87eef372cf2fddf1b99ef68245cb643 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 3 Jun 2021 20:05:00 +0530 Subject: [PATCH 1003/1264] ci: Update ERPNext backup patch test >= v10 * Generated v10 backup archive * used old v7 erpnext backup hosted via build.erpnext.com * upgraded to v10 frappe + erpnext * Hosted backup on https://erpnext.com/files/v10-erpnext.sql.gz --- .github/workflows/patch.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 7c9e0272c9..b96a3d6bbe 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -66,4 +66,8 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Patch Tests - run: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate + run: | + cd ~/frappe-bench/ + wget https://erpnext.com/files/v10-erpnext.sql.gz + bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz + bench --site test_site migrate From 96f8ebc308ba2ceada92ab5a46ec582a7a9ba15f Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 4 Jun 2021 17:07:45 +0530 Subject: [PATCH 1004/1264] fix(patch): Handle NULL values from fieldtype change --- erpnext/patches/v11_0/rename_bom_wo_fields.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/patches/v11_0/rename_bom_wo_fields.py b/erpnext/patches/v11_0/rename_bom_wo_fields.py index b4a740fabb..882ec84e64 100644 --- a/erpnext/patches/v11_0/rename_bom_wo_fields.py +++ b/erpnext/patches/v11_0/rename_bom_wo_fields.py @@ -6,6 +6,10 @@ import frappe from frappe.model.utils.rename_field import rename_field def execute(): + # updating column value to handle field change from Data to Currency + changed_field = "base_scrap_material_cost" + frappe.db.sql(f"update `tabBOM` set {changed_field} = '0' where trim(coalesce({changed_field}, ''))= ''") + for doctype in ['BOM Explosion Item', 'BOM Item', 'Work Order Item', 'Item']: if frappe.db.has_column(doctype, 'allow_transfer_for_manufacture'): if doctype != 'Item': From 137d08a9d7bddb5903e5b01612a07c5a1223c378 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 4 Jun 2021 20:11:34 +0530 Subject: [PATCH 1005/1264] fix: Manually link_fields from flags before rename_doc --- .../v13_0/healthcare_lab_module_rename_doctypes.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index 9af0a8dbef..2549a1e91e 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -2,6 +2,7 @@ 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 @@ -17,7 +18,12 @@ def execute(): 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.flags.link_fields = {} + should_rename = ( + frappe.db.table_exists(old_dt) + and not frappe.db.table_exists(new_dt) + ) + if should_rename: frappe.reload_doc('healthcare', 'doctype', frappe.scrub(old_dt)) frappe.rename_doc('DocType', old_dt, new_dt, force=True) frappe.reload_doc('healthcare', 'doctype', frappe.scrub(new_dt)) From 88176e65e46c43f1a5a6e88f3d08a2bcb6e3e5fb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 10 Jun 2021 17:31:28 +0530 Subject: [PATCH 1006/1264] fix: Check for gst_hsn_code --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 5a53d49399..11ebef724c 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -194,7 +194,7 @@ def get_item_list(invoice): item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None - item.is_service_item = 'Y' if item.gst_hsn_code[:2] == "99" else 'N' + item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N' item.serial_no = "" item = update_item_taxes(invoice, item) From a5c3427293a8bfeb38bd83c08fe155b9d33179a4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 10 Jun 2021 18:09:49 +0530 Subject: [PATCH 1007/1264] fix: Sort account balances by account name --- .../report/account_balance/account_balance.py | 4 +--- .../account_balance/test_account_balance.py | 22 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/report/account_balance/account_balance.py b/erpnext/accounts/report/account_balance/account_balance.py index 65e7d789bb..be64c327fd 100644 --- a/erpnext/accounts/report/account_balance/account_balance.py +++ b/erpnext/accounts/report/account_balance/account_balance.py @@ -58,11 +58,9 @@ def get_conditions(filters): def get_data(filters): data = [] - conditions = get_conditions(filters) - accounts = frappe.db.get_all("Account", fields=["name", "account_currency"], - filters=conditions) + filters=conditions, order_by='name') for d in accounts: balance = get_balance_on(d.name, date=filters.report_date) diff --git a/erpnext/accounts/report/account_balance/test_account_balance.py b/erpnext/accounts/report/account_balance/test_account_balance.py index b6ced312d0..14ddf4a30f 100644 --- a/erpnext/accounts/report/account_balance/test_account_balance.py +++ b/erpnext/accounts/report/account_balance/test_account_balance.py @@ -23,7 +23,7 @@ class TestAccountBalance(unittest.TestCase): expected_data = [ { - "account": 'Sales - _TC2', + "account": 'Direct Income - _TC2', "currency": 'EUR', "balance": -100.0, }, @@ -32,21 +32,21 @@ class TestAccountBalance(unittest.TestCase): "currency": 'EUR', "balance": -100.0, }, - { - "account": 'Service - _TC2', - "currency": 'EUR', - "balance": 0.0, - }, - { - "account": 'Direct Income - _TC2', - "currency": 'EUR', - "balance": -100.0, - }, { "account": 'Indirect Income - _TC2', "currency": 'EUR', "balance": 0.0, }, + { + "account": 'Sales - _TC2', + "currency": 'EUR', + "balance": -100.0, + }, + { + "account": 'Service - _TC2', + "currency": 'EUR', + "balance": 0.0, + } ] self.assertEqual(expected_data, report[1]) From 9a243ed4699f2ae9e96397b221193f1fe40a2cb0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 10 Jun 2021 18:09:49 +0530 Subject: [PATCH 1008/1264] fix: Sort account balances by account name --- .../report/account_balance/account_balance.py | 4 +--- .../account_balance/test_account_balance.py | 22 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/report/account_balance/account_balance.py b/erpnext/accounts/report/account_balance/account_balance.py index 65e7d789bb..be64c327fd 100644 --- a/erpnext/accounts/report/account_balance/account_balance.py +++ b/erpnext/accounts/report/account_balance/account_balance.py @@ -58,11 +58,9 @@ def get_conditions(filters): def get_data(filters): data = [] - conditions = get_conditions(filters) - accounts = frappe.db.get_all("Account", fields=["name", "account_currency"], - filters=conditions) + filters=conditions, order_by='name') for d in accounts: balance = get_balance_on(d.name, date=filters.report_date) diff --git a/erpnext/accounts/report/account_balance/test_account_balance.py b/erpnext/accounts/report/account_balance/test_account_balance.py index b6ced312d0..14ddf4a30f 100644 --- a/erpnext/accounts/report/account_balance/test_account_balance.py +++ b/erpnext/accounts/report/account_balance/test_account_balance.py @@ -23,7 +23,7 @@ class TestAccountBalance(unittest.TestCase): expected_data = [ { - "account": 'Sales - _TC2', + "account": 'Direct Income - _TC2', "currency": 'EUR', "balance": -100.0, }, @@ -32,21 +32,21 @@ class TestAccountBalance(unittest.TestCase): "currency": 'EUR', "balance": -100.0, }, - { - "account": 'Service - _TC2', - "currency": 'EUR', - "balance": 0.0, - }, - { - "account": 'Direct Income - _TC2', - "currency": 'EUR', - "balance": -100.0, - }, { "account": 'Indirect Income - _TC2', "currency": 'EUR', "balance": 0.0, }, + { + "account": 'Sales - _TC2', + "currency": 'EUR', + "balance": -100.0, + }, + { + "account": 'Service - _TC2', + "currency": 'EUR', + "balance": 0.0, + } ] self.assertEqual(expected_data, report[1]) From 9a67141bfb0069d9254a902dd2f366d7a22b36be Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 10 Jun 2021 18:48:55 +0530 Subject: [PATCH 1009/1264] fix(einvoicing): service item check (#26010) --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index a1179ff9b6..0eaf790538 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -199,7 +199,7 @@ def get_item_list(invoice): item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None - item.is_service_item = 'Y' if item.gst_hsn_code[:2] == "99" else 'N' + item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N' item.serial_no = "" item = update_item_taxes(invoice, item) From 687ad9b9421074a073d3087e3c266a72473bb7c7 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 10 Jun 2021 21:18:19 +0530 Subject: [PATCH 1010/1264] fix: Report Raw Materials to be Transferred --- ...tracted_raw_materials_to_be_transferred.py | 118 ++++++------------ 1 file changed, 39 insertions(+), 79 deletions(-) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py index de2ae8fc73..5a0381b017 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py @@ -9,10 +9,10 @@ def execute(filters=None): if filters.from_date >= filters.to_date: frappe.msgprint(_("To Date must be greater than From Date")) - data = [] columns = get_columns() - get_data(data , filters) - return columns, data + data = get_data(filters) + + return columns, data or [] def get_columns(): return [ @@ -21,13 +21,12 @@ def get_columns(): "fieldtype": "Link", "fieldname": "purchase_order", "options": "Purchase Order", - "width": 150 + "width": 200 }, { "label": _("Date"), "fieldtype": "Date", "fieldname": "date", - "hidden": 1, "width": 150 }, { @@ -41,97 +40,58 @@ def get_columns(): "label": _("Item Code"), "fieldtype": "Data", "fieldname": "rm_item_code", - "width": 100 + "width": 150 }, { "label": _("Required Quantity"), "fieldtype": "Float", - "fieldname": "r_qty", - "width": 100 + "fieldname": "reqd_qty", + "width": 150 }, { "label": _("Transferred Quantity"), "fieldtype": "Float", - "fieldname": "t_qty", - "width": 100 + "fieldname": "transferred_qty", + "width": 200 }, { "label": _("Pending Quantity"), "fieldtype": "Float", "fieldname": "p_qty", - "width": 100 + "width": 150 } ] -def get_data(data, filters): - po = get_po(filters) - po_transferred_qty_map = frappe._dict(get_transferred_quantity([v.name for v in po])) +def get_data(filters): + po_rm_item_details = get_po_items_to_supply(filters) - sub_items = get_purchase_order_item_supplied([v.name for v in po]) + data = [] + for row in po_rm_item_details: + transferred_qty = row.get("transferred_qty") or 0 + if transferred_qty < row.get("reqd_qty", 0): + pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty) + row.p_qty = pending_qty if pending_qty > 0 else 0 + data.append(row) - for order in po: - for item in sub_items: - if order.name == item.parent and order.name in po_transferred_qty_map and \ - item.required_qty != po_transferred_qty_map.get(order.name).get(item.rm_item_code): - transferred_qty = po_transferred_qty_map.get(order.name).get(item.rm_item_code) \ - if po_transferred_qty_map.get(order.name).get(item.rm_item_code) else 0 - row ={ - 'purchase_order': item.parent, - 'date': order.transaction_date, - 'supplier': order.supplier, - 'rm_item_code': item.rm_item_code, - 'r_qty': item.required_qty, - 't_qty':transferred_qty, - 'p_qty':item.required_qty - transferred_qty - } + return data - data.append(row) - - return(data) - -def get_po(filters): - record_filters = [ - ["is_subcontracted", "=", "Yes"], - ["supplier", "=", filters.supplier], - ["transaction_date", "<=", filters.to_date], - ["transaction_date", ">=", filters.from_date], - ["docstatus", "=", 1] - ] - return frappe.get_all("Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"]) - -def get_transferred_quantity(po_name): - stock_entries = get_stock_entry(po_name) - stock_entries_detail = get_stock_entry_detail([v.name for v in stock_entries]) - po_transferred_qty_map = {} - - - for entry in stock_entries: - for details in stock_entries_detail: - if details.parent == entry.name: - details["Purchase_order"] = entry.purchase_order - if entry.purchase_order not in po_transferred_qty_map: - po_transferred_qty_map[entry.purchase_order] = {} - po_transferred_qty_map[entry.purchase_order][details.item_code] = details.qty - else: - po_transferred_qty_map[entry.purchase_order][details.item_code] = po_transferred_qty_map[entry.purchase_order].get(details.item_code, 0) + details.qty - - return po_transferred_qty_map - - -def get_stock_entry(po): - return frappe.get_all("Stock Entry", filters=[ - ('purchase_order', 'IN', po), - ('stock_entry_type', '=', 'Send to Subcontractor'), - ('docstatus', '=', 1) - ], fields=["name", "purchase_order"]) - -def get_stock_entry_detail(se): - return frappe.get_all("Stock Entry Detail", filters=[ - ["parent", "in", se] +def get_po_items_to_supply(filters): + return frappe.db.get_all( + "Purchase Order", + fields=[ + "name as purchase_order", + "transaction_date as date", + "supplier as supplier", + "`tabPurchase Order Item Supplied`.rm_item_code as rm_item_code", + "`tabPurchase Order Item Supplied`.required_qty as reqd_qty", + "`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty" ], - fields=["parent", "item_code", "qty"]) - -def get_purchase_order_item_supplied(po): - return frappe.get_all("Purchase Order Item Supplied", filters=[ - ('parent', 'IN', po) - ], fields=['parent', 'rm_item_code', 'required_qty']) + filters = [ + ["Purchase Order", "per_received", "<", "100"], + ["Purchase Order", "is_subcontracted", "=", "Yes"], + ["Purchase Order", "supplier", "=", filters.supplier], + ["Purchase Order", "transaction_date", "<=", filters.to_date], + ["Purchase Order", "transaction_date", ">=", filters.from_date], + ["Purchase Order", "docstatus", "=", 1] + ] + ) \ No newline at end of file From ec4a3723cc1343e1ce99d9114ecb9bd610f8c034 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 11 Jun 2021 12:47:06 +0530 Subject: [PATCH 1011/1264] fix: Sider --- .../subcontracted_raw_materials_to_be_transferred.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py index 5a0381b017..68426abbb0 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py @@ -70,7 +70,7 @@ def get_data(filters): transferred_qty = row.get("transferred_qty") or 0 if transferred_qty < row.get("reqd_qty", 0): pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty) - row.p_qty = pending_qty if pending_qty > 0 else 0 + row.p_qty = pending_qty if pending_qty > 0 else 0 data.append(row) return data From 5bb89b0ead96f0bb7e2346fb01bd8710bbb7a3b2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 11 Jun 2021 15:57:01 +0530 Subject: [PATCH 1012/1264] test(perf): eliminate repeat creation of HSN codes (#25947) --- erpnext/regional/india/setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 229e0c031e..3e0b9b733b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -27,6 +27,9 @@ def setup_company_independent_fixtures(patch=False): add_print_formats() def add_hsn_sac_codes(): + if frappe.flags.in_test and frappe.flags.created_hsn_codes: + return + # HSN codes with open(os.path.join(os.path.dirname(__file__), 'hsn_code_data.json'), 'r') as f: hsn_codes = json.loads(f.read()) @@ -38,6 +41,9 @@ def add_hsn_sac_codes(): sac_codes = json.loads(f.read()) create_hsn_codes(sac_codes, code_field="sac_code") + if frappe.flags.in_test: + frappe.flags.created_hsn_codes = True + def create_hsn_codes(data, code_field): for d in data: hsn_code = frappe.new_doc('GST HSN Code') From a9c84f749a64f2627885d61d571bf10c04fd61a8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 11 Jun 2021 16:00:48 +0530 Subject: [PATCH 1013/1264] perf(minor): remove unnecessary comprehensions (#25645) --- erpnext/accounts/doctype/c_form/c_form.py | 2 +- .../doctype/coupon_code/coupon_code.py | 2 +- .../invoice_discounting/invoice_discounting.py | 2 +- .../doctype/journal_entry/journal_entry.py | 4 ++-- .../monthly_distribution.py | 2 +- .../doctype/payment_entry/payment_entry.py | 10 +++++----- .../doctype/payment_request/payment_request.py | 2 +- erpnext/accounts/doctype/pricing_rule/utils.py | 18 +++++++++--------- .../purchase_invoice/test_purchase_invoice.py | 2 +- .../test_tax_withholding_category.py | 2 +- erpnext/accounts/general_ledger.py | 2 +- .../report/cash_flow/custom_cash_flow.py | 8 ++++---- .../customer_ledger_summary.py | 2 +- .../accounts/report/financial_statements.py | 2 +- .../item_wise_purchase_register.py | 2 +- .../item_wise_sales_register.py | 2 +- .../report/pos_register/pos_register.py | 6 +++--- .../purchase_register/purchase_register.py | 14 +++++++------- .../sales_payment_summary.py | 4 ++-- .../report/sales_register/sales_register.py | 16 ++++++++-------- .../tds_computation_summary.py | 4 ++-- erpnext/accounts/report/utils.py | 2 +- erpnext/accounts/utils.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 2 +- .../asset_value_adjustment.py | 2 +- .../doctype/purchase_order/purchase_order.py | 6 +++--- .../purchase_order/test_purchase_order.py | 2 +- .../request_for_quotation.py | 2 +- erpnext/controllers/accounts_controller.py | 8 ++++---- erpnext/controllers/buying_controller.py | 6 +++--- erpnext/controllers/queries.py | 2 +- erpnext/controllers/selling_controller.py | 2 +- erpnext/controllers/status_updater.py | 4 ++-- erpnext/controllers/stock_controller.py | 6 +++--- erpnext/controllers/taxes_and_totals.py | 12 ++++++------ erpnext/controllers/tests/test_mapper.py | 4 ++-- .../controllers/website_list_for_contact.py | 2 +- .../manufacturing/doctype/job_card/job_card.py | 2 +- erpnext/projects/doctype/task/task.py | 2 +- .../projects/doctype/timesheet/timesheet.py | 4 ++-- .../report/project_summary/project_summary.py | 2 +- erpnext/selling/doctype/customer/customer.py | 2 +- erpnext/selling/doctype/quotation/quotation.py | 2 +- .../selling/doctype/sales_order/sales_order.py | 2 +- erpnext/setup/doctype/company/company.py | 4 ++-- erpnext/setup/doctype/item_group/item_group.py | 4 ++-- erpnext/stock/doctype/batch/batch.py | 2 +- .../doctype/delivery_note/delivery_note.py | 2 +- .../doctype/delivery_trip/delivery_trip.py | 6 +++--- .../landed_cost_voucher/landed_cost_voucher.py | 8 ++++---- .../test_landed_cost_voucher.py | 2 +- .../stock/doctype/packing_slip/packing_slip.py | 4 ++-- .../purchase_receipt/test_purchase_receipt.py | 2 +- erpnext/stock/doctype/serial_no/serial_no.py | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 2 +- .../item_price_stock/item_price_stock.py | 2 +- .../itemwise_recommended_reorder_level.py | 2 +- .../product_bundle_balance.py | 2 +- .../report/stock_balance/stock_balance.py | 6 +++--- .../stock/report/stock_ledger/stock_ledger.py | 4 ++-- erpnext/stock/stock_ledger.py | 2 +- .../doctype/warranty_claim/warranty_claim.py | 2 +- erpnext/utilities/product.py | 2 +- erpnext/utilities/transaction_base.py | 4 ++-- 64 files changed, 126 insertions(+), 126 deletions(-) diff --git a/erpnext/accounts/doctype/c_form/c_form.py b/erpnext/accounts/doctype/c_form/c_form.py index fd86ed4c90..cfe28f3ff9 100644 --- a/erpnext/accounts/doctype/c_form/c_form.py +++ b/erpnext/accounts/doctype/c_form/c_form.py @@ -54,7 +54,7 @@ class CForm(Document): frappe.throw(_("Please enter atleast 1 invoice in the table")) def set_total_invoiced_amount(self): - total = sum([flt(d.grand_total) for d in self.get('invoices')]) + total = sum(flt(d.grand_total) for d in self.get('invoices')) frappe.db.set(self, 'total_invoiced_amount', total) @frappe.whitelist() diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.py b/erpnext/accounts/doctype/coupon_code/coupon_code.py index 7829c9320d..55c119315e 100644 --- a/erpnext/accounts/doctype/coupon_code/coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/coupon_code.py @@ -14,7 +14,7 @@ class CouponCode(Document): if not self.coupon_code: if self.coupon_type == "Promotional": - self.coupon_code =''.join([i for i in self.coupon_name if not i.isdigit()])[0:8].upper() + self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper() elif self.coupon_type == "Gift Card": self.coupon_code = frappe.generate_hash()[:10].upper() diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 95d2ee56d9..b73d8bfbb1 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -42,7 +42,7 @@ class InvoiceDiscounting(AccountsController): record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice))) def calculate_total_amount(self): - self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices]) + self.total_amount = sum(flt(d.outstanding_amount) for d in self.invoices) def on_submit(self): self.update_sales_invoice() diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ed1bd28223..937597bc55 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -196,8 +196,8 @@ class JournalEntry(AccountsController): frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account)) def check_credit_limit(self): - customers = list(set([d.party for d in self.get("accounts") - if d.party_type=="Customer" and d.party and flt(d.debit) > 0])) + customers = list(set(d.party for d in self.get("accounts") + if d.party_type=="Customer" and d.party and flt(d.debit) > 0)) if customers: from erpnext.selling.doctype.customer.customer import check_credit_limit for customer in customers: diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py index 88667d7207..bff6422732 100644 --- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py +++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py @@ -21,7 +21,7 @@ class MonthlyDistribution(Document): idx += 1 def validate(self): - total = sum([flt(d.percentage_allocation) for d in self.get("percentages")]) + total = sum(flt(d.percentage_allocation) for d in self.get("percentages")) if flt(total, 2) != 100.0: frappe.throw(_("Percentage Allocation should be equal to 100%") + \ diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index edca210142..2c6deb3896 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -309,7 +309,7 @@ class PaymentEntry(AccountsController): for k, v in no_oustanding_refs.items(): frappe.msgprint( _("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.") - .format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")) + .format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount")) + "

    " + _("If this is undesirable please cancel the corresponding Payment Entry."), title=_("Warning"), indicator="orange") @@ -524,7 +524,7 @@ class PaymentEntry(AccountsController): def set_unallocated_amount(self): self.unallocated_amount = 0 if self.party: - total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) + total_deductions = sum(flt(d.amount) for d in self.get("deductions")) if self.payment_type == "Receive" \ and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \ and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate): @@ -549,7 +549,7 @@ class PaymentEntry(AccountsController): else: self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax) - total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) + total_deductions = sum(flt(d.amount) for d in self.get("deductions")) self.difference_amount = flt(self.difference_amount - total_deductions, self.precision("difference_amount")) @@ -565,8 +565,8 @@ class PaymentEntry(AccountsController): if ((self.payment_type=="Pay" and self.party_type=="Customer") or (self.payment_type=="Receive" and self.party_type=="Supplier")): - total_negative_outstanding = sum([abs(flt(d.outstanding_amount)) - for d in self.get("references") if flt(d.outstanding_amount) < 0]) + total_negative_outstanding = sum(abs(flt(d.outstanding_amount)) + for d in self.get("references") if flt(d.outstanding_amount) < 0) paid_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount additional_charges = sum([flt(d.amount) for d in self.deductions]) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 468978785b..438951db62 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -112,7 +112,7 @@ class PaymentRequest(Document): if not data_of_completed_requests: return self.grand_total - request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests]) + request_amounts = sum(json.loads(d).get('request_amount') for d in data_of_completed_requests) return request_amounts def on_cancel(self): diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index d23b952bdc..b54d0e73a8 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -20,9 +20,9 @@ from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money class MultiplePricingRuleConflict(frappe.ValidationError): pass apply_on_table = { - 'Item Code': 'items', - 'Item Group': 'item_groups', - 'Brand': 'brands' + 'Item Code': 'items', + 'Item Group': 'item_groups', + 'Brand': 'brands' } def get_pricing_rules(args, doc=None): @@ -183,7 +183,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): condition = "ifnull({table}.{field}, '') in ({parent_groups})".format( table=table, field=field, - parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups]) + parent_groups=", ".join(frappe.db.escape(d) for d in parent_groups) ) frappe.flags.tree_conditions[key] = condition @@ -264,7 +264,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None): # find pricing rule with highest priority if pricing_rules: - max_priority = max([cint(p.priority) for p in pricing_rules]) + max_priority = max(cint(p.priority) for p in pricing_rules) if max_priority: pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules)) @@ -272,14 +272,14 @@ def filter_pricing_rules(args, pricing_rules, doc=None): pricing_rules = list(pricing_rules) if len(pricing_rules) > 1: - rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules])) + rate_or_discount = list(set(d.rate_or_discount for d in pricing_rules)) if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage": pricing_rules = list(filter(lambda x: x.for_price_list==args.price_list, pricing_rules)) \ or pricing_rules if len(pricing_rules) > 1 and not args.for_shopping_cart: frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}") - .format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict) + .format("\n".join(d.name for d in pricing_rules)), MultiplePricingRuleConflict) elif pricing_rules: return pricing_rules[0] @@ -541,7 +541,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): if pricing_rule_args: - items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item]) + items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item) for args in pricing_rule_args: if not items or (args.get('item_code'), args.get('pricing_rules')) not in items: @@ -589,4 +589,4 @@ def update_coupon_code_count(coupon_name,transaction_type): elif transaction_type=='cancelled': if coupon.used>0: coupon.used=coupon.used-1 - coupon.save(ignore_permissions=True) \ No newline at end of file + coupon.save(ignore_permissions=True) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 723d151ad8..503dda7728 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -632,7 +632,7 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(len(pi.get("supplied_items")), 2) - rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")]) + rm_supp_cost = sum(d.amount for d in pi.get("supplied_items")) self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2)) def test_rejected_serial_no(self): diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 0cea7612dd..dd26be7c99 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -112,7 +112,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): si = create_sales_invoice(customer = "Test TCS Customer", rate=5000) si.submit() - tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC']) + tcs_charged = sum(d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC') self.assertEqual(tcs_charged, 500) invoices.append(si) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index d4b249429b..59009ae621 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -143,7 +143,7 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): validate_expense_against_budget(args) def validate_cwip_accounts(gl_map): - cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) + cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")) if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py index ff87276a87..c11c15390b 100644 --- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py +++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py @@ -32,7 +32,7 @@ def get_accounts_in_mappers(mapping_names): join `tabCash Flow Mapping` cfm on cfma.parent=cfm.name where cfma.parent in (%s) order by cfm.is_working_capital - ''', (', '.join(['"%s"' % d for d in mapping_names]))) + ''', (', '.join('"%s"' % d for d in mapping_names))) def setup_mappers(mappers): @@ -83,8 +83,8 @@ def setup_mappers(mappers): account_types_labels = sorted( set( - [(d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense']) - for d in account_types] + (d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense']) + for d in account_types ), key=lambda x: x[1] ) @@ -375,7 +375,7 @@ def _get_account_type_based_data(filters, account_names, period_list, accumulate total = 0 for period in period_list: start_date = get_start_date(period, accumulated_values, company) - accounts = ', '.join(['"%s"' % d for d in account_names]) + accounts = ', '.join('"%s"' % d for d in account_names) if opening_balances: date_info = dict(date=start_date) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 10b32fea56..c79d7401e6 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -145,7 +145,7 @@ class PartyLedgerSummaryReport(object): out = [] for party, row in iteritems(self.party_data): if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount: - total_party_adjustment = sum([amount for amount in itervalues(self.party_adjustment_details.get(party, {}))]) + total_party_adjustment = sum(amount for amount in itervalues(self.party_adjustment_details.get(party, {}))) row.paid_amount -= total_party_adjustment adjustments = self.party_adjustment_details.get(party, {}) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index d20ddbde5c..39ff804518 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -369,7 +369,7 @@ def set_gl_entries_by_account( if accounts: additional_conditions += " and account in ({})"\ - .format(", ".join([frappe.db.escape(d) for d in accounts])) + .format(", ".join(frappe.db.escape(d) for d in accounts)) gl_filters = { "company": company, diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index cb4d9b43db..685419a17e 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -334,7 +334,7 @@ def get_aii_accounts(): def get_purchase_receipts_against_purchase_order(item_list): po_pr_map = frappe._dict() - po_item_rows = list(set([d.po_detail for d in item_list])) + po_item_rows = list(set(d.po_detail for d in item_list)) if po_item_rows: purchase_receipts = frappe.db.sql(""" diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 928b373eff..2e794da842 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -23,7 +23,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) - mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list])) + mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list)) so_dn_map = get_delivery_notes_against_sales_order(item_list) data = [] diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py index cfbd7fd0c8..6a42bb4fb6 100644 --- a/erpnext/accounts/report/pos_register/pos_register.py +++ b/erpnext/accounts/report/pos_register/pos_register.py @@ -77,14 +77,14 @@ def get_pos_entries(filters, group_by_field): ), filters, as_dict=1) def concat_mode_of_payments(pos_entries): - mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries])) + mode_of_payments = get_mode_of_payments(set(d.pos_invoice for d in pos_entries)) for entry in pos_entries: if mode_of_payments.get(entry.pos_invoice): entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, [])) def add_subtotal_row(data, group_invoices, group_by_field, group_by_value): - grand_total = sum([d.grand_total for d in group_invoices]) - paid_amount = sum([d.paid_amount for d in group_invoices]) + grand_total = sum(d.grand_total for d in group_invoices) + paid_amount = sum(d.paid_amount for d in group_invoices) data.append({ group_by_field: group_by_value, "grand_total": grand_total, diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 8ac749d629..10edd41aa8 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -26,7 +26,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts) invoice_po_pr_map = get_invoice_po_pr_map(invoice_list) - suppliers = list(set([d.supplier for d in invoice_list])) + suppliers = list(set(d.supplier for d in invoice_list)) supplier_details = get_supplier_details(suppliers) company_currency = frappe.get_cached_value('Company', filters.company, "default_currency") @@ -120,13 +120,13 @@ def get_columns(invoice_list, additional_table_columns): and docstatus = 1 and (account_head is not null and account_head != '') and category in ('Total', 'Valuation and Total') and parent in (%s) order by account_head""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list)) unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account from `tabPurchase Invoice` where docstatus = 1 and name in (%s) and ifnull(unrealized_profit_loss_account, '') != '' order by unrealized_profit_loss_account""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list)) expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts] unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts] @@ -208,7 +208,7 @@ def get_invoice_expense_map(invoice_list): from `tabPurchase Invoice Item` where parent in (%s) group by parent, expense_account - """ % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + """ % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) invoice_expense_map = {} for d in expense_details: @@ -221,7 +221,7 @@ def get_internal_invoice_map(invoice_list): unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account, base_net_total as amount from `tabPurchase Invoice` where name in (%s) and is_internal_supplier = 1 and company = represents_company""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) internal_invoice_map = {} for d in unrealized_amount_details: @@ -238,7 +238,7 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts): where parent in (%s) and category in ('Total', 'Valuation and Total') and base_tax_amount_after_discount_amount != 0 group by parent, account_head, add_deduct_tax - """ % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + """ % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) invoice_tax_map = {} for d in tax_details: @@ -258,7 +258,7 @@ def get_invoice_po_pr_map(invoice_list): select parent, purchase_order, purchase_receipt, po_detail, project from `tabPurchase Invoice Item` where parent in (%s) - """ % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + """ % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) invoice_po_pr_map = {} for d in pi_items: diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py index c234da0fe3..ff774681a2 100644 --- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py +++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py @@ -158,7 +158,7 @@ def get_sales_invoice_data(filters): def get_mode_of_payments(filters): mode_of_payments = {} invoice_list = get_invoices(filters) - invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list]) + invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list) if invoice_list: inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment from `tabSales Invoice` a, `tabSales Invoice Payment` b @@ -197,7 +197,7 @@ def get_invoices(filters): def get_mode_of_payment_details(filters): mode_of_payment_details = {} invoice_list = get_invoices(filters) - invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list]) + 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 diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index cb2c98b64a..909959323f 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -248,19 +248,19 @@ def get_columns(invoice_list, additional_table_columns): income_accounts = frappe.db.sql_list("""select distinct income_account from `tabSales Invoice Item` where docstatus = 1 and parent in (%s) order by income_account""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list)) tax_accounts = frappe.db.sql_list("""select distinct account_head from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice' and docstatus = 1 and base_tax_amount_after_discount_amount != 0 and parent in (%s) order by account_head""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list)) unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account from `tabSales Invoice` where docstatus = 1 and name in (%s) and ifnull(unrealized_profit_loss_account, '') != '' order by unrealized_profit_loss_account""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list)) for account in income_accounts: income_columns.append({ @@ -406,7 +406,7 @@ def get_invoices(filters, additional_query_columns): def get_invoice_income_map(invoice_list): income_details = frappe.db.sql("""select parent, income_account, sum(base_net_amount) as amount from `tabSales Invoice Item` where parent in (%s) group by parent, income_account""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) invoice_income_map = {} for d in income_details: @@ -419,7 +419,7 @@ def get_internal_invoice_map(invoice_list): unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account, base_net_total as amount from `tabSales Invoice` where name in (%s) and is_internal_customer = 1 and company = represents_company""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) internal_invoice_map = {} for d in unrealized_amount_details: @@ -432,7 +432,7 @@ def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts): tax_details = frappe.db.sql("""select parent, account_head, sum(base_tax_amount_after_discount_amount) as tax_amount from `tabSales Taxes and Charges` where parent in (%s) group by parent, account_head""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) invoice_tax_map = {} for d in tax_details: @@ -451,7 +451,7 @@ def get_invoice_so_dn_map(invoice_list): si_items = frappe.db.sql("""select parent, sales_order, delivery_note, so_detail from `tabSales Invoice Item` where parent in (%s) and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) invoice_so_dn_map = {} for d in si_items: @@ -475,7 +475,7 @@ def get_invoice_cc_wh_map(invoice_list): si_items = frappe.db.sql("""select parent, cost_center, warehouse from `tabSales Invoice Item` where parent in (%s) and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')""" % - ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1) + ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1) invoice_cc_wh_map = {} for d in si_items: diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index a8280c1b18..e15715dccd 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -78,7 +78,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f and company=%s and posting_date between %s and %s """, (supplier, company, from_date, to_date), as_dict=1) - supplier_credit_amount = flt(sum([d.credit for d in entries])) + supplier_credit_amount = flt(sum(d.credit for d in entries)) vouchers = [d.voucher_no for d in entries] vouchers += get_advance_vouchers([supplier], company=company, @@ -91,7 +91,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f from `tabGL Entry` where account=%s and posting_date between %s and %s and company=%s and credit > 0 and voucher_no in ({0}) - """.format(', '.join(["'%s'" % d for d in vouchers])), + """.format(', '.join("'%s'" % d for d in vouchers)), (account, from_date, to_date, company))[0][0]) date_range_filter = [fiscal_year, from_date, to_date] diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index b020d0a506..ba461edaf8 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -139,6 +139,6 @@ def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=N gross_profit_data = GrossProfitGenerator(filters) result = gross_profit_data.grouped_data if not with_item_data: - result = sum([d.gross_profit for d in result]) + result = sum(d.gross_profit for d in result) return result diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5a64e27ccb..66a9b60125 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -635,7 +635,7 @@ def get_held_invoices(party_type, party): 'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()', as_dict=1 ) - held_invoices = set([d['name'] for d in held_invoices]) + held_invoices = set(d['name'] for d in held_invoices) return held_invoices diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 3cd4b802c1..8845f24d10 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -470,7 +470,7 @@ class TestAsset(unittest.TestCase): }) asset.insert() accumulated_depreciation_after_full_schedule = \ - max([d.accumulated_depreciation_amount for d in asset.get("schedules")]) + max(d.accumulated_depreciation_amount for d in asset.get("schedules")) asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - flt(accumulated_depreciation_after_full_schedule)) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 14308277c1..2f6b5ee2dc 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -92,7 +92,7 @@ class AssetValueAdjustment(Document): d.value_after_depreciation = asset_value if d.depreciation_method in ("Straight Line", "Manual"): - end_date = max([s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx]) + end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx) total_days = date_diff(end_date, self.date) rate_per_day = flt(d.value_after_depreciation) / flt(total_days) from_date = self.date diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 782593a5c5..2629ba7d61 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -139,7 +139,7 @@ class PurchaseOrder(BuyingController): def validate_minimum_order_qty(self): if not self.get("items"): return - items = list(set([d.item_code for d in self.get("items")])) + items = list(set(d.item_code for d in self.get("items"))) itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty from tabItem where name in ({0})""".format(", ".join(["%s"] * len(items))), items)) @@ -326,10 +326,10 @@ class PurchaseOrder(BuyingController): so.notify_update() def has_drop_ship_item(self): - return any([d.delivered_by_supplier for d in self.items]) + return any(d.delivered_by_supplier for d in self.items) def is_against_so(self): - return any([d.sales_order for d in self.items if d.sales_order]) + return any(d.sales_order for d in self.items if d.sales_order) def set_received_qty_for_drop_ship_items(self): for item in self.items: diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 39171960d8..3b9f8e9775 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -359,7 +359,7 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() - total_reqd_qty_after_change = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")]) + total_reqd_qty_after_change = sum(d.get("required_qty") for d in po.as_dict().get("supplied_items")) self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 180ba93666..0127eb8163 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -391,7 +391,7 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc = def get_supplier_tag(): if not frappe.cache().hget("Supplier", "Tags"): filters = {"document_type": "Supplier"} - tags = list(set([tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag])) + tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag)) frappe.cache().hset("Supplier", "Tags", tags) return frappe.cache().hget("Supplier", "Tags") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 53ded33b6f..7c6061defa 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -610,8 +610,8 @@ class AccountsController(TransactionBase): order_field = "purchase_order" order_doctype = "Purchase Order" - order_list = list(set([d.get(order_field) - for d in self.get("items") if d.get(order_field)])) + order_list = list(set(d.get(order_field) + for d in self.get("items") if d.get(order_field))) journal_entries = get_advance_journal_entries(party_type, party, party_account, amount_field, order_doctype, order_list, include_unallocated) @@ -635,8 +635,8 @@ class AccountsController(TransactionBase): def validate_advance_entries(self): order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order" - order_list = list(set([d.get(order_field) - for d in self.get("items") if d.get(order_field)])) + order_list = list(set(d.get(order_field) + for d in self.get("items") if d.get(order_field))) if not order_list: return diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 3f2d3390c0..da819119b1 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -181,8 +181,8 @@ class BuyingController(StockController): stock_and_asset_items_amount += flt(d.base_net_amount) last_item_idx = d.idx - total_valuation_amount = sum([flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes") - if d.category in ["Valuation", "Valuation and Total"]]) + total_valuation_amount = sum(flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes") + if d.category in ["Valuation", "Valuation and Total"]) valuation_amount_adjustment = total_valuation_amount for i, item in enumerate(self.get("items")): @@ -325,7 +325,7 @@ class BuyingController(StockController): def update_raw_materials_supplied_based_on_stock_entries(self): self.set('supplied_items', []) - purchase_orders = set([d.purchase_order for d in self.items]) + purchase_orders = set(d.purchase_order for d in self.items) # qty of raw materials backflushed (for each item per purchase order) backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 81ac234e70..7bd739a6ad 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -88,7 +88,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): fields = get_fields("Customer", fields) searchfields = frappe.get_meta("Customer").get_search_fields() - searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) + searchfields = " or ".join(field + " like %(txt)s" for field in searchfields) return frappe.db.sql("""select {fields} from `tabCustomer` where docstatus < 2 diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 54156f379c..7f28289760 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -428,7 +428,7 @@ class SellingController(StockController): self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) def get_po_nos(self, ref_doctype, ref_fieldname, po_nos): - doc_list = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)])) + doc_list = list(set(d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname))) if doc_list: po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')] diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 83d4c33140..943f7aaeb1 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -299,8 +299,8 @@ class StatusUpdater(Document): args['name'] = self.get(args['percent_join_field_parent']) self._update_percent_field(args, update_modified) else: - distinct_transactions = set([d.get(args['percent_join_field']) - for d in self.get_all_children(args['source_dt'])]) + distinct_transactions = set(d.get(args['percent_join_field']) + for d in self.get_all_children(args['source_dt'])) for name in distinct_transactions: if name: diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 0da723d56e..9c29b0076b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -313,7 +313,7 @@ class StockController(AccountsController): def get_serialized_items(self): serialized_items = [] - item_codes = list(set([d.item_code for d in self.get("items")])) + item_codes = list(set(d.item_code for d in self.get("items"))) if item_codes: serialized_items = frappe.db.sql_list("""select name from `tabItem` where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))), @@ -324,8 +324,8 @@ class StockController(AccountsController): def validate_warehouse(self): from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company - warehouses = list(set([d.warehouse for d in - self.get("items") if getattr(d, "warehouse", None)])) + warehouses = list(set(d.warehouse for d in + self.get("items") if getattr(d, "warehouse", None))) target_warehouses = list(set([d.target_warehouse for d in self.get("items") if getattr(d, "target_warehouse", None)])) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 0b4fb3a3ed..2bb83ea7f0 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -378,10 +378,10 @@ class calculate_taxes_and_totals(object): def manipulate_grand_total_for_inclusive_tax(self): # if fully inclusive taxes and diff - if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]): + if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")): last_tax = self.doc.get("taxes")[-1] - non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount) - for d in self.doc.get("taxes") if not d.included_in_print_rate]) + non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount) + for d in self.doc.get("taxes") if not d.included_in_print_rate) diff = self.doc.total + non_inclusive_tax_amount \ - flt(last_tax.total, last_tax.precision("total")) @@ -521,8 +521,8 @@ class calculate_taxes_and_totals(object): def calculate_total_advance(self): if self.doc.docstatus < 2: - total_allocated_amount = sum([flt(adv.allocated_amount, adv.precision("allocated_amount")) - for adv in self.doc.get("advances")]) + total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount")) + for adv in self.doc.get("advances")) self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance")) @@ -622,7 +622,7 @@ class calculate_taxes_and_totals(object): if self.doc.doctype == "Sales Invoice" \ and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \ - and any([d.type == "Cash" for d in self.doc.payments]): + and any(d.type == "Cash" for d in self.doc.payments): grand_total = self.doc.rounded_total or self.doc.grand_total base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py index 66459fdbf8..7a4b2d3614 100644 --- a/erpnext/controllers/tests/test_mapper.py +++ b/erpnext/controllers/tests/test_mapper.py @@ -26,8 +26,8 @@ class TestMapper(unittest.TestCase): # Assert that all inserted items are present in updated sales order src_items = item_list_1 + item_list_2 + item_list_3 - self.assertEqual(set([d for d in src_items]), - set([d.item_code for d in updated_so.items])) + self.assertEqual(set(d for d in src_items), + set(d.item_code for d in updated_so.items)) def make_quotation(self, item_list, customer): diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index ecf041efd1..7c072e4fad 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -113,7 +113,7 @@ def post_process(doctype, data): doc.set_indicator() doc.status_display = ", ".join(doc.status_display) - doc.items_preview = ", ".join([d.item_name for d in doc.items if d.item_name]) + doc.items_preview = ", ".join(d.item_name for d in doc.items if d.item_name) result.append(doc) return result diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index d764db33f8..cdc4518894 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -317,7 +317,7 @@ class JobCard(Document): 'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id') if job_cards: - qty = min([d.qty for d in job_cards]) + qty = min(d.qty for d in job_cards) doc.db_set('material_transferred_for_manufacturing', qty) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index d1583f1473..39a6024e2c 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -232,7 +232,7 @@ def get_project(doctype, txt, searchfield, start, page_len, filters): meta = frappe.get_meta(doctype) searchfields = meta.get_search_fields() search_columns = ", " + ", ".join(searchfields) if searchfields else '' - search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields]) + search_cond = " or " + " or ".join(field + " like %(txt)s" for field in searchfields) return frappe.db.sql(""" select name {search_columns} from `tabProject` where %(key)s like %(txt)s diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index a3e4577f90..c8bd80fca0 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -87,8 +87,8 @@ class Timesheet(Document): def set_dates(self): if self.docstatus < 2 and self.time_logs: - start_date = min([getdate(d.from_time) for d in self.time_logs]) - end_date = max([getdate(d.to_time) for d in self.time_logs]) + start_date = min(getdate(d.from_time) for d in self.time_logs) + end_date = max(getdate(d.to_time) for d in self.time_logs) if start_date and end_date: self.start_date = getdate(start_date) diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index 2c7bb49cfb..98dd617f9b 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -122,7 +122,7 @@ def get_report_summary(data): if not data: return None - avg_completion = sum([project.percent_complete for project in data]) / len(data) + avg_completion = sum(project.percent_complete for project in data) / len(data) total = sum([project.total_tasks for project in data]) total_overdue = sum([project.overdue_tasks for project in data]) completed = sum([project.completed_tasks for project in data]) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 51d86ff0bf..818888c0c1 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -75,7 +75,7 @@ class Customer(TransactionBase): self.loyalty_program_tier = customer.loyalty_program_tier if self.sales_team: - if sum([member.allocated_percentage or 0 for member in self.sales_team]) != 100: + if sum(member.allocated_percentage or 0 for member in self.sales_team) != 100: frappe.throw(_("Total contribution percentage should be equal to 100")) def check_customer_group_change(self): diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 246f9234a4..e4f8a47581 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -50,7 +50,7 @@ class Quotation(SellingController): self.customer_name = company_name or lead_name def update_opportunity(self, status): - for opportunity in list(set([d.prevdoc_docname for d in self.get("items")])): + for opportunity in set(d.prevdoc_docname for d in self.get("items")): if opportunity: self.update_opportunity_status(status, opportunity) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d9e52e1d69..551f715bd5 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -151,7 +151,7 @@ class SalesOrder(SellingController): frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0])) def update_prevdoc_status(self, flag=None): - for quotation in list(set([d.prevdoc_docname for d in self.get("items")])): + for quotation in set(d.prevdoc_docname for d in self.get("items")): if quotation: doc = frappe.get_doc("Quotation", quotation) if doc.docstatus==2: diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 077538d479..27e023c1e5 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -54,7 +54,7 @@ class Company(NestedSet): def validate_abbr(self): if not self.abbr: - self.abbr = ''.join([c[0] for c in self.company_name.split()]).upper() + self.abbr = ''.join(c[0] for c in self.company_name.split()).upper() self.abbr = self.abbr.strip() @@ -335,7 +335,7 @@ class Company(NestedSet): clear_defaults_cache() def abbreviate(self): - self.abbr = ''.join([c[0].upper() for c in self.company_name.split()]) + self.abbr = ''.join(c[0].upper() for c in self.company_name.split()) def on_trash(self): """ diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index bff806d547..db31d6d699 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -139,7 +139,7 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non # return child item groups if the type is of "Is Group" return get_child_groups_for_list_in_html(item_group, start, limit, search) - child_groups = ", ".join([frappe.db.escape(i[0]) for i in get_child_groups(product_group)]) + child_groups = ", ".join(frappe.db.escape(i[0]) for i in get_child_groups(product_group)) # base query query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group, @@ -239,7 +239,7 @@ def get_item_for_list_in_html(context): return frappe.get_template(products_template).render(context) def get_group_item_count(item_group): - child_groups = ", ".join(['"' + i[0] + '"' for i in get_child_groups(item_group)]) + child_groups = ", ".join('"' + i[0] + '"' for i in get_child_groups(item_group)) return frappe.db.sql("""select count(*) from `tabItem` where docstatus = 0 and show_in_website = 1 and (item_group in (%s) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 8fdda565d2..508e17c340 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -304,7 +304,7 @@ def validate_serial_no_with_batch(serial_nos, item_code): frappe.throw(_("The serial no {0} does not belong to item {1}") .format(get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code))) - serial_no_link = ','.join([get_link_to_form("Serial No", sn) for sn in serial_nos]) + serial_no_link = ','.join(get_link_to_form("Serial No", sn) for sn in serial_nos) message = "Serial Nos" if len(serial_nos) > 1 else "Serial No" frappe.throw(_("There is no batch found against the {0}: {1}") diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index cce51cb9b1..dd31965fac 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -264,7 +264,7 @@ class DeliveryNote(SellingController): """ Validate that if packed qty exists, it should be equal to qty """ - if not any([flt(d.get('packed_qty')) for d in self.get("items")]): + if not any(flt(d.get('packed_qty')) for d in self.get("items")): return has_error = False for d in self.get("items"): diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index 81e730126e..9ec28d8981 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -68,7 +68,7 @@ class DeliveryTrip(Document): delete (bool, optional): Defaults to `False`. `True` if driver details need to be emptied, else `False`. """ - delivery_notes = list(set([stop.delivery_note for stop in self.delivery_stops if stop.delivery_note])) + delivery_notes = list(set(stop.delivery_note for stop in self.delivery_stops if stop.delivery_note)) update_fields = { "driver": self.driver, @@ -136,8 +136,8 @@ class DeliveryTrip(Document): # Include last leg in the final distance calculation self.uom = self.default_distance_uom - total_distance = sum([leg.get("distance", {}).get("value", 0.0) - for leg in directions.get("legs")]) # in meters + total_distance = sum(leg.get("distance", {}).get("value", 0.0) + for leg in directions.get("legs")) # in meters self.total_distance = total_distance * self.uom_conversion_factor else: idx += len(route) - 1 diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 83109469fc..5df4d8743f 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -78,7 +78,7 @@ class LandedCostVoucher(Document): .format(item.idx, item.item_code)) def set_total_taxes_and_charges(self): - self.total_taxes_and_charges = sum([flt(d.base_amount) for d in self.get("taxes")]) + self.total_taxes_and_charges = sum(flt(d.base_amount) for d in self.get("taxes")) def set_applicable_charges_on_item(self): if self.get('taxes') and self.distribute_charges_based_on != 'Distribute Manually': @@ -104,15 +104,15 @@ class LandedCostVoucher(Document): based_on = self.distribute_charges_based_on.lower() if based_on != 'distribute manually': - total = sum([flt(d.get(based_on)) for d in self.get("items")]) + total = sum(flt(d.get(based_on)) for d in self.get("items")) else: # consider for proportion while distributing manually - total = sum([flt(d.get('applicable_charges')) for d in self.get("items")]) + total = sum(flt(d.get('applicable_charges')) for d in self.get("items")) if not total: frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on)) - total_applicable_charges = sum([flt(d.applicable_charges) for d in self.get("items")]) + total_applicable_charges = sum(flt(d.applicable_charges) for d in self.get("items")) precision = get_field_precision(frappe.get_meta("Landed Cost Item").get_field("applicable_charges"), currency=frappe.get_cached_value('Company', self.company, "default_currency")) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 984ae46c66..32b08f60c4 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -311,7 +311,7 @@ def create_landed_cost_voucher(receipt_document_type, receipt_document, company, def distribute_landed_cost_on_items(lcv): based_on = lcv.distribute_charges_based_on.lower() - total = sum([flt(d.get(based_on)) for d in lcv.get("items")]) + total = sum(flt(d.get(based_on)) for d in lcv.get("items")) for item in lcv.get("items"): item.applicable_charges = flt(item.get(based_on)) * flt(lcv.total_taxes_and_charges) / flt(total) diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py index 2008bffcd3..4a843e0fde 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/packing_slip.py @@ -88,9 +88,9 @@ class PackingSlip(Document): rows = [d.item_code for d in self.get("items")] # also pick custom fields from delivery note - custom_fields = ', '.join(['dni.`{0}`'.format(d.fieldname) + custom_fields = ', '.join('dni.`{0}`'.format(d.fieldname) for d in frappe.get_meta("Delivery Note Item").get_custom_fields() - if d.fieldtype not in no_value_fields]) + if d.fieldtype not in no_value_fields) if custom_fields: custom_fields = ', ' + custom_fields diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 5095a80214..8d9b675bed 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -246,7 +246,7 @@ class TestPurchaseReceipt(unittest.TestCase): pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes") self.assertEqual(len(pr.get("supplied_items")), 2) - rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")]) + rm_supp_cost = sum(d.amount for d in pr.get("supplied_items")) self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) pr.cancel() diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 5ecc9f8140..b236f6a999 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -613,7 +613,7 @@ def fetch_serial_numbers(filters, qty, do_not_include=[]): batch_nos = filters.get("batch_no") expiry_date = filters.get("expiry_date") if batch_nos: - batch_no_condition = """and sr.batch_no in ({}) """.format(', '.join(["'%s'" % d for d in batch_nos])) + batch_no_condition = """and sr.batch_no in ({}) """.format(', '.join("'%s'" % d for d in batch_nos)) if expiry_date: batch_join_selection = "LEFT JOIN `tabBatch` batch on sr.batch_no = batch.name " diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 2f76bc7d56..560ceaa917 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -465,7 +465,7 @@ class StockEntry(StockController): """ # Set rate for outgoing items outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate) - finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item]) + finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item) # Set basic rate for incoming items for d in self.get('items'): diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py index 5296211fae..db7498bb21 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.py +++ b/erpnext/stock/report/item_price_stock/item_price_stock.py @@ -89,7 +89,7 @@ def get_item_price_qty_data(filters): {conditions}""" .format(conditions=conditions), filters, as_dict=1) - price_list_names = list(set([item.price_list_name for item in item_results])) + price_list_names = list(set(item.price_list_name for item in item_results)) buying_price_map = get_price_map(price_list_names, buying=1) selling_price_map = get_price_map(price_list_names, selling=1) diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index 2f70523264..2e13aa0b04 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -66,7 +66,7 @@ def get_consumed_items(condition): purpose is NULL or purpose not in ({}) ) - """.format(', '.join([f"'{p}'" for p in purpose_to_exclude])) + """.format(', '.join(f"'{p}'" for p in purpose_to_exclude)) condition = condition.replace("posting_date", "sle.posting_date") consumed_items = frappe.db.sql(""" diff --git a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py index 276e42ee43..8fffbccab3 100644 --- a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py +++ b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py @@ -141,7 +141,7 @@ def get_stock_ledger_entries(filters, items): return [] item_conditions_sql = ' and sle.item_code in ({})' \ - .format(', '.join([frappe.db.escape(i) for i in items])) + .format(', '.join(frappe.db.escape(i) for i in items)) conditions = get_sle_conditions(filters) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index bbd73e9112..b6a8063189 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -157,7 +157,7 @@ def get_stock_ledger_entries(filters, items): item_conditions_sql = '' if items: item_conditions_sql = ' and sle.item_code in ({})'\ - .format(', '.join([frappe.db.escape(i, percent=False) for i in items])) + .format(', '.join(frappe.db.escape(i, percent=False) for i in items)) conditions = get_conditions(filters) @@ -253,7 +253,7 @@ def get_items(filters): def get_item_details(items, sle, filters): item_details = {} if not items: - items = list(set([d.item_code for d in sle])) + items = list(set(d.item_code for d in sle)) if not items: return item_details @@ -291,7 +291,7 @@ def get_item_reorder_details(items): select parent, warehouse, warehouse_reorder_qty, warehouse_reorder_level from `tabItem Reorder` where parent in ({0}) - """.format(', '.join([frappe.db.escape(i, percent=False) for i in items])), as_dict=1) + """.format(', '.join(frappe.db.escape(i, percent=False) for i in items)), as_dict=1) return dict((d.parent + d.warehouse, d) for d in item_reorder_details) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 36996e9674..8909f217f4 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -115,7 +115,7 @@ def get_stock_ledger_entries(filters, items): item_conditions_sql = '' if items: item_conditions_sql = 'and sle.item_code in ({})'\ - .format(', '.join([frappe.db.escape(i) for i in items])) + .format(', '.join(frappe.db.escape(i) for i in items)) sl_entries = frappe.db.sql(""" SELECT @@ -169,7 +169,7 @@ def get_items(filters): def get_item_details(items, sl_entries, include_uom): item_details = {} if not items: - items = list(set([d.item_code for d in sl_entries])) + items = list(set(d.item_code for d in sl_entries)) if not items: return item_details diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b2825fc26f..fc82c789cc 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -521,7 +521,7 @@ class update_entries_after(object): fields=["purchase_rate", "name", "company"], filters = {'name': ('in', serial_nos)}) - incoming_values = sum([flt(d.purchase_rate) for d in all_serial_nos if d.company==sle.company]) + incoming_values = sum(flt(d.purchase_rate) for d in all_serial_nos if d.company==sle.company) # Get rate for serial nos which has been transferred to other company invalid_serial_nos = [d.name for d in all_serial_nos if d.company!=sle.company] diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.py b/erpnext/support/doctype/warranty_claim/warranty_claim.py index a3428a25af..a20e7a801b 100644 --- a/erpnext/support/doctype/warranty_claim/warranty_claim.py +++ b/erpnext/support/doctype/warranty_claim/warranty_claim.py @@ -29,7 +29,7 @@ class WarrantyClaim(TransactionBase): where t2.parent = t1.name and t2.prevdoc_docname = %s and t1.docstatus!=2""", (self.name)) if lst: - lst1 = ','.join([x[0] for x in lst]) + lst1 = ','.join(x[0] for x in lst) frappe.throw(_("Cancel Material Visit {0} before cancelling this Warranty Claim").format(lst1)) else: frappe.db.set(self, 'status', 'Cancelled') diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 66d6cd3888..70b41767d6 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -131,6 +131,6 @@ def get_non_stock_item_status(item_code, item_warehouse_field): if frappe.db.exists("Product Bundle", item_code): items = frappe.get_doc("Product Bundle", item_code).get_all_children() bundle_warehouse = frappe.db.get_value('Item', item_code, item_warehouse_field) - return all([ get_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items ]) + return all(get_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items) else: return 1 diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index f99da58e46..db997263c1 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -147,7 +147,7 @@ class TransactionBase(StatusUpdater): if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype): fieldname = self.prev_link_mapper[for_doctype]["fieldname"] - values = filter(None, tuple([item.as_dict()[fieldname] for item in self.items])) + values = filter(None, tuple(item.as_dict()[fieldname] for item in self.items)) if values: ret = { @@ -180,7 +180,7 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None): if isinstance(qty_fields, string_types): qty_fields = [qty_fields] - distinct_uoms = list(set([d.get(uom_field) for d in doc.get_all_children()])) + distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children())) integer_uoms = list(filter(lambda uom: frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) or None, distinct_uoms)) From c4d851e45f591667a33e3fb7e50c5bf1cf14a993 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 11 Jun 2021 17:27:43 +0530 Subject: [PATCH 1014/1264] fix: Test --- ...tracted_raw_materials_to_be_transferred.py | 68 ++++++++++++++----- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index c1fc6fb82f..11ec7669b0 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -12,34 +12,68 @@ import json, frappe, unittest class TestSubcontractedItemToBeTransferred(unittest.TestCase): def test_pending_and_transferred_qty(self): - po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes') + po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC") + + # Material Receipt of RMs make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100) make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100) - transfer_subcontracted_raw_materials(po.name) - col, data = execute(filters=frappe._dict({'supplier': po.supplier, - 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)), - 'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))})) - self.assertEqual(data[0]['purchase_order'], po.name) - self.assertIn(data[0]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100']) - self.assertIn(data[0]['p_qty'], [9, 18]) - self.assertIn(data[0]['t_qty'], [1, 2]) + + se = transfer_subcontracted_raw_materials(po) + + col, data = execute(filters=frappe._dict( + { + 'supplier': po.supplier, + 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)), + 'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10)) + } + )) + po.reload() + + po_data = [row for row in data if row.get('purchase_order') == po.name] + + self.assertEqual(len(po_data), 2) + self.assertIn(po_data[0]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100']) + self.assertIn(po_data[0]['p_qty'], [9, 18]) + self.assertIn(po_data[0]['transferred_qty'], [1, 2]) self.assertEqual(data[1]['purchase_order'], po.name) self.assertIn(data[1]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100']) self.assertIn(data[1]['p_qty'], [9, 18]) - self.assertIn(data[1]['t_qty'], [1, 2]) + self.assertIn(data[1]['transferred_qty'], [1, 2]) + se.cancel() + po.cancel() def transfer_subcontracted_raw_materials(po): rm_item = [ - {'item_code': '_Test Item', 'rm_item_code': '_Test Item', 'item_name': '_Test Item', 'qty': 1, - 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100, 'stock_uom': 'Nos'}, - {'item_code': '_Test Item Home Desktop 100', 'rm_item_code': '_Test Item Home Desktop 100', 'item_name': '_Test Item Home Desktop 100', 'qty': 2, - 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}] + { + 'name': po.supplied_items[0].name, + 'item_code': '_Test Item Home Desktop 100', + 'rm_item_code': '_Test Item Home Desktop 100', + 'item_name': '_Test Item Home Desktop 100', + 'qty': 2, + 'warehouse': '_Test Warehouse - _TC', + 'rate': 100, + 'amount': 200, + 'stock_uom': 'Nos' + }, + { + 'name': po.supplied_items[1].name, + 'item_code': '_Test Item', + 'rm_item_code': '_Test Item', + 'item_name': '_Test Item', + 'qty': 1, + 'warehouse': '_Test Warehouse - _TC', + 'rate': 100, + 'amount': 100, + 'stock_uom': 'Nos' + } + ] rm_item_string = json.dumps(rm_item) - se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string)) - se.from_warehouse = '_Test Warehouse 1 - _TC' - se.to_warehouse = '_Test Warehouse 1 - _TC' + se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string)) + se.from_warehouse = '_Test Warehouse - _TC' + se.to_warehouse = '_Test Warehouse - _TC' se.stock_entry_type = 'Send to Subcontractor' se.save() se.submit() + return se From 400205cc8a9632614617af2f21d598491db8cde8 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Sat, 12 Jun 2021 12:30:53 +0530 Subject: [PATCH 1015/1264] fix: payroll entry employee detail issue (#25968) * fix: payroll entry employee detail issue * fix: review changes --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 3953b463f1..7a70679db4 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -665,6 +665,8 @@ def get_employee_list(filters): emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date) return emp_list + return [] + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): From 17550fb4bfb968ea5c0be9ab1e233ac55ed35936 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 12 Jun 2021 13:33:21 +0530 Subject: [PATCH 1016/1264] feat: add Inactive status to Employee --- erpnext/accounts/party.py | 2 +- erpnext/hr/doctype/employee/employee.json | 4 ++-- erpnext/hr/doctype/employee/employee.py | 4 ++-- erpnext/hr/doctype/employee/employee_list.js | 2 +- erpnext/hr/doctype/employee_promotion/employee_promotion.py | 6 +++--- erpnext/hr/doctype/employee_transfer/employee_transfer.py | 6 +++--- erpnext/payroll/doctype/retention_bonus/retention_bonus.py | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e01cb6e151..e025fc6905 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -457,7 +457,7 @@ def validate_party_frozen_disabled(party_type, party_name): frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) elif party_type == "Employee": - if frappe.db.get_value("Employee", party_name, "status") == "Left": + if frappe.db.get_value("Employee", party_name, "status") != "Active": frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True) def get_timeline_data(doctype, name): diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 5123d6a5a7..5442ed56c3 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -207,7 +207,7 @@ "label": "Status", "oldfieldname": "status", "oldfieldtype": "Select", - "options": "Active\nLeft", + "options": "Active\nInactive\nLeft", "reqd": 1, "search_index": 1 }, @@ -813,7 +813,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2021-01-02 16:54:33.477439", + "modified": "2021-06-12 11:31:37.730760", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index ed7d588434..bc5694226a 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -37,7 +37,7 @@ class Employee(NestedSet): def validate(self): from erpnext.controllers.status_updater import validate_status - validate_status(self.status, ["Active", "Temporary Leave", "Left"]) + validate_status(self.status, ["Active", "Inactive", "Left"]) self.employee = self.name self.set_employee_name() @@ -478,7 +478,7 @@ def get_employee_emails(employee_list): @frappe.whitelist() def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False): - filters = [['status', '!=', 'Left']] + filters = [['status', '=', 'Active']] if company and company != 'All Companies': filters.append(['company', '=', company]) diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js index 44837030be..6679e318c2 100644 --- a/erpnext/hr/doctype/employee/employee_list.js +++ b/erpnext/hr/doctype/employee/employee_list.js @@ -3,7 +3,7 @@ frappe.listview_settings['Employee'] = { filters: [["status","=", "Active"]], get_indicator: function(doc) { var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status]; - indicator[1] = {"Active": "green", "Temporary Leave": "red", "Left": "gray"}[doc.status]; + indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray"}[doc.status]; return indicator; } }; diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index 4994921268..83fb235f92 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -11,12 +11,12 @@ from erpnext.hr.utils import update_employee class EmployeePromotion(Document): def validate(self): - if frappe.get_value("Employee", self.employee, "status") == "Left": - frappe.throw(_("Cannot promote Employee with status Left")) + if frappe.get_value("Employee", self.employee, "status") != "Active": + frappe.throw(_("Cannot promote Employee with status Left or Inactive")) def before_submit(self): if getdate(self.promotion_date) > getdate(): - frappe.throw(_("Employee Promotion cannot be submitted before Promotion Date "), + frappe.throw(_("Employee Promotion cannot be submitted before Promotion Date"), frappe.DocstatusTransitionError) def on_submit(self): diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index 3539970a32..6eec9fa12a 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -11,12 +11,12 @@ from erpnext.hr.utils import update_employee class EmployeeTransfer(Document): def validate(self): - if frappe.get_value("Employee", self.employee, "status") == "Left": - frappe.throw(_("Cannot transfer Employee with status Left")) + if frappe.get_value("Employee", self.employee, "status") != "Active": + frappe.throw(_("Cannot transfer Employee with status Left or Inactive")) def before_submit(self): if getdate(self.transfer_date) > getdate(): - frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date "), + frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"), frappe.DocstatusTransitionError) def on_submit(self): diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py index b8e56ae42a..049ea265cc 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py @@ -10,8 +10,8 @@ from frappe.utils import getdate class RetentionBonus(Document): def validate(self): - if frappe.get_value('Employee', self.employee, 'status') == 'Left': - frappe.throw(_('Cannot create Retention Bonus for left Employees')) + if frappe.get_value('Employee', self.employee, 'status') != 'Active': + frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees')) if getdate(self.bonus_payment_date) < getdate(): frappe.throw(_('Bonus Payment Date cannot be a past date')) From cf349aadf9ea7108bb0a95b86da23ffab87d62e5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 12 Jun 2021 14:05:12 +0530 Subject: [PATCH 1017/1264] fix: unable to enter score in Assessment Result details grid (#25945) (#26031) --- .../education/doctype/assessment_result/assessment_result.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/education/doctype/assessment_result/assessment_result.js b/erpnext/education/doctype/assessment_result/assessment_result.js index 617a873b82..c35f607a99 100644 --- a/erpnext/education/doctype/assessment_result/assessment_result.js +++ b/erpnext/education/doctype/assessment_result/assessment_result.js @@ -6,7 +6,8 @@ frappe.ui.form.on('Assessment Result', { if (!frm.doc.__islocal) { frm.trigger('setup_chart'); } - frm.set_df_property('details', 'read_only', 1); + + frm.get_field('details').grid.cannot_add_rows = true; frm.set_query('course', function() { return { From 28cdff10cfe981c6e91b1a9ef897638e7c800af6 Mon Sep 17 00:00:00 2001 From: Anoop <3326959+akurungadam@users.noreply.github.com> Date: Sat, 12 Jun 2021 14:17:04 +0530 Subject: [PATCH 1018/1264] fix: update linked Customer on Patient update only if Link Customer to Patient is enabled (#25926) --- erpnext/healthcare/doctype/patient/patient.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index 789d452c07..cebcb2068e 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -33,21 +33,21 @@ class Patient(Document): self.reload() # self.notify_update() def on_update(self): - if self.customer: - customer = frappe.get_doc('Customer', self.customer) - if self.customer_group: - customer.customer_group = self.customer_group - if self.territory: - customer.territory = self.territory + if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'): + if self.customer: + customer = frappe.get_doc('Customer', self.customer) + if self.customer_group: + customer.customer_group = self.customer_group + if self.territory: + customer.territory = self.territory - customer.customer_name = self.patient_name - customer.default_price_list = self.default_price_list - customer.default_currency = self.default_currency - customer.language = self.language - customer.ignore_mandatory = True - customer.save(ignore_permissions=True) - else: - if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'): + customer.customer_name = self.patient_name + customer.default_price_list = self.default_price_list + customer.default_currency = self.default_currency + customer.language = self.language + customer.ignore_mandatory = True + customer.save(ignore_permissions=True) + else: create_customer(self) def set_full_name(self): From 580346360fc3a6dd168e726cd035f5cc42f8f3c2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 14 Jun 2021 11:16:39 +0530 Subject: [PATCH 1019/1264] fix: Auto tax calculations in Payment Entry --- .../doctype/payment_entry/payment_entry.js | 86 +++++++++++++++++-- .../doctype/payment_entry/payment_entry.py | 44 +++++----- .../purchase_taxes_and_charges.json | 3 +- .../sales_taxes_and_charges.json | 3 +- erpnext/controllers/accounts_controller.py | 34 ++++---- 5 files changed, 118 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 939f3546ff..d3ac3a6676 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -1087,6 +1087,8 @@ frappe.ui.form.on('Payment Entry', { initialize_taxes: function(frm) { $.each(frm.doc["taxes"] || [], function(i, tax) { + frm.events.validate_taxes_and_charges(tax); + frm.events.validate_inclusive_tax(tax); tax.item_wise_tax_detail = {}; let tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]; @@ -1101,6 +1103,73 @@ frappe.ui.form.on('Payment Entry', { }); }, + validate_taxes_and_charges: function(d) { + let msg = ""; + + if (d.account_head && !d.description) { + // set description from account head + d.description = d.account_head.split(' - ').slice(0, -1).join(' - '); + } + + if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) { + msg = __("Please select Charge Type first"); + d.row_id = ""; + d.rate = d.tax_amount = 0.0; + } else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) { + msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"); + d.row_id = ""; + } else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) { + if (d.idx == 1) { + msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"); + d.charge_type = ''; + } else if (!d.row_id) { + msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]); + d.row_id = ""; + } else if (d.row_id && d.row_id >= d.idx) { + msg = __("Cannot refer row number greater than or equal to current row number for this Charge type"); + d.row_id = ""; + } + } + if (msg) { + frappe.validated = false; + refresh_field("taxes"); + frappe.throw(msg); + } + + }, + + validate_inclusive_tax: function(tax) { + let actual_type_error = function() { + let msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx]) + frappe.throw(msg); + }; + + let on_previous_row_error = function(row_range) { + let msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included", + [tax.idx, __(tax.doctype), tax.charge_type, row_range]) + frappe.throw(msg); + }; + + if(cint(tax.included_in_paid_amount)) { + if(tax.charge_type == "Actual") { + // inclusive tax cannot be of type Actual + actual_type_error(); + } else if(tax.charge_type == "On Previous Row Amount" && + !cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_paid_amount) + ) { + // referred row should also be an inclusive tax + on_previous_row_error(tax.row_id); + } else if(tax.charge_type == "On Previous Row Total") { + let taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id), + function(t) { return cint(t.included_in_paid_amount) ? null : t; }); + if(taxes_not_included.length > 0) { + // all rows above this tax should be inclusive + on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id); + } + } + } + }, + determine_exclusive_rate: function(frm) { let has_inclusive_tax = false; $.each(frm.doc["taxes"] || [], function(i, row) { @@ -1110,8 +1179,7 @@ frappe.ui.form.on('Payment Entry', { let cumulated_tax_fraction = 0.0; $.each(frm.doc["taxes"] || [], function(i, tax) { - let current_tax_fraction = frm.events.get_current_tax_fraction(frm, tax); - tax.tax_fraction_for_current_item = current_tax_fraction[0]; + tax.tax_fraction_for_current_item = frm.events.get_current_tax_fraction(frm, tax); if(i==0) { tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; @@ -1132,9 +1200,7 @@ frappe.ui.form.on('Payment Entry', { if(cint(tax.included_in_paid_amount)) { let tax_rate = tax.rate; - if (tax.charge_type == "Actual") { - current_tax_fraction = tax.tax_amount/(frm.doc.paid_amount_after_tax + frm.doc.tax_amount); - } else if(tax.charge_type == "On Paid Amount") { + if(tax.charge_type == "On Paid Amount") { current_tax_fraction = (tax_rate / 100.0); } else if(tax.charge_type == "On Previous Row Amount") { current_tax_fraction = (tax_rate / 100.0) * @@ -1147,7 +1213,6 @@ frappe.ui.form.on('Payment Entry', { if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { current_tax_fraction *= -1; - inclusive_tax_amount_per_qty *= -1; } return current_tax_fraction; }, @@ -1207,10 +1272,8 @@ frappe.ui.form.on('Payment Entry', { frappe.throw( __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")); } - if (!tax.row_id) { - tax.row_id = tax.idx - 1; - } } + if(tax.charge_type == "Actual") { current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax)) } else if(tax.charge_type == "On Paid Amount") { @@ -1296,6 +1359,11 @@ frappe.ui.form.on('Advance Taxes and Charges', { included_in_paid_amount: function(frm) { frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); + }, + + charge_type: function(frm) { + frm.events.apply_taxes(frm); + frm.events.set_unallocated_amount(frm); } }) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 2c6deb3896..70b38735e7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -16,9 +16,11 @@ from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_ac from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details - from six import string_types, iteritems +from erpnext.controllers.accounts_controller import validate_conversion_rate, \ + validate_taxes_and_charges, validate_inclusive_tax + class InvalidPaymentEntry(ValidationError): pass @@ -407,20 +409,6 @@ class PaymentEntry(AccountsController): net_total = self.paid_amount included_in_paid_amount = 0 - if self.get('references'): - for doc in self.get('references'): - if doc.reference_doctype == 'Purchase Order': - reference_doclist.append(doc.reference_name) - - if reference_doclist: - order_amount = frappe.db.get_all('Purchase Order', fields=['sum(net_total)'], - filters = {'name': ('in', reference_doclist), 'docstatus': 1, - 'apply_tds': 1}, as_list=1) - - if order_amount: - net_total = order_amount[0][0] - included_in_paid_amount = 1 - # Adding args as purchase invoice to get TDS amount args = frappe._dict({ 'company': self.company, @@ -719,9 +707,9 @@ class PaymentEntry(AccountsController): if account_currency != self.company_currency: frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency)) - if (self.payment_type == 'Pay' and self.advance_tax_account) or self.payment_type == 'Receive': + if self.payment_type == 'Pay': dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" - elif (self.payment_type == 'Receive' and self.advance_tax_account) or self.payment_type == 'Pay': + elif self.payment_type == 'Receive': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" payment_or_advance_account = self.get_party_account_for_taxes() @@ -747,6 +735,8 @@ class PaymentEntry(AccountsController): if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, + "party_type": self.party_type, + "party": self.party }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): @@ -770,9 +760,9 @@ class PaymentEntry(AccountsController): def get_party_account_for_taxes(self): if self.advance_tax_account: return self.advance_tax_account - elif self.payment_type == 'Pay': - return self.paid_from elif self.payment_type == 'Receive': + return self.paid_from + elif self.payment_type == 'Pay': return self.paid_to def update_advance_paid(self): @@ -823,6 +813,9 @@ class PaymentEntry(AccountsController): def initialize_taxes(self): for tax in self.get("taxes"): + validate_taxes_and_charges(tax) + validate_inclusive_tax(tax, self) + tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] if tax.charge_type != "Actual": @@ -918,15 +911,11 @@ class PaymentEntry(AccountsController): if cint(tax.included_in_paid_amount): tax_rate = tax.rate - if tax.charge_type == 'Actual': - current_tax_fraction = tax.tax_amount/ (self.paid_amount_after_tax + tax.tax_amount) - elif tax.charge_type == "On Paid Amount": + if tax.charge_type == "On Paid Amount": current_tax_fraction = tax_rate / 100.0 - elif tax.charge_type == "On Previous Row Amount": current_tax_fraction = (tax_rate / 100.0) * \ self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item - elif tax.charge_type == "On Previous Row Total": current_tax_fraction = (tax_rate / 100.0) * \ self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item @@ -1626,6 +1615,13 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta paid_amount = received_amount * doc.get('conversion_rate', 1) if dt == "Employee Advance": paid_amount = received_amount * doc.get('exchange_rate', 1) + + if dt == "Purchase Order" and doc.apply_tds: + if party_account_currency == bank.account_currency: + paid_amount = received_amount = doc.base_net_total + else: + paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1) + return paid_amount, received_amount def apply_early_payment_discount(paid_amount, received_amount, doc): diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index 9b07645ccc..1fa68e0a8a 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -218,6 +218,7 @@ }, { "default": "0", + "depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", "fieldname": "included_in_paid_amount", "fieldtype": "Check", @@ -227,7 +228,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-09 11:48:25.335733", + "modified": "2021-06-14 01:43:50.750455", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 170d34e651..1b7a0fe562 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -195,6 +195,7 @@ }, { "default": "0", + "depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", "fieldname": "included_in_paid_amount", "fieldtype": "Check", @@ -205,7 +206,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-09 11:48:04.691596", + "modified": "2021-06-14 01:44:36.899147", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7c6061defa..a507159cb6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1192,7 +1192,7 @@ def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, c def validate_taxes_and_charges(tax): - if tax.charge_type in ['Actual', 'On Net Total'] and tax.row_id: + if tax.charge_type in ['Actual', 'On Net Total', 'On Paid Amount'] and tax.row_id: frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'")) elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']: if cint(tax.idx) == 1: @@ -1209,23 +1209,23 @@ def validate_taxes_and_charges(tax): def validate_inclusive_tax(tax, doc): def _on_previous_row_error(row_range): - throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, - row_range)) + throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range)) - if cint(getattr(tax, "included_in_print_rate", None)): - if tax.charge_type == "Actual": - # inclusive tax cannot be of type Actual - throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate").format(tax.idx)) - elif tax.charge_type == "On Previous Row Amount" and \ - not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate): - # referred row should also be inclusive - _on_previous_row_error(tax.row_id) - elif tax.charge_type == "On Previous Row Total" and \ - not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]): - # all rows about the reffered tax should be inclusive - _on_previous_row_error("1 - %d" % (tax.row_id,)) - elif tax.get("category") == "Valuation": - frappe.throw(_("Valuation type charges can not be marked as Inclusive")) + for fieldname in ['included_in_print_rate', 'included_in_paid_amount']: + if cint(getattr(tax, fieldname, None)): + if tax.charge_type == "Actual": + # inclusive tax cannot be of type Actual + throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx)) + elif tax.charge_type == "On Previous Row Amount" and \ + not cint(doc.get("taxes")[cint(tax.row_id) - 1].get(fieldname)): + # referred row should also be inclusive + _on_previous_row_error(tax.row_id) + elif tax.charge_type == "On Previous Row Total" and \ + not all([cint(t.get(fieldname) for t in doc.get("taxes")[:cint(tax.row_id) - 1])]): + # all rows about the referred tax should be inclusive + _on_previous_row_error("1 - %d" % (cint(tax.row_id),)) + elif tax.get("category") == "Valuation": + frappe.throw(_("Valuation type charges can not be marked as Inclusive")) def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): From 0511ffcf30459a8646978429101c7edf6315f69b Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 14 Jun 2021 13:22:44 +0530 Subject: [PATCH 1020/1264] fix(General Ledger): Implement multi-account selection --- .../report/general_ledger/general_ledger.js | 15 +++++------- .../report/general_ledger/general_ledger.py | 24 ++++++++++++++----- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 84f786814d..f3c3865b4e 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -36,16 +36,13 @@ frappe.query_reports["General Ledger"] = { { "fieldname":"account", "label": __("Account"), - "fieldtype": "Link", + "fieldtype": "MultiSelectList", "options": "Account", - "get_query": function() { - var company = frappe.query_report.get_filter_value('company'); - return { - "doctype": "Account", - "filters": { - "company": company, - } - } + get_data: function(txt) { + console.log("txt = ", txt) + return frappe.db.get_link_options('Account', txt, { + company: frappe.query_report.get_filter_value("company") + }); } }, { diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 562df4f6f7..53c638bf4a 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -49,8 +49,12 @@ def validate_filters(filters, account_details): if not filters.get("from_date") and not filters.get("to_date"): frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) - if filters.get("account") and not account_details.get(filters.account): - frappe.throw(_("Account {0} does not exists").format(filters.account)) + for account in filters.account: + if not account_details.get(account): + frappe.throw(_("Account {0} does not exists").format(account)) + + if filters.get('account'): + filters.account = frappe.parse_json(filters.get('account')) if (filters.get("account") and filters.get("group_by") == _('Group by Account') and account_details[filters.account].is_group == 0): @@ -87,7 +91,7 @@ def set_account_currency(filters): account_currency = None if filters.get("account"): - account_currency = get_account_currency(filters.account) + account_currency = get_account_currency(filters.account[0]) elif filters.get("party"): gle_currency = frappe.db.get_value( "GL Entry", { @@ -205,10 +209,18 @@ def get_gl_entries(filters, accounting_dimensions): def get_conditions(filters): conditions = [] + if filters.get("account") and not filters.get("include_dimensions"): - lft, rgt = frappe.db.get_value("Account", filters["account"], ["lft", "rgt"]) - conditions.append("""account in (select name from tabAccount - where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt)) + account_conditions = "" + for account in filters["account"]: + lft, rgt = frappe.db.get_value("Account", account, ["lft", "rgt"]) + account_conditions += """account in (select name from tabAccount + where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt) + + # so that the OR doesn't get added to the last account condition + if account != filters["account"][-1]: + account_conditions += " OR " + conditions.append(account_conditions) if filters.get("cost_center"): filters.cost_center = get_cost_centers_with_children(filters.cost_center) From 8718013c96441a7ab71b22bc97fe9bc06bdb01d7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 14 Jun 2021 14:34:44 +0530 Subject: [PATCH 1021/1264] fix: Add separate function to validate payment entry taxes --- .../doctype/payment_entry/payment_entry.py | 22 ++++++++++++-- erpnext/controllers/accounts_controller.py | 29 +++++++++---------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 70b38735e7..3a1c7b9234 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -18,8 +18,7 @@ from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from six import string_types, iteritems -from erpnext.controllers.accounts_controller import validate_conversion_rate, \ - validate_taxes_and_charges, validate_inclusive_tax +from erpnext.controllers.accounts_controller import validate_taxes_and_charges class InvalidPaymentEntry(ValidationError): pass @@ -925,6 +924,25 @@ class PaymentEntry(AccountsController): return current_tax_fraction +def validate_inclusive_tax(tax, doc): + def _on_previous_row_error(row_range): + throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range)) + + if cint(getattr(tax, "included_in_paid_amount", None)): + if tax.charge_type == "Actual": + # inclusive tax cannot be of type Actual + throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx)) + elif tax.charge_type == "On Previous Row Amount" and \ + not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_paid_amount): + # referred row should also be inclusive + _on_previous_row_error(tax.row_id) + elif tax.charge_type == "On Previous Row Total" and \ + not all([cint(t.included_in_paid_amount for t in doc.get("taxes")[:cint(tax.row_id) - 1])]): + # all rows about the referred tax should be inclusive + _on_previous_row_error("1 - %d" % (cint(tax.row_id),)) + elif tax.get("category") == "Valuation": + frappe.throw(_("Valuation type charges can not be marked as Inclusive")) + @frappe.whitelist() def get_outstanding_reference_documents(args): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a507159cb6..1cd0f5f5b2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1211,21 +1211,20 @@ def validate_inclusive_tax(tax, doc): def _on_previous_row_error(row_range): throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range)) - for fieldname in ['included_in_print_rate', 'included_in_paid_amount']: - if cint(getattr(tax, fieldname, None)): - if tax.charge_type == "Actual": - # inclusive tax cannot be of type Actual - throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx)) - elif tax.charge_type == "On Previous Row Amount" and \ - not cint(doc.get("taxes")[cint(tax.row_id) - 1].get(fieldname)): - # referred row should also be inclusive - _on_previous_row_error(tax.row_id) - elif tax.charge_type == "On Previous Row Total" and \ - not all([cint(t.get(fieldname) for t in doc.get("taxes")[:cint(tax.row_id) - 1])]): - # all rows about the referred tax should be inclusive - _on_previous_row_error("1 - %d" % (cint(tax.row_id),)) - elif tax.get("category") == "Valuation": - frappe.throw(_("Valuation type charges can not be marked as Inclusive")) + if cint(getattr(tax, "included_in_print_rate", None)): + if tax.charge_type == "Actual": + # inclusive tax cannot be of type Actual + throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx)) + elif tax.charge_type == "On Previous Row Amount" and \ + not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate): + # referred row should also be inclusive + _on_previous_row_error(tax.row_id) + elif tax.charge_type == "On Previous Row Total" and \ + not all([cint(t.included_in_print_rate for t in doc.get("taxes")[:cint(tax.row_id) - 1])]): + # all rows about the referred tax should be inclusive + _on_previous_row_error("1 - %d" % (cint(tax.row_id),)) + elif tax.get("category") == "Valuation": + frappe.throw(_("Valuation type charges can not be marked as Inclusive")) def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): From 9eac4d0af66ab1952e20d0236d675ff09f03657a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 14 Jun 2021 14:44:19 +0530 Subject: [PATCH 1022/1264] fix: Import throw --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 3a1c7b9234..b6b2bef963 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext, json -from frappe import _, scrub, ValidationError +from frappe import _, scrub, ValidationError, throw from frappe.utils import flt, comma_or, nowdate, getdate, cint from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on from erpnext.accounts.party import get_party_account From 3b070d1b0540d320edecceefec2bf51a3e308bb2 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 14 Jun 2021 14:51:33 +0530 Subject: [PATCH 1023/1264] fix: Flaky test for Report Subcontracted Raw materials to be transferred --- ...tracted_raw_materials_to_be_transferred.py | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index 11ec7669b0..2448e17c50 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -30,42 +30,54 @@ class TestSubcontractedItemToBeTransferred(unittest.TestCase): po.reload() po_data = [row for row in data if row.get('purchase_order') == po.name] + # Alphabetically sort to be certain of order + po_data = sorted(po_data, key = lambda i: i['rm_item_code']) self.assertEqual(len(po_data), 2) - self.assertIn(po_data[0]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100']) - self.assertIn(po_data[0]['p_qty'], [9, 18]) - self.assertIn(po_data[0]['transferred_qty'], [1, 2]) + self.assertEqual(po_data[0]['purchase_order'], po.name) - self.assertEqual(data[1]['purchase_order'], po.name) - self.assertIn(data[1]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100']) - self.assertIn(data[1]['p_qty'], [9, 18]) - self.assertIn(data[1]['transferred_qty'], [1, 2]) + self.assertEqual(po_data[0]['rm_item_code'], '_Test Item') + self.assertEqual(po_data[0]['p_qty'], 8) + self.assertEqual(po_data[0]['transferred_qty'], 2) + + self.assertEqual(po_data[1]['rm_item_code'], '_Test Item Home Desktop 100') + self.assertEqual(po_data[1]['p_qty'], 19) + self.assertEqual(po_data[1]['transferred_qty'], 1) se.cancel() po.cancel() def transfer_subcontracted_raw_materials(po): + # Order of supplied items fetched in PO is flaky + transfer_qty_map = { + '_Test Item': 2, + '_Test Item Home Desktop 100': 1 + } + + item_1 = po.supplied_items[0].rm_item_code + item_2 = po.supplied_items[1].rm_item_code + rm_item = [ { 'name': po.supplied_items[0].name, - 'item_code': '_Test Item Home Desktop 100', - 'rm_item_code': '_Test Item Home Desktop 100', - 'item_name': '_Test Item Home Desktop 100', - 'qty': 2, + 'item_code': item_1, + 'rm_item_code': item_1, + 'item_name': item_1, + 'qty': transfer_qty_map[item_1], 'warehouse': '_Test Warehouse - _TC', 'rate': 100, - 'amount': 200, + 'amount': 100 * transfer_qty_map[item_1], 'stock_uom': 'Nos' }, { 'name': po.supplied_items[1].name, - 'item_code': '_Test Item', - 'rm_item_code': '_Test Item', - 'item_name': '_Test Item', - 'qty': 1, + 'item_code': item_2, + 'rm_item_code': item_2, + 'item_name': item_2, + 'qty': transfer_qty_map[item_2], 'warehouse': '_Test Warehouse - _TC', 'rate': 100, - 'amount': 100, + 'amount': 100 * transfer_qty_map[item_2], 'stock_uom': 'Nos' } ] From 27ec51f021931c6d9505e73e6852cb6c4bb97f86 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 14 Jun 2021 16:41:56 +0530 Subject: [PATCH 1024/1264] fix(General Ledger): Filter Cost Center drop-down list by Company --- erpnext/accounts/report/general_ledger/general_ledger.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 84f786814d..80a25c907e 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -135,7 +135,9 @@ frappe.query_reports["General Ledger"] = { "label": __("Cost Center"), "fieldtype": "MultiSelectList", get_data: function(txt) { - return frappe.db.get_link_options('Cost Center', txt); + return frappe.db.get_link_options('Cost Center', txt, { + company: frappe.query_report.get_filter_value("company") + }); } }, { From 75b30efb050ed333d519ebad924b4cb188098521 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 14 Jun 2021 16:42:27 +0530 Subject: [PATCH 1025/1264] fix(General Ledger): Filter Project drop-down list by Company --- erpnext/accounts/report/general_ledger/general_ledger.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 80a25c907e..a8e55307f9 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -145,7 +145,9 @@ frappe.query_reports["General Ledger"] = { "label": __("Project"), "fieldtype": "MultiSelectList", get_data: function(txt) { - return frappe.db.get_link_options('Project', txt); + return frappe.db.get_link_options('Project', txt, { + company: frappe.query_report.get_filter_value("company") + }); } }, { From 4cd0f6ce23f703e2f98baab83ae0b38ac9e26943 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 14 Jun 2021 20:01:04 +0530 Subject: [PATCH 1026/1264] fix: Revert unintended changes --- erpnext/controllers/accounts_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1cd0f5f5b2..243939b275 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1220,9 +1220,9 @@ def validate_inclusive_tax(tax, doc): # referred row should also be inclusive _on_previous_row_error(tax.row_id) elif tax.charge_type == "On Previous Row Total" and \ - not all([cint(t.included_in_print_rate for t in doc.get("taxes")[:cint(tax.row_id) - 1])]): + not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]): # all rows about the referred tax should be inclusive - _on_previous_row_error("1 - %d" % (cint(tax.row_id),)) + _on_previous_row_error("1 - %d" % (tax.row_id,)) elif tax.get("category") == "Valuation": frappe.throw(_("Valuation type charges can not be marked as Inclusive")) From b5a14911763fa29ad9f300fdd0dd1b88a2e5126f Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 15 Jun 2021 12:44:04 +0530 Subject: [PATCH 1027/1264] fix: escaped warehouse value for sql query (#26049) --- erpnext/controllers/stock_controller.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 9c29b0076b..6a7c9e3d0e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -558,11 +558,8 @@ def future_sle_exists(args): or_conditions = [] for warehouse, items in warehouse_items_map.items(): or_conditions.append( - "warehouse = '{}' and item_code in ({})".format( - warehouse, - ", ".join(frappe.db.escape(item) for item in items) - ) - ) + f"""warehouse = {frappe.db.escape(warehouse)} + and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""") return frappe.db.sql(""" select name From 79dc0f0afce6df44e88b223f8db7fb947047c710 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 16 Jun 2021 03:37:11 +0530 Subject: [PATCH 1028/1264] fix: Remove console.log() --- erpnext/accounts/report/general_ledger/general_ledger.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index f3c3865b4e..28139d14b2 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -39,7 +39,6 @@ frappe.query_reports["General Ledger"] = { "fieldtype": "MultiSelectList", "options": "Account", get_data: function(txt) { - console.log("txt = ", txt) return frappe.db.get_link_options('Account', txt, { company: frappe.query_report.get_filter_value("company") }); From 53a9ac44662be877160c5acbbafc9fcaaa16bf21 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 16 Jun 2021 04:10:29 +0530 Subject: [PATCH 1029/1264] fix(General Ledger): Condense account_conditions --- erpnext/accounts/report/general_ledger/general_ledger.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 53c638bf4a..aed9c4a049 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -211,15 +211,16 @@ def get_conditions(filters): conditions = [] if filters.get("account") and not filters.get("include_dimensions"): - account_conditions = "" + account_conditions = "account in (select name from tabAccount where" for account in filters["account"]: lft, rgt = frappe.db.get_value("Account", account, ["lft", "rgt"]) - account_conditions += """account in (select name from tabAccount - where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt) + account_conditions += """ (lft>=%s and rgt<=%s) """ % (lft, rgt) # so that the OR doesn't get added to the last account condition if account != filters["account"][-1]: - account_conditions += " OR " + account_conditions += "OR" + + account_conditions += "and docstatus<2)" conditions.append(account_conditions) if filters.get("cost_center"): From 1fd80992d705f55215a89a89a90e445224937e1b Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 16 Jun 2021 13:27:34 +0530 Subject: [PATCH 1030/1264] fix(pos): 'NoneType' object is not iterable (#26066) --- erpnext/selling/page/point_of_sale/point_of_sale.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 7742f24385..296c8c2fd9 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -9,7 +9,7 @@ from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability def search_by_term(search_term, warehouse, price_list): - result = search_for_serial_or_batch_or_barcode_number(search_term) + result = search_for_serial_or_batch_or_barcode_number(search_term) or {} item_code = result.get("item_code") or search_term serial_no = result.get("serial_no") or "" @@ -23,9 +23,9 @@ def search_by_term(search_term, warehouse, price_list): item_stock_qty = get_stock_availability(item_code, warehouse) price_list_rate, currency = frappe.db.get_value('Item Price', { - 'price_list': price_list, - 'item_code': item_code - }, ["price_list_rate", "currency"]) + 'price_list': price_list, + 'item_code': item_code + }, ["price_list_rate", "currency"]) or [None, None] item_info.update({ 'serial_no': serial_no, @@ -46,7 +46,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te result = [] if search_term: - result = search_by_term(search_term, warehouse, price_list) + result = search_by_term(search_term, warehouse, price_list) or [] if result: return result From b1c72da7d7511dbdf865f8014984f47c6b6d6849 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 16 Jun 2021 14:08:32 +0530 Subject: [PATCH 1031/1264] fix: Training event --- .../training_scheduled.json | 4 ++-- .../training_scheduled/training_scheduled.md | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json index e49541e321..f3650038fd 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.json +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json @@ -11,8 +11,8 @@ "event": "Submit", "idx": 0, "is_standard": 1, - "message": "\n \n \n \n \n \n \n \n
    \n
    \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n
    \n
    \n\n\n \n \n \n \n \n \n \n
    \n
    \n {{ doc.introduction }}\n
      \n
    • {{_(\"Event Location\")}}: {{ doc.location }}
    • \n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n
    • {{_(\"Date\")}}: {{ start.strftime(\"%A, %d %b %Y\") }}
    • \n
    • \n {{_(\"Timing\")}}: {{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}\n
    • \n {% else %}\n
    • {{_(\"Start Time\")}}: {{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n
    • {{_(\"End Time\")}}: {{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n {% endif %}\n
    • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
    • \n {% if doc.is_mandatory %}\n
    • Note: This Training Event is mandatory
    • \n {% endif %}\n
    \n
    \n
    ", - "modified": "2021-05-24 16:29:13.165930", + "message": "\n \n \n \n \n \n \n \n
    \n
    \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n
    \n
    \n\n\n \n \n \n \n \n \n \n
    \n
    \n {{ doc.introduction }}\n
      \n
    • {{_(\"Event Location\")}}: {{ doc.location }}
    • \n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n
    • {{_(\"Date\")}}: {{ start.strftime(\"%A, %d %b %Y\") }}
    • \n
    • \n {{_(\"Timing\")}}: {{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}\n
    • \n {% else %}\n
    • \n {{_(\"Start Time\")}}: {{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n
    • {{_(\"End Time\")}}: {{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}
    • \n {% endif %}\n
    • {{ _(\"Event Link\") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
    • \n {% if doc.is_mandatory %}\n
    • {{ _(\"Note: This Training Event is mandatory\") }}
    • \n {% endif %}\n
    \n
    \n
    ", + "modified": "2021-06-16 14:08:12.933367", "modified_by": "Administrator", "module": "HR", "name": "Training Scheduled", diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md index 418fd4990e..b9ba846be5 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.md +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md @@ -24,19 +24,19 @@ {% set start = frappe.utils.get_datetime(doc.start_time) %} {% set end = frappe.utils.get_datetime(doc.end_time) %} {% if start.date() == end.date() %} -
  • {{_("Date")}}: {{ start.strftime("%A, %d %b %Y") }}
  • -
  • - {{_("Timing")}}: {{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }} -
  • +
  • {{_("Date")}}: {{ start.strftime("%A, %d %b %Y") }}
  • +
  • + {{_("Timing")}}: {{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }} +
  • {% else %} -
  • {{_("Start Time")}}: {{ start.strftime("%A, %d %b %Y at %I:%M %p") }} -
  • -
  • {{_("End Time")}}: {{ end.strftime("%A, %d %b %Y at %I:%M %p") }} -
  • +
  • + {{_("Start Time")}}: {{ start.strftime("%A, %d %b %Y at %I:%M %p") }} +
  • +
  • {{_("End Time")}}: {{ end.strftime("%A, %d %b %Y at %I:%M %p") }}
  • {% endif %} -
  • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
  • +
  • {{ _("Event Link") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
  • {% if doc.is_mandatory %} -
  • Note: This Training Event is mandatory
  • +
  • {{ _("Note: This Training Event is mandatory") }}
  • {% endif %}
    @@ -44,4 +44,4 @@ - \ No newline at end of file + From 86f689e54aeafc33d2c90e5511fd04cf0458d613 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 16 Jun 2021 14:23:02 +0530 Subject: [PATCH 1032/1264] fix(General Ledger): Get account_currency accurately --- .../report/general_ledger/general_ledger.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index aed9c4a049..8be6aaf564 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -91,7 +91,19 @@ def set_account_currency(filters): account_currency = None if filters.get("account"): - account_currency = get_account_currency(filters.account[0]) + if len(filters.get("account")) == 1: + account_currency = get_account_currency(filters.account[0]) + else: + currency = get_account_currency(filters.account[0]) + is_same_account_currency = True + for account in filters.get("account"): + if get_account_currency(account) != currency: + is_same_account_currency = False + break + + if is_same_account_currency: + account_currency = currency + elif filters.get("party"): gle_currency = frappe.db.get_value( "GL Entry", { From 60ce00531d0028741044bf7ed92b3feded139126 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 16 Jun 2021 14:25:55 +0530 Subject: [PATCH 1033/1264] fix: label for enabling ledger posting of change amount (#26070) --- .../doctype/accounts_settings/accounts_settings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 2735b1ccee..0ff7230e55 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -257,9 +257,10 @@ }, { "default": "1", + "description": "If enabled, ledger entries will be posted for change amount in POS transactions", "fieldname": "post_change_gl_entries", "fieldtype": "Check", - "label": "Post Ledger Entries for Given Change" + "label": "Change Ledger Entries for Change Amount" } ], "icon": "icon-cog", @@ -267,7 +268,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-05-25 12:34:05.858669", + "modified": "2021-06-16 13:14:45.739107", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 8c73f6f19e90e1be54d17a3e1f88e7ed535bef90 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:28:26 +0530 Subject: [PATCH 1034/1264] fix(pos): pos loyalty card alignment (#26051) --- erpnext/public/scss/point-of-sale.scss | 10 +++++++++- erpnext/selling/page/point_of_sale/pos_payment.js | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 9bdaa8d1ee..c77b2ce3df 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -806,6 +806,9 @@ display: none; float: right; font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } > .cash-shortcuts { @@ -829,6 +832,11 @@ } } } + + > .loyalty-card { + display: flex; + flex-direction: column; + } } } @@ -1134,4 +1142,4 @@ } } } -} \ 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 index 156fb777fe..c484873d3e 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -481,7 +481,7 @@ erpnext.PointOfSale.Payment = class { const amount = doc.loyalty_amount > 0 ? format_currency(doc.loyalty_amount, doc.currency) : ''; this.$payment_modes.append( `
    -
    +
    Redeem Loyalty Points
    ${amount}
    ${loyalty_program}
    @@ -563,4 +563,4 @@ erpnext.PointOfSale.Payment = class { toggle_component(show) { show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } -}; \ No newline at end of file +}; From 3b1b4684ba37b950657fe32d44001738c4438df9 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:30:45 +0530 Subject: [PATCH 1035/1264] fix: check for duplicate payment terms in Payment Term Template (#26003) --- .../doctype/payment_terms_template/payment_terms_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py index 80e3348d81..39627eb376 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py @@ -26,7 +26,7 @@ class PaymentTermsTemplate(Document): def check_duplicate_terms(self): terms = [] for term in self.terms: - term_info = (term.credit_days, term.credit_months, term.due_date_based_on) + term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on) if term_info in terms: frappe.msgprint( _('The Payment Term at row {0} is possibly a duplicate.').format(term.idx), From 41b7c1aec0ae507430a6aa433c0c52b91a1ff92e Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 16 Jun 2021 14:40:14 +0530 Subject: [PATCH 1036/1264] fix(General Ledger): Improve account filter --- .../report/general_ledger/general_ledger.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 8be6aaf564..03808c3640 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -223,17 +223,8 @@ def get_conditions(filters): conditions = [] if filters.get("account") and not filters.get("include_dimensions"): - account_conditions = "account in (select name from tabAccount where" - for account in filters["account"]: - lft, rgt = frappe.db.get_value("Account", account, ["lft", "rgt"]) - account_conditions += """ (lft>=%s and rgt<=%s) """ % (lft, rgt) - - # so that the OR doesn't get added to the last account condition - if account != filters["account"][-1]: - account_conditions += "OR" - - account_conditions += "and docstatus<2)" - conditions.append(account_conditions) + filters.account = get_accounts_with_children(filters.account) + conditions.append("account in %(account)s") if filters.get("cost_center"): filters.cost_center = get_cost_centers_with_children(filters.cost_center) @@ -291,6 +282,20 @@ def get_conditions(filters): return "and {}".format(" and ".join(conditions)) if conditions else "" +def get_accounts_with_children(accounts): + if not isinstance(accounts, list): + accounts = [d.strip() for d in accounts.strip().split(',') if d] + + all_accounts = [] + for d in accounts: + if frappe.db.exists("Account", d): + lft, rgt = frappe.db.get_value("Account", d, ["lft", "rgt"]) + children = frappe.get_all("Account", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) + all_accounts += [c.name for c in children] + else: + frappe.throw(_("Account: {0} does not exist").format(d)) + + return list(set(all_accounts)) def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries): data = [] From 6f9de8c86fbabd0498609ed3d645c89c902c8ba3 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 16 Jun 2021 20:01:29 +0530 Subject: [PATCH 1037/1264] fix: removed extra space from label rate --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 10e1c73ea9..8a55ff87e3 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -272,7 +272,7 @@ "fieldname": "rate", "fieldtype": "Currency", "in_list_view": 1, - "label": "Rate ", + "label": "Rate", "oldfieldname": "import_rate", "oldfieldtype": "Currency", "options": "currency", @@ -854,7 +854,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-03-30 09:02:39.256602", + "modified": "2021-06-16 19:57:03.101571", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", From 5c19a9251f864449fed77dd403839d865f0c547d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Jun 2021 22:14:29 +0530 Subject: [PATCH 1038/1264] fix: Accouting Dimensions for payroll entry accrual Journal Entry --- .../doctype/payroll_entry/payroll_entry.py | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 7a70679db4..697d2f6167 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -11,6 +11,7 @@ from frappe import _ from erpnext.accounts.utils import get_fiscal_year from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from frappe.desk.reportview import get_match_cond, get_filters_cond +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions class PayrollEntry(Document): def onload(self): @@ -211,7 +212,7 @@ class PayrollEntry(Document): return account_dict def make_accrual_jv_entry(self): - self.check_permission('write') + self.check_permission("write") earnings = self.get_salary_component_total(component_type = "earnings") or {} deductions = self.get_salary_component_total(component_type = "deductions") or {} payroll_payable_account = self.payroll_payable_account @@ -219,12 +220,13 @@ class PayrollEntry(Document): precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") if earnings or deductions: - journal_entry = frappe.new_doc('Journal Entry') - journal_entry.voucher_type = 'Journal Entry' - journal_entry.user_remark = _('Accrual Journal Entry for salaries from {0} to {1}')\ + journal_entry = frappe.new_doc("Journal Entry") + journal_entry.voucher_type = "Journal Entry" + journal_entry.user_remark = _("Accrual Journal Entry for salaries from {0} to {1}")\ .format(self.start_date, self.end_date) journal_entry.company = self.company journal_entry.posting_date = self.posting_date + accounting_dimensions = get_accounting_dimensions() or [] accounts = [] currencies = [] @@ -236,37 +238,34 @@ class PayrollEntry(Document): for acc_cc, amount in earnings.items(): exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies) payable_amount += flt(amount, precision) - accounts.append({ + accounts.append(self.update_accounting_dimensions({ "account": acc_cc[0], "debit_in_account_currency": flt(amt, precision), "exchange_rate": flt(exchange_rate), - "party_type": '', "cost_center": acc_cc[1] or self.cost_center, "project": self.project - }) + }, accounting_dimensions)) # Deductions for acc_cc, amount in deductions.items(): exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies) payable_amount -= flt(amount, precision) - accounts.append({ + accounts.append(self.update_accounting_dimensions({ "account": acc_cc[0], "credit_in_account_currency": flt(amt, precision), "exchange_rate": flt(exchange_rate), "cost_center": acc_cc[1] or self.cost_center, - "party_type": '', "project": self.project - }) + }, accounting_dimensions)) # Payable amount exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies) - accounts.append({ + accounts.append(self.update_accounting_dimensions({ "account": payroll_payable_account, "credit_in_account_currency": flt(payable_amt, precision), "exchange_rate": flt(exchange_rate), - "party_type": '', "cost_center": self.cost_center - }) + }, accounting_dimensions)) journal_entry.set("accounts", accounts) if len(currencies) > 1: @@ -286,6 +285,12 @@ class PayrollEntry(Document): return jv_name + def update_accounting_dimensions(self, row, accounting_dimensions): + for dimension in accounting_dimensions: + row.update({ dimension: self.get(dimension)}) + + return row + def get_amount_and_exchange_rate_for_journal_entry(self, account, amount, company_currency, currencies): conversion_rate = 1 exchange_rate = self.exchange_rate From 510077b3d4743297d290205c555cfeb239faa50b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Jun 2021 11:21:21 +0530 Subject: [PATCH 1039/1264] fix(minor): Translation and linting issues --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 697d2f6167..e71d81f323 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -42,7 +42,7 @@ class PayrollEntry(Document): emp_with_sal_slip.append(employee_details.employee) if len(emp_with_sal_slip): - frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip))) + frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip))) def on_cancel(self): frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip` @@ -287,7 +287,7 @@ class PayrollEntry(Document): def update_accounting_dimensions(self, row, accounting_dimensions): for dimension in accounting_dimensions: - row.update({ dimension: self.get(dimension)}) + row.update({dimension: self.get(dimension)}) return row From f9390f596d41004ed7645b967f29fdd9df08e412 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Thu, 17 Jun 2021 18:13:23 +0530 Subject: [PATCH 1040/1264] fix: auto unlink warehouse from item on delete (#26073) * fix: auto unlink warehouse from item on delete * fix: sider * refactor: use delete_doc * test: add test for unlinking warehouse from item * refactor: add msgprint to inform user of unlink * refactor: cleanup and reuse extant functions * fix: don't delete row, update table --- .../stock/doctype/warehouse/test_warehouse.py | 34 +++++++++++++++++++ erpnext/stock/doctype/warehouse/warehouse.py | 7 ++++ 2 files changed, 41 insertions(+) diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 95478f61f0..e3981c913e 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -11,6 +11,7 @@ from frappe.test_runner import make_test_records import erpnext from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account +from erpnext.stock.doctype.item.test_item import create_item test_records = frappe.get_test_records('Warehouse') @@ -92,6 +93,39 @@ class TestWarehouse(unittest.TestCase): self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": "Test Warehouse for Merging 2 - TCP1"})) + def test_unlinking_warehouse_from_item_defaults(self): + company = "_Test Company" + + warehouse_names = [f'_Test Warehouse {i} for Unlinking' for i in range(2)] + warehouse_ids = [] + for warehouse in warehouse_names: + warehouse_id = create_warehouse(warehouse, company=company) + warehouse_ids.append(warehouse_id) + + item_names = [f'_Test Item {i} for Unlinking' for i in range(2)] + for item, warehouse in zip(item_names, warehouse_ids): + create_item(item, warehouse=warehouse, company=company) + + # Delete warehouses + for warehouse in warehouse_ids: + frappe.delete_doc("Warehouse", warehouse) + + # Check Item existance + for item in item_names: + self.assertTrue( + bool(frappe.db.exists("Item", item)), + f"{item} doesn't exist" + ) + + item_doc = frappe.get_doc("Item", item) + for item_default in item_doc.item_defaults: + self.assertNotIn( + item_default.default_warehouse, + warehouse_ids, + f"{item} linked to {item_default.default_warehouse} in {warehouse_ids}." + ) + + def create_warehouse(warehouse_name, properties=None, company=None): if not company: company = "_Test Company" diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 2062bddc7c..3abc13907c 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -54,6 +54,7 @@ class Warehouse(NestedSet): throw(_("Child warehouse exists for this warehouse. You can not delete this warehouse.")) self.update_nsm_model() + self.unlink_from_items() def check_if_sle_exists(self): return frappe.db.sql("""select name from `tabStock Ledger Entry` @@ -138,6 +139,12 @@ class Warehouse(NestedSet): self.save() return 1 + def unlink_from_items(self): + frappe.db.sql(""" + update `tabItem Default` + set default_warehouse=NULL + where default_warehouse=%s""", self.name) + @frappe.whitelist() def get_children(doctype, parent=None, company=None, is_root=False): if is_root: From ddef85ae97024376915b94d52ad347ba2b9c9c8c Mon Sep 17 00:00:00 2001 From: Eben van Deventer Date: Thu, 17 Jun 2021 15:13:30 +0200 Subject: [PATCH 1041/1264] fix: Correct South Africa VAT Rate (#25894) On 1 April 2018 South Africa increased the VAT rate from 14% to 15%, this proposed change seeks to update the default parameters for a fresh ERPNext installation. --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index ec9a6d6b70..daaa626a81 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1867,7 +1867,7 @@ "South Africa": { "South Africa Tax": { "account_name": "VAT", - "tax_rate": 14.00 + "tax_rate": 15.00 } }, From 59e2e4989b4cdb88d7812161837924ba00a3029d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Jun 2021 19:58:16 +0530 Subject: [PATCH 1042/1264] fix: Incorrect billed qty in Sales Order analytics --- .../selling/report/sales_order_analysis/sales_order_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index f5feb95f1a..8cb24460f7 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -59,7 +59,7 @@ def get_data(conditions, filters): IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay, soi.qty, soi.delivered_qty, (soi.qty - soi.delivered_qty) AS pending_qty, - IFNULL(sii.qty, 0) as billed_qty, + IFNULL(SUM(sii.qty), 0) as billed_qty, soi.base_amount as amount, (soi.delivered_qty * soi.base_rate) as delivered_qty_amount, (soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount, From ef972693861bb2b39071351249292fe1ab136432 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 18 Jun 2021 10:11:53 +0530 Subject: [PATCH 1043/1264] fix: timeout while cancelling stock reconciliation --- .../doctype/stock_reconciliation/stock_reconciliation.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 306df99b3c..2b51c1a5c3 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -473,6 +473,13 @@ class StockReconciliation(StockController): else: self._submit() + def cancel(self): + if len(self.items) > 100: + msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage")) + self.queue_action('cancel', timeout=2000) + else: + self._cancel() + @frappe.whitelist() def get_items(warehouse, posting_date, posting_time, company): lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) From a58b571ccb727ffadded90c1b8b7541bd8c6c8c4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 18 Jun 2021 10:45:35 +0530 Subject: [PATCH 1044/1264] fix: Billing address not fetched in Purchase Invoice --- .../doctype/purchase_invoice/purchase_invoice.js | 10 +++++----- erpnext/public/js/controllers/transaction.js | 3 --- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index f58c8f4526..dc9094c3e9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -27,10 +27,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }); }, - company: function() { - erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype); - }, - onload: function() { this._super(); @@ -569,5 +565,9 @@ frappe.ui.form.on("Purchase Invoice", { frm: frm, freeze_message: __("Creating Purchase Receipt ...") }) - } + }, + + company: function(frm) { + erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + }, }) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 89fed3bf0d..340c0933ef 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -864,9 +864,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } - if (this.frm.doc.posting_date) var date = this.frm.doc.posting_date; - else var date = this.frm.doc.transaction_date; - if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") && in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) { erpnext.utils.get_shipping_address(this.frm, function(){ From b066fe9519726adc26cf7a0a065f4504d8cc6e1d Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:29:07 +0530 Subject: [PATCH 1045/1264] fix: insufficient permission for dunning error (#26092) --- erpnext/accounts/doctype/dunning/dunning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 1a6dbedf56..c6c689212b 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -86,7 +86,7 @@ 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')}) + 'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')}, ignore_permissions=True) for dunning in dunnings: frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved') @@ -96,7 +96,7 @@ def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_inte grand_total = 0 if rate_of_interest: interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100 - interest_amount = (interest_per_year * cint(overdue_days)) / 365 + interest_amount = (interest_per_year * cint(overdue_days)) / 365 grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee) dunning_amount = flt(interest_amount) + flt(dunning_fee) return { From 3d8f82459b0bd90c80aec473f9a0daa5a7564db8 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 18 Jun 2021 11:42:28 +0530 Subject: [PATCH 1046/1264] fix(Issue): reset response_by and resolution_by if SLA is removed (#25997) --- erpnext/support/doctype/issue/issue.json | 6 +++--- erpnext/support/doctype/issue/issue.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index bc29821ee2..14712f89fe 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -166,7 +166,7 @@ "options": "Service Level Agreement" }, { - "depends_on": "eval: doc.status != 'Replied';", + "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;", "fieldname": "response_by", "fieldtype": "Datetime", "label": "Response By", @@ -180,7 +180,7 @@ "read_only": 1 }, { - "depends_on": "eval: doc.status != 'Replied';", + "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;", "fieldname": "resolution_by", "fieldtype": "Datetime", "label": "Resolution By", @@ -410,7 +410,7 @@ "icon": "fa fa-ticket", "idx": 7, "links": [], - "modified": "2021-05-26 10:49:07.574769", + "modified": "2021-06-10 03:22:27.098898", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index b068363f06..9c69deb6a4 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -29,6 +29,9 @@ class Issue(Document): self.update_status() self.set_lead_contact(self.raised_by) + if not self.service_level_agreement: + self.reset_sla_fields() + def on_update(self): # Add a communication in the issue timeline if self.flags.create_communication and self.via_customer_portal: @@ -54,6 +57,13 @@ class Issue(Document): self.company = frappe.db.get_value("Lead", self.lead, "company") or \ frappe.db.get_default("Company") + def reset_sla_fields(self): + self.agreement_status = "" + self.response_by = "" + self.resolution_by = "" + self.response_by_variance = 0 + self.resolution_by_variance = 0 + def update_status(self): status = frappe.db.get_value("Issue", self.name, "status") if self.status != "Open" and status == "Open" and not self.first_responded_on: @@ -511,4 +521,4 @@ def get_time_in_timedelta(time): Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215) """ import datetime - return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second) \ No newline at end of file + return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second) From 81c97c13ce1b697acc16d38f1e1084f5da573882 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 18 Jun 2021 17:25:37 +0530 Subject: [PATCH 1047/1264] fix: Sanctioned loan amount limit check --- erpnext/loan_management/doctype/loan/loan.py | 29 +++++- .../loan_management/doctype/loan/test_loan.py | 45 ++++++++- .../loan_application/loan_application.py | 4 +- .../doctype/loan_repayment/loan_repayment.py | 91 ++++++++++--------- 4 files changed, 116 insertions(+), 53 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 69d11a8653..ff7fbbdf49 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -60,8 +60,9 @@ class Loan(AccountsController): self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods) def check_sanctioned_amount_limit(self): - total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company) sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company) + if sanctioned_amount_limit: + total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company) if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit): frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant))) @@ -155,9 +156,29 @@ def update_total_amount_paid(doc): frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid) def get_total_loan_amount(applicant_type, applicant, company): - return frappe.db.get_value('Loan', - {'applicant_type': applicant_type, 'company': company, 'applicant': applicant, 'docstatus': 1}, - 'sum(loan_amount)') + pending_amount = 0 + loan_details = frappe.db.get_all("Loan", + filters={"applicant_type": applicant_type, "company": company, "applicant": applicant, "docstatus": 1, + "status": ("!=", "Closed")}, + fields=["status", "total_payment", "disbursed_amount", "total_interest_payable", "total_principal_paid", + "written_off_amount"]) + + interest_amount = flt(frappe.db.get_value("Loan Interest Accrual", {"applicant_type": applicant_type, + "company": company, "applicant": applicant, "docstatus": 1}, "sum(interest_amount - paid_interest_amount)")) + + for loan in loan_details: + if loan.status in ("Disbursed", "Loan Closure Requested"): + pending_amount += flt(loan.total_payment) - flt(loan.total_interest_payable) \ + - flt(loan.total_principal_paid) - flt(loan.written_off_amount) + elif loan.status == "Partially Disbursed": + pending_amount += flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \ + - flt(loan.total_principal_paid) - flt(loan.written_off_amount) + elif loan.status == "Sanctioned": + pending_amount += flt(loan.total_payment) + + pending_amount += interest_amount + + return pending_amount def get_sanctioned_amount_limit(applicant_type, applicant, company): return frappe.db.get_value('Sanctioned Loan Amount', diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index fa4707ce2b..314f58dd15 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -49,7 +49,11 @@ class TestLoan(unittest.TestCase): if not frappe.db.exists("Customer", "_Test Loan Customer"): frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True) - self.applicant2 = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name') + if not frappe.db.exists("Customer", "_Test Loan Customer 1"): + frappe.get_doc(get_customer_dict("_Test Loan Customer 1")).insert(ignore_permissions=True) + + self.applicant2 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name") + self.applicant3 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer 1"}, "name") create_loan(self.applicant1, "Personal Loan", 280000, "Repay Over Number of Periods", 20) @@ -125,6 +129,38 @@ class TestLoan(unittest.TestCase): self.assertTrue(gl_entries1) self.assertTrue(gl_entries2) + def test_sanctioned_amount_limit(self): + # Clear loan docs before checking + frappe.db.sql("DELETE FROM `tabLoan` where applicant = '_Test Loan Customer 1'") + frappe.db.sql("DELETE FROM `tabLoan Application` where applicant = '_Test Loan Customer 1'") + frappe.db.sql("DELETE FROM `tabLoan Security Pledge` where applicant = '_Test Loan Customer 1'") + + if not frappe.db.get_value("Sanctioned Loan Amount", filters={"applicant_type": "Customer", + "applicant": "_Test Loan Customer 1", "company": "_Test Company"}): + frappe.get_doc({ + "doctype": "Sanctioned Loan Amount", + "applicant_type": "Customer", + "applicant": "_Test Loan Customer 1", + "sanctioned_amount_limit": 1500000, + "company": "_Test Company" + }).insert(ignore_permissions=True) + + # Make First Loan + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge) + create_pledge(loan_application) + loan = create_demand_loan(self.applicant3, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + # Make second loan greater than the sanctioned amount + loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge, + do_not_save=True) + self.assertRaises(frappe.ValidationError, loan_application.save) + def test_regular_loan_repayment(self): pledge = [{ "loan_security": "Test Security 1", @@ -367,7 +403,7 @@ class TestLoan(unittest.TestCase): unpledge_request.load_from_db() self.assertEqual(unpledge_request.docstatus, 1) - def test_santined_loan_security_unpledge(self): + def test_sanctioned_loan_security_unpledge(self): pledge = [{ "loan_security": "Test Security 1", "qty": 4000.00 @@ -858,7 +894,7 @@ def create_repayment_entry(loan, applicant, posting_date, paid_amount): return lr def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None, - repayment_periods=None, posting_date=None): + repayment_periods=None, posting_date=None, do_not_save=False): loan_application = frappe.new_doc('Loan Application') loan_application.applicant_type = 'Customer' loan_application.company = company @@ -874,6 +910,9 @@ def create_loan_application(company, applicant, loan_type, proposed_pledges, rep for pledge in proposed_pledges: loan_application.append('proposed_pledges', pledge) + if do_not_save: + return loan_application + loan_application.save() loan_application.submit() diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index 9c0147e55b..d8f3577b2c 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -46,9 +46,11 @@ class LoanApplication(Document): frappe.throw(_("Loan Amount exceeds maximum loan amount of {0} as per proposed securities").format(self.maximum_loan_amount)) def check_sanctioned_amount_limit(self): - total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company) sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company) + if sanctioned_amount_limit: + total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company) + if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit): frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant))) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 3d99b1f304..b8b1a40b5f 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -235,70 +235,71 @@ class LoanRepayment(AccountsController): else: remarks = _("Repayment against Loan: ") + self.against_loan - if self.total_penalty_paid: + if not loan_details.repay_from_salary: + if self.total_penalty_paid: + gle_map.append( + self.get_gl_dict({ + "account": loan_details.loan_account, + "against": loan_details.payment_account, + "debit": self.total_penalty_paid, + "debit_in_account_currency": self.total_penalty_paid, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": _("Penalty against loan:") + self.against_loan, + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": getdate(self.posting_date) + }) + ) + + gle_map.append( + self.get_gl_dict({ + "account": loan_details.penalty_income_account, + "against": loan_details.payment_account, + "credit": self.total_penalty_paid, + "credit_in_account_currency": self.total_penalty_paid, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": _("Penalty against loan:") + self.against_loan, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date) + }) + ) + gle_map.append( self.get_gl_dict({ - "account": loan_details.loan_account, - "against": loan_details.payment_account, - "debit": self.total_penalty_paid, - "debit_in_account_currency": self.total_penalty_paid, + "account": loan_details.payment_account, + "against": loan_details.loan_account + ", " + loan_details.interest_income_account + + ", " + loan_details.penalty_income_account, + "debit": self.amount_paid, + "debit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Penalty against loan:") + self.against_loan, + "remarks": remarks, "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, "posting_date": getdate(self.posting_date) }) ) gle_map.append( self.get_gl_dict({ - "account": loan_details.penalty_income_account, + "account": loan_details.loan_account, + "party_type": loan_details.applicant_type, + "party": loan_details.applicant, "against": loan_details.payment_account, - "credit": self.total_penalty_paid, - "credit_in_account_currency": self.total_penalty_paid, + "credit": self.amount_paid, + "credit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Penalty against loan:") + self.against_loan, + "remarks": remarks, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date) }) ) - gle_map.append( - self.get_gl_dict({ - "account": loan_details.payment_account, - "against": loan_details.loan_account + ", " + loan_details.interest_income_account - + ", " + loan_details.penalty_income_account, - "debit": self.amount_paid, - "debit_in_account_currency": self.amount_paid, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": remarks, - "cost_center": self.cost_center, - "posting_date": getdate(self.posting_date) - }) - ) - - gle_map.append( - self.get_gl_dict({ - "account": loan_details.loan_account, - "party_type": loan_details.applicant_type, - "party": loan_details.applicant, - "against": loan_details.payment_account, - "credit": self.amount_paid, - "credit_in_account_currency": self.amount_paid, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": remarks, - "cost_center": self.cost_center, - "posting_date": getdate(self.posting_date) - }) - ) - - if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) def create_repayment_entry(loan, applicant, company, posting_date, loan_type, payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None): From 8520edc952d252aa4ebdbf9bf56e2b04a12dd614 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jun 2021 10:21:44 +0530 Subject: [PATCH 1048/1264] fix: time out while submitting the stock transactions with more than 50 items --- erpnext/controllers/buying_controller.py | 10 ++- erpnext/controllers/stock_controller.py | 79 ++++++++++++++----- .../stock_ledger_entry/stock_ledger_entry.py | 19 ++--- erpnext/stock/stock_ledger.py | 16 +++- erpnext/stock/utils.py | 2 +- 5 files changed, 93 insertions(+), 33 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index da819119b1..20f5445725 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -171,12 +171,13 @@ class BuyingController(StockController): TODO: rename item_tax_amount to valuation_tax_amount """ + stock_and_asset_items = [] stock_and_asset_items = self.get_stock_items() + self.get_asset_items() stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0 last_item_idx = 1 for d in self.get("items"): - if d.item_code and d.item_code in stock_and_asset_items: + if (d.item_code and d.item_code in stock_and_asset_items): stock_and_asset_items_qty += flt(d.qty) stock_and_asset_items_amount += flt(d.base_net_amount) last_item_idx = d.idx @@ -683,7 +684,8 @@ class BuyingController(StockController): self.process_fixed_asset() self.update_fixed_asset(field) - update_last_purchase_rate(self, is_submit = 1) + if self.doctype in ['Purchase Order', 'Purchase Receipt']: + update_last_purchase_rate(self, is_submit = 1) def on_cancel(self): super(BuyingController, self).on_cancel() @@ -691,7 +693,9 @@ class BuyingController(StockController): if self.get('is_return'): return - update_last_purchase_rate(self, is_submit = 0) + if self.doctype in ['Purchase Order', 'Purchase Receipt']: + update_last_purchase_rate(self, is_submit = 0) + if self.doctype in ['Purchase Receipt', 'Purchase Invoice']: field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt' diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 6a7c9e3d0e..35097b97b9 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -501,7 +501,6 @@ class StockController(AccountsController): check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) - @frappe.whitelist() def make_quality_inspections(doctype, docname, items): if isinstance(items, str): @@ -533,21 +532,75 @@ def make_quality_inspections(doctype, docname, items): return inspections - def is_reposting_pending(): return frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) +def future_sle_exists(args, sl_entries=None): + key = (args.voucher_type, args.voucher_no) -def future_sle_exists(args): - sl_entries = frappe.get_all("Stock Ledger Entry", + if validate_future_sle_not_exists(args, key, sl_entries): + return False + elif get_cached_data(args, key): + return True + + if not sl_entries: + sl_entries = get_sle_entries_against_voucher(args) + if not sl_entries: + return + + or_conditions = get_conditions_to_validate_future_sle(sl_entries) + + data = frappe.db.sql(""" + select item_code, warehouse, count(name) as total_row + from `tabStock Ledger Entry` + where + ({}) + and timestamp(posting_date, posting_time) + >= timestamp(%(posting_date)s, %(posting_time)s) + and voucher_no != %(voucher_no)s + and is_cancelled = 0 + GROUP BY + item_code, warehouse + """.format(" or ".join(or_conditions)), args, as_dict=1) + + for d in data: + frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row + + return len(data) + +def validate_future_sle_not_exists(args, key, sl_entries=None): + item_key = '' + if args.get('item_code'): + item_key = (args.get('item_code'), args.get('warehouse')) + + if not sl_entries and hasattr(frappe.local, 'future_sle'): + if (not frappe.local.future_sle.get(key) or + (item_key and item_key not in frappe.local.future_sle.get(key))): + return True + +def get_cached_data(args, key): + if not hasattr(frappe.local, 'future_sle'): + frappe.local.future_sle = {} + + if key not in frappe.local.future_sle: + frappe.local.future_sle[key] = frappe._dict({}) + + if args.get('item_code'): + item_key = (args.get('item_code'), args.get('warehouse')) + count = frappe.local.future_sle[key].get(item_key) + + return True if (count or count == 0) else False + else: + return frappe.local.future_sle[key] + +def get_sle_entries_against_voucher(args): + return frappe.get_all("Stock Ledger Entry", filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no}, fields=["item_code", "warehouse"], order_by="creation asc") - if not sl_entries: - return - +def get_conditions_to_validate_future_sle(sl_entries): warehouse_items_map = {} for entry in sl_entries: if entry.warehouse not in warehouse_items_map: @@ -561,17 +614,7 @@ def future_sle_exists(args): f"""warehouse = {frappe.db.escape(warehouse)} and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""") - return frappe.db.sql(""" - select name - from `tabStock Ledger Entry` - where - ({}) - and timestamp(posting_date, posting_time) - >= timestamp(%(posting_date)s, %(posting_time)s) - and voucher_no != %(voucher_no)s - and is_cancelled = 0 - limit 1 - """.format(" or ".join(or_conditions)), args) + return or_conditions def create_repost_item_valuation_entry(args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index b0e7440e6c..0febcb6891 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, date_diff +from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, cint from frappe.model.document import Document from datetime import date from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock @@ -108,17 +108,18 @@ class StockLedgerEntry(Document): self.stock_uom = item_det.stock_uom def check_stock_frozen_date(self): - stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or '' - if stock_frozen_upto: - stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role') - if getdate(self.posting_date) <= getdate(stock_frozen_upto) and not stock_auth_role in frappe.get_roles(): - frappe.throw(_("Stock transactions before {0} are frozen").format(formatdate(stock_frozen_upto)), StockFreezeError) + stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings') - stock_frozen_upto_days = int(frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto_days') or 0) + if stock_settings.stock_frozen_upto: + if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto) + and stock_settings.stock_auth_role not in frappe.get_roles()): + frappe.throw(_("Stock transactions before {0} are frozen") + .format(formatdate(stock_settings.stock_frozen_upto)), StockFreezeError) + + stock_frozen_upto_days = cint(stock_settings.stock_frozen_upto_days) if stock_frozen_upto_days: - stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role') older_than_x_days_ago = (add_days(getdate(self.posting_date), stock_frozen_upto_days) <= date.today()) - if older_than_x_days_ago and not stock_auth_role in frappe.get_roles(): + if older_than_x_days_ago and stock_settings.stock_auth_role not in frappe.get_roles(): frappe.throw(_("Not allowed to update stock transactions older than {0}").format(stock_frozen_upto_days), StockFreezeError) def scrub_posting_time(self): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index fc82c789cc..fb2ecab249 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -22,6 +22,7 @@ _exceptions = frappe.local('stockledger_exceptions') # _exceptions = [] def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False): + from erpnext.controllers.stock_controller import future_sle_exists if sl_entries: from erpnext.stock.utils import update_bin @@ -30,6 +31,9 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc validate_cancellation(sl_entries) set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) + args = get_args_for_future_sle(sl_entries[0]) + future_sle_exists(args, sl_entries) + for sle in sl_entries: if sle.serial_no: validate_serial_no(sle) @@ -53,6 +57,14 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc args = sle_doc.as_dict() update_bin(args, allow_negative_stock, via_landed_cost_voucher) +def get_args_for_future_sle(row): + return frappe._dict({ + 'voucher_type': row.get('voucher_type'), + 'voucher_no': row.get('voucher_no'), + 'posting_date': row.get('posting_date'), + 'posting_time': row.get('posting_time') + }) + def validate_serial_no(sle): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos for sn in get_serial_nos(sle.serial_no): @@ -472,8 +484,8 @@ class update_entries_after(object): frappe.db.set_value("Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate) # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice - if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): - doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) + if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes': + doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no) doc.update_valuation_rate(reset_outgoing_rate=False) for d in (doc.items + doc.supplied_items): d.db_update() diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 034d3ebbb5..8a6a3a3e4a 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -177,7 +177,7 @@ def get_bin(item_code, warehouse): return bin_obj def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False): - is_stock_item = frappe.db.get_value('Item', args.get("item_code"), 'is_stock_item') + is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item') if is_stock_item: bin = get_bin(args.get("item_code"), args.get("warehouse")) bin.update_stock(args, allow_negative_stock, via_landed_cost_voucher) From 8c844e4515afc5f9f241c522578b2e81d19f24b9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 11 Jun 2021 17:27:08 +0530 Subject: [PATCH 1049/1264] fix: material request and supplier quotation not linked if sq created from supplier portal against rfq --- .../request_for_quotation.py | 22 ++++++++++--------- .../templates/includes/transaction_row.html | 8 ++++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 0127eb8163..a4ce84e1cf 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -317,19 +317,21 @@ def add_items(sq_doc, supplier, items): create_rfq_items(sq_doc, supplier, data) def create_rfq_items(sq_doc, supplier, data): - sq_doc.append('items', { - "item_code": data.item_code, - "item_name": data.item_name, - "description": data.description, - "qty": data.qty, - "rate": data.rate, - "conversion_factor": data.conversion_factor if data.conversion_factor else None, - "supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"), - "warehouse": data.warehouse or '', + args = {} + + for field in ['item_code', 'item_name', 'description', 'qty', 'rate', 'conversion_factor', + 'warehouse', 'material_request', 'material_request_item', 'stock_qty']: + args[field] = data.get(field) + + args.update({ "request_for_quotation_item": data.name, - "request_for_quotation": data.parent + "request_for_quotation": data.parent, + "supplier_part_no": frappe.db.get_value("Item Supplier", + {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no") }) + sq_doc.append('items', args) + @frappe.whitelist() def get_pdf(doctype, name, supplier): doc = get_rfq_doc(doctype, name, supplier) diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index 383413103e..3cfb8d8440 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -13,9 +13,11 @@ {{ doc.items_preview }}
    -
    - {{ doc.get_formatted("grand_total") }} -
    + {% if doc.get('grand_total') %} +
    + {{ doc.get_formatted("grand_total") }} +
    + {% endif %}
    Link
    From a94b89727c550cc1eaea86379a2cb4d53297a8b8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 May 2021 20:11:15 +0530 Subject: [PATCH 1050/1264] feat: subcontract code refactor and enhancement --- .../purchase_invoice/test_purchase_invoice.py | 2 + .../doctype/purchase_order/purchase_order.js | 38 +- .../purchase_order/purchase_order.json | 3 +- .../doctype/purchase_order/purchase_order.py | 59 +- .../purchase_order/test_purchase_order.py | 100 +-- .../purchase_order_item_supplied.json | 45 +- .../purchase_receipt_item_supplied.json | 17 +- .../subcontract_order_summary/__init__.py | 0 .../subcontract_order_summary.js | 45 ++ .../subcontract_order_summary.json | 32 + .../subcontract_order_summary.py | 158 +++++ erpnext/controllers/buying_controller.py | 427 +------------ erpnext/controllers/subcontracting.py | 342 ++++++++++ erpnext/manufacturing/doctype/bom/test_bom.py | 2 + erpnext/stock/doctype/bin/bin.py | 4 +- .../item_alternative/test_item_alternative.py | 5 + .../purchase_receipt/purchase_receipt.js | 2 + .../purchase_receipt/purchase_receipt.json | 5 +- .../purchase_receipt/purchase_receipt.py | 2 + .../purchase_receipt/test_purchase_receipt.py | 4 + .../stock/doctype/stock_entry/stock_entry.js | 4 + .../doctype/stock_entry/stock_entry.json | 15 +- .../stock/doctype/stock_entry/stock_entry.py | 79 ++- erpnext/tests/test_subcontracting.py | 583 ++++++++++++++++++ 24 files changed, 1418 insertions(+), 555 deletions(-) create mode 100644 erpnext/buying/report/subcontract_order_summary/__init__.py create mode 100644 erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js create mode 100644 erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json create mode 100644 erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py create mode 100644 erpnext/controllers/subcontracting.py create mode 100644 erpnext/tests/test_subcontracting.py diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 503dda7728..ff433b962f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -621,8 +621,10 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(actual_qty_0, get_qty_after_transaction()) def test_subcontracting_via_purchase_invoice(self): + from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + update_backflush_based_on('BOM') make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100) make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 0f6d927b36..440cde6d9e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -53,6 +53,38 @@ frappe.ui.form.on("Purchase Order", { } else { frm.set_value("tax_withholding_category", frm.supplier_tds); } + }, + + refresh: function(frm) { + frm.trigger('get_materials_from_supplier'); + }, + + get_materials_from_supplier: function(frm) { + let po_details = []; + + if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) { + frm.doc.supplied_items.forEach(d => { + if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) { + po_details.push(d.name) + } + }); + } + + if (po_details && po_details.length) { + frm.add_custom_button(__('Return of Components'), () => { + frm.call({ + method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier', + freeze_message: __('Creating Stock Entry'), + args: { purchase_order: frm.doc.name, po_details: po_details }, + callback: function(r) { + if (r && r.message) { + const doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + } + } + }); + }, __('Create')); + } } }); @@ -217,7 +249,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }, has_unsupplied_items: function() { - return this.frm.doc['supplied_items'].some(item => item.required_qty != item.supplied_qty) + return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty) }, make_stock_entry: function() { @@ -513,12 +545,14 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( ], primary_action: function() { var data = d.get_values(); + var content_msg = 'Reason for hold: ' + data.reason_for_hold; + frappe.call({ method: "frappe.desk.form.utils.add_comment", args: { reference_doctype: me.frm.doctype, reference_name: me.frm.docname, - content: __('Reason for hold:') + " " +data.reason_for_hold, + content: __(content_msg), comment_email: frappe.session.user, comment_by: frappe.session.user_fullname }, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 41668c6291..bb0ad60cab 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -609,6 +609,7 @@ "fieldname": "supplied_items", "fieldtype": "Table", "label": "Supplied Items", + "no_copy": 1, "oldfieldname": "po_raw_material_details", "oldfieldtype": "Table", "options": "Purchase Order Item Supplied", @@ -1377,7 +1378,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-04-19 00:55:30.781375", + "modified": "2021-05-30 15:17:53.663648", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 2629ba7d61..724f863e0f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -503,9 +503,10 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions @frappe.whitelist() def make_rm_stock_entry(purchase_order, rm_items): + rm_items_list = rm_items if isinstance(rm_items, string_types): rm_items_list = json.loads(rm_items) - else: + elif not rm_items: frappe.throw(_("No Items available for transfer")) if rm_items_list: @@ -543,6 +544,8 @@ def make_rm_stock_entry(purchase_order, rm_items): 'qty': rm_item_data["qty"], 'from_warehouse': rm_item_data["warehouse"], 'stock_uom': rm_item_data["stock_uom"], + 'serial_no': rm_item_data.get('serial_no'), + 'batch_no': rm_item_data.get('batch_no'), 'main_item_code': rm_item_data["item_code"], 'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item') } @@ -582,3 +585,57 @@ def update_status(status, name): def make_inter_company_sales_order(source_name, target_doc=None): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction return make_inter_company_transaction("Purchase Order", source_name, target_doc) + +@frappe.whitelist() +def get_materials_from_supplier(purchase_order, po_details): + if isinstance(po_details, string_types): + po_details = json.loads(po_details) + + doc = frappe.get_cached_doc('Purchase Order', purchase_order) + doc.initialized_fields() + doc.purchase_orders = [doc.name] + doc.get_available_materials() + + if not doc.available_materials: + frappe.throw(_('Materials are already received against the purchase order {0}') + .format(purchase_order)) + + return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details) + +def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details): + ste_doc = frappe.new_doc('Stock Entry') + ste_doc.purpose = 'Material Transfer' + ste_doc.purchase_order = po_doc.name + ste_doc.company = po_doc.company + ste_doc.is_return = 1 + + for key, value in available_materials.items(): + if not value.qty: + continue + + if value.batch_no: + for batch_no, qty in value.batch_no.items(): + add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no) + else: + add_items_in_ste(ste_doc, value, value.qty, po_details) + + ste_doc.set_stock_entry_type() + ste_doc.calculate_rate_and_amount() + + return ste_doc + +def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None): + item = ste_doc.append('items', row.item_details) + + po_detail = list(set(row.po_details).intersection(po_details)) + item.update({ + 'qty': qty, + 'batch_no': batch_no, + 'basic_rate': row.item_details['rate'], + 'po_detail': po_detail[0] if po_detail else '', + 's_warehouse': row.item_details['t_warehouse'], + 't_warehouse': row.item_details['s_warehouse'], + 'item_code': row.item_details['rm_item_code'], + 'subcontracted_item': row.item_details['main_item_code'], + 'serial_no': '\n'.join(row.serial_no) if row.serial_no else '' + }) \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3b9f8e9775..33d1971451 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -20,7 +20,6 @@ from erpnext.controllers.status_updater import OverAllowanceError from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order from erpnext.stock.doctype.batch.test_batch import make_new_batch -from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials class TestPurchaseOrder(unittest.TestCase): def test_make_purchase_receipt(self): @@ -771,7 +770,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) def test_exploded_items_in_subcontracted(self): - item_code = "_Test Subcontracted FG Item 1" + item_code = "_Test Subcontracted FG Item 11" make_subcontracted_item(item_code=item_code) po = create_purchase_order(item_code=item_code, qty=1, @@ -853,76 +852,6 @@ class TestPurchaseOrder(unittest.TestCase): update_backflush_based_on("BOM") - def test_backflushed_based_on_for_multiple_batches(self): - item_code = "_Test Subcontracted FG Item 2" - make_item('Sub Contracted Raw Material 2', { - 'is_stock_item': 1, - 'is_sub_contracted_item': 1 - }) - - make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1, - raw_materials=["Sub Contracted Raw Material 2"]) - - update_backflush_based_on("Material Transferred for Subcontract") - - order_qty = 500 - po = create_purchase_order(item_code=item_code, qty=order_qty, - is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") - - make_stock_entry(target="_Test Warehouse - _TC", - item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100) - - rm_items = [ - {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item", - "qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}] - - rm_item_string = json.dumps(rm_items) - se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.submit() - - for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]: - make_new_batch(batch_id=batch, item_code=item_code) - - pr = make_purchase_receipt(po.name) - - # partial receipt - pr.get('items')[0].qty = 30 - pr.get('items')[0].batch_no = "ABCD1" - - purchase_order = po.name - purchase_order_item = po.items[0].name - - for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items(): - pr.append("items", { - "item_code": pr.get('items')[0].item_code, - "item_name": pr.get('items')[0].item_name, - "uom": pr.get('items')[0].uom, - "stock_uom": pr.get('items')[0].stock_uom, - "warehouse": pr.get('items')[0].warehouse, - "conversion_factor": pr.get('items')[0].conversion_factor, - "cost_center": pr.get('items')[0].cost_center, - "rate": pr.get('items')[0].rate, - "qty": qty, - "batch_no": batch_no, - "purchase_order": purchase_order, - "purchase_order_item": purchase_order_item - }) - - pr.submit() - - pr1 = make_purchase_receipt(po.name) - pr1.get('items')[0].qty = 300 - pr1.get('items')[0].batch_no = "ABCD1" - pr1.save() - - pr_key = ("Sub Contracted Raw Material 2", po.name) - consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key) - - self.assertTrue(pr1.supplied_items[0].consumed_qty > 0) - self.assertTrue(pr1.supplied_items[0].consumed_qty, flt(552.0) - flt(consumed_qty)) - - update_backflush_based_on("BOM") - def test_supplied_qty_against_subcontracted_po(self): item_code = "_Test Subcontracted FG Item 5" make_item('Sub Contracted Raw Material 4', { @@ -1117,22 +1046,29 @@ def create_purchase_order(**args): po.conversion_factor = args.conversion_factor or 1 po.supplier_warehouse = args.supplier_warehouse or None - po.append("items", { - "item_code": args.item or args.item_code or "_Test Item", - "warehouse": args.warehouse or "_Test Warehouse - _TC", - "qty": args.qty or 10, - "rate": args.rate or 500, - "schedule_date": add_days(nowdate(), 1), - "include_exploded_items": args.get('include_exploded_items', 1), - "against_blanket_order": args.against_blanket_order - }) + if args.rm_items: + for row in args.rm_items: + po.append("items", row) + else: + po.append("items", { + "item_code": args.item or args.item_code or "_Test Item", + "warehouse": args.warehouse or "_Test Warehouse - _TC", + "qty": args.qty or 10, + "rate": args.rate or 500, + "schedule_date": add_days(nowdate(), 1), + "include_exploded_items": args.get('include_exploded_items', 1), + "against_blanket_order": args.against_blanket_order + }) + + po.set_missing_values() if not args.do_not_save: po.insert() if not args.do_not_submit: if po.is_subcontracted == "Yes": supp_items = po.get("supplied_items") for d in supp_items: - d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC" + if not d.reserve_warehouse: + d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC" po.submit() return po diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json index d7ea9c1ccc..505ecd84c5 100644 --- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json +++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json @@ -6,21 +6,25 @@ "engine": "InnoDB", "field_order": [ "main_item_code", - "bom_detail_no", + "rm_item_code", + "column_break_3", "stock_uom", + "reserve_warehouse", "conversion_factor", "column_break_6", - "rm_item_code", + "bom_detail_no", "reference_name", - "reserve_warehouse", "section_break2", "rate", "col_break2", "amount", "section_break1", "required_qty", + "supplied_qty", "col_break1", - "supplied_qty" + "returned_qty", + "total_supplied_qty", + "consumed_qty" ], "fields": [ { @@ -125,6 +129,8 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Supplied Qty", + "no_copy": 1, + "print_hide": 1, "read_only": 1 }, { @@ -142,13 +148,42 @@ { "fieldname": "col_break2", "fieldtype": "Column Break" + }, + { + "fieldname": "consumed_qty", + "fieldtype": "Float", + "label": "Consumed Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "returned_qty", + "fieldtype": "Float", + "label": "Returned Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "total_supplied_qty", + "fieldtype": "Float", + "hidden": 1, + "label": "Total Supplied Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" } ], "hide_toolbar": 1, "idx": 1, "istable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-06-01 00:41:54.123436", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item Supplied", diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json index dc00bca5cc..d8c37f5881 100644 --- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json +++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json @@ -6,10 +6,11 @@ "engine": "InnoDB", "field_order": [ "main_item_code", - "description", + "rm_item_code", + "item_name", "bom_detail_no", "col_break1", - "rm_item_code", + "description", "stock_uom", "conversion_factor", "reference_name", @@ -52,7 +53,6 @@ "fieldname": "description", "fieldtype": "Text Editor", "in_global_search": 1, - "in_list_view": 1, "label": "Description", "oldfieldname": "description", "oldfieldtype": "Data", @@ -87,12 +87,13 @@ "read_only": 1 }, { + "columns": 2, "fieldname": "consumed_qty", "fieldtype": "Float", + "in_list_view": 1, "label": "Consumed Qty", "oldfieldname": "consumed_qty", "oldfieldtype": "Currency", - "read_only": 1, "reqd": 1 }, { @@ -183,12 +184,18 @@ { "fieldname": "col_break4", "fieldtype": "Column Break" + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-05-29 17:22:14.977117", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Receipt Item Supplied", diff --git a/erpnext/buying/report/subcontract_order_summary/__init__.py b/erpnext/buying/report/subcontract_order_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js new file mode 100644 index 0000000000..5ba52f1b21 --- /dev/null +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js @@ -0,0 +1,45 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Subcontract Order Summary"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1 + }, + { + label: __("Purchase Order"), + fieldname: "name", + fieldtype: "Link", + options: "Purchase Order", + get_query: function() { + return { + filters: { + docstatus: 1, + is_subcontracted: 'Yes', + company: frappe.query_report.get_filter_value('company') + } + } + } + } + ] +}; diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json new file mode 100644 index 0000000000..526a8d8ad0 --- /dev/null +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-05-31 14:43:32.417694", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-05-31 14:43:32.417694", + "modified_by": "Administrator", + "module": "Buying", + "name": "Subcontract Order Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Purchase Order", + "report_name": "Subcontract Order Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Purchase Manager" + }, + { + "role": "Purchase User" + } + ] +} \ No newline at end of file diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py new file mode 100644 index 0000000000..8b08d2a284 --- /dev/null +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -0,0 +1,158 @@ +# 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): + columns, data = [], [] + columns = get_columns() + data = get_data(filters) + + return columns, data + +def get_data(report_filters): + data = [] + orders = get_subcontracted_orders(report_filters) + + if orders: + supplied_items = get_supplied_items(orders, report_filters) + po_details = prepare_subcontracted_data(orders, supplied_items) + get_subcontracted_data(po_details, data) + + return data + +def get_subcontracted_orders(report_filters): + fields = ['`tabPurchase Order Item`.`parent` as po_id', '`tabPurchase Order Item`.`item_code`', + '`tabPurchase Order Item`.`item_name`', '`tabPurchase Order Item`.`qty`', '`tabPurchase Order Item`.`name`', + '`tabPurchase Order Item`.`received_qty`', '`tabPurchase Order`.`status`'] + + filters = get_filters(report_filters) + + return frappe.get_all('Purchase Order', fields = fields, filters=filters) or [] + +def get_filters(report_filters): + filters = [['Purchase Order', 'docstatus', '=', 1], ['Purchase Order', 'is_subcontracted', '=', 'Yes'], + ['Purchase Order', 'transaction_date', 'between', (report_filters.from_date, report_filters.to_date)]] + + for field in ['name', 'company']: + if report_filters.get(field): + filters.append(['Purchase Order', field, '=', report_filters.get(field)]) + + return filters + +def get_supplied_items(orders, report_filters): + if not orders: + return [] + + fields = ['parent', 'main_item_code', 'rm_item_code', 'required_qty', + 'supplied_qty', 'returned_qty', 'total_supplied_qty', 'consumed_qty', 'reference_name'] + + filters = {'parent': ('in', [d.po_id for d in orders]), 'docstatus': 1} + + supplied_items = {} + for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters): + new_key = (row.parent, row.reference_name, row.main_item_code) + + supplied_items.setdefault(new_key, []).append(row) + + return supplied_items + +def prepare_subcontracted_data(orders, supplied_items): + po_details = {} + for row in orders: + key = (row.po_id, row.name, row.item_code) + if key not in po_details: + po_details.setdefault(key, frappe._dict({'po_item': row, 'supplied_items': []})) + + details = po_details[key] + + if supplied_items.get(key): + for supplied_item in supplied_items[key]: + details['supplied_items'].append(supplied_item) + + return po_details + +def get_subcontracted_data(po_details, data): + for key, details in po_details.items(): + res = details.po_item + for index, row in enumerate(details.supplied_items): + if index != 0: + res = {} + + res.update(row) + data.append(res) + +def get_columns(): + return [ + { + "label": _("Id"), + "fieldname": "po_id", + "fieldtype": "Link", + "options": "Purchase Order", + "width": 100 + }, + { + "label": _("Status"), + "fieldname": "status", + "fieldtype": "Data", + "width": 80 + }, + { + "label": _("Subcontracted Item"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 140 + }, + { + "label": _("Qty"), + "fieldname": "qty", + "fieldtype": "Float", + "width": 70 + }, + { + "label": _("Received"), + "fieldname": "received_qty", + "fieldtype": "Float", + "width": 80 + }, + { + "label": _("Supplied Item"), + "fieldname": "rm_item_code", + "fieldtype": "Link", + "options": "Item", + "width": 140 + }, + { + "label": _("Required Qty"), + "fieldname": "required_qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Supplied Qty"), + "fieldname": "supplied_qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Returned Qty"), + "fieldname": "returned_qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Total Supplied"), + "fieldname": "total_supplied_qty", + "fieldtype": "Float", + "width": 120 + }, + { + "label": _("Consumed Qty"), + "fieldname": "consumed_qty", + "fieldtype": "Float", + "width": 110 + }, + ] \ No newline at end of file diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 20f5445725..1907885717 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -11,16 +11,17 @@ from erpnext.accounts.party import get_party_details from erpnext.stock.get_item_details import get_conversion_factor from erpnext.buying.utils import validate_for_items, update_last_purchase_rate from erpnext.stock.stock_ledger import get_valuation_rate -from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos, get_serial_nos from frappe.contacts.doctype.address.address import get_address_display from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget -from erpnext.controllers.stock_controller import StockController from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.stock.utils import get_incoming_rate -class BuyingController(StockController): +from erpnext.controllers.stock_controller import StockController +from erpnext.controllers.subcontracting import Subcontracting + +class BuyingController(StockController, Subcontracting): def get_feed(self): if self.get("supplier_name"): @@ -256,7 +257,7 @@ class BuyingController(StockController): supplied_items_cost = 0.0 for d in self.get("supplied_items"): if d.reference_name == item_row_id: - if reset_outgoing_rate and frappe.db.get_value('Item', d.rm_item_code, 'is_stock_item'): + if reset_outgoing_rate and frappe.get_cached_value('Item', d.rm_item_code, 'is_stock_item'): rate = get_incoming_rate({ "item_code": d.rm_item_code, "warehouse": self.supplier_warehouse, @@ -298,23 +299,7 @@ class BuyingController(StockController): def create_raw_materials_supplied(self, raw_material_table): if self.is_subcontracted=="Yes": - parent_items = [] - backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings", - "backflush_raw_materials_of_subcontract_based_on") - if (self.doctype == 'Purchase Receipt' and - backflush_raw_materials_based_on != 'BOM'): - self.update_raw_materials_supplied_based_on_stock_entries() - else: - for item in self.get("items"): - if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: - item.rm_supp_cost = 0.0 - if item.bom and item.item_code in self.sub_contracted_items: - self.update_raw_materials_supplied_based_on_bom(item, raw_material_table) - - if [item.item_code, item.name] not in parent_items: - parent_items.append([item.item_code, item.name]) - - self.cleanup_raw_materials_supplied(parent_items, raw_material_table) + self.set_materials_for_subcontracted_items(raw_material_table) elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]: for item in self.get("items"): @@ -323,176 +308,6 @@ class BuyingController(StockController): if self.is_subcontracted == "No" and self.get("supplied_items"): self.set('supplied_items', []) - def update_raw_materials_supplied_based_on_stock_entries(self): - self.set('supplied_items', []) - - purchase_orders = set(d.purchase_order for d in self.items) - - # qty of raw materials backflushed (for each item per purchase order) - backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders) - - # qty of "finished good" item yet to be received - qty_to_be_received_map = get_qty_to_be_received(purchase_orders) - - for item in self.get('items'): - if not item.purchase_order: - continue - - # reset raw_material cost - item.rm_supp_cost = 0 - - # qty of raw materials transferred to the supplier - transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code) - - non_stock_items = get_non_stock_items(item.purchase_order, item.item_code) - - item_key = '{}{}'.format(item.item_code, item.purchase_order) - - fg_yet_to_be_received = qty_to_be_received_map.get(item_key) - - if not fg_yet_to_be_received: - frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}") - .format(item.idx, frappe.bold(item.item_code), - frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)), - title=_("Limit Crossed")) - - transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) - # backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) - - for raw_material in transferred_raw_materials + non_stock_items: - rm_item_key = (raw_material.rm_item_code, item.item_code, item.purchase_order) - raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {}) - - consumed_qty = raw_material_data.get('qty', 0) - consumed_serial_nos = raw_material_data.get('serial_no', '') - consumed_batch_nos = raw_material_data.get('batch_nos', '') - - transferred_qty = raw_material.qty - - rm_qty_to_be_consumed = transferred_qty - consumed_qty - - # backflush all remaining transferred qty in the last Purchase Receipt - if fg_yet_to_be_received == item.qty: - qty = rm_qty_to_be_consumed - else: - qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty - - if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'): - qty = frappe.utils.ceil(qty) - - if qty > rm_qty_to_be_consumed: - qty = rm_qty_to_be_consumed - - if not qty: continue - - if raw_material.serial_nos: - set_serial_nos(raw_material, consumed_serial_nos, qty) - - if raw_material.batch_nos: - backflushed_batch_qty_map = raw_material_data.get('consumed_batch', {}) - - batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code, - qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order) - - for batch_data in batches_qty: - qty = batch_data['qty'] - raw_material.batch_no = batch_data['batch'] - if qty > 0: - self.append_raw_material_to_be_backflushed(item, raw_material, qty) - else: - self.append_raw_material_to_be_backflushed(item, raw_material, qty) - - def append_raw_material_to_be_backflushed(self, fg_item_row, raw_material_data, qty): - rm = self.append('supplied_items', {}) - rm.update(raw_material_data) - - if not rm.main_item_code: - rm.main_item_code = fg_item_row.item_code - - rm.reference_name = fg_item_row.name - rm.required_qty = qty - rm.consumed_qty = qty - - def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table): - exploded_item = 1 - if hasattr(item, 'include_exploded_items'): - exploded_item = item.get('include_exploded_items') - - bom_items = get_items_from_bom(item.item_code, item.bom, exploded_item) - - used_alternative_items = [] - if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order: - used_alternative_items = get_used_alternative_items(purchase_order = item.purchase_order) - - raw_materials_cost = 0 - items = list(set([d.item_code for d in bom_items])) - item_wh = frappe._dict(frappe.db.sql("""select i.item_code, id.default_warehouse - from `tabItem` i, `tabItem Default` id - where id.parent=i.name and id.company=%s and i.name in ({0})""" - .format(", ".join(["%s"] * len(items))), [self.company] + items)) - - for bom_item in bom_items: - if self.doctype == "Purchase Order": - reserve_warehouse = bom_item.source_warehouse or item_wh.get(bom_item.item_code) - if frappe.db.get_value("Warehouse", reserve_warehouse, "company") != self.company: - reserve_warehouse = None - - conversion_factor = item.conversion_factor - if (self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order and - bom_item.item_code in used_alternative_items): - alternative_item_data = used_alternative_items.get(bom_item.item_code) - bom_item.item_code = alternative_item_data.item_code - bom_item.item_name = alternative_item_data.item_name - bom_item.stock_uom = alternative_item_data.stock_uom - conversion_factor = alternative_item_data.conversion_factor - bom_item.description = alternative_item_data.description - - # check if exists - exists = 0 - for d in self.get(raw_material_table): - if d.main_item_code == item.item_code and d.rm_item_code == bom_item.item_code \ - and d.reference_name == item.name: - rm, exists = d, 1 - break - - if not exists: - rm = self.append(raw_material_table, {}) - - required_qty = flt(flt(bom_item.qty_consumed_per_unit) * (flt(item.qty) + getattr(item, 'rejected_qty', 0)) * - flt(conversion_factor), rm.precision("required_qty")) - rm.reference_name = item.name - rm.bom_detail_no = bom_item.name - rm.main_item_code = item.item_code - rm.rm_item_code = bom_item.item_code - rm.stock_uom = bom_item.stock_uom - rm.required_qty = required_qty - rm.rate = bom_item.rate - rm.conversion_factor = conversion_factor - - if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: - rm.consumed_qty = required_qty - rm.description = bom_item.description - if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no: - rm.batch_no = item.batch_no - elif not rm.reserve_warehouse: - rm.reserve_warehouse = reserve_warehouse - - def cleanup_raw_materials_supplied(self, parent_items, raw_material_table): - """Remove all those child items which are no longer present in main item table""" - delete_list = [] - for d in self.get(raw_material_table): - if [d.main_item_code, d.reference_name] not in parent_items: - # mark for deletion from doclist - delete_list.append(d) - - # delete from doclist - if delete_list: - rm_supplied_details = self.get(raw_material_table) - self.set(raw_material_table, []) - for d in rm_supplied_details: - if d not in delete_list: - self.append(raw_material_table, d) - @property def sub_contracted_items(self): if not hasattr(self, "_sub_contracted_items"): @@ -867,104 +682,6 @@ class BuyingController(StockController): else: validate_item_type(self, "is_purchase_item", "purchase") - -def get_items_from_bom(item_code, bom, exploded_item=1): - doctype = "BOM Item" if not exploded_item else "BOM Explosion Item" - - bom_items = frappe.db.sql("""select t2.item_code, t2.name, - t2.rate, t2.stock_uom, t2.source_warehouse, t2.description, - t2.stock_qty / ifnull(t1.quantity, 1) as qty_consumed_per_unit - from - `tabBOM` t1, `tab{0}` t2, tabItem t3 - where - t2.parent = t1.name and t1.item = %s - and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s - and t2.sourced_by_supplier = 0 - and t2.item_code = t3.name""".format(doctype), - (item_code, bom), as_dict=1) - - if not bom_items: - msgprint(_("Specified BOM {0} does not exist for Item {1}").format(bom, item_code), raise_exception=1) - - return bom_items - -def get_subcontracted_raw_materials_from_se(purchase_order, fg_item): - common_query = """ - SELECT - sed.item_code AS rm_item_code, - SUM(sed.qty) AS qty, - sed.description, - sed.stock_uom, - sed.subcontracted_item AS main_item_code, - {serial_no_concat_syntax} AS serial_nos, - {batch_no_concat_syntax} AS batch_nos - FROM `tabStock Entry` se,`tabStock Entry Detail` sed - WHERE - se.name = sed.parent - AND se.docstatus=1 - AND se.purpose='Send to Subcontractor' - AND se.purchase_order = %s - AND IFNULL(sed.t_warehouse, '') != '' - AND IFNULL(sed.subcontracted_item, '') in ('', %s) - GROUP BY sed.item_code, sed.subcontracted_item - """ - raw_materials = frappe.db.multisql({ - 'mariadb': common_query.format( - serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)", - batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)" - ), - 'postgres': common_query.format( - serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')", - batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')" - ) - }, (purchase_order, fg_item), as_dict=1) - - return raw_materials - -def get_backflushed_subcontracted_raw_materials(purchase_orders): - purchase_receipts = frappe.get_all("Purchase Receipt Item", - fields = ["purchase_order", "item_code", "name", "parent"], - filters={"docstatus": 1, "purchase_order": ("in", list(purchase_orders))}) - - distinct_purchase_receipts = {} - for pr in purchase_receipts: - key = (pr.purchase_order, pr.item_code, pr.parent) - distinct_purchase_receipts.setdefault(key, []).append(pr.name) - - backflushed_raw_materials_map = frappe._dict() - for args, references in iteritems(distinct_purchase_receipts): - purchase_receipt_supplied_items = get_supplied_items(args[1], args[2], references) - - for data in purchase_receipt_supplied_items: - pr_key = (data.rm_item_code, data.main_item_code, args[0]) - if pr_key not in backflushed_raw_materials_map: - backflushed_raw_materials_map.setdefault(pr_key, frappe._dict({ - "qty": 0.0, - "serial_no": [], - "batch_no": [], - "consumed_batch": {} - })) - - row = backflushed_raw_materials_map.get(pr_key) - row.qty += data.consumed_qty - - for field in ["serial_no", "batch_no"]: - if data.get(field): - row[field].append(data.get(field)) - - if data.get("batch_no"): - if data.get("batch_no") in row.consumed_batch: - row.consumed_batch[data.get("batch_no")] += data.consumed_qty - else: - row.consumed_batch[data.get("batch_no")] = data.consumed_qty - - return backflushed_raw_materials_map - -def get_supplied_items(item_code, purchase_receipt, references): - return frappe.get_all("Purchase Receipt Item Supplied", - fields=["rm_item_code", "main_item_code", "consumed_qty", "serial_no", "batch_no"], - filters={"main_item_code": item_code, "parent": purchase_receipt, "reference_name": ("in", references)}) - def get_asset_item_details(asset_items): asset_items_data = {} for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"], @@ -996,135 +713,3 @@ def validate_item_type(doc, fieldname, message): error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message) frappe.throw(error_message) - -def get_qty_to_be_received(purchase_orders): - return frappe._dict(frappe.db.sql(""" - SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key, - SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received - FROM `tabPurchase Order Item` poi - WHERE - poi.`parent` in %s - GROUP BY poi.`item_code`, poi.`parent` - HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`) - """, (purchase_orders))) - -def get_non_stock_items(purchase_order, fg_item_code): - return frappe.db.sql(""" - SELECT - pois.main_item_code, - pois.rm_item_code, - item.description, - pois.required_qty AS qty, - pois.rate, - 1 as non_stock_item, - pois.stock_uom - FROM `tabPurchase Order Item Supplied` pois, `tabItem` item - WHERE - pois.`rm_item_code` = item.`name` - AND item.is_stock_item = 0 - AND pois.`parent` = %s - AND pois.`main_item_code` = %s - """, (purchase_order, fg_item_code), as_dict=1) - - -def set_serial_nos(raw_material, consumed_serial_nos, qty): - serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \ - set(get_serial_nos(consumed_serial_nos)) - if serial_nos and qty <= len(serial_nos): - raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)]) - -def get_transferred_batch_qty_map(purchase_order, fg_item): - # returns - # { - # (item_code, fg_code): { - # batch1: 10, # qty - # batch2: 16 - # }, - # } - transferred_batch_qty_map = {} - transferred_batches = frappe.db.sql(""" - SELECT - sed.batch_no, - SUM(sed.qty) AS qty, - sed.item_code, - sed.subcontracted_item - FROM `tabStock Entry` se,`tabStock Entry Detail` sed - WHERE - se.name = sed.parent - AND se.docstatus=1 - AND se.purpose='Send to Subcontractor' - AND se.purchase_order = %s - AND ifnull(sed.subcontracted_item, '') in ('', %s) - AND sed.batch_no IS NOT NULL - GROUP BY - sed.batch_no, - sed.item_code - """, (purchase_order, fg_item), as_dict=1) - - for batch_data in transferred_batches: - key = ((batch_data.item_code, fg_item) - if batch_data.subcontracted_item else (batch_data.item_code, purchase_order)) - transferred_batch_qty_map.setdefault(key, OrderedDict()) - transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty - - return transferred_batch_qty_map - -def get_backflushed_batch_qty_map(purchase_order, fg_item): - # returns - # { - # (item_code, fg_code): { - # batch1: 10, # qty - # batch2: 16 - # }, - # } - backflushed_batch_qty_map = {} - backflushed_batches = frappe.db.sql(""" - SELECT - pris.batch_no, - SUM(pris.consumed_qty) AS qty, - pris.rm_item_code AS item_code - FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris - WHERE - pr.name = pri.parent - AND pri.parent = pris.parent - AND pri.purchase_order = %s - AND pri.item_code = pris.main_item_code - AND pr.docstatus = 1 - AND pris.main_item_code = %s - AND pris.batch_no IS NOT NULL - GROUP BY - pris.rm_item_code, pris.batch_no - """, (purchase_order, fg_item), as_dict=1) - - for batch_data in backflushed_batches: - backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) - backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty - - return backflushed_batch_qty_map - -def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batches, po): - # Returns available batches to be backflushed based on requirements - transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {}) - if not transferred_batches: - transferred_batches = transferred_batch_qty_map.get((item_code, po), {}) - - available_batches = [] - - for (batch, transferred_qty) in transferred_batches.items(): - backflushed_qty = backflushed_batches.get(batch, 0) - available_qty = transferred_qty - backflushed_qty - - if available_qty >= required_qty: - available_batches.append({'batch': batch, 'qty': required_qty}) - break - elif available_qty != 0: - available_batches.append({'batch': batch, 'qty': available_qty}) - required_qty -= available_qty - - for row in available_batches: - if backflushed_batches.get(row.get('batch'), 0) > 0: - backflushed_batches[row.get('batch')] += row.get('qty') - else: - backflushed_batches[row.get('batch')] = row.get('qty') - - return available_batches diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py new file mode 100644 index 0000000000..fe775766da --- /dev/null +++ b/erpnext/controllers/subcontracting.py @@ -0,0 +1,342 @@ +from __future__ import unicode_literals + +import frappe +from frappe import _ +from frappe.utils import flt, cint +from collections import defaultdict +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + +class Subcontracting(object): + def set_materials_for_subcontracted_items(self, raw_material_table): + if self.doctype == 'Purchase Invoice' and not self.update_stock: + return + + self.raw_material_table = raw_material_table + self.identify_change_in_item_table() + self.prepare_supplied_items() + self.validate_consumed_qty() + + def prepare_supplied_items(self): + self.initialized_fields() + self.get_purchase_orders() + self.get_pending_qty_to_receive() + self.get_available_materials() + self.remove_changed_rows() + self.set_supplied_items() + + def initialized_fields(self): + self.available_materials = frappe._dict() + self.alternative_item_details = frappe._dict() + self.get_backflush_based_on() + + def get_backflush_based_on(self): + self.backflush_based_on = frappe.db.get_single_value("Buying Settings", + "backflush_raw_materials_of_subcontract_based_on") + + def get_purchase_orders(self): + self.purchase_orders = [] + + if self.doctype == 'Purchase Order': + return + + self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order] + + def identify_change_in_item_table(self): + self.changed_name = [] + + if self.doctype == 'Purchase Order' or not self.get(self.raw_material_table): + self.set(self.raw_material_table, []) + return + + item_dict = self.get_data_before_save() + if not item_dict: + return True + + for n_row in self.items: + if (n_row.name not in item_dict) or (n_row.item_code, n_row.qty) != item_dict[n_row.name]: + self.changed_name.append(n_row.name) + + if item_dict.get(n_row.name): + del item_dict[n_row.name] + + self.changed_name.extend(item_dict.keys()) + + def get_data_before_save(self): + item_dict = {} + if self.doctype == 'Purchase Receipt' and self._doc_before_save: + for row in self._doc_before_save.get('items'): + item_dict[row.name] = (row.item_code, row.qty) + + return item_dict + + def get_available_materials(self): + ''' Get the available raw materials which has been transferred to the supplier. + available_materials = { + (item_code, subcontracted_item, purchase_order): { + 'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details + } + } + ''' + if not self.purchase_orders: + return + + for row in self.get_transferred_items(): + key = (row.rm_item_code, row.main_item_code, row.purchase_order) + + if key not in self.available_materials: + self.available_materials.setdefault(key, frappe._dict({'qty': 0, 'serial_no': [], + 'batch_no': defaultdict(float), 'item_details': row, 'po_details': []}) + ) + + details = self.available_materials[key] + details.qty += row.qty + details.po_details.append(row.po_detail) + + if row.serial_no: + details.serial_no.extend(get_serial_nos(row.serial_no)) + + if row.batch_no: + details.batch_no[row.batch_no] += row.qty + + self.set_alternative_item_details(row) + + for doctype in ['Purchase Receipt', 'Purchase Invoice']: + self.remove_consumed_materials(doctype) + + def remove_consumed_materials(self, doctype, return_consumed_items=False): + '''Deduct the consumed materials from the available materials.''' + + pr_items = self.get_received_items(doctype) + if not pr_items: + return ([], {}) if return_consumed_items else None + + pr_items = {d.name: d.get(self.get('po_field') or 'purchase_order') for d in pr_items} + consumed_materials = self.get_consumed_items(doctype, pr_items.keys()) + + if return_consumed_items: + return (consumed_materials, pr_items) + + for row in consumed_materials: + key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name)) + if not self.available_materials.get(key): + continue + + self.available_materials[key]['qty'] -= row.consumed_qty + if row.serial_no: + self.available_materials[key]['serial_no'] = list( + set(self.available_materials[key]['serial_no']) - set(get_serial_nos(row.serial_no)) + ) + + if row.batch_no: + self.available_materials[key]['batch_no'][row.batch_no] -= row.consumed_qty + + def get_transferred_items(self): + fields = ['`tabStock Entry`.`purchase_order`'] + alias_dict = {'item_code': 'rm_item_code', 'subcontracted_item': 'main_item_code', 'basic_rate': 'rate'} + + child_table_fields = ['item_code', 'item_name', 'description', 'qty', 'basic_rate', 'amount', + 'serial_no', 'uom', 'subcontracted_item', 'stock_uom', 'batch_no', 'conversion_factor', + 's_warehouse', 't_warehouse', 'item_group', 'po_detail'] + + if self.backflush_based_on == 'BOM': + child_table_fields.append('original_item') + + for field in child_table_fields: + fields.append(f'`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}') + + filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purpose', '=', 'Send to Subcontractor'], + ['Stock Entry', 'purchase_order', 'in', self.purchase_orders]] + + return frappe.get_all('Stock Entry', fields = fields, filters=filters) + + def get_received_items(self, doctype): + fields = [] + self.po_field = 'purchase_order' if doctype == 'Purchase Receipt' else 'po_detail' + + for field in ['name', self.po_field, 'parent']: + fields.append(f'`tab{doctype} Item`.`{field}`') + + filters = [[doctype, 'docstatus', '=', 1], [f'{doctype} Item', self.po_field, 'in', self.purchase_orders]] + if doctype == 'Purchase Invoice': + filters.append(['Purchase Invoice', 'update_stock', "=", 1]) + + return frappe.get_all(f'{doctype}', fields = fields, filters = filters) + + def get_consumed_items(self, doctype, pr_items): + return frappe.get_all(f'{doctype} Item Supplied', + fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'], + filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items))}) + + def set_alternative_item_details(self, row): + if row.get('original_item'): + self.alternative_item_details[row.get('original_item')] = row + + def get_pending_qty_to_receive(self): + '''Get qty to be received against the purchase order.''' + + self.qty_to_be_received = defaultdict(float) + + if self.doctype != 'Purchase Order' and self.backflush_based_on != 'BOM' and self.purchase_orders: + for row in frappe.get_all('Purchase Order Item', + fields = ['item_code', '(qty - received_qty) as qty', 'parent', 'name'], + filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}): + + self.qty_to_be_received[(row.item_code, row.parent)] += row.qty + + def get_materials_from_bom(self, item_code, bom_no, exploded_item=0): + doctype = 'BOM Item' if not exploded_item else 'BOM Explosion Item' + fields = [f'`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit'] + + alias_dict = {'item_code': 'rm_item_code', 'name': 'bom_detail_no', 'source_warehouse': 'reserve_warehouse'} + for field in ['item_code', 'name', 'rate', 'stock_uom', + 'source_warehouse', 'description', 'item_name', 'stock_uom']: + fields.append(f'`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}') + + filters = [[doctype, 'parent', '=', bom_no], [doctype, 'docstatus', '=', 1], + ['BOM', 'item', '=', item_code], [doctype, 'sourced_by_supplier', '=', 0]] + + return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or [] + + def remove_changed_rows(self): + if not self.changed_name: + return + + i=1 + self.set(self.raw_material_table, []) + for d in self._doc_before_save.supplied_items: + if d.reference_name in self.changed_name: + continue + + d.idx = i + self.append('supplied_items', d) + + i += 1 + + def set_supplied_items(self): + self.bom_items = {} + + has_supplied_items = True if self.get(self.raw_material_table) else False + for row in self.items: + if (self.doctype != 'Purchase Order' and ((self.changed_name and row.name not in self.changed_name) + or (has_supplied_items and not self.changed_name))): + continue + + if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM': + for bom_item in self.get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')): + qty = (flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor) + bom_item.main_item_code = row.item_code + self.update_reserve_warehouse(bom_item, row) + self.set_alternative_item(bom_item) + self.add_supplied_item(row, bom_item, qty) + + elif self.backflush_based_on != 'BOM': + for key, transfer_item in self.available_materials.items(): + if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0: + qty = self.get_qty_based_on_material_transfer(row, transfer_item) or 0 + transfer_item.qty -= qty + self.add_supplied_item(row, transfer_item.get('item_details'), qty) + + if self.qty_to_be_received: + self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty + + def update_reserve_warehouse(self, row, item): + if self.doctype == 'Purchase Order': + row.reserve_warehouse = (self.set_reserve_warehouse or item.warehouse) + + def get_qty_based_on_material_transfer(self, item_row, transfer_item): + key = (item_row.item_code, item_row.purchase_order) + + if self.qty_to_be_received == item_row.qty: + return transfer_item.qty + + if self.qty_to_be_received: + qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0)) + if (transfer_item.serial_no or frappe.get_cached_value('UOM', + transfer_item.item_details.stock_uom, 'must_be_whole_number')): + return frappe.utils.ceil(qty) + + return qty + + def set_alternative_item(self, bom_item): + if self.alternative_item_details.get(bom_item.rm_item_code): + bom_item.update(self.alternative_item_details[bom_item.rm_item_code]) + + def add_supplied_item(self, item_row, bom_item, qty): + bom_item.conversion_factor = item_row.conversion_factor + rm_obj = self.append(self.raw_material_table, bom_item) + rm_obj.reference_name = item_row.name + + if self.doctype == 'Purchase Order': + rm_obj.required_qty = qty + else: + self.set_batch_nos(bom_item, item_row, rm_obj, qty) + + def set_batch_nos(self, bom_item, item_row, rm_obj, qty): + key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order) + + if (self.available_materials.get(key) and self.available_materials[key]['batch_no']): + for batch_no, batch_qty in self.available_materials[key]['batch_no'].items(): + if batch_qty >= qty: + self.set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty) + self.available_materials[key]['batch_no'][batch_no] -= qty + return + + elif qty > 0 and batch_qty > 0: + qty -= batch_qty + new_rm_obj = self.append(self.raw_material_table, bom_item) + new_rm_obj.reference_name = item_row.name + self.set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty) + self.available_materials[key]['batch_no'][batch_no] = 0 + else: + rm_obj.required_qty = qty + rm_obj.consumed_qty = qty + self.set_serial_nos(item_row, rm_obj) + + def set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty): + rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no, 'required_qty': qty}) + self.set_serial_nos(item_row, rm_obj) + + def set_serial_nos(self, item_row, rm_obj): + key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order) + if (self.available_materials.get(key) and self.available_materials[key]['serial_no']): + used_serial_nos = self.available_materials[key]['serial_no'][0: cint(rm_obj.consumed_qty)] + rm_obj.serial_no = '\n'.join(used_serial_nos) + + # Removed the used serial nos from the list + for sn in used_serial_nos: + self.available_materials[key]['serial_no'].remove(sn) + + def set_consumed_qty_in_po(self): + if self.is_subcontracted != 'Yes': + return + + self.get_purchase_orders() + consumed_items, pr_items = self.remove_consumed_materials(self.doctype, return_consumed_items=True) + + itemwise_consumed_qty = defaultdict(float) + for row in consumed_items: + key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name)) + itemwise_consumed_qty[key] += row.consumed_qty + + self.update_consumed_qty_in_po(itemwise_consumed_qty) + + def update_consumed_qty_in_po(self, itemwise_consumed_qty): + fields = ['main_item_code', 'rm_item_code', 'parent', 'supplied_qty', 'name'] + filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)} + + for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters, order_by='idx'): + key = (row.rm_item_code, row.main_item_code, row.parent) + consumed_qty = itemwise_consumed_qty.get(key, 0) + + if row.supplied_qty < consumed_qty: + consumed_qty = row.supplied_qty + + itemwise_consumed_qty[key] -= consumed_qty + frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty) + + def validate_consumed_qty(self): + for row in self.get(self.raw_material_table): + if flt(row.consumed_qty) == 0.0 and row.get('serial_no'): + msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}' + + frappe.throw(_(msg),title=_('Consumed Items Qty Check')) \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index e1cca9e3ef..42b23f223d 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -12,6 +12,7 @@ from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update from six import string_types from erpnext.stock.doctype.item.test_item import make_item from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.tests.test_subcontracting import set_backflush_based_on test_records = frappe.get_test_records('BOM') @@ -160,6 +161,7 @@ class TestBOM(unittest.TestCase): def test_subcontractor_sourced_item(self): item_code = "_Test Subcontracted FG Item 1" + set_backflush_based_on('Material Transferred for Subcontract') if not frappe.db.exists('Item', item_code): make_item(item_code, { diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 0514bd2394..43642013ce 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -54,7 +54,7 @@ class Bin(Document): self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty")) self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty")) self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty")) - + self.set_projected_qty() self.db_update() @@ -115,7 +115,7 @@ class Bin(Document): #Get Transferred Entries materials_transferred = frappe.db.sql(""" select - ifnull(sum(transfer_qty),0) + ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0) from `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po where diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index d5700fe514..8f76844bde 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -18,6 +18,9 @@ class TestItemAlternative(unittest.TestCase): make_items() def test_alternative_item_for_subcontract_rm(self): + frappe.db.set_value('Buying Settings', None, + 'backflush_raw_materials_of_subcontract_based_on', 'BOM') + create_stock_reconciliation(item_code='Alternate Item For A RW 1', warehouse='_Test Warehouse - _TC', qty=5, rate=2000) create_stock_reconciliation(item_code='Test FG A RW 2', warehouse='_Test Warehouse - _TC', @@ -65,6 +68,8 @@ class TestItemAlternative(unittest.TestCase): status = True self.assertEqual(status, True) + frappe.db.set_value('Buying Settings', None, + 'backflush_raw_materials_of_subcontract_based_on', 'Material Transferred for Subcontract') def test_alternative_item_for_production_rm(self): create_stock_reconciliation(item_code='Alternate Item For A RW 1', diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index befdad9692..887b15a211 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -41,6 +41,8 @@ frappe.ui.form.on("Purchase Receipt", { } }); + frm.set_df_property('supplied_items', 'cannot_add_rows', 1); + }, onload: function(frm) { erpnext.queries.setup_queries(frm, "Warehouse", function() { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index ad350d344f..44fb736304 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -514,8 +514,7 @@ "oldfieldname": "pr_raw_material_details", "oldfieldtype": "Table", "options": "Purchase Receipt Item Supplied", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "section_break0", @@ -1149,7 +1148,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2021-04-19 01:01:00.754119", + "modified": "2021-05-25 00:15:12.239017", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 83ba324495..b8580f95a3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -202,6 +202,7 @@ class PurchaseReceipt(BuyingController): self.make_gl_entries() self.repost_future_sle_and_gle() + self.set_consumed_qty_in_po() def check_next_docstatus(self): submit_rv = frappe.db.sql("""select t1.name @@ -233,6 +234,7 @@ class PurchaseReceipt(BuyingController): self.repost_future_sle_and_gle() self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') self.delete_auto_created_batches() + self.set_consumed_qty_in_po() @frappe.whitelist() def get_current_stock(self): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 8d9b675bed..95096d77d7 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -335,6 +335,10 @@ class TestPurchaseReceipt(unittest.TestCase): se2.cancel() se3.cancel() po.reload() + pr2.load_from_db() + pr2.cancel() + + po.load_from_db() po.cancel() def test_serial_no_supplier(self): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 1a25994b24..6708393027 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1079,6 +1079,10 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => { } function attach_bom_items(bom_no) { + if (!bom_no) { + return + } + if (check_should_not_attach_bom_items(bom_no)) return frappe.db.get_doc("BOM",bom_no).then(bom => { const {name, items} = bom diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index a0b5457dd7..523d332b8f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -74,7 +74,8 @@ "total_amount", "job_card", "amended_from", - "credit_note" + "credit_note", + "is_return" ], "fields": [ { @@ -611,6 +612,16 @@ "fieldname": "apply_putaway_rule", "fieldtype": "Check", "label": "Apply Putaway Rule" + }, + { + "default": "0", + "fieldname": "is_return", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Return", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -618,7 +629,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-24 11:32:23.904307", + "modified": "2021-05-26 17:07:58.015737", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 560ceaa917..213280870a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -97,8 +97,7 @@ class StockEntry(StockController): update_serial_nos_after_submit(self, "items") self.update_work_order() self.validate_purchase_order() - if self.purchase_order and self.purpose == "Send to Subcontractor": - self.update_purchase_order_supplied_items() + self.update_purchase_order_supplied_items() self.make_gl_entries() @@ -117,9 +116,7 @@ class StockEntry(StockController): self.set_material_request_transfer_status('Completed') def on_cancel(self): - - if self.purchase_order and self.purpose == "Send to Subcontractor": - self.update_purchase_order_supplied_items() + self.update_purchase_order_supplied_items() if self.work_order and self.purpose == "Material Consumption for Manufacture": self.validate_work_order_status() @@ -1347,7 +1344,7 @@ class StockEntry(StockController): se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0) for field in ["idx", "po_detail", "original_item", - "expense_account", "description", "item_name"]: + "expense_account", "description", "item_name", "serial_no", "batch_no"]: if item_dict[d].get(field): se_child.set(field, item_dict[d].get(field)) @@ -1400,33 +1397,26 @@ class StockEntry(StockController): .format(item.batch_no, item.item_code)) def update_purchase_order_supplied_items(self): - #Get PO Supplied Items Details - item_wh = frappe._dict(frappe.db.sql(""" - select rm_item_code, reserve_warehouse - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup - where po.name = poitemsup.parent - and po.name = %s""", self.purchase_order)) + if (self.purchase_order and + (self.purpose in ['Send to Subcontractor', 'Material Transfer'] or self.is_return)): - #Update Supplied Qty in PO Supplied Items + #Get PO Supplied Items Details + item_wh = frappe._dict(frappe.db.sql(""" + select rm_item_code, reserve_warehouse + from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup + where po.name = poitemsup.parent + and po.name = %s""", self.purchase_order)) - frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos - SET - pos.supplied_qty = IFNULL((SELECT ifnull(sum(transfer_qty), 0) - FROM - `tabStock Entry Detail` sed, `tabStock Entry` se - WHERE - pos.name = sed.po_detail AND pos.rm_item_code = sed.item_code - AND pos.parent = se.purchase_order AND sed.docstatus = 1 - AND se.name = sed.parent and se.purchase_order = %(po)s - ), 0) - WHERE pos.docstatus = 1 and pos.parent = %(po)s""", {"po": self.purchase_order}) + supplied_items = get_supplied_items(self.purchase_order) + for name, item in supplied_items.items(): + frappe.db.set_value('Purchase Order Item Supplied', name, item) - #Update reserved sub contracted quantity in bin based on Supplied Item Details and - for d in self.get("items"): - item_code = d.get('original_item') or d.get('item_code') - reserve_warehouse = item_wh.get(item_code) - stock_bin = get_bin(item_code, reserve_warehouse) - stock_bin.update_reserved_qty_for_sub_contracting() + #Update reserved sub contracted quantity in bin based on Supplied Item Details and + for d in self.get("items"): + item_code = d.get('original_item') or d.get('item_code') + reserve_warehouse = item_wh.get(item_code) + stock_bin = get_bin(item_code, reserve_warehouse) + stock_bin.update_reserved_qty_for_sub_contracting() def update_so_in_serial_number(self): so_name, item_code = frappe.db.get_value("Work Order", self.work_order, ["sales_order", "production_item"]) @@ -1480,7 +1470,7 @@ class StockEntry(StockController): cond += """ WHEN (parent = %s and name = %s) THEN %s """ %(frappe.db.escape(data[0]), frappe.db.escape(data[1]), transferred_qty) - if cond and stock_entries_child_list: + if stock_entries_child_list: frappe.db.sql(""" UPDATE `tabStock Entry Detail` SET transferred_qty = CASE {cond} END @@ -1751,3 +1741,30 @@ def validate_sample_quantity(item_code, sample_quantity, qty, batch_no = None): format(max_retain_qty, batch_no, item_code), alert=True) sample_quantity = qty_diff return sample_quantity + +def get_supplied_items(purchase_order): + fields = ['`tabStock Entry Detail`.`transfer_qty`', '`tabStock Entry`.`is_return`', + '`tabStock Entry Detail`.`po_detail`', '`tabStock Entry Detail`.`item_code`'] + + filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purchase_order', '=', purchase_order]] + + supplied_item_details = {} + for row in frappe.get_all('Stock Entry', fields = fields, filters = filters): + if not row.po_detail: + continue + + key = row.po_detail + if key not in supplied_item_details: + supplied_item_details.setdefault(key, + frappe._dict({'supplied_qty': 0, 'returned_qty':0, 'total_supplied_qty':0})) + + supplied_item = supplied_item_details[key] + + if row.is_return: + supplied_item.returned_qty += row.transfer_qty + else: + supplied_item.supplied_qty += row.transfer_qty + + supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty) + + return supplied_item_details \ No newline at end of file diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py new file mode 100644 index 0000000000..c1a458a6dd --- /dev/null +++ b/erpnext/tests/test_subcontracting.py @@ -0,0 +1,583 @@ +from __future__ import unicode_literals +import frappe +import unittest +import copy +from frappe.utils import cint +from collections import defaultdict +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.buying.doctype.purchase_order.purchase_order import (make_rm_stock_entry, + make_purchase_receipt, get_materials_from_supplier) + +class TestSubcontracting(unittest.TestCase): + def setUp(self): + make_subcontract_items() + make_raw_materials() + make_bom_for_subcontracted_items() + + def test_po_with_bom(self): + ''' + - Set backflush based on BOM + - Create subcontracted PO for the item Subcontracted Item SA1 and add same item two times. + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Create purchase receipt against the PO and check serial nos and batch no. + ''' + + set_backflush_based_on('BOM') + item_code = 'Subcontracted Item SA1' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 5, 'rate': 100}, + {'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 6, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 5}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 5}, + {'item_code': 'Subcontracted SRM Item 1', 'qty': 6}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 6}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 6} + ] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + transferred_detais = itemwise_details.get(key) + + for field in ['qty', 'serial_no', 'batch_no']: + if value.get(field): + transfer, consumed = (transferred_detais.get(field), value.get(field)) + if field == 'serial_no': + transfer, consumed = (sorted(transfer), sorted(consumed)) + + self.assertEqual(transfer, consumed) + + def test_po_with_material_transfer(self): + ''' + - Set backflush based on Material Transfer + - Create subcontracted PO for the item Subcontracted Item SA1 and Subcontracted Item SA5. + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5. + - Create partial purchase receipt against the PO and check serial nos and batch no. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA1', 'qty': 5, 'rate': 100}, + {'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA5', 'qty': 6, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'}, + {'item_code': 'Subcontracted SRM Item 5', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'}, + {'item_code': 'Subcontracted SRM Item 4', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'} + ] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name + + make_stock_transfer_entry(po_no = po.name, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.remove(pr1.items[1]) + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + transferred_detais = itemwise_details.get(key) + + for field in ['qty', 'serial_no', 'batch_no']: + if value.get(field): + self.assertEqual(value.get(field), transferred_detais.get(field)) + + pr2 = make_purchase_receipt(po.name) + pr2.submit() + + for key, value in get_supplied_items(pr2).items(): + transferred_detais = itemwise_details.get(key) + + for field in ['qty', 'serial_no', 'batch_no']: + if value.get(field): + self.assertEqual(value.get(field), transferred_detais.get(field)) + + def test_subcontract_with_same_components_different_fg(self): + ''' + - Set backflush based on Material Transfer + - Create subcontracted PO for the item Subcontracted Item SA2 and Subcontracted Item SA3. + - Transfer the components from Stores to Supplier warehouse with serial nos. + - Transfer extra qty of components for the item Subcontracted Item SA2. + - Create partial purchase receipt against the PO and check serial nos and batch no. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100}, + {'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA3', 'qty': 6, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA3'} + ] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name + + make_stock_transfer_entry(po_no = po.name, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 3 + pr1.remove(pr1.items[1]) + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + transferred_detais = itemwise_details.get(key) + self.assertEqual(value.qty, 4) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:4])) + + pr2 = make_purchase_receipt(po.name) + pr2.items[0].qty = 2 + pr2.remove(pr2.items[1]) + pr2.submit() + + for key, value in get_supplied_items(pr2).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 2) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[4:6])) + + pr3 = make_purchase_receipt(po.name) + pr3.submit() + for key, value in get_supplied_items(pr3).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 6) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[6:12])) + + def test_return_non_consumed_materials(self): + ''' + - Set backflush based on Material Transfer + - Create subcontracted PO for the item Subcontracted Item SA2. + - Transfer the components from Stores to Supplier warehouse with serial nos. + - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2. + - Create purchase receipt for full qty against the PO and change the qty of raw material. + - After that return the non consumed material back to the store from supplier's warehouse. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100}] + rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'}] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.save() + pr1.supplied_items[0].consumed_qty = 5 + pr1.supplied_items[0].serial_no = '\n'.join(sorted( + itemwise_details.get('Subcontracted SRM Item 2').get('serial_no')[0:5] + )) + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + transferred_detais = itemwise_details.get(key) + self.assertEqual(value.qty, 5) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:5])) + + po.load_from_db() + self.assertEqual(po.supplied_items[0].consumed_qty, 5) + doc = get_materials_from_supplier(po.name, [d.name for d in po.supplied_items]) + self.assertEqual(doc.items[0].qty, 1) + self.assertEqual(doc.items[0].s_warehouse, '_Test Warehouse 1 - _TC') + self.assertEqual(doc.items[0].t_warehouse, '_Test Warehouse - _TC') + self.assertEqual(get_serial_nos(doc.items[0].serial_no), + itemwise_details.get(doc.items[0].item_code)['serial_no'][5:6]) + + def test_item_with_batch_based_on_bom(self): + ''' + - Set backflush based on BOM + - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no). + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer the components in multiple batches. + - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches. + - Keep the qty as 2 for Subcontracted Item in the purchase receipt. + ''' + + set_backflush_based_on('BOM') + item_code = 'Subcontracted Item SA4' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 1} + ] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 2 + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 4) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 2 + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 4) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 2 + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 2) + + def test_item_with_batch_based_on_material_transfer(self): + ''' + - Set backflush based on Material Transferred for Subcontract + - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no). + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer the components in multiple batches with extra 2 qty for the batched item. + - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches. + - Keep the qty as 2 for Subcontracted Item in the purchase receipt. + - In the first purchase receipt the batched raw materials will be consumed 2 extra qty. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + item_code = 'Subcontracted Item SA4' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3} + ] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 2 + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + qty = 4 if key != 'Subcontracted SRM Item 3' else 6 + self.assertEqual(value.qty, qty) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 2 + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 4) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 2 + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 2) + + def test_partial_transfer_serial_no_components_based_on_material_transfer(self): + ''' + - Set backflush based on Material Transferred for Subcontract + - Create subcontracted PO for the item Subcontracted Item SA2. + - Transfer the partial components from Stores to Supplier warehouse with serial nos. + - Create partial purchase receipt against the PO and change the qty manually. + - Transfer the remaining components from Stores to Supplier warehouse with serial nos. + - Create purchase receipt for remaining qty against the PO and change the qty manually. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + item_code = 'Subcontracted Item SA2' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 5 + pr1.save() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, 3) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3])) + + pr1.load_from_db() + pr1.supplied_items[0].consumed_qty = 5 + pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no']) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + + def test_partial_transfer_batch_based_on_material_transfer(self): + ''' + - Set backflush based on Material Transferred for Subcontract + - Create subcontracted PO for the item Subcontracted Item SA6. + - Transfer the partial components from Stores to Supplier warehouse with batch. + - Create partial purchase receipt against the PO and change the qty manually. + - Transfer the remaining components from Stores to Supplier warehouse with batch. + - Create purchase receipt for remaining qty against the PO and change the qty manually. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + item_code = 'Subcontracted Item SA6' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 5 + pr1.save() + + transferred_batch_no = '' + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, 3) + transferred_batch_no = details.batch_no + self.assertEqual(value.batch_no, details.batch_no) + + pr1.load_from_db() + pr1.supplied_items[0].consumed_qty = 5 + pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0] + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(value.batch_no, details.batch_no) + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(value.batch_no, details.batch_no) + +def add_second_row_in_pr(pr): + item_dict = {} + for column in ['item_code', 'item_name', 'qty', 'uom', 'warehouse', 'stock_uom', + 'purchase_order', 'purchase_order_item', 'conversion_factor', 'rate']: + item_dict[column] = pr.items[0].get(column) + + pr.append('items', item_dict) + pr.set_missing_values() + +def get_supplied_items(pr_doc): + supplied_items = {} + for row in pr_doc.get('supplied_items'): + if row.rm_item_code not in supplied_items: + supplied_items.setdefault(row.rm_item_code, + frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)})) + + details = supplied_items[row.rm_item_code] + update_item_details(row, details) + + return supplied_items + +def make_stock_in_entry(**args): + args = frappe._dict(args) + + items = {} + for row in args.rm_items: + row = frappe._dict(row) + + doc = make_stock_entry(target=row.warehouse or '_Test Warehouse - _TC', + item_code=row.item_code, qty=row.qty or 1, basic_rate=row.rate or 100) + + if row.item_code not in items: + items.setdefault(row.item_code, frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)})) + + child_row = doc.items[0] + details = items[child_row.item_code] + update_item_details(child_row, details) + + return items + +def update_item_details(child_row, details): + details.qty += (child_row.get('qty') if child_row.doctype == 'Stock Entry Detail' + else child_row.get('consumed_qty')) + + if child_row.serial_no: + details.serial_no.extend(get_serial_nos(child_row.serial_no)) + + if child_row.batch_no: + details.batch_no[child_row.batch_no] += (child_row.get('qty') or child_row.get('consumed_qty')) + +def make_stock_transfer_entry(**args): + args = frappe._dict(args) + + items = [] + for row in args.rm_items: + row = frappe._dict(row) + + item = {'item_code': row.main_item_code or args.main_item_code, 'rm_item_code': row.item_code, + 'qty': row.qty or 1, 'item_name': row.item_code, 'rate': row.rate or 100, + 'stock_uom': row.stock_uom or 'Nos', 'warehouse': row.warehuose or '_Test Warehouse - _TC'} + + item_details = args.itemwise_details.get(row.item_code) + + if item_details and item_details.serial_no: + serial_nos = item_details.serial_no[0:cint(row.qty)] + item['serial_no'] = '\n'.join(serial_nos) + item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos)) + + if item_details and item_details.batch_no: + for batch_no, batch_qty in item_details.batch_no.items(): + if batch_qty >= row.qty: + item['batch_no'] = batch_no + item_details.batch_no[batch_no] -= row.qty + break + + items.append(item) + + ste_dict = make_rm_stock_entry(args.po_no, items) + doc = frappe.get_doc(ste_dict) + doc.insert() + doc.submit() + + return doc + +def make_subcontract_items(): + sub_contracted_items = {'Subcontracted Item SA1': {}, 'Subcontracted Item SA2': {}, 'Subcontracted Item SA3': {}, + 'Subcontracted Item SA4': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'SBAT.####'}, + 'Subcontracted Item SA5': {}, 'Subcontracted Item SA6': {}} + + for item, properties in sub_contracted_items.items(): + if not frappe.db.exists('Item', item): + properties.update({'is_stock_item': 1, 'is_sub_contracted_item': 1}) + make_item(item, properties) + +def make_raw_materials(): + raw_materials = {'Subcontracted SRM Item 1': {}, + 'Subcontracted SRM Item 2': {'has_serial_no': 1, 'serial_no_series': 'SRI.####'}, + 'Subcontracted SRM Item 3': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'BAT.####'}, + 'Subcontracted SRM Item 4': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'}, + 'Subcontracted SRM Item 5': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'}} + + for item, properties in raw_materials.items(): + if not frappe.db.exists('Item', item): + properties.update({'is_stock_item': 1}) + make_item(item, properties) + +def make_bom_for_subcontracted_items(): + boms = { + 'Subcontracted Item SA1': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'], + 'Subcontracted Item SA2': ['Subcontracted SRM Item 2'], + 'Subcontracted Item SA3': ['Subcontracted SRM Item 2'], + 'Subcontracted Item SA4': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'], + 'Subcontracted Item SA5': ['Subcontracted SRM Item 5'], + 'Subcontracted Item SA6': ['Subcontracted SRM Item 3'] + } + + for item_code, raw_materials in boms.items(): + if not frappe.db.exists('BOM', {'item': item_code}): + make_bom(item=item_code, raw_materials=raw_materials, rate=100) + +def set_backflush_based_on(based_on): + frappe.db.set_value('Buying Settings', None, + 'backflush_raw_materials_of_subcontract_based_on', based_on) \ No newline at end of file From 9a2db0b5b196597802ddd79119108a1b4615c3b0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 1 Jun 2021 11:24:01 +0530 Subject: [PATCH 1051/1264] fix: semgrep error --- .../subcontract_order_summary.py | 28 ++++++++----------- erpnext/controllers/subcontracting.py | 6 ++-- .../stock/doctype/stock_entry/stock_entry.py | 3 +- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py index 8b08d2a284..0b14e119ab 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -104,26 +104,26 @@ def get_columns(): "fieldname": "item_code", "fieldtype": "Link", "options": "Item", - "width": 140 + "width": 160 }, { - "label": _("Qty"), + "label": _("Order Qty"), "fieldname": "qty", "fieldtype": "Float", - "width": 70 + "width": 90 }, { - "label": _("Received"), + "label": _("Received Qty"), "fieldname": "received_qty", "fieldtype": "Float", - "width": 80 + "width": 110 }, { "label": _("Supplied Item"), "fieldname": "rm_item_code", "fieldtype": "Link", "options": "Item", - "width": 140 + "width": 160 }, { "label": _("Required Qty"), @@ -138,21 +138,15 @@ def get_columns(): "width": 110 }, { - "label": _("Returned Qty"), - "fieldname": "returned_qty", - "fieldtype": "Float", - "width": 110 - }, - { - "label": _("Total Supplied"), - "fieldname": "total_supplied_qty", + "label": _("Consumed Qty"), + "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120 }, { - "label": _("Consumed Qty"), - "fieldname": "consumed_qty", + "label": _("Returned Qty"), + "fieldname": "returned_qty", "fieldtype": "Float", "width": 110 - }, + } ] \ No newline at end of file diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index fe775766da..a9a38bd02d 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -101,9 +101,9 @@ class Subcontracting(object): self.set_alternative_item_details(row) for doctype in ['Purchase Receipt', 'Purchase Invoice']: - self.remove_consumed_materials(doctype) + self.update_consumed_materials(doctype) - def remove_consumed_materials(self, doctype, return_consumed_items=False): + def update_consumed_materials(self, doctype, return_consumed_items=False): '''Deduct the consumed materials from the available materials.''' pr_items = self.get_received_items(doctype) @@ -311,7 +311,7 @@ class Subcontracting(object): return self.get_purchase_orders() - consumed_items, pr_items = self.remove_consumed_materials(self.doctype, return_consumed_items=True) + consumed_items, pr_items = self.update_consumed_materials(self.doctype, return_consumed_items=True) itemwise_consumed_qty = defaultdict(float) for row in consumed_items: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 213280870a..0009926f5d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1291,7 +1291,8 @@ class StockEntry(StockController): item_dict[item]["qty"] = 0 # delete items with 0 qty - for item in item_dict.keys(): + list_of_items = item_dict.keys() + for item in list_of_items: if not item_dict[item]["qty"]: del item_dict[item] From 2fb5291785acf9b1e33f2cf8ebe6a4fa2c9ab887 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 7 Jun 2021 21:20:33 +0530 Subject: [PATCH 1052/1264] fix: toggle consumed qty field based on condition --- .../doctype/purchase_receipt/purchase_receipt.js | 11 +++++++++-- .../doctype/purchase_receipt/purchase_receipt.py | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 887b15a211..cac6bf884b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -41,8 +41,6 @@ frappe.ui.form.on("Purchase Receipt", { } }); - frm.set_df_property('supplied_items', 'cannot_add_rows', 1); - }, onload: function(frm) { erpnext.queries.setup_queries(frm, "Warehouse", function() { @@ -77,6 +75,15 @@ frappe.ui.form.on("Purchase Receipt", { } frm.events.add_custom_buttons(frm); + frm.trigger('toggle_subcontracting_fields'); + }, + + toggle_subcontracting_fields: function(frm) { + frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', + 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM'); + + frm.set_df_property('supplied_items', 'cannot_add_rows', 1); + frm.set_df_property('supplied_items', 'cannot_delete_rows', 1); }, add_custom_buttons: function(frm) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index b8580f95a3..264561f376 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -102,6 +102,11 @@ class PurchaseReceipt(BuyingController): if self.get("items") and self.apply_putaway_rule and not self.get("is_return"): apply_putaway_rule(self.doctype, self.get("items"), self.company) + def onload(self): + super(PurchaseReceipt, self).onload() + self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings', + 'backflush_raw_materials_of_subcontract_based_on')) + def validate(self): self.validate_posting_time() super(PurchaseReceipt, self).validate() From ddb0ec261af7470ac2b07f103b24d9386b7b111e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 8 Jun 2021 10:36:39 +0530 Subject: [PATCH 1053/1264] fix: code cleanup and convert public method to private for subcontracting class --- .../doctype/purchase_order/purchase_order.js | 5 +- .../doctype/purchase_order/purchase_order.py | 13 ++- .../purchase_order_item_supplied.json | 6 +- .../subcontract_order_summary.py | 2 +- erpnext/controllers/subcontracting.py | 106 +++++++++--------- erpnext/public/js/controllers/transaction.js | 4 + .../stock/doctype/stock_entry/stock_entry.py | 10 +- 7 files changed, 76 insertions(+), 70 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 440cde6d9e..233a9c87e5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -74,6 +74,7 @@ frappe.ui.form.on("Purchase Order", { frm.add_custom_button(__('Return of Components'), () => { frm.call({ method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier', + freeze: true, freeze_message: __('Creating Stock Entry'), args: { purchase_order: frm.doc.name, po_details: po_details }, callback: function(r) { @@ -545,14 +546,14 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( ], primary_action: function() { var data = d.get_values(); - var content_msg = 'Reason for hold: ' + data.reason_for_hold; + let reason_for_hold = 'Reason for hold: ' + data.reason_for_hold; frappe.call({ method: "frappe.desk.form.utils.add_comment", args: { reference_doctype: me.frm.doctype, reference_name: me.frm.docname, - content: __(content_msg), + content: __(reason_for_hold), comment_email: frappe.session.user, comment_by: frappe.session.user_fullname }, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 724f863e0f..eaa502ff7f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -14,12 +14,11 @@ from frappe.desk.notifications import clear_doctype_notifications from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status from erpnext.stock.utils import get_bin from erpnext.accounts.party import get_party_account_currency -from six import string_types from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details -from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ - unlink_inter_company_doc +from erpnext.accounts.doctype.sales_invoice.sales_invoice import (validate_inter_company_party, + update_linked_doc, unlink_inter_company_doc) form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -504,7 +503,8 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions @frappe.whitelist() def make_rm_stock_entry(purchase_order, rm_items): rm_items_list = rm_items - if isinstance(rm_items, string_types): + + if isinstance(rm_items, str): rm_items_list = json.loads(rm_items) elif not rm_items: frappe.throw(_("No Items available for transfer")) @@ -588,7 +588,7 @@ def make_inter_company_sales_order(source_name, target_doc=None): @frappe.whitelist() def get_materials_from_supplier(purchase_order, po_details): - if isinstance(po_details, string_types): + if isinstance(po_details, str): po_details = json.loads(po_details) doc = frappe.get_cached_doc('Purchase Order', purchase_order) @@ -615,7 +615,8 @@ def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_deta if value.batch_no: for batch_no, qty in value.batch_no.items(): - add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no) + if qty > 0: + add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no) else: add_items_in_ste(ste_doc, value, value.qty, po_details) diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json index 505ecd84c5..60247bd90b 100644 --- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json +++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json @@ -22,9 +22,9 @@ "required_qty", "supplied_qty", "col_break1", + "consumed_qty", "returned_qty", - "total_supplied_qty", - "consumed_qty" + "total_supplied_qty" ], "fields": [ { @@ -183,7 +183,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-01 00:41:54.123436", + "modified": "2021-06-09 15:17:58.128242", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item Supplied", diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py index 0b14e119ab..0c0d4f0531 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -87,7 +87,7 @@ def get_subcontracted_data(po_details, data): def get_columns(): return [ { - "label": _("Id"), + "label": _("Purchase Order"), "fieldname": "po_id", "fieldtype": "Link", "options": "Purchase Order", diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index a9a38bd02d..e81c0f5732 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -1,39 +1,37 @@ -from __future__ import unicode_literals - import frappe from frappe import _ from frappe.utils import flt, cint from collections import defaultdict from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos -class Subcontracting(object): +class Subcontracting(): def set_materials_for_subcontracted_items(self, raw_material_table): if self.doctype == 'Purchase Invoice' and not self.update_stock: return self.raw_material_table = raw_material_table - self.identify_change_in_item_table() - self.prepare_supplied_items() - self.validate_consumed_qty() + self.__identify_change_in_item_table() + self.__prepare_supplied_items() + self.__validate_consumed_qty() - def prepare_supplied_items(self): + def __prepare_supplied_items(self): self.initialized_fields() - self.get_purchase_orders() - self.get_pending_qty_to_receive() + self.__get_purchase_orders() + self.__get_pending_qty_to_receive() self.get_available_materials() - self.remove_changed_rows() - self.set_supplied_items() + self.__remove_changed_rows() + self.__set_supplied_items() def initialized_fields(self): self.available_materials = frappe._dict() self.alternative_item_details = frappe._dict() - self.get_backflush_based_on() + self.__get_backflush_based_on() - def get_backflush_based_on(self): + def __get_backflush_based_on(self): self.backflush_based_on = frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") - def get_purchase_orders(self): + def __get_purchase_orders(self): self.purchase_orders = [] if self.doctype == 'Purchase Order': @@ -41,14 +39,14 @@ class Subcontracting(object): self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order] - def identify_change_in_item_table(self): + def __identify_change_in_item_table(self): self.changed_name = [] if self.doctype == 'Purchase Order' or not self.get(self.raw_material_table): self.set(self.raw_material_table, []) return - item_dict = self.get_data_before_save() + item_dict = self.__get_data_before_save() if not item_dict: return True @@ -61,7 +59,7 @@ class Subcontracting(object): self.changed_name.extend(item_dict.keys()) - def get_data_before_save(self): + def __get_data_before_save(self): item_dict = {} if self.doctype == 'Purchase Receipt' and self._doc_before_save: for row in self._doc_before_save.get('items'): @@ -80,7 +78,7 @@ class Subcontracting(object): if not self.purchase_orders: return - for row in self.get_transferred_items(): + for row in self.__get_transferred_items(): key = (row.rm_item_code, row.main_item_code, row.purchase_order) if key not in self.available_materials: @@ -98,20 +96,20 @@ class Subcontracting(object): if row.batch_no: details.batch_no[row.batch_no] += row.qty - self.set_alternative_item_details(row) + self.__set_alternative_item_details(row) for doctype in ['Purchase Receipt', 'Purchase Invoice']: - self.update_consumed_materials(doctype) + self.__update_consumed_materials(doctype) - def update_consumed_materials(self, doctype, return_consumed_items=False): + def __update_consumed_materials(self, doctype, return_consumed_items=False): '''Deduct the consumed materials from the available materials.''' - pr_items = self.get_received_items(doctype) + pr_items = self.__get_received_items(doctype) if not pr_items: return ([], {}) if return_consumed_items else None pr_items = {d.name: d.get(self.get('po_field') or 'purchase_order') for d in pr_items} - consumed_materials = self.get_consumed_items(doctype, pr_items.keys()) + consumed_materials = self.__get_consumed_items(doctype, pr_items.keys()) if return_consumed_items: return (consumed_materials, pr_items) @@ -130,7 +128,7 @@ class Subcontracting(object): if row.batch_no: self.available_materials[key]['batch_no'][row.batch_no] -= row.consumed_qty - def get_transferred_items(self): + def __get_transferred_items(self): fields = ['`tabStock Entry`.`purchase_order`'] alias_dict = {'item_code': 'rm_item_code', 'subcontracted_item': 'main_item_code', 'basic_rate': 'rate'} @@ -149,7 +147,7 @@ class Subcontracting(object): return frappe.get_all('Stock Entry', fields = fields, filters=filters) - def get_received_items(self, doctype): + def __get_received_items(self, doctype): fields = [] self.po_field = 'purchase_order' if doctype == 'Purchase Receipt' else 'po_detail' @@ -162,16 +160,16 @@ class Subcontracting(object): return frappe.get_all(f'{doctype}', fields = fields, filters = filters) - def get_consumed_items(self, doctype, pr_items): + def __get_consumed_items(self, doctype, pr_items): return frappe.get_all(f'{doctype} Item Supplied', fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'], filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items))}) - def set_alternative_item_details(self, row): + def __set_alternative_item_details(self, row): if row.get('original_item'): self.alternative_item_details[row.get('original_item')] = row - def get_pending_qty_to_receive(self): + def __get_pending_qty_to_receive(self): '''Get qty to be received against the purchase order.''' self.qty_to_be_received = defaultdict(float) @@ -183,7 +181,7 @@ class Subcontracting(object): self.qty_to_be_received[(row.item_code, row.parent)] += row.qty - def get_materials_from_bom(self, item_code, bom_no, exploded_item=0): + def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0): doctype = 'BOM Item' if not exploded_item else 'BOM Explosion Item' fields = [f'`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit'] @@ -197,7 +195,7 @@ class Subcontracting(object): return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or [] - def remove_changed_rows(self): + def __remove_changed_rows(self): if not self.changed_name: return @@ -212,7 +210,7 @@ class Subcontracting(object): i += 1 - def set_supplied_items(self): + def __set_supplied_items(self): self.bom_items = {} has_supplied_items = True if self.get(self.raw_material_table) else False @@ -222,28 +220,28 @@ class Subcontracting(object): continue if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM': - for bom_item in self.get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')): + for bom_item in self.__get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')): qty = (flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor) bom_item.main_item_code = row.item_code - self.update_reserve_warehouse(bom_item, row) - self.set_alternative_item(bom_item) - self.add_supplied_item(row, bom_item, qty) + self.__update_reserve_warehouse(bom_item, row) + self.__set_alternative_item(bom_item) + self.__add_supplied_item(row, bom_item, qty) elif self.backflush_based_on != 'BOM': for key, transfer_item in self.available_materials.items(): if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0: - qty = self.get_qty_based_on_material_transfer(row, transfer_item) or 0 + qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0 transfer_item.qty -= qty - self.add_supplied_item(row, transfer_item.get('item_details'), qty) + self.__add_supplied_item(row, transfer_item.get('item_details'), qty) if self.qty_to_be_received: self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty - def update_reserve_warehouse(self, row, item): + def __update_reserve_warehouse(self, row, item): if self.doctype == 'Purchase Order': row.reserve_warehouse = (self.set_reserve_warehouse or item.warehouse) - def get_qty_based_on_material_transfer(self, item_row, transfer_item): + def __get_qty_based_on_material_transfer(self, item_row, transfer_item): key = (item_row.item_code, item_row.purchase_order) if self.qty_to_be_received == item_row.qty: @@ -257,11 +255,11 @@ class Subcontracting(object): return qty - def set_alternative_item(self, bom_item): + def __set_alternative_item(self, bom_item): if self.alternative_item_details.get(bom_item.rm_item_code): bom_item.update(self.alternative_item_details[bom_item.rm_item_code]) - def add_supplied_item(self, item_row, bom_item, qty): + def __add_supplied_item(self, item_row, bom_item, qty): bom_item.conversion_factor = item_row.conversion_factor rm_obj = self.append(self.raw_material_table, bom_item) rm_obj.reference_name = item_row.name @@ -269,15 +267,15 @@ class Subcontracting(object): if self.doctype == 'Purchase Order': rm_obj.required_qty = qty else: - self.set_batch_nos(bom_item, item_row, rm_obj, qty) + self.__set_batch_nos(bom_item, item_row, rm_obj, qty) - def set_batch_nos(self, bom_item, item_row, rm_obj, qty): + def __set_batch_nos(self, bom_item, item_row, rm_obj, qty): key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order) if (self.available_materials.get(key) and self.available_materials[key]['batch_no']): for batch_no, batch_qty in self.available_materials[key]['batch_no'].items(): if batch_qty >= qty: - self.set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty) + self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty) self.available_materials[key]['batch_no'][batch_no] -= qty return @@ -285,18 +283,18 @@ class Subcontracting(object): qty -= batch_qty new_rm_obj = self.append(self.raw_material_table, bom_item) new_rm_obj.reference_name = item_row.name - self.set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty) + self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty) self.available_materials[key]['batch_no'][batch_no] = 0 else: rm_obj.required_qty = qty rm_obj.consumed_qty = qty - self.set_serial_nos(item_row, rm_obj) + self.__set_serial_nos(item_row, rm_obj) - def set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty): + def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty): rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no, 'required_qty': qty}) - self.set_serial_nos(item_row, rm_obj) + self.__set_serial_nos(item_row, rm_obj) - def set_serial_nos(self, item_row, rm_obj): + def __set_serial_nos(self, item_row, rm_obj): key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order) if (self.available_materials.get(key) and self.available_materials[key]['serial_no']): used_serial_nos = self.available_materials[key]['serial_no'][0: cint(rm_obj.consumed_qty)] @@ -310,17 +308,17 @@ class Subcontracting(object): if self.is_subcontracted != 'Yes': return - self.get_purchase_orders() - consumed_items, pr_items = self.update_consumed_materials(self.doctype, return_consumed_items=True) + self.__get_purchase_orders() + consumed_items, pr_items = self.__update_consumed_materials(self.doctype, return_consumed_items=True) itemwise_consumed_qty = defaultdict(float) for row in consumed_items: key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name)) itemwise_consumed_qty[key] += row.consumed_qty - self.update_consumed_qty_in_po(itemwise_consumed_qty) + self.__update_consumed_qty_in_po(itemwise_consumed_qty) - def update_consumed_qty_in_po(self, itemwise_consumed_qty): + def __update_consumed_qty_in_po(self, itemwise_consumed_qty): fields = ['main_item_code', 'rm_item_code', 'parent', 'supplied_qty', 'name'] filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)} @@ -334,7 +332,7 @@ class Subcontracting(object): itemwise_consumed_qty[key] -= consumed_qty frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty) - def validate_consumed_qty(self): + def __validate_consumed_qty(self): for row in self.get(self.raw_material_table): if flt(row.consumed_qty) == 0.0 and row.get('serial_no'): msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}' diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 89fed3bf0d..978c8f4879 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -723,6 +723,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var me = this; var item = frappe.get_doc(cdt, cdn); + if (item && item.doctype === 'Purchase Receipt Item Supplied') { + return; + } + if (item && item.serial_no) { if (!item.item_code) { this.frm.trigger("item_code", cdt, cdn); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0009926f5d..66f8b63cb9 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1005,10 +1005,12 @@ class StockEntry(StockController): if self.purchase_order and self.purpose == "Send to Subcontractor": #Get PO Supplied Items Details item_wh = frappe._dict(frappe.db.sql(""" - select rm_item_code, reserve_warehouse - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup - where po.name = poitemsup.parent - and po.name = %s""",self.purchase_order)) + SELECT + rm_item_code, reserve_warehouse + FROM + `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup + WHERE + po.name = poitemsup.parent and po.name = %s """,self.purchase_order)) for item in itervalues(item_dict): if self.pro_doc and cint(self.pro_doc.from_wip_warehouse): From 5cc3f14506bb75fd525eb774cc5d70a5f4204947 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jun 2021 17:29:52 +0530 Subject: [PATCH 1054/1264] fix: purchase invoice qty change not recalculate the consumed qty and added test cases for purchase invoice --- .../purchase_invoice/purchase_invoice.json | 617 +++++------------- .../purchase_invoice/purchase_invoice.py | 2 + erpnext/controllers/buying_controller.py | 5 + erpnext/controllers/subcontracting.py | 41 +- erpnext/public/js/controllers/buying.js | 11 + .../purchase_receipt/purchase_receipt.js | 9 - .../purchase_receipt/purchase_receipt.py | 5 - erpnext/tests/test_subcontracting.py | 267 +++++++- 8 files changed, 461 insertions(+), 496 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index a714ac7827..00ef7d5c18 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -175,9 +175,7 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "naming_series", @@ -189,9 +187,7 @@ "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "supplier", @@ -203,9 +199,7 @@ "options": "Supplier", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "bold": 1, @@ -217,9 +211,7 @@ "label": "Supplier Name", "oldfieldname": "supplier_name", "oldfieldtype": "Data", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fetch_from": "supplier.tax_id", @@ -227,27 +219,21 @@ "fieldtype": "Read Only", "label": "Tax Id", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "due_date", "fieldtype": "Date", "label": "Due Date", "oldfieldname": "due_date", - "oldfieldtype": "Date", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Date" }, { "default": "0", "fieldname": "is_paid", "fieldtype": "Check", "label": "Is Paid", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", @@ -255,25 +241,19 @@ "fieldtype": "Check", "label": "Is Return (Debit Note)", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply Tax Withholding Amount", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -283,17 +263,13 @@ "label": "Company", "options": "Company", "print_hide": 1, - "remember_last_selected_value": 1, - "show_days": 1, - "show_seconds": 1 + "remember_last_selected_value": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "default": "Today", @@ -305,9 +281,7 @@ "oldfieldtype": "Date", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "posting_time", @@ -316,8 +290,6 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", - "show_days": 1, - "show_seconds": 1, "width": "100px" }, { @@ -326,9 +298,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": "amended_from", @@ -340,58 +310,44 @@ "oldfieldtype": "Link", "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.on_hold", "fieldname": "sb_14", "fieldtype": "Section Break", - "label": "Hold Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Hold Invoice" }, { "default": "0", "fieldname": "on_hold", "fieldtype": "Check", - "label": "Hold Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Hold Invoice" }, { "depends_on": "eval:doc.on_hold", "description": "Once set, this invoice will be on hold till the set date", "fieldname": "release_date", "fieldtype": "Date", - "label": "Release Date", - "show_days": 1, - "show_seconds": 1 + "label": "Release Date" }, { "fieldname": "cb_17", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:doc.on_hold", "fieldname": "hold_comment", "fieldtype": "Small Text", - "label": "Reason For Putting On Hold", - "show_days": 1, - "show_seconds": 1 + "label": "Reason For Putting On Hold" }, { "collapsible": 1, "collapsible_depends_on": "bill_no", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", - "label": "Supplier Invoice Details", - "show_days": 1, - "show_seconds": 1 + "label": "Supplier Invoice Details" }, { "fieldname": "bill_no", @@ -399,15 +355,11 @@ "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_15", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "bill_date", @@ -416,17 +368,13 @@ "no_copy": 1, "oldfieldname": "bill_date", "oldfieldtype": "Date", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "return_against", "fieldname": "returns", "fieldtype": "Section Break", - "label": "Returns", - "show_days": 1, - "show_seconds": 1 + "label": "Returns" }, { "depends_on": "return_against", @@ -436,34 +384,26 @@ "no_copy": 1, "options": "Purchase Invoice", "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": "address_display", "fieldtype": "Small Text", "label": "Address", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_person", @@ -471,67 +411,51 @@ "in_global_search": 1, "label": "Contact Person", "options": "Contact", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "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", "fieldtype": "Small Text", "label": "Contact 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", @@ -540,9 +464,7 @@ "oldfieldname": "currency", "oldfieldtype": "Select", "options": "Currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "conversion_rate", @@ -551,24 +473,18 @@ "oldfieldname": "conversion_rate", "oldfieldtype": "Currency", "precision": "9", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "buying_price_list", "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "price_list_currency", @@ -576,18 +492,14 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "plc_conversion_rate", "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", @@ -596,15 +508,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" }, { "depends_on": "update_stock", @@ -613,9 +521,7 @@ "fieldtype": "Link", "label": "Set Accepted Warehouse", "options": "Warehouse", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "update_stock", @@ -625,15 +531,11 @@ "label": "Rejected Warehouse", "no_copy": 1, "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", @@ -641,33 +543,25 @@ "fieldtype": "Select", "label": "Raw Materials Supplied", "options": "No\nYes", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "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" }, { "default": "0", "fieldname": "update_stock", "fieldtype": "Check", "label": "Update Stock", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode", - "show_days": 1, - "show_seconds": 1 + "label": "Scan Barcode" }, { "allow_bulk_edit": 1, @@ -677,56 +571,43 @@ "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Purchase Invoice Item", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 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 }, { "collapsible_depends_on": "supplied_items", "fieldname": "raw_materials_supplied", "fieldtype": "Section Break", - "label": "Raw Materials Supplied", - "show_days": 1, - "show_seconds": 1 + "label": "Raw Materials Supplied" }, { + "depends_on": "update_stock", "fieldname": "supplied_items", "fieldtype": "Table", "label": "Supplied Items", - "options": "Purchase Receipt Item Supplied", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "no_copy": 1, + "options": "Purchase Receipt Item Supplied" }, { "fieldname": "section_break_26", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "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", @@ -734,9 +615,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", @@ -746,24 +625,18 @@ "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_28", - "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", @@ -773,56 +646,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 }, { "fieldname": "taxes_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": "column_break_49", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", "options": "Shipping Rule", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_51", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "taxes_and_charges", @@ -831,9 +690,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", @@ -841,17 +698,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", @@ -860,17 +713,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", @@ -880,9 +729,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", @@ -892,9 +739,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", @@ -904,15 +749,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_40", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "taxes_and_charges_added", @@ -922,9 +763,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", @@ -934,9 +773,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", @@ -944,18 +781,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_44", "fieldtype": "Section Break", - "label": "Additional Discount", - "show_days": 1, - "show_seconds": 1 + "label": "Additional Discount" }, { "default": "Grand Total", @@ -963,9 +796,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", @@ -973,38 +804,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_46", - "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_49", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "base_grand_total", @@ -1014,9 +835,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1026,9 +845,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1038,9 +855,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", @@ -1050,17 +865,13 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break8", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_hide": 1, - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -1071,9 +882,7 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1083,9 +892,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", @@ -1095,9 +902,7 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "in_words", @@ -1107,9 +912,7 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_advance", @@ -1120,9 +923,7 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "outstanding_amount", @@ -1133,18 +934,14 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total", - "show_days": 1, - "show_seconds": 1 + "label": "Disable Rounded Total" }, { "collapsible": 1, @@ -1152,26 +949,20 @@ "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", - "label": "Payments", - "show_days": 1, - "show_seconds": 1 + "label": "Payments" }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", "options": "Mode of Payment", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "clearance_date", @@ -1179,15 +970,11 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "col_br_payments", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "is_paid", @@ -1196,9 +983,7 @@ "label": "Paid Amount", "no_copy": 1, "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_paid_amount", @@ -1207,9 +992,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, @@ -1217,9 +1000,7 @@ "depends_on": "grand_total", "fieldname": "write_off", "fieldtype": "Section Break", - "label": "Write Off", - "show_days": 1, - "show_seconds": 1 + "label": "Write Off" }, { "fieldname": "write_off_amount", @@ -1227,9 +1008,7 @@ "label": "Write Off Amount", "no_copy": 1, "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_write_off_amount", @@ -1238,15 +1017,11 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_61", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1254,9 +1029,7 @@ "fieldtype": "Link", "label": "Write Off Account", "options": "Account", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1264,9 +1037,7 @@ "fieldtype": "Link", "label": "Write Off Cost Center", "options": "Cost Center", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1276,17 +1047,13 @@ "label": "Advance Payments", "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", "fieldname": "allocate_advances_automatically", "fieldtype": "Check", - "label": "Set Advances and Allocate (FIFO)", - "show_days": 1, - "show_seconds": 1 + "label": "Set Advances and Allocate (FIFO)" }, { "depends_on": "eval:!doc.allocate_advances_automatically", @@ -1294,9 +1061,7 @@ "fieldtype": "Button", "label": "Get Advances Paid", "oldfieldtype": "Button", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "advances", @@ -1306,26 +1071,20 @@ "oldfieldname": "advance_allocation_details", "oldfieldtype": "Table", "options": "Purchase Invoice Advance", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:(!doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", - "label": "Payment Terms", - "show_days": 1, - "show_seconds": 1 + "label": "Payment Terms" }, { "fieldname": "payment_terms_template", "fieldtype": "Link", "label": "Payment Terms Template", - "options": "Payment Terms Template", - "show_days": 1, - "show_seconds": 1 + "options": "Payment Terms Template" }, { "fieldname": "payment_schedule", @@ -1333,9 +1092,7 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1343,33 +1100,25 @@ "fieldname": "terms_section_break", "fieldtype": "Section Break", "label": "Terms and Conditions", - "options": "fa fa-legal", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-legal" }, { "fieldname": "tc_name", "fieldtype": "Link", "label": "Terms", "options": "Terms and Conditions", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", - "label": "Terms and Conditions1", - "show_days": 1, - "show_seconds": 1 + "label": "Terms and Conditions1" }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Printing Settings" }, { "allow_on_submit": 1, @@ -1377,9 +1126,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, @@ -1387,15 +1134,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_112", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "allow_on_submit": 1, @@ -1407,18 +1150,14 @@ "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", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, @@ -1427,9 +1166,7 @@ "label": "More Information", "oldfieldtype": "Section Break", "options": "fa fa-file-text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "credit_to", @@ -1440,9 +1177,7 @@ "options": "Account", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "party_account_currency", @@ -1452,9 +1187,7 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "No", @@ -1464,9 +1197,7 @@ "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "against_expense_account", @@ -1476,15 +1207,11 @@ "no_copy": 1, "oldfieldname": "against_expense_account", "oldfieldtype": "Small Text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_63", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "Draft", @@ -1493,9 +1220,7 @@ "in_standard_filter": 1, "label": "Status", "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "inter_company_invoice_reference", @@ -1504,9 +1229,7 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "remarks", @@ -1515,18 +1238,14 @@ "no_copy": 1, "oldfieldname": "remarks", "oldfieldtype": "Text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1535,9 +1254,7 @@ "fieldtype": "Date", "label": "From Date", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1546,15 +1263,11 @@ "fieldtype": "Date", "label": "To Date", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_114", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "auto_repeat", @@ -1563,32 +1276,24 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 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", - "show_days": 1, - "show_seconds": 1 + "label": "Update Auto Repeat Reference" }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions ", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions " }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0", @@ -1596,9 +1301,7 @@ "fieldname": "is_internal_supplier", "fieldtype": "Check", "label": "Is Internal Supplier", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "tax_withholding_category", @@ -1606,25 +1309,19 @@ "hidden": 1, "label": "Tax Withholding Category", "options": "Tax Withholding Category", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "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 }, { "fieldname": "project", @@ -1638,9 +1335,7 @@ "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1649,9 +1344,7 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" }, { "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", @@ -1663,8 +1356,6 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { @@ -1692,7 +1383,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-06-09 12:30:25.632109", + "modified": "2021-06-15 18:20:56.806195", "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 0ee0bc7e11..45d89ad1c8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -400,6 +400,7 @@ class PurchaseInvoice(BuyingController): # because updating ordered qty in bin depends upon updated ordered qty in PO if self.update_stock == 1: self.update_stock_ledger() + self.set_consumed_qty_in_po() from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit update_serial_nos_after_submit(self, "items") @@ -998,6 +999,7 @@ class PurchaseInvoice(BuyingController): if self.update_stock == 1: self.update_stock_ledger() self.delete_auto_created_batches() + self.set_consumed_qty_in_po() self.make_gl_entries_on_cancel() diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 1907885717..0b0da5f413 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -58,6 +58,11 @@ class BuyingController(StockController, Subcontracting): if self.doctype in ("Purchase Receipt", "Purchase Invoice"): self.update_valuation_rate() + def onload(self): + super(BuyingController, self).onload() + self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings', + 'backflush_raw_materials_of_subcontract_based_on')) + def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index e81c0f5732..db841626a5 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -40,9 +40,10 @@ class Subcontracting(): self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order] def __identify_change_in_item_table(self): - self.changed_name = [] + self.__changed_name = [] + self.__reference_name = [] - if self.doctype == 'Purchase Order' or not self.get(self.raw_material_table): + if self.doctype == 'Purchase Order' or self.is_new(): self.set(self.raw_material_table, []) return @@ -51,17 +52,18 @@ class Subcontracting(): return True for n_row in self.items: + self.__reference_name.append(n_row.name) if (n_row.name not in item_dict) or (n_row.item_code, n_row.qty) != item_dict[n_row.name]: - self.changed_name.append(n_row.name) + self.__changed_name.append(n_row.name) if item_dict.get(n_row.name): del item_dict[n_row.name] - self.changed_name.extend(item_dict.keys()) + self.__changed_name.extend(item_dict.keys()) def __get_data_before_save(self): item_dict = {} - if self.doctype == 'Purchase Receipt' and self._doc_before_save: + if self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self._doc_before_save: for row in self._doc_before_save.get('items'): item_dict[row.name] = (row.item_code, row.qty) @@ -149,7 +151,7 @@ class Subcontracting(): def __get_received_items(self, doctype): fields = [] - self.po_field = 'purchase_order' if doctype == 'Purchase Receipt' else 'po_detail' + self.po_field = 'purchase_order' for field in ['name', self.po_field, 'parent']: fields.append(f'`tab{doctype} Item`.`{field}`') @@ -161,9 +163,9 @@ class Subcontracting(): return frappe.get_all(f'{doctype}', fields = fields, filters = filters) def __get_consumed_items(self, doctype, pr_items): - return frappe.get_all(f'{doctype} Item Supplied', + return frappe.get_all('Purchase Receipt Item Supplied', fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'], - filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items))}) + filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items)), 'parenttype': doctype}) def __set_alternative_item_details(self, row): if row.get('original_item'): @@ -196,13 +198,16 @@ class Subcontracting(): return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or [] def __remove_changed_rows(self): - if not self.changed_name: + if not self.__changed_name: return i=1 self.set(self.raw_material_table, []) for d in self._doc_before_save.supplied_items: - if d.reference_name in self.changed_name: + if d.reference_name in self.__changed_name: + continue + + if (d.reference_name not in self.__reference_name): continue d.idx = i @@ -215,8 +220,8 @@ class Subcontracting(): has_supplied_items = True if self.get(self.raw_material_table) else False for row in self.items: - if (self.doctype != 'Purchase Order' and ((self.changed_name and row.name not in self.changed_name) - or (has_supplied_items and not self.changed_name))): + if (self.doctype != 'Purchase Order' and ((self.__changed_name and row.name not in self.__changed_name) + or (has_supplied_items and not self.__changed_name))): continue if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM': @@ -305,16 +310,18 @@ class Subcontracting(): self.available_materials[key]['serial_no'].remove(sn) def set_consumed_qty_in_po(self): + # Update consumed qty back in the purchase order if self.is_subcontracted != 'Yes': return self.__get_purchase_orders() - consumed_items, pr_items = self.__update_consumed_materials(self.doctype, return_consumed_items=True) - itemwise_consumed_qty = defaultdict(float) - for row in consumed_items: - key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name)) - itemwise_consumed_qty[key] += row.consumed_qty + for doctype in ['Purchase Receipt', 'Purchase Invoice']: + consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True) + + for row in consumed_items: + key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name)) + itemwise_consumed_qty[key] += row.consumed_qty self.__update_consumed_qty_in_po(itemwise_consumed_qty) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index e7dcd41068..5c9f5d7da4 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -122,9 +122,20 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ this.set_from_product_bundle(); } + this.toggle_subcontracting_fields(); this._super(); }, + toggle_subcontracting_fields: function() { + if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) { + this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', + 'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM'); + + this.frm.set_df_property('supplied_items', 'cannot_add_rows', 1); + this.frm.set_df_property('supplied_items', 'cannot_delete_rows', 1); + } + }, + supplier: function() { var me = this; erpnext.utils.get_party_details(this.frm, null, null, function(){ diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index cac6bf884b..befdad9692 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -75,15 +75,6 @@ frappe.ui.form.on("Purchase Receipt", { } frm.events.add_custom_buttons(frm); - frm.trigger('toggle_subcontracting_fields'); - }, - - toggle_subcontracting_fields: function(frm) { - frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', - 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM'); - - frm.set_df_property('supplied_items', 'cannot_add_rows', 1); - frm.set_df_property('supplied_items', 'cannot_delete_rows', 1); }, add_custom_buttons: function(frm) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 264561f376..b8580f95a3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -102,11 +102,6 @@ class PurchaseReceipt(BuyingController): if self.get("items") and self.apply_putaway_rule and not self.get("is_return"): apply_putaway_rule(self.doctype, self.get("items"), self.company) - def onload(self): - super(PurchaseReceipt, self).onload() - self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings', - 'backflush_raw_materials_of_subcontract_based_on')) - def validate(self): self.validate_posting_time() super(PurchaseReceipt, self).validate() diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py index c1a458a6dd..d2438f8c60 100644 --- a/erpnext/tests/test_subcontracting.py +++ b/erpnext/tests/test_subcontracting.py @@ -10,7 +10,7 @@ from erpnext.stock.doctype.item.test_item import make_item from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.purchase_order import (make_rm_stock_entry, - make_purchase_receipt, get_materials_from_supplier) + make_purchase_receipt, make_purchase_invoice, get_materials_from_supplier) class TestSubcontracting(unittest.TestCase): def setUp(self): @@ -458,10 +458,273 @@ class TestSubcontracting(unittest.TestCase): self.assertEqual(value.qty, details.qty) self.assertEqual(value.batch_no, details.batch_no) + + def test_item_with_batch_based_on_material_transfer_for_purchase_invoice(self): + ''' + - Set backflush based on Material Transferred for Subcontract + - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no). + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer the components in multiple batches with extra 2 qty for the batched item. + - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches. + - Keep the qty as 2 for Subcontracted Item in the purchase receipt. + - In the first purchase receipt the batched raw materials will be consumed 2 extra qty. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + item_code = 'Subcontracted Item SA4' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3} + ] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].qty = 2 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + qty = 4 if key != 'Subcontracted SRM Item 3' else 6 + self.assertEqual(value.qty, qty) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + pr1.items[0].qty = 2 + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 4) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].qty = 2 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 2) + + def test_partial_transfer_serial_no_components_based_on_material_transfer_for_purchase_invoice(self): + ''' + - Set backflush based on Material Transferred for Subcontract + - Create subcontracted PO for the item Subcontracted Item SA2. + - Transfer the partial components from Stores to Supplier warehouse with serial nos. + - Create partial purchase receipt against the PO and change the qty manually. + - Transfer the remaining components from Stores to Supplier warehouse with serial nos. + - Create purchase receipt for remaining qty against the PO and change the qty manually. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + item_code = 'Subcontracted Item SA2' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].qty = 5 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + pr1.save() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, 3) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3])) + + pr1.load_from_db() + pr1.supplied_items[0].consumed_qty = 5 + pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no']) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + + def test_partial_transfer_batch_based_on_material_transfer_for_purchase_invoice(self): + ''' + - Set backflush based on Material Transferred for Subcontract + - Create subcontracted PO for the item Subcontracted Item SA6. + - Transfer the partial components from Stores to Supplier warehouse with batch. + - Create partial purchase receipt against the PO and change the qty manually. + - Transfer the remaining components from Stores to Supplier warehouse with batch. + - Create purchase receipt for remaining qty against the PO and change the qty manually. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + item_code = 'Subcontracted Item SA6' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].qty = 5 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + pr1.save() + + transferred_batch_no = '' + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, 3) + transferred_batch_no = details.batch_no + self.assertEqual(value.batch_no, details.batch_no) + + pr1.load_from_db() + pr1.supplied_items[0].consumed_qty = 5 + pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0] + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(value.batch_no, details.batch_no) + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + details = itemwise_details.get(key) + self.assertEqual(value.qty, details.qty) + self.assertEqual(value.batch_no, details.batch_no) + + def test_item_with_batch_based_on_bom_for_purchase_invoice(self): + ''' + - Set backflush based on BOM + - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no). + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer the components in multiple batches. + - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches. + - Keep the qty as 2 for Subcontracted Item in the purchase receipt. + ''' + + set_backflush_based_on('BOM') + item_code = 'Subcontracted Item SA4' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 2', 'qty': 10}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}, + {'item_code': 'Subcontracted SRM Item 3', 'qty': 1} + ] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].qty = 2 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 4) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].qty = 2 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + add_second_row_in_pr(pr1) + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 4) + + pr1 = make_purchase_invoice(po.name) + pr1.update_stock = 1 + pr1.items[0].qty = 2 + pr1.items[0].expense_account = 'Stock Adjustment - _TC' + pr1.save() + pr1.submit() + + for key, value in get_supplied_items(pr1).items(): + self.assertEqual(value.qty, 2) + def add_second_row_in_pr(pr): item_dict = {} for column in ['item_code', 'item_name', 'qty', 'uom', 'warehouse', 'stock_uom', - 'purchase_order', 'purchase_order_item', 'conversion_factor', 'rate']: + 'purchase_order', 'purchase_order_item', 'conversion_factor', 'rate', 'expense_account', 'po_detail']: item_dict[column] = pr.items[0].get(column) pr.append('items', item_dict) From f5db407461c1ee898a84d83587cf6f3eae3791c1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 18 Jun 2021 20:37:42 +0530 Subject: [PATCH 1055/1264] fix: available qty for consumption --- .../purchase_order/test_purchase_order.py | 3 - .../purchase_receipt_item_supplied.json | 20 ++++-- erpnext/controllers/buying_controller.py | 10 +-- erpnext/controllers/subcontracting.py | 66 ++++++++++++++++--- erpnext/stock/stock_ledger.py | 2 +- erpnext/tests/test_subcontracting.py | 31 +++++++++ 6 files changed, 110 insertions(+), 22 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 33d1971451..8563b97ab7 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -847,9 +847,6 @@ class TestPurchaseOrder(unittest.TestCase): for item in rm_items: transferred_rm_map[item.get('rm_item_code')] = item - for item in pr.get('supplied_items'): - self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty) - update_backflush_based_on("BOM") def test_supplied_qty_against_subcontracted_po(self): diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json index d8c37f5881..f9cd72015a 100644 --- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json +++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json @@ -26,7 +26,8 @@ "secbreak_3", "batch_no", "col_break4", - "serial_no" + "serial_no", + "purchase_order" ], "fields": [ { @@ -81,9 +82,10 @@ "fieldname": "required_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Required Qty", + "label": "Available Qty For Consumption", "oldfieldname": "required_qty", "oldfieldtype": "Currency", + "print_hide": 1, "read_only": 1 }, { @@ -91,7 +93,7 @@ "fieldname": "consumed_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Consumed Qty", + "label": "Qty to Be Consumed", "oldfieldname": "consumed_qty", "oldfieldtype": "Currency", "reqd": 1 @@ -190,12 +192,22 @@ "fieldtype": "Data", "label": "Item Name", "read_only": 1 + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "hidden": 1, + "label": "Purchase Order", + "no_copy": 1, + "options": "Purchase Order", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-29 17:22:14.977117", + "modified": "2021-06-19 19:33:04.431213", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Receipt Item Supplied", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0b0da5f413..6a550e0e97 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -292,11 +292,13 @@ class BuyingController(StockController, Subcontracting): if item in self.sub_contracted_items and not item.bom: frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code)) - if self.doctype == "Purchase Order": - for supplied_item in self.get("supplied_items"): - if not supplied_item.reserve_warehouse: - frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code))) + if self.doctype != "Purchase Order": + return + for row in self.get("supplied_items"): + if not row.reserve_warehouse: + msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied" + frappe.throw(_(msg)) else: for item in self.get("items"): if item.bom: diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index db841626a5..36ae110216 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -1,6 +1,7 @@ import frappe +import copy from frappe import _ -from frappe.utils import flt, cint +from frappe.utils import flt, cint, get_link_to_form from collections import defaultdict from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -12,7 +13,7 @@ class Subcontracting(): self.raw_material_table = raw_material_table self.__identify_change_in_item_table() self.__prepare_supplied_items() - self.__validate_consumed_qty() + self.__validate_supplied_items() def __prepare_supplied_items(self): self.initialized_fields() @@ -24,6 +25,7 @@ class Subcontracting(): def initialized_fields(self): self.available_materials = frappe._dict() + self.__transferred_items = frappe._dict() self.alternative_item_details = frappe._dict() self.__get_backflush_based_on() @@ -100,6 +102,7 @@ class Subcontracting(): self.__set_alternative_item_details(row) + self.__transferred_items = copy.deepcopy(self.available_materials) for doctype in ['Purchase Receipt', 'Purchase Invoice']: self.__update_consumed_materials(doctype) @@ -254,6 +257,8 @@ class Subcontracting(): if self.qty_to_be_received: qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0)) + transfer_item.item_details.required_qty = transfer_item.qty + if (transfer_item.serial_no or frappe.get_cached_value('UOM', transfer_item.item_details.stock_uom, 'must_be_whole_number')): return frappe.utils.ceil(qty) @@ -272,12 +277,15 @@ class Subcontracting(): if self.doctype == 'Purchase Order': rm_obj.required_qty = qty else: + rm_obj.consumed_qty = 0 + rm_obj.purchase_order = item_row.purchase_order self.__set_batch_nos(bom_item, item_row, rm_obj, qty) def __set_batch_nos(self, bom_item, item_row, rm_obj, qty): key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order) if (self.available_materials.get(key) and self.available_materials[key]['batch_no']): + new_rm_obj = None for batch_no, batch_qty in self.available_materials[key]['batch_no'].items(): if batch_qty >= qty: self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty) @@ -290,13 +298,21 @@ class Subcontracting(): new_rm_obj.reference_name = item_row.name self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty) self.available_materials[key]['batch_no'][batch_no] = 0 + + if abs(qty) > 0 and not new_rm_obj: + self.__set_consumed_qty(rm_obj, qty) else: - rm_obj.required_qty = qty - rm_obj.consumed_qty = qty + self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty) self.__set_serial_nos(item_row, rm_obj) + def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0): + rm_obj.required_qty = required_qty + rm_obj.consumed_qty = consumed_qty + def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty): - rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no, 'required_qty': qty}) + rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no, + 'required_qty': qty, 'purchase_order': item_row.purchase_order}) + self.__set_serial_nos(item_row, rm_obj) def __set_serial_nos(self, item_row, rm_obj): @@ -339,9 +355,39 @@ class Subcontracting(): itemwise_consumed_qty[key] -= consumed_qty frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty) - def __validate_consumed_qty(self): - for row in self.get(self.raw_material_table): - if flt(row.consumed_qty) == 0.0 and row.get('serial_no'): - msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}' + def __validate_supplied_items(self): + if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']: + return - frappe.throw(_(msg),title=_('Consumed Items Qty Check')) \ No newline at end of file + for row in self.get(self.raw_material_table): + self.__validate_consumed_qty(row) + + key = (row.rm_item_code, row.main_item_code, row.purchase_order) + if not self.__transferred_items or not self.__transferred_items.get(key): + return + + self.__validate_batch_no(row, key) + self.__validate_serial_no(row, key) + + def __validate_consumed_qty(self, row): + if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0: + msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}' + + frappe.throw(_(msg),title=_('Consumed Items Qty Check')) + + def __validate_batch_no(self, row, key): + if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'): + link = get_link_to_form('Purchase Order', row.purchase_order) + msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}' + frappe.throw(_(msg), title=_("Incorrect Batch Consumed")) + + def __validate_serial_no(self, row, key): + if row.get('serial_no'): + serial_nos = get_serial_nos(row.get('serial_no')) + incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no')) + + if incorrect_sn: + incorrect_sn = "\n".join(incorrect_sn) + link = get_link_to_form('Purchase Order', row.purchase_order) + msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}' + frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed")) \ No newline at end of file diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index fb2ecab249..9fe89c3fa5 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -485,7 +485,7 @@ class update_entries_after(object): # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes': - doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no) + doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) doc.update_valuation_rate(reset_outgoing_rate=False) for d in (doc.items + doc.supplied_items): d.db_update() diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py index d2438f8c60..8b0ce0957d 100644 --- a/erpnext/tests/test_subcontracting.py +++ b/erpnext/tests/test_subcontracting.py @@ -395,6 +395,37 @@ class TestSubcontracting(unittest.TestCase): self.assertEqual(value.qty, details.qty) self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + def test_incorrect_serial_no_components_based_on_material_transfer(self): + ''' + - Set backflush based on Material Transferred for Subcontract + - Create subcontracted PO for the item Subcontracted Item SA2. + - Transfer the serialized componenets to the supplier. + - Create purchase receipt and change the serial no which is not transferred. + - System should throw the error and not allowed to save the purchase receipt. + ''' + + set_backflush_based_on('Material Transferred for Subcontract') + item_code = 'Subcontracted Item SA2' + items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}] + + rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 10}] + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + po = create_purchase_order(rm_items = items, is_subcontracted="Yes", + supplier_warehouse="_Test Warehouse 1 - _TC") + + for d in rm_items: + d['po_detail'] = po.items[0].name + + make_stock_transfer_entry(po_no = po.name, main_item_code=item_code, + rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)) + + pr1 = make_purchase_receipt(po.name) + pr1.save() + pr1.supplied_items[0].serial_no = 'ABCD' + self.assertRaises(frappe.ValidationError, pr1.save) + pr1.delete() + def test_partial_transfer_batch_based_on_material_transfer(self): ''' - Set backflush based on Material Transferred for Subcontract From 3d7f660bec36faf4c73dab15c7e6974e4473591f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 20 Jun 2021 10:20:35 +0530 Subject: [PATCH 1056/1264] fix: test case for Project Profitability report --- .../project_profitability/test_project_profitability.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index ea6bdb54ca..180926fe25 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.utils import getdate, nowdate +from frappe.utils import getdate, nowdate, add_days from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice @@ -16,17 +16,22 @@ class TestProjectProfitability(unittest.TestCase): make_salary_structure_for_timesheet(emp, company='_Test Company') self.timesheet = make_timesheet(emp, simulate = True, is_billable=1) self.salary_slip = make_salary_slip(self.timesheet.name) + holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate()) + if holidays: + frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1) + self.salary_slip.submit() self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') self.sales_invoice.due_date = nowdate() self.sales_invoice.submit() frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8) + frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 0) def test_project_profitability(self): filters = { 'company': '_Test Company', - 'start_date': getdate(), + 'start_date': add_days(getdate(), -3), 'end_date': getdate() } From 582f18772632d98ab138390532aa52a836aa9ef3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 21 Jun 2021 00:59:02 +0530 Subject: [PATCH 1057/1264] fix: rate not able to change in purchase order --- erpnext/controllers/taxes_and_totals.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 2bb83ea7f0..56da5b71da 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -658,7 +658,13 @@ class calculate_taxes_and_totals(object): item.margin_type = None item.margin_rate_or_amount = 0.0 - if item.margin_type and item.margin_rate_or_amount: + if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate): + item.margin_type = "Amount" + item.margin_rate_or_amount = flt(item.rate - item.price_list_rate, + item.precision("margin_rate_or_amount")) + item.rate_with_margin = item.rate + + elif item.margin_type and item.margin_rate_or_amount: margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100 rate_with_margin = flt(item.price_list_rate) + flt(margin_value) base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate) From fb89008a13a49e57825228bd8a179c9e8e963aa2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 21 Jun 2021 10:49:09 +0530 Subject: [PATCH 1058/1264] fix(pos): unsupported operand type -=: for 'float' and 'NoneType' (#26097) --- .../accounts/doctype/accounts_settings/accounts_settings.json | 4 ++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/public/js/controllers/transaction.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 0ff7230e55..703e93c075 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -260,7 +260,7 @@ "description": "If enabled, ledger entries will be posted for change amount in POS transactions", "fieldname": "post_change_gl_entries", "fieldtype": "Check", - "label": "Change Ledger Entries for Change Amount" + "label": "Create Ledger Entries for Change Amount" } ], "icon": "icon-cog", @@ -268,7 +268,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-16 13:14:45.739107", + "modified": "2021-06-17 20:26:03.721202", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e14f305fc5..55a5b99907 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -989,7 +989,7 @@ class SalesInvoice(SellingController): for payment_mode in self.payments: if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount: - payment_mode.base_amount -= self.change_amount + payment_mode.base_amount -= flt(self.change_amount) if payment_mode.amount: # POS, make payment entries diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 978c8f4879..6dc40f05e7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -387,7 +387,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(this.frm.doc.scan_barcode) { frappe.call({ - method: "erpnext.selling.page.point_of_sale.point_of_sale.search_serial_or_batch_or_barcode_number", + method: "erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number", args: { search_value: this.frm.doc.scan_barcode } }).then(r => { const data = r && r.message; From 4b32ccb1245ff8256b776cc992da64b5e37cd737 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 21 Jun 2021 10:49:25 +0530 Subject: [PATCH 1059/1264] fix(pos): unsupported operand type -=: for 'float' and 'NoneType' (#26097) From e78364c1917e0eeccd45587221fc51f00e185586 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 21 Jun 2021 11:15:16 +0530 Subject: [PATCH 1060/1264] fix: status indicator for delivery notes (#26062) On list view `per_returned` isn't fetched i.e. `undefined` which become 0 hence the list view indicator is false. This "computation" is already done by status updater, so relying on doc.status is better than redefining it. --- erpnext/stock/doctype/delivery_note/delivery_note_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index f08125b199..0402898047 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -6,8 +6,8 @@ frappe.listview_settings['Delivery Note'] = { return [__("Return"), "gray", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; - } else if (flt(doc.per_returned, 2) === 100) { - return [__("Return Issued"), "grey", "per_returned,=,100"]; + } else if (doc.status === "Return Issued") { + return [__("Return Issued"), "grey", "status,=,Return Issued"]; } else if (flt(doc.per_billed, 2) < 100) { return [__("To Bill"), "orange", "per_billed,<,100"]; } else if (flt(doc.per_billed, 2) === 100) { From 773aabae440a8d85d772ea0cd8f9749faee02dfc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 21 Jun 2021 14:42:40 +0530 Subject: [PATCH 1061/1264] fix: allow to select group warehouse while downloading materials from production plan --- .../production_plan/production_plan.js | 21 ++++++- .../production_plan/production_plan.py | 59 ++++++++++--------- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 64d584118f..056f600c3b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -306,8 +306,25 @@ frappe.ui.form.on('Production Plan', { }, download_materials_required: function(frm) { - let get_template_url = 'erpnext.manufacturing.doctype.production_plan.production_plan.download_raw_materials'; - open_url_post(frappe.request.url, { cmd: get_template_url, doc: frm.doc }); + const fields = [{ + fieldname: 'warehouses', + fieldtype: 'Table MultiSelect', + label: __('Warehouses'), + default: frm.doc.from_warehouse, + options: "Production Plan Material Request Warehouse", + get_query: function () { + return { + filters: { + company: frm.doc.company + } + }; + }, + }]; + + frappe.prompt(fields, (row) => { + let get_template_url = 'erpnext.manufacturing.doctype.production_plan.production_plan.download_raw_materials'; + open_url_post(frappe.request.url, { cmd: get_template_url, doc: frm.doc, warehouses:row.warehouses }); + }, __('Select Warehouses to get Stock for Materials Planning'), __('Get Stock')); }, show_progress: function(frm) { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 46e047654b..0ede1bd4ab 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -98,7 +98,7 @@ class ProductionPlan(Document): def get_items(self): self.set('po_items', []) if self.get_items_from == "Sales Order": - self.get_so_items() + self.get_so_items() elif self.get_items_from == "Material Request": self.get_mr_items() @@ -170,11 +170,11 @@ class ProductionPlan(Document): refs = {} for data in items: item_details = get_item_details(data.item_code) - if self.combine_items: + if self.combine_items: if item_details.bom_no in refs: refs[item_details.bom_no]['so_details'].append({ 'sales_order': data.parent, - 'sales_order_item': data.name, + 'sales_order_item': data.name, 'qty': data.pending_qty }) refs[item_details.bom_no]['qty'] += data.pending_qty @@ -188,10 +188,10 @@ class ProductionPlan(Document): } refs[item_details.bom_no]['so_details'].append({ 'sales_order': data.parent, - 'sales_order_item': data.name, + 'sales_order_item': data.name, 'qty': data.pending_qty }) - + pi = self.append('po_items', { 'include_exploded_items': 1, 'warehouse': data.warehouse, @@ -209,12 +209,12 @@ class ProductionPlan(Document): pi.sales_order = data.parent pi.sales_order_item = data.name pi.description = data.description - + elif self.get_items_from == "Material Request": pi.material_request = data.parent pi.material_request_item = data.name pi.description = data.description - + if refs: for po_item in self.po_items: po_item.planned_qty = refs[po_item.bom_no]['qty'] @@ -477,18 +477,19 @@ class ProductionPlan(Document): msgprint(_("No material request created")) @frappe.whitelist() -def download_raw_materials(doc): +def download_raw_materials(doc, warehouses=None): if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM', - 'Projected Qty', 'Actual Qty', 'Ordered Qty', 'Reserved Qty for Production', - 'Safety Stock', 'Required Qty']] + 'Projected Qty', 'Available Qty In Hand', 'Ordered Qty', 'Planned Qty', + 'Reserved Qty for Production', 'Safety Stock', 'Required Qty']] - for d in get_items_for_material_requests(doc): + doc.warehouse = None + for d in get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True): item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('warehouse'), d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), - d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')]) + d.get('planned_qty'), d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')]) if not doc.get('for_warehouse'): row = {'item_code': d.get('item_code')} @@ -507,7 +508,7 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name, bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse, item.default_material_request_type, item.min_order_qty, item_default.default_warehouse, - item.purchase_uom, item_uom.conversion_factor + item.purchase_uom, item_uom.conversion_factor, item.safety_stock from `tabBOM Explosion Item` bei JOIN `tabBOM` bom ON bom.name = bei.parent @@ -677,32 +678,36 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty, - ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse from `tabBin` - where item_code = %(item_code)s {conditions} + ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse, + ifnull(sum(planned_qty),0) as planned_qty + from `tabBin` where item_code = %(item_code)s {conditions} group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) +def get_warehouse_list(warehouses, warehouse_list=[]): + if isinstance(warehouses, string_types): + warehouses = json.loads(warehouses) + + for row in warehouses: + child_warehouses = frappe.db.get_descendants('Warehouse', row.get("warehouse")) + if child_warehouses: + warehouse_list.extend(child_warehouses) + else: + warehouse_list.append(row.get("warehouse")) + @frappe.whitelist() -def get_items_for_material_requests(doc, warehouses=None): +def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) warehouse_list = [] if warehouses: - if isinstance(warehouses, string_types): - warehouses = json.loads(warehouses) - - for row in warehouses: - child_warehouses = frappe.db.get_descendants('Warehouse', row.get("warehouse")) - if child_warehouses: - warehouse_list.extend(child_warehouses) - else: - warehouse_list.append(row.get("warehouse")) + get_warehouse_list(warehouses, warehouse_list) if warehouse_list: warehouses = list(set(warehouse_list)) - if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses: + if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses: warehouses.remove(doc.get("for_warehouse")) warehouse_list = None @@ -795,7 +800,7 @@ def get_items_for_material_requests(doc, warehouses=None): if items: mr_items.append(items) - if not ignore_existing_ordered_qty and warehouses: + if (not ignore_existing_ordered_qty or get_parent_warehouse_data) and warehouses: new_mr_items = [] for item in mr_items: get_materials_from_other_locations(item, warehouses, new_mr_items, company) From 8347eb1dbae8591cb42fd4a2398c7fad0cdcf6fb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 21 Jun 2021 15:38:44 +0530 Subject: [PATCH 1062/1264] Update production_plan.js --- .../manufacturing/doctype/production_plan/production_plan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 056f600c3b..450aa04a73 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -323,7 +323,7 @@ frappe.ui.form.on('Production Plan', { frappe.prompt(fields, (row) => { let get_template_url = 'erpnext.manufacturing.doctype.production_plan.production_plan.download_raw_materials'; - open_url_post(frappe.request.url, { cmd: get_template_url, doc: frm.doc, warehouses:row.warehouses }); + open_url_post(frappe.request.url, { cmd: get_template_url, doc: frm.doc, warehouses: row.warehouses }); }, __('Select Warehouses to get Stock for Materials Planning'), __('Get Stock')); }, From 49ec0e5ac3be9333d6ee07980fb408d03e107de4 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 21 Jun 2021 16:18:35 +0530 Subject: [PATCH 1063/1264] feat: Optionally allow rejected quality inspection on submission --- erpnext/controllers/stock_controller.py | 84 ++++++++++++------- .../stock_entry_detail.json | 3 +- .../stock_settings/stock_settings.json | 21 ++++- 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 35097b97b9..3112fa7a6c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -356,42 +356,68 @@ class StockController(AccountsController): }, update_modified) def validate_inspection(self): - '''Checks if quality inspection is set for Items that require inspection. - On submit, throw an exception''' - inspection_required_fieldname = None - if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: - inspection_required_fieldname = "inspection_required_before_purchase" - elif self.doctype in ["Delivery Note", "Sales Invoice"]: - inspection_required_fieldname = "inspection_required_before_delivery" + """Checks if quality inspection is set/ is valid for Items that require inspection.""" + inspection_fieldname_map = { + "Purchase Receipt": "inspection_required_before_purchase", + "Purchase Invoice": "inspection_required_before_purchase", + "Sales Invoice": "inspection_required_before_delivery", + "Delivery Note": "inspection_required_before_delivery" + } + inspection_required_fieldname = inspection_fieldname_map.get(self.doctype) + # return if inspection is not required on document level if ((not inspection_required_fieldname and self.doctype != "Stock Entry") or (self.doctype == "Stock Entry" and not self.inspection_required) or (self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)): return - for d in self.get('items'): - qa_required = False - if (inspection_required_fieldname and not d.quality_inspection and - frappe.db.get_value("Item", d.item_code, inspection_required_fieldname)): - qa_required = True - elif self.doctype == "Stock Entry" and not d.quality_inspection and d.t_warehouse: - qa_required = True - if self.docstatus == 1 and d.quality_inspection: - qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection) - if qa_doc.docstatus == 0: - link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection) - frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError) + for row in self.get('items'): + qi_required = False + if (inspection_required_fieldname and frappe.db.get_value("Item", row.item_code, inspection_required_fieldname)): + qi_required = True + elif self.doctype == "Stock Entry" and row.t_warehouse: + qi_required = True # inward stock needs inspection - if qa_doc.status != 'Accepted': - frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}") - .format(d.idx, d.item_code), QualityInspectionRejectedError) - elif qa_required : - action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted - if self.docstatus==1 and action == 'Stop': - frappe.throw(_("Quality Inspection required for Item {0} to submit").format(frappe.bold(d.item_code)), - exc=QualityInspectionRequiredError) - else: - frappe.msgprint(_("Create Quality Inspection for Item {0}").format(frappe.bold(d.item_code))) + if qi_required: # validate row only if inspection is required on item level + self.validate_qi_presence(row) + if self.docstatus == 1: + self.validate_qi_submission(row) + self.validate_qi_rejection(row) + + def validate_qi_presence(self, row): + """Check if QI is present on row level. Warn on save and stop on submit if missing.""" + if not row.quality_inspection: + msg = _(f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}") + if self.docstatus == 1: + frappe.throw(msg, title=_("Inspection Required"), exc=QualityInspectionRequiredError) + else: + frappe.msgprint(msg, title=_("Inspection Required"), indicator="blue") + + def validate_qi_submission(self, row): + """Check if QI is submitted on row level, during submission""" + action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted or "Stop" + qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus") + + if not qa_docstatus == 1: + link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection) + msg = _(f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}") + if action == "Stop": + frappe.throw(msg, title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError) + else: + frappe.msgprint(msg, alert=True) + + def validate_qi_rejection(self, row): + """Check if QI is rejected on row level, during submission""" + action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_rejected or "Stop" + qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status") + + if qa_status == "Rejected": + link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection) + msg = _(f"Row #{row.idx}: Quality Inspection was rejected for item {row.item_code}") + if action == "Stop": + frappe.throw(msg, title=_("Inspection Rejected"), exc=QualityInspectionRejectedError) + else: + frappe.msgprint(msg, alert=True, indicator="orange") def update_blanket_order(self): blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order])) diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 864ff488b2..a007389f7a 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -307,6 +307,7 @@ "fieldname": "quality_inspection", "fieldtype": "Link", "label": "Quality Inspection", + "no_copy": 1, "options": "Quality Inspection" }, { @@ -548,7 +549,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-11 13:47:50.158754", + "modified": "2021-06-21 16:03:18.834880", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index cf5d98d092..d07e26b536 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -23,7 +23,10 @@ "allow_negative_stock", "show_barcode_field", "clean_description_html", + "quality_inspection_settings_section", "action_if_quality_inspection_is_not_submitted", + "column_break_21", + "action_if_quality_inspection_is_rejected", "section_break_7", "automatically_set_serial_nos_based_on_fifo", "set_qty_in_transactions_based_on_serial_no_input", @@ -264,6 +267,22 @@ { "fieldname": "column_break_31", "fieldtype": "Column Break" + }, + { + "fieldname": "quality_inspection_settings_section", + "fieldtype": "Section Break", + "label": "Quality Inspection Settings" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "default": "Stop", + "fieldname": "action_if_quality_inspection_is_rejected", + "fieldtype": "Select", + "label": "Action If Quality Inspection Is Rejected", + "options": "Stop\nWarn" } ], "icon": "icon-cog", @@ -271,7 +290,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-30 17:27:42.709231", + "modified": "2021-06-21 16:17:42.159829", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From ea0dea46e0c6126da7d304f5e3d6c9dae552fc75 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 21 Jun 2021 16:51:12 +0530 Subject: [PATCH 1064/1264] fix: sider and semgrep --- erpnext/controllers/stock_controller.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 3112fa7a6c..9bac27deb1 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -387,11 +387,11 @@ class StockController(AccountsController): def validate_qi_presence(self, row): """Check if QI is present on row level. Warn on save and stop on submit if missing.""" if not row.quality_inspection: - msg = _(f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}") + msg = f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}" if self.docstatus == 1: - frappe.throw(msg, title=_("Inspection Required"), exc=QualityInspectionRequiredError) + frappe.throw(_(msg), title=_("Inspection Required"), exc=QualityInspectionRequiredError) else: - frappe.msgprint(msg, title=_("Inspection Required"), indicator="blue") + frappe.msgprint(_(msg), title=_("Inspection Required"), indicator="blue") def validate_qi_submission(self, row): """Check if QI is submitted on row level, during submission""" @@ -400,11 +400,11 @@ class StockController(AccountsController): if not qa_docstatus == 1: link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection) - msg = _(f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}") + msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}" if action == "Stop": - frappe.throw(msg, title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError) + frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError) else: - frappe.msgprint(msg, alert=True) + frappe.msgprint(_(msg), alert=True) def validate_qi_rejection(self, row): """Check if QI is rejected on row level, during submission""" @@ -413,11 +413,11 @@ class StockController(AccountsController): if qa_status == "Rejected": link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection) - msg = _(f"Row #{row.idx}: Quality Inspection was rejected for item {row.item_code}") + msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}" if action == "Stop": - frappe.throw(msg, title=_("Inspection Rejected"), exc=QualityInspectionRejectedError) + frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError) else: - frappe.msgprint(msg, alert=True, indicator="orange") + frappe.msgprint(_(msg), alert=True, indicator="orange") def update_blanket_order(self): blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order])) From f97206b3cbb9aeee730d67db8161f75138404595 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 21 Jun 2021 19:38:37 +0530 Subject: [PATCH 1065/1264] fix: Sort website products by weightage mentioned in Item master --- erpnext/shopping_cart/product_query.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index 36d446ed0f..dd94c26bc6 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -61,7 +61,8 @@ class ProductQuery: ], or_filters=self.or_filters, start=start, - limit=self.page_length + limit=self.page_length, + order_by="weightage desc" ) items_dict = {item.name: item for item in items} @@ -71,7 +72,15 @@ class ProductQuery: result = [items_dict.get(item) for item in list(set.intersection(*all_items))] else: - result = frappe.get_all("Item", fields=self.fields, filters=self.filters, or_filters=self.or_filters, start=start, limit=self.page_length) + result = frappe.get_all( + "Item", + fields=self.fields, + filters=self.filters, + or_filters=self.or_filters, + start=start, + limit=self.page_length, + order_by="weightage desc" + ) for item in result: product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info') From 703c30f5f89183937b2dcdecf33089a993e98a82 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 22 Jun 2021 11:20:17 +0530 Subject: [PATCH 1066/1264] fix: Consistent alert indicators --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 9bac27deb1..c83de3da9e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -404,7 +404,7 @@ class StockController(AccountsController): if action == "Stop": frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError) else: - frappe.msgprint(_(msg), alert=True) + frappe.msgprint(_(msg), alert=True, indicator="orange") def validate_qi_rejection(self, row): """Check if QI is rejected on row level, during submission""" From cfdda21dd2a353cafbe7d2d349ad06714d6061dd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 22 Jun 2021 13:03:22 +0530 Subject: [PATCH 1067/1264] fix: Export invoices not visible in GSTR-1 report --- erpnext/regional/report/gstr_1/gstr_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 80e2d725a2..10961593e1 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -201,7 +201,7 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ - conditions += " AND billing_address_gstin NOT IN %(company_gstins)s" + conditions += " AND IFNULL(billing_address_gstin, '') NOT IN %(company_gstins)s" return conditions From 889140fd8c5c8684f394a36516878a2490efe21c Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 22 Jun 2021 16:26:19 +0530 Subject: [PATCH 1068/1264] fix: sql syntax error in get_project_name method (#26147) --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 7bd739a6ad..4ada834425 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -315,7 +315,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select {fields} from `tabProject` where `tabProject`.status not in ("Completed", "Cancelled") - and {cond} {match_cond} {scond} + and {cond} {scond} {match_cond} order by if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), idx desc, From 219a87d53072cab01a79009f1195a359ce059a4a Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 22 Jun 2021 16:28:58 +0530 Subject: [PATCH 1069/1264] fix: disable sales order cancellation if linked to draft invoice (#26125) --- erpnext/selling/doctype/sales_order/sales_order.py | 2 +- .../selling/doctype/sales_order/test_sales_order.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 551f715bd5..41f57a34d3 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -233,7 +233,7 @@ class SalesOrder(SellingController): # Checks Sales Invoice submit_rv = frappe.db.sql_list("""select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 - where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""", + where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus < 2""", self.name) if submit_rv: diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 987371066a..974648d6d4 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1217,6 +1217,19 @@ class TestSalesOrder(unittest.TestCase): # To test if the SO does NOT have a Blanket Order self.assertEqual(so_doc.items[0].blanket_order, None) + def test_so_cancellation_when_si_drafted(self): + """ + Test to check if Sales Order gets cancelled if Sales Invoice is in Draft state + Expected result: sales order should not get cancelled + """ + so = make_sales_order() + so.submit() + si = make_sales_invoice(so.name) + si.save() + + self.assertRaises(frappe.ValidationError, so.cancel) + + def make_sales_order(**args): so = frappe.new_doc("Sales Order") From c05496a5a7c52e7f7c4a8a4f5c55f71b52d810a8 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 22 Jun 2021 17:53:53 +0530 Subject: [PATCH 1070/1264] fix: fixed rounding off ordered percent to 100 in condition (#26152) --- erpnext/stock/doctype/material_request/material_request.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 92c8d21387..6e66f9869c 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -101,7 +101,8 @@ frappe.ui.form.on('Material Request', { } if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') { - if (flt(frm.doc.per_ordered, 2) < 100) { + let precision = frappe.defaults.get_default("float_precision"); + if (flt(frm.doc.per_ordered, precision) < 100) { let add_create_pick_list_button = () => { frm.add_custom_button(__('Pick List'), () => frm.events.create_pick_list(frm), __('Create')); From ea2c02738d55d17c2d8161bdae5ce6454f7d4fa9 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 22 Jun 2021 21:35:25 +0530 Subject: [PATCH 1071/1264] fix: Include Stock Reco logic in update_qty_in_future_sle --- erpnext/stock/stock_ledger.py | 75 ++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9fe89c3fa5..94bd3077a7 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -215,7 +215,7 @@ class update_entries_after(object): """ self.data.setdefault(args.warehouse, frappe._dict()) warehouse_dict = self.data[args.warehouse] - previous_sle = self.get_previous_sle_of_current_voucher(args) + previous_sle = get_previous_sle_of_current_voucher(args) warehouse_dict.previous_sle = previous_sle for key in ("qty_after_transaction", "valuation_rate", "stock_value"): @@ -227,29 +227,6 @@ class update_entries_after(object): "stock_value_difference": 0.0 }) - def get_previous_sle_of_current_voucher(self, args): - """get stock ledger entries filtered by specific posting datetime conditions""" - - args['time_format'] = '%H:%i:%s' - if not args.get("posting_date"): - args["posting_date"] = "1900-01-01" - if not args.get("posting_time"): - args["posting_time"] = "00:00" - - sle = frappe.db.sql(""" - select *, timestamp(posting_date, posting_time) as "timestamp" - from `tabStock Ledger Entry` - where item_code = %(item_code)s - and warehouse = %(warehouse)s - and is_cancelled = 0 - and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) - order by timestamp(posting_date, posting_time) desc, creation desc - limit 1 - for update""", args, as_dict=1) - - return sle[0] if sle else frappe._dict() - - def build(self): from erpnext.controllers.stock_controller import future_sle_exists @@ -734,6 +711,35 @@ class update_entries_after(object): bin_doc.flags.via_stock_ledger_entry = True bin_doc.save(ignore_permissions=True) + +def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): + """get stock ledger entries filtered by specific posting datetime conditions""" + + args['time_format'] = '%H:%i:%s' + if not args.get("posting_date"): + args["posting_date"] = "1900-01-01" + if not args.get("posting_time"): + args["posting_time"] = "00:00" + + voucher_condition = "" + if exclude_current_voucher: + voucher_no = args.get("voucher_no") + voucher_condition = f"and voucher_no != '{voucher_no}'" + + sle = frappe.db.sql(""" + select *, timestamp(posting_date, posting_time) as "timestamp" + from `tabStock Ledger Entry` + where item_code = %(item_code)s + and warehouse = %(warehouse)s + and is_cancelled = 0 + {voucher_condition} + and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) + order by timestamp(posting_date, posting_time) desc, creation desc + limit 1 + for update""".format(voucher_condition=voucher_condition), args, as_dict=1) + + return sle[0] if sle else frappe._dict() + def get_previous_sle(args, for_update=False): """ get the last sle on or before the current time-bucket, @@ -862,9 +868,24 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, return valuation_rate def update_qty_in_future_sle(args, allow_negative_stock=None): + """Recalculate Qty after Transaction in future SLEs based on current SLE.""" + qty_shift = args.actual_qty + + # find difference/shift in qty caused by stock reconciliation + if args.voucher_type == "Stock Reconciliation": + last_balance = get_previous_sle_of_current_voucher( + args, + exclude_current_voucher=True + ).get("qty_after_transaction") + if last_balance is not None: + stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) + else: + stock_reco_qty_shift = args.qty_after_transaction + qty_shift = stock_reco_qty_shift + frappe.db.sql(""" update `tabStock Ledger Entry` - set qty_after_transaction = qty_after_transaction + {qty} + set qty_after_transaction = qty_after_transaction + {qty_shift} where item_code = %(item_code)s and warehouse = %(warehouse)s @@ -876,7 +897,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=None): and creation > %(creation)s ) ) - """.format(qty=args.actual_qty), args) + """.format(qty_shift=qty_shift), args) validate_negative_qty_in_future_sle(args, allow_negative_stock) @@ -884,7 +905,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): allow_negative_stock = allow_negative_stock \ or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) - if args.actual_qty < 0 and not allow_negative_stock: + if (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock: sle = get_future_sle_with_negative_qty(args) if sle: message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format( From fc98abece9b6c1975b228e8b37b8921d45a3bfac Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 23 Jun 2021 11:21:38 +0530 Subject: [PATCH 1072/1264] feat: Employee Grievance (#25705) * feat: Employee Grievance * feat: link to desk and automatic unsuspend * test: Employee Grievance * fix: Sider and Translation * fix: sider * fix: formatting * feat: changes requested * feat: Employee Grievance * feat: link to desk and automatic unsuspend * test: Employee Grievance * fix: Sider and Translation * fix: sider * fix: formatting * feat: changes requested * fix: patch test and sider issue * fix: make Employee Responsible non-mandatory since there cannot be an employee responsible for all sorts of grievances - show pay cut and suspension buttons only if Employee Resposible is set - some label changes * feat: added subject field for more context - set title for documents - added list view settings - refactor suspend and unsuspend functions - add submit and cancel perms for system and hr managers - fix tests * fix: sider issues * fix: removed suspension and paycut * fix:sider * fix: test * fix: test * fix: resolved Conflicts * fix: sider * fix: remove debugging print statements * fix: validation message * fix: unnecessary comma Co-authored-by: Rucha Mahabal --- erpnext/controllers/queries.py | 2 +- erpnext/hr/doctype/employee/employee.json | 4 +- erpnext/hr/doctype/employee/employee.py | 5 +- .../hr/doctype/employee/employee_dashboard.py | 5 +- erpnext/hr/doctype/employee/employee_list.js | 2 +- .../hr/doctype/employee_grievance/__init__.py | 0 .../employee_grievance/employee_grievance.js | 39 +++ .../employee_grievance.json | 261 ++++++++++++++++++ .../employee_grievance/employee_grievance.py | 15 + .../employee_grievance_list.js | 12 + .../test_employee_grievance.py | 51 ++++ erpnext/hr/doctype/grievance_type/__init__.py | 0 .../doctype/grievance_type/grievance_type.js | 8 + .../grievance_type/grievance_type.json | 70 +++++ .../doctype/grievance_type/grievance_type.py | 8 + .../grievance_type/test_grievance_type.py | 8 + erpnext/hr/workspace/hr/hr.json | 20 +- .../additional_salary/additional_salary.js | 17 +- .../doctype/salary_slip/test_salary_slip.py | 1 + .../salary_structure/test_salary_structure.py | 4 +- 20 files changed, 516 insertions(+), 16 deletions(-) create mode 100644 erpnext/hr/doctype/employee_grievance/__init__.py create mode 100644 erpnext/hr/doctype/employee_grievance/employee_grievance.js create mode 100644 erpnext/hr/doctype/employee_grievance/employee_grievance.json create mode 100644 erpnext/hr/doctype/employee_grievance/employee_grievance.py create mode 100644 erpnext/hr/doctype/employee_grievance/employee_grievance_list.js create mode 100644 erpnext/hr/doctype/employee_grievance/test_employee_grievance.py create mode 100644 erpnext/hr/doctype/grievance_type/__init__.py create mode 100644 erpnext/hr/doctype/grievance_type/grievance_type.js create mode 100644 erpnext/hr/doctype/grievance_type/grievance_type.json create mode 100644 erpnext/hr/doctype/grievance_type/grievance_type.py create mode 100644 erpnext/hr/doctype/grievance_type/test_grievance_type.py diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 4ada834425..280319321f 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -19,7 +19,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): fields = get_fields("Employee", ["name", "employee_name"]) return frappe.db.sql("""select {fields} from `tabEmployee` - where status = 'Active' + where status in ('Active', 'Suspended') and docstatus < 2 and ({key} like %(txt)s or employee_name like %(txt)s) diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 5442ed56c3..d592a9c79e 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -207,7 +207,7 @@ "label": "Status", "oldfieldname": "status", "oldfieldtype": "Select", - "options": "Active\nInactive\nLeft", + "options": "Active\nInactive\nSuspended\nLeft", "reqd": 1, "search_index": 1 }, @@ -813,7 +813,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2021-06-12 11:31:37.730760", + "modified": "2021-06-17 11:31:37.730760", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index bc5694226a..fa017d9d4c 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr +from frappe.utils import getdate, validate_email_address, today, add_years, cstr from frappe.model.naming import set_name_by_naming_series from frappe import throw, _, scrub from frappe.permissions import add_user_permission, remove_user_permission, \ @@ -12,7 +12,6 @@ from frappe.permissions import add_user_permission, remove_user_permission, \ from frappe.model.document import Document from erpnext.utilities.transaction_base import delete_events from frappe.utils.nestedset import NestedSet -from erpnext.hr.doctype.job_offer.job_offer import get_staffing_plan_detail class EmployeeUserDisabledError(frappe.ValidationError): pass class EmployeeLeftValidationError(frappe.ValidationError): pass @@ -37,7 +36,7 @@ class Employee(NestedSet): def validate(self): from erpnext.controllers.status_updater import validate_status - validate_status(self.status, ["Active", "Inactive", "Left"]) + validate_status(self.status, ["Active", "Inactive", "Suspended", "Left"]) self.employee = self.name self.set_employee_name() diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py index 285374d9f6..e853bee69f 100644 --- a/erpnext/hr/doctype/employee/employee_dashboard.py +++ b/erpnext/hr/doctype/employee/employee_dashboard.py @@ -7,7 +7,8 @@ def get_data(): 'heatmap_message': _('This is based on the attendance of this Employee'), 'fieldname': 'employee', 'non_standard_fieldnames': { - 'Bank Account': 'party' + 'Bank Account': 'party', + 'Employee Grievance': 'raised_by' }, 'transactions': [ { @@ -20,7 +21,7 @@ def get_data(): }, { 'label': _('Lifecycle'), - 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation'] + 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation', 'Employee Grievance'] }, { 'label': _('Shift'), diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js index 6679e318c2..d37e1496ca 100644 --- a/erpnext/hr/doctype/employee/employee_list.js +++ b/erpnext/hr/doctype/employee/employee_list.js @@ -3,7 +3,7 @@ frappe.listview_settings['Employee'] = { filters: [["status","=", "Active"]], get_indicator: function(doc) { var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status]; - indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray"}[doc.status]; + indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray", "Suspended": "orange"}[doc.status]; return indicator; } }; diff --git a/erpnext/hr/doctype/employee_grievance/__init__.py b/erpnext/hr/doctype/employee_grievance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.js b/erpnext/hr/doctype/employee_grievance/employee_grievance.js new file mode 100644 index 0000000000..25c5badbc7 --- /dev/null +++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.js @@ -0,0 +1,39 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Grievance', { + setup: function(frm) { + frm.set_query('grievance_against_party', function() { + return { + filters: { + name: ['in', [ + 'Company', 'Department', 'Employee Group', 'Employee Grade', 'Employee'] + ] + } + }; + }); + frm.set_query('associated_document_type', function() { + let ignore_modules = ["Setup", "Core", "Integrations", "Automation", "Website", + "Utilities", "Event Streaming", "Social", "Chat", "Data Migration", "Printing", "Desk", "Custom"]; + return { + filters: { + istable: 0, + issingle: 0, + module: ["Not In", ignore_modules] + } + }; + }); + }, + + grievance_against_party: function(frm) { + let filters = {}; + if (frm.doc.grievance_against_party == 'Employee' && frm.doc.raised_by) { + filters.name = ["!=", frm.doc.raised_by]; + } + frm.set_query('grievance_against', function() { + return { + filters: filters + }; + }); + }, +}); diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.json b/erpnext/hr/doctype/employee_grievance/employee_grievance.json new file mode 100644 index 0000000000..5a918562af --- /dev/null +++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.json @@ -0,0 +1,261 @@ +{ + "actions": [], + "autoname": "HR-GRIEV-.YYYY.-.#####", + "creation": "2021-05-11 13:41:51.485295", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "subject", + "raised_by", + "employee_name", + "designation", + "column_break_3", + "date", + "status", + "reports_to", + "grievance_details_section", + "grievance_against_party", + "grievance_against", + "grievance_type", + "column_break_11", + "associated_document_type", + "associated_document", + "section_break_14", + "description", + "investigation_details_section", + "cause_of_grievance", + "resolution_details_section", + "resolved_by", + "resolution_date", + "employee_responsible", + "column_break_16", + "resolution_detail", + "amended_from" + ], + "fields": [ + { + "fieldname": "grievance_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Grievance Type", + "options": "Grievance Type", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date ", + "reqd": 1 + }, + { + "default": "Open", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Open\nInvestigated\nResolved\nInvalid", + "reqd": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text", + "label": "Description", + "reqd": 1 + }, + { + "fieldname": "cause_of_grievance", + "fieldtype": "Text", + "label": "Cause of Grievance", + "mandatory_depends_on": "eval: doc.status == \"Investigated\" || doc.status == \"Resolved\"" + }, + { + "fieldname": "resolution_details_section", + "fieldtype": "Section Break", + "label": "Resolution Details" + }, + { + "fieldname": "resolved_by", + "fieldtype": "Link", + "label": "Resolved By", + "mandatory_depends_on": "eval: doc.status == \"Resolved\"", + "options": "User" + }, + { + "fieldname": "employee_responsible", + "fieldtype": "Link", + "label": "Employee Responsible ", + "options": "Employee" + }, + { + "fieldname": "resolution_detail", + "fieldtype": "Small Text", + "label": "Resolution Details", + "mandatory_depends_on": "eval: doc.status == \"Resolved\"" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "resolution_date", + "fieldtype": "Date", + "label": "Resolution Date", + "mandatory_depends_on": "eval: doc.status == \"Resolved\"" + }, + { + "fieldname": "grievance_against", + "fieldtype": "Dynamic Link", + "label": "Grievance Against", + "options": "grievance_against_party", + "reqd": 1 + }, + { + "fieldname": "raised_by", + "fieldtype": "Link", + "label": "Raised By", + "options": "Employee", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Grievance", + "print_hide": 1, + "read_only": 1 + }, + { + "fetch_from": "raised_by.designation", + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fetch_from": "raised_by.reports_to", + "fieldname": "reports_to", + "fieldtype": "Link", + "label": "Reports To", + "options": "Employee", + "read_only": 1 + }, + { + "fieldname": "grievance_details_section", + "fieldtype": "Section Break", + "label": "Grievance Details" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, + { + "fieldname": "grievance_against_party", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Grievance Against Party", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "associated_document_type", + "fieldtype": "Link", + "label": "Associated Document Type", + "options": "DocType" + }, + { + "fieldname": "associated_document", + "fieldtype": "Dynamic Link", + "label": "Associated Document", + "options": "associated_document_type" + }, + { + "fieldname": "investigation_details_section", + "fieldtype": "Section Break", + "label": "Investigation Details" + }, + { + "fetch_from": "raised_by.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, + { + "fieldname": "subject", + "fieldtype": "Data", + "label": "Subject", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-06-21 12:51:01.499486", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Grievance", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "select": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "select": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + } + ], + "search_fields": "subject,raised_by,grievance_against_party", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "subject", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py new file mode 100644 index 0000000000..503b5ea444 --- /dev/null +++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py @@ -0,0 +1,15 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _, bold +from frappe.model.document import Document + +class EmployeeGrievance(Document): + def on_submit(self): + if self.status not in ["Invalid", "Resolved"]: + frappe.throw(_("Only Employee Grievance with status {0} or {1} can be submitted").format( + bold("Invalid"), + bold("Resolved")) + ) + diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js new file mode 100644 index 0000000000..fc08e21609 --- /dev/null +++ b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js @@ -0,0 +1,12 @@ +frappe.listview_settings["Employee Grievance"] = { + has_indicator_for_draft: 1, + get_indicator: function(doc) { + var colors = { + "Open": "red", + "Investigated": "orange", + "Resolved": "green", + "Invalid": "grey" + }; + return [__(doc.status), colors[doc.status], "status,=," + doc.status]; + } +}; \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py new file mode 100644 index 0000000000..a615b20a5a --- /dev/null +++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py @@ -0,0 +1,51 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +import unittest +from frappe.utils import today +from erpnext.hr.doctype.employee.test_employee import make_employee +class TestEmployeeGrievance(unittest.TestCase): + def test_create_employee_grievance(self): + create_employee_grievance() + +def create_employee_grievance(): + grievance_type = create_grievance_type() + emp_1 = make_employee("test_emp_grievance_@example.com", company="_Test Company") + emp_2 = make_employee("testculprit@example.com", company="_Test Company") + + grievance = frappe.new_doc("Employee Grievance") + grievance.subject = "Test Employee Grievance" + grievance.raised_by = emp_1 + grievance.date = today() + grievance.grievance_type = grievance_type + grievance.grievance_against_party = "Employee" + grievance.grievance_against = emp_2 + grievance.description = "test descrip" + + #set cause + grievance.cause_of_grievance = "test cause" + + #resolution details + grievance.resolution_date = today() + grievance.resolution_detail = "test resolution detail" + grievance.resolved_by = "test_emp_grievance_@example.com" + grievance.employee_responsible = emp_2 + grievance.status = "Resolved" + + grievance.save() + grievance.submit() + + return grievance + + +def create_grievance_type(): + if frappe.db.exists("Grievance Type", "Employee Abuse"): + return frappe.get_doc("Grievance Type", "Employee Abuse") + grievance_type = frappe.new_doc("Grievance Type") + grievance_type.name = "Employee Abuse" + grievance_type.description = "Test" + grievance_type.save() + + return grievance_type.name + diff --git a/erpnext/hr/doctype/grievance_type/__init__.py b/erpnext/hr/doctype/grievance_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.js b/erpnext/hr/doctype/grievance_type/grievance_type.js new file mode 100644 index 0000000000..425f2fd5b5 --- /dev/null +++ b/erpnext/hr/doctype/grievance_type/grievance_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Grievance Type', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.json b/erpnext/hr/doctype/grievance_type/grievance_type.json new file mode 100644 index 0000000000..1dce00a0e2 --- /dev/null +++ b/erpnext/hr/doctype/grievance_type/grievance_type.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "autoname": "Prompt", + "creation": "2021-05-11 12:41:50.256071", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_5", + "description" + ], + "fields": [ + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Text", + "label": "Description" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-06-21 12:54:37.764712", + "modified_by": "Administrator", + "module": "HR", + "name": "Grievance 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": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.py b/erpnext/hr/doctype/grievance_type/grievance_type.py new file mode 100644 index 0000000000..618cf0a031 --- /dev/null +++ b/erpnext/hr/doctype/grievance_type/grievance_type.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class GrievanceType(Document): + pass diff --git a/erpnext/hr/doctype/grievance_type/test_grievance_type.py b/erpnext/hr/doctype/grievance_type/test_grievance_type.py new file mode 100644 index 0000000000..a02a34d41f --- /dev/null +++ b/erpnext/hr/doctype/grievance_type/test_grievance_type.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestGrievanceType(unittest.TestCase): + pass diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index c5201c22c9..4500ba4560 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -153,6 +153,24 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Grievance Type", + "link_to": "Grievance Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Employee Grievance", + "link_to": "Employee Grievance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Employee", "hidden": 0, @@ -823,7 +841,7 @@ "type": "Link" } ], - "modified": "2021-04-26 13:36:15.413819", + "modified": "2021-05-13 17:19:40.524444", "modified_by": "Administrator", "module": "HR", "name": "HR", diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js index d1ed91fac7..24ffce537c 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.js +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js @@ -12,8 +12,12 @@ frappe.ui.form.on('Additional Salary', { } }; }); + }, - frm.trigger('set_earning_component'); + onload: function(frm) { + if (frm.doc.type) { + frm.trigger('set_component_query'); + } }, employee: function(frm) { @@ -46,14 +50,19 @@ frappe.ui.form.on('Additional Salary', { }, company: function(frm) { - frm.trigger('set_earning_component'); + frm.set_value("type", ""); + frm.trigger('set_component_query'); }, - set_earning_component: function(frm) { + set_component_query: function(frm) { if (!frm.doc.company) return; + let filters = {company: frm.doc.company}; + if (frm.doc.type) { + filters.type = frm.doc.type; + } frm.set_query("salary_component", function() { return { - filters: {type: ["in", ["earning", "deduction"]], company: frm.doc.company} + filters: filters }; }); }, diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 9e7db977ab..ce88cc3f1e 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -481,6 +481,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): if not salary_structure: salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" + employee = frappe.db.get_value("Employee", {"user_id": user}) salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee) salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}) diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index dce6b7aa3d..e7d123c996 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -124,8 +124,8 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, "doctype": "Salary Structure", "name": salary_structure, "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"]), + "earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), + "deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), "payroll_frequency": payroll_frequency, "payment_account": get_random("Account", filters={'account_currency': currency}), "currency": currency From 44815393b375b0a97d461b93595133262fbcb499 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 23 Jun 2021 12:28:02 +0530 Subject: [PATCH 1073/1264] fix: job applicant link issue (#25934) --- erpnext/hr/doctype/job_applicant/job_applicant_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_list.js b/erpnext/hr/doctype/job_applicant/job_applicant_list.js index 3b9141ba79..2ad0d591d8 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant_list.js +++ b/erpnext/hr/doctype/job_applicant/job_applicant_list.js @@ -2,7 +2,7 @@ // MIT License. See license.txt frappe.listview_settings['Job Applicant'] = { - add_fields: ["company", "designation", "job_applicant", "status"], + add_fields: ["status"], get_indicator: function (doc) { if (doc.status == "Accepted") { return [__(doc.status), "green", "status,=," + doc.status]; From 40a4330ec1240492484d67bbdd0eeb777440c34f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 12:38:37 +0530 Subject: [PATCH 1074/1264] fix: Move tax categories up in country wise json --- .../setup_wizard/data/country_wise_tax.json | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 6df57f1738..e36bf5cbe0 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1164,35 +1164,35 @@ }, "India": { + "tax_categories": [ + { + "title": "In-State", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Out-State", + "is_inter_state": 1, + "gst_state": "" + }, + { + "title": "Reverse Charge In-State", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Reverse Charge Out-State", + "is_inter_state": 1, + "gst_state": "" + }, + { + "title": "Registered Composition", + "is_inter_state": 0, + "gst_state": "" + } + ], "chart_of_accounts": { "*": { - "tax_categories": [ - { - "title": "In-State", - "is_inter_state": 0, - "gst_state": "" - }, - { - "title": "Out-State", - "is_inter_state": 1, - "gst_state": "" - }, - { - "title": "Reverse Charge In-State", - "is_inter_state": 0, - "gst_state": "" - }, - { - "title": "Reverse Charge Out-State", - "is_inter_state": 0, - "gst_state": "" - }, - { - "title": "Registered Composition", - "is_inter_state": 0, - "gst_state": "" - } - ], "item_tax_templates": [ { "title": "GST 9%", From 208b5f9e7303bd89b694aa569c6143d4656c4c6b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 12:44:56 +0530 Subject: [PATCH 1075/1264] chore: Add comments --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index a664914f08..d5682b6f4c 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -143,6 +143,9 @@ def make_taxes_and_charges_template(company_name, doctype, template): tax_row[fieldname] = default_value doc = frappe.get_doc(template) + + # Data in country wise json is already pre validated, hence validations can be ignored + # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True doc.insert(ignore_permissions=True) @@ -172,6 +175,9 @@ def make_item_tax_template(company_name, template): tax_row['tax_rate'] = account_data.get('tax_rate') doc = frappe.get_doc(template) + + # Data in country wise json is already pre validated, hence validations can be ignored + # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True doc.insert(ignore_permissions=True) From 35e11fbea6baeb140a08bee8a30a2d156f20e7b1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 13:17:01 +0530 Subject: [PATCH 1076/1264] fix: Tests --- erpnext/setup/setup_wizard/operations/install_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 5c725d332d..9c7d9e5259 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -480,7 +480,7 @@ def update_stock_settings(): stock_settings.save() def create_bank_account(args): - if not args.bank_account: + if not args.get('bank_account'): return company_name = args.company_name From 9ec0ded28ff5e2aea03d6b015e272c9d69209792 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 23 Jun 2021 14:05:10 +0530 Subject: [PATCH 1077/1264] fix: Staffing plan vacancies data type issue (#25941) * fix: staffing plan vacancies data type issue * fix: translation issue * fix: removed greater than 0 condition --- .../hr/doctype/staffing_plan/staffing_plan.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 533149a823..e6c783aca2 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -41,7 +41,7 @@ class StaffingPlan(Document): detail.total_estimated_cost = 0 if detail.number_of_positions > 0: - if detail.vacancies > 0 and detail.estimated_cost_per_position: + if detail.vacancies and detail.estimated_cost_per_position: detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position) self.total_estimated_budget += detail.total_estimated_cost @@ -76,12 +76,12 @@ class StaffingPlan(Document): if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or \ flt(staffing_plan_detail.total_estimated_cost) > flt(parent_plan_details[0].total_estimated_cost): frappe.throw(_("You can only plan for upto {0} vacancies and budget {1} \ - for {2} as per staffing plan {3} for parent company {4}." - .format(cint(parent_plan_details[0].vacancies), + for {2} as per staffing plan {3} for parent company {4}.").format( + cint(parent_plan_details[0].vacancies), parent_plan_details[0].total_estimated_cost, frappe.bold(staffing_plan_detail.designation), parent_plan_details[0].name, - parent_company)), ParentCompanyError) + parent_company), ParentCompanyError) #Get vacanices already planned for all companies down the hierarchy of Parent Company lft, rgt = frappe.get_cached_value('Company', parent_company, ["lft", "rgt"]) @@ -98,14 +98,14 @@ class StaffingPlan(Document): (flt(parent_plan_details[0].total_estimated_cost) < \ (flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost))): frappe.throw(_("{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \ - You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}." - .format(cint(all_sibling_details.vacancies), + You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}.").format( + cint(all_sibling_details.vacancies), all_sibling_details.total_estimated_cost, frappe.bold(staffing_plan_detail.designation), parent_company, cint(parent_plan_details[0].vacancies), parent_plan_details[0].total_estimated_cost, - parent_plan_details[0].name))) + parent_plan_details[0].name)) def validate_with_subsidiary_plans(self, staffing_plan_detail): #Valdate this plan with all child company plan @@ -121,11 +121,11 @@ class StaffingPlan(Document): cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies) or \ flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost): frappe.throw(_("Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \ - Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies" - .format(self.company, + Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies").format( + self.company, cint(children_details.vacancies), children_details.total_estimated_cost, - frappe.bold(staffing_plan_detail.designation))), SubsidiaryCompanyError) + frappe.bold(staffing_plan_detail.designation)), SubsidiaryCompanyError) @frappe.whitelist() def get_designation_counts(designation, company): @@ -170,4 +170,4 @@ def get_active_staffing_plan_details(company, designation, from_date=getdate(now designation, from_date, to_date) # Only a single staffing plan can be active for a designation on given date - return staffing_plan if staffing_plan else None \ No newline at end of file + return staffing_plan if staffing_plan else None From d802d7397313a857fca97c8f7f7a190a11fa5e5f Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 23 Jun 2021 14:08:07 +0530 Subject: [PATCH 1078/1264] fix: Consider Website Item Groups in Item group page product listing - Passed an argument to query engine to know when query is for item group page - If for item group page, get data with regards to website item group table - This query should be fast since there's one filter and that shortens the table beforehand - This data is merged with the results from the Item master (results only considering item attributes and field filters) - The combined data is then sorted as per weightage Co-authored-by: Gavin D'souza --- .../setup/doctype/item_group/item_group.py | 2 +- erpnext/shopping_cart/product_query.py | 32 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index db31d6d699..1c72cebfa9 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -91,7 +91,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): field_filters['item_group'] = self.name engine = ProductQuery() - context.items = engine.query(attribute_filters, field_filters, search, start) + context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name) filter_engine = ProductFiltersBuilder(self.name) diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index dd94c26bc6..bb31220447 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -22,13 +22,14 @@ class ProductQuery: self.settings = frappe.get_doc("Products Settings") self.cart_settings = frappe.get_doc("Shopping Cart Settings") self.page_length = self.settings.products_per_page or 20 - self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants', 'item_group', 'image', 'web_long_description', 'description', 'route'] + self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants', + 'item_group', 'image', 'web_long_description', 'description', 'route', 'weightage'] self.filters = [] self.or_filters = [['show_in_website', '=', 1]] if not self.settings.get('hide_variants'): self.or_filters.append(['show_variant_in_website', '=', 1]) - def query(self, attributes=None, fields=None, search_term=None, start=0): + def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None): """Summary Args: @@ -44,6 +45,15 @@ class ProductQuery: if search_term: self.build_search_filters(search_term) result = [] + website_item_groups = [] + + # if from item group page consider website item group table + if item_group: + website_item_groups = frappe.db.get_all( + "Item", + fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"], + filters=[["Website Item Group", "item_group", "=", item_group]] + ) if attributes: all_items = [] @@ -61,12 +71,10 @@ class ProductQuery: ], or_filters=self.or_filters, start=start, - limit=self.page_length, - order_by="weightage desc" + limit=self.page_length ) items_dict = {item.name: item for item in items} - # TODO: Replace Variants by their parent templates all_items.append(set(items_dict.keys())) @@ -78,14 +86,22 @@ class ProductQuery: filters=self.filters, or_filters=self.or_filters, start=start, - limit=self.page_length, - order_by="weightage desc" + limit=self.page_length ) + # Combine results having context of website item groups into item results + if item_group and website_item_groups: + items_list = {row.name for row in result} + for row in website_item_groups: + if row.wig_parent not in items_list: + result.append(row) + + result = sorted(result, key=lambda x: x.get("weightage"), reverse=True) + for item in result: product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info') if product_info: - item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None + item.formatted_price = product_info.get('price', {}).get('formatted_price') return result From 0bfffddac470cb003efba73d4ee6c1bca2620609 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 15:37:17 +0530 Subject: [PATCH 1079/1264] fix: Test Cases --- erpnext/setup/setup_wizard/operations/install_fixtures.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 9c7d9e5259..7dfb9f4d3c 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -483,14 +483,14 @@ def create_bank_account(args): if not args.get('bank_account'): return - company_name = args.company_name + company_name = args.get('company_name') bank_account_group = frappe.db.get_value("Account", {"account_type": "Bank", "is_group": 1, "root_type": "Asset", "company": company_name}) if bank_account_group: bank_account = frappe.get_doc({ "doctype": "Account", - 'account_name': args.bank_account, + 'account_name': args.get('bank_account'), 'parent_account': bank_account_group, 'is_group':0, 'company': company_name, @@ -499,10 +499,10 @@ def create_bank_account(args): try: doc = bank_account.insert() - frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) + frappe.db.set_value("Company", args.get('company_name'), "default_bank_account", bank_account.name, update_modified=False) except RootNotEditable: - frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) + frappe.throw(_("Bank account cannot be named as {0}").format(args.get('bank_account'))) except frappe.DuplicateEntryError: # bank account same as a CoA entry pass From 1b9b72d12eac988f03b1feda17f6524c96ab5b72 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 23 Jun 2021 16:03:24 +0530 Subject: [PATCH 1080/1264] fix: Filters did not consider Website Item Group --- erpnext/shopping_cart/filters.py | 21 +++++++++++++++------ erpnext/shopping_cart/product_query.py | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py index 6c63d8759b..979afd3c13 100644 --- a/erpnext/shopping_cart/filters.py +++ b/erpnext/shopping_cart/filters.py @@ -22,12 +22,15 @@ class ProductFiltersBuilder: filter_data = [] for df in fields: - filters = {} + filters, or_filters = {}, [] if df.fieldtype == "Link": if self.item_group: - filters['item_group'] = self.item_group + or_filters.extend([ + ["item_group", "=", self.item_group], + ["Website Item Group", "item_group", "=", self.item_group] + ]) - values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, distinct="True", pluck=df.fieldname) + values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname, debug=1) else: doctype = df.get_link_doctype() @@ -44,7 +47,9 @@ class ProductFiltersBuilder: values = [d.name for d in frappe.get_all(doctype, filters)] # Remove None - values = values.remove(None) if None in values else values + if None in values: + values.remove(None) + if values: filter_data.append([df, values]) @@ -61,14 +66,18 @@ class ProductFiltersBuilder: for attr_doc in attribute_docs: selected_attributes = [] for attr in attr_doc.item_attribute_values: + or_filters = [] filters= [ ["Item Variant Attribute", "attribute", "=", attr.parent], ["Item Variant Attribute", "attribute_value", "=", attr.attribute_value] ] if self.item_group: - filters.append(["item_group", "=", self.item_group]) + or_filters.extend([ + ["item_group", "=", self.item_group], + ["Website Item Group", "item_group", "=", self.item_group] + ]) - if frappe.db.get_all("Item", filters, limit=1): + if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1): selected_attributes.append(attr) if selected_attributes: diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index bb31220447..0b05f68ae9 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -101,7 +101,7 @@ class ProductQuery: for item in result: product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info') if product_info: - item.formatted_price = product_info.get('price', {}).get('formatted_price') + item.formatted_price = (product_info.get('price') or {}).get('formatted_price') return result From f913e0dd05b41921d8ad8c6f0fa1620bb1adc545 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 23 Jun 2021 20:06:11 +0530 Subject: [PATCH 1081/1264] fix: Consider Table Multiselect fields in Query engine - Since table multiselect fields were not handled, the query tried searching for this child field in item master - This broke the query - On trying to reload or go back to all-products page with field filters that are table mutiselect, page breaks --- erpnext/shopping_cart/product_query.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index 0b05f68ae9..cd4a176921 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -115,6 +115,17 @@ class ProductQuery: if not values: continue + # handle multiselect fields in filter addition + meta = frappe.get_meta('Item', cached=True) + df = meta.get_field(field) + if df.fieldtype == 'Table MultiSelect': + child_doctype = df.options + child_meta = frappe.get_meta(child_doctype, cached=True) + fields = child_meta.get("fields", { "fieldtype": "Link", "in_list_view": 1 }) + if fields: + self.filters.append([child_doctype, fields[0].fieldname, 'IN', values]) + continue + if isinstance(values, list): # If value is a list use `IN` query self.filters.append([field, 'IN', values]) From 078826d510aef7d1a1a0e3bce63c451f1d47e727 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 23 Jun 2021 20:12:59 +0530 Subject: [PATCH 1082/1264] fix: Sider --- erpnext/shopping_cart/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py index 979afd3c13..9f06d20bde 100644 --- a/erpnext/shopping_cart/filters.py +++ b/erpnext/shopping_cart/filters.py @@ -30,7 +30,7 @@ class ProductFiltersBuilder: ["Website Item Group", "item_group", "=", self.item_group] ]) - values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname, debug=1) + values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname, debug=1) else: doctype = df.get_link_doctype() From 1f7b95f39039981fb20e5040d1b9fe68fa78fb59 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 20:56:27 +0530 Subject: [PATCH 1083/1264] fix: User is not able to change item tax template --- .../public/js/controllers/taxes_and_totals.js | 9 +++++---- erpnext/stock/get_item_details.py | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index e5a5fcfe3b..4a14a665cd 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -270,11 +270,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ let me = this; let item_codes = []; let item_rates = {}; + let item_tax_templates = {}; + $.each(this.frm.doc.items || [], function(i, item) { if (item.item_code) { // Use combination of name and item code in case same item is added multiple times item_codes.push([item.item_code, item.name]); item_rates[item.name] = item.net_rate; + item_tax_templates[item.name] = item.item_tax_template } }); @@ -285,18 +288,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ company: me.frm.doc.company, tax_category: cstr(me.frm.doc.tax_category), item_codes: item_codes, + item_tax_templates: item_tax_templates, item_rates: item_rates }, callback: function(r) { if (!r.exc) { $.each(me.frm.doc.items || [], function(i, item) { - if (item.name && r.message.hasOwnProperty(item.name)) { + if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) { item.item_tax_template = r.message[item.name].item_tax_template; item.item_tax_rate = r.message[item.name].item_tax_rate; me.add_taxes_from_item_tax_template(item.item_tax_rate); - } else { - item.item_tax_template = ""; - item.item_tax_rate = "{}"; } }); } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 746cbbf601..bab004ec92 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -436,7 +436,7 @@ def get_barcode_data(items_list): return itemwise_barcode @frappe.whitelist() -def get_item_tax_info(company, tax_category, item_codes, item_rates=None): +def get_item_tax_info(company, tax_category, item_codes, item_tax_templates, item_rates=None): out = {} if isinstance(item_codes, string_types): item_codes = json.loads(item_codes) @@ -444,12 +444,18 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None): if isinstance(item_rates, string_types): item_rates = json.loads(item_rates) + if isinstance(item_tax_templates, string_types): + item_tax_templates = json.loads(item_tax_templates) + for item_code in item_codes: - if not item_code or item_code[1] in out: + if not item_code or item_code[1] in out or not item_tax_templates.get(item_code[1]): continue + out[item_code[1]] = {} item = frappe.get_cached_doc("Item", item_code[0]) - args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]]} + args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]], + "item_tax_template": item_tax_templates.get(item_code[1])} + get_item_tax_template(args, item, out[item_code[1]]) out[item_code[1]]["item_tax_rate"] = get_item_tax_map(company, out[item_code[1]].get("item_tax_template"), as_json=True) @@ -463,9 +469,7 @@ def get_item_tax_template(args, item, out): } """ item_tax_template = args.get("item_tax_template") - - if not item_tax_template: - item_tax_template = _get_item_tax_template(args, item.taxes, out) + item_tax_template = _get_item_tax_template(args, item.taxes, out) if not item_tax_template: item_group = item.item_group @@ -508,7 +512,8 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): return None # do not change if already a valid template - if args.get('item_tax_template') in taxes: + if args.get('item_tax_template') in [t.item_tax_template for t in taxes]: + out["item_tax_template"] = args.get('item_tax_template') return args.get('item_tax_template') for tax in taxes: From c9c1d10435a327db4b19c4529802a01aa19ccf31 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 22:47:29 +0530 Subject: [PATCH 1084/1264] fix: Make item tax templates optional --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index bab004ec92..773a18fbf9 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -436,7 +436,7 @@ def get_barcode_data(items_list): return itemwise_barcode @frappe.whitelist() -def get_item_tax_info(company, tax_category, item_codes, item_tax_templates, item_rates=None): +def get_item_tax_info(company, tax_category, item_codes, item_tax_templates=None, item_rates=None): out = {} if isinstance(item_codes, string_types): item_codes = json.loads(item_codes) From 7e006496dd199c5c46e2e9cc77f2868583c53d16 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 22:52:51 +0530 Subject: [PATCH 1085/1264] fix: Check if item tax template exists --- erpnext/stock/get_item_details.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 773a18fbf9..37850350ab 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -453,8 +453,10 @@ def get_item_tax_info(company, tax_category, item_codes, item_tax_templates=None out[item_code[1]] = {} item = frappe.get_cached_doc("Item", item_code[0]) - args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]], - "item_tax_template": item_tax_templates.get(item_code[1])} + args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]]} + + if item_tax_templates: + args.update({"item_tax_template": item_tax_templates.get(item_code[1])}) get_item_tax_template(args, item, out[item_code[1]]) out[item_code[1]]["item_tax_rate"] = get_item_tax_map(company, out[item_code[1]].get("item_tax_template"), as_json=True) From f5fa1ee4b65c5f38c1398da934fdc2cd25a09777 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 24 Jun 2021 11:03:32 +0530 Subject: [PATCH 1086/1264] fix: Country Link field in 'Add address' website modal auto-clears --- erpnext/templates/includes/cart/cart_address.html | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html index 84a9430956..4482bc10cf 100644 --- a/erpnext/templates/includes/cart/cart_address.html +++ b/erpnext/templates/includes/cart/cart_address.html @@ -99,6 +99,7 @@ frappe.ready(() => { fieldname: 'country', fieldtype: 'Link', options: 'Country', + only_select: true, reqd: 1 }, { From a9b5dc6030c3a25f65793170f1b8a05b78a3ba9a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 24 Jun 2021 11:53:28 +0530 Subject: [PATCH 1087/1264] fix: chart not visible for First Response Time reports (#26032) (#26185) --- .../first_response_time_for_opportunity.js | 7 +++---- .../first_response_time_for_issues.js | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js index 3f5c95ab0a..fe5707af29 100644 --- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js +++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js @@ -22,10 +22,10 @@ frappe.query_reports["First Response Time for Opportunity"] = { get_chart_data: function (_columns, result) { return { data: { - labels: result.map(d => d[0]), + labels: result.map(d => d.creation_date), datasets: [{ name: "First Response Time", - values: result.map(d => d[1]) + values: result.map(d => d.first_response_time) }] }, type: "line", @@ -35,8 +35,7 @@ frappe.query_reports["First Response Time for Opportunity"] = { hide_days: 0, hide_seconds: 0 }; - value = frappe.utils.get_formatted_duration(d, duration_options); - return value; + return frappe.utils.get_formatted_duration(d, duration_options); } } } diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js index 576e0b76da..18691fe264 100644 --- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js +++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js @@ -22,10 +22,10 @@ frappe.query_reports["First Response Time for Issues"] = { get_chart_data: function(_columns, result) { return { data: { - labels: result.map(d => d[0]), + labels: result.map(d => d.creation_date), datasets: [{ name: 'First Response Time', - values: result.map(d => d[1]) + values: result.map(d => d.first_response_time) }] }, type: "line", @@ -35,8 +35,7 @@ frappe.query_reports["First Response Time for Issues"] = { hide_days: 0, hide_seconds: 0 }; - value = frappe.utils.get_formatted_duration(d, duration_options); - return value; + return frappe.utils.get_formatted_duration(d, duration_options); } } } From bbe64b560446e5e812d55a0bb104e0fbb4f2a683 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 24 Jun 2021 12:01:12 +0530 Subject: [PATCH 1088/1264] fix: (style) Address card buttons hover state --- erpnext/public/scss/shopping_cart.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 9402cf9ea4..5962859be5 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -467,11 +467,15 @@ body.product-page { .btn-change-address { color: var(--blue-500); - box-shadow: none; - border: 1px solid var(--blue-500); } } +.btn-new-address:hover, .btn-change-address:hover { + box-shadow: none; + color: var(--blue-500) !important; + border: 1px solid var(--blue-500); +} + .modal .address-card { .card-body { padding: var(--padding-sm); From bd9317956beb1ffe4aaefd59cee72f39d9a7ad4f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 22 Jun 2021 19:48:08 +0530 Subject: [PATCH 1089/1264] fix: Taxes on Internal Transfer payment entry --- 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 b6b2bef963..adaf99a790 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -706,7 +706,7 @@ class PaymentEntry(AccountsController): if account_currency != self.company_currency: frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency)) - if self.payment_type == 'Pay': + if self.payment_type in ('Pay', 'Internal Transfer'): dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" elif self.payment_type == 'Receive': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" @@ -761,7 +761,7 @@ class PaymentEntry(AccountsController): return self.advance_tax_account elif self.payment_type == 'Receive': return self.paid_from - elif self.payment_type == 'Pay': + elif self.payment_type in ('Pay', 'Internal Transfer'): return self.paid_to def update_advance_paid(self): From 9d8e8f8bdfabbbc8b3721d354bf441098b212933 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 22 Jun 2021 20:38:35 +0530 Subject: [PATCH 1090/1264] fix: Do not show received amount after tax for internal tarnsfers --- erpnext/accounts/doctype/payment_entry/payment_entry.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 54623dd6cd..51f18a5a4e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -690,7 +690,7 @@ "options": "Account" }, { - "depends_on": "eval:doc.received_amount", + "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'", "fieldname": "received_amount_after_tax", "fieldtype": "Currency", "label": "Received Amount After Tax", @@ -707,7 +707,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-09 11:55:04.215050", + "modified": "2021-06-22 20:37:06.154206", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From a4f5dcaa9ab610c10de24a1bc0c835cd615d6a09 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 23 Jun 2021 22:38:10 +0530 Subject: [PATCH 1091/1264] chore: Test for Item visibility in multiple item group pages --- .../test_product_configurator.py | 63 +++++++++++++++++++ erpnext/shopping_cart/filters.py | 2 +- erpnext/shopping_cart/product_query.py | 6 +- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index 3521e7e8bf..daaba67173 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -43,6 +43,30 @@ class TestProductConfigurator(unittest.TestCase): "show_variant_in_website": 1 }).insert() + def create_regular_web_item(self, name, item_group=None): + if not frappe.db.exists('Item', name): + doc = frappe.get_doc({ + "description": name, + "item_code": name, + "item_name": name, + "doctype": "Item", + "is_stock_item": 1, + "item_group": item_group or "_Test Item Group", + "stock_uom": "_Test UOM", + "item_defaults": [{ + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + }], + "show_in_website": 1 + }).insert() + else: + doc = frappe.get_doc("Item", name) + return doc + def test_product_list(self): template_items = frappe.get_all('Item', {'show_in_website': 1}) variant_items = frappe.get_all('Item', {'show_variant_in_website': 1}) @@ -79,3 +103,42 @@ class TestProductConfigurator(unittest.TestCase): 'Test Size': ['2XL'] }) self.assertEqual(len(items), 1) + + def test_products_in_multiple_item_groups(self): + """Check if product is visible on multiple item group pages barring its own.""" + from erpnext.shopping_cart.product_query import ProductQuery + + if not frappe.db.exists("Item Group", {"name": "Tech Items"}): + item_group_doc = frappe.get_doc({ + "doctype": "Item Group", + "item_group_name": "Tech Items", + "parent_item_group": "All Item Groups", + "show_in_website": 1 + }).insert() + else: + item_group_doc = frappe.get_doc("Item Group", "Tech Items") + + doc = self.create_regular_web_item("Portal Item", item_group="Tech Items") + if not frappe.db.exists("Website Item Group", {"parent": "Portal Item"}): + doc.append("website_item_groups", { + "item_group": "_Test Item Group Desktops" + }) + doc.save() + + # check if item is visible in its own Item Group's page + engine = ProductQuery() + items = engine.query({}, {"item_group": "Tech Items"}, None, start=0, item_group="Tech Items") + self.assertEqual(len(items), 1) + self.assertEqual(items[0].item_code, "Portal Item") + + # check if item is visible in configured foreign Item Group's page + engine = ProductQuery() + items = engine.query({}, {"item_group": "_Test Item Group Desktops"}, None, start=0, item_group="_Test Item Group Desktops") + item_codes = [row.item_code for row in items] + + self.assertIn(len(items), [2, 3]) + self.assertIn("Portal Item", item_codes) + + # teardown + doc.delete() + item_group_doc.delete() \ No newline at end of file diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py index 9f06d20bde..7dfa09e2d6 100644 --- a/erpnext/shopping_cart/filters.py +++ b/erpnext/shopping_cart/filters.py @@ -30,7 +30,7 @@ class ProductFiltersBuilder: ["Website Item Group", "item_group", "=", self.item_group] ]) - values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname, debug=1) + values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname) else: doctype = df.get_link_doctype() diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index cd4a176921..d96d803416 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -121,12 +121,10 @@ class ProductQuery: if df.fieldtype == 'Table MultiSelect': child_doctype = df.options child_meta = frappe.get_meta(child_doctype, cached=True) - fields = child_meta.get("fields", { "fieldtype": "Link", "in_list_view": 1 }) + fields = child_meta.get("fields") if fields: self.filters.append([child_doctype, fields[0].fieldname, 'IN', values]) - continue - - if isinstance(values, list): + elif isinstance(values, list): # If value is a list use `IN` query self.filters.append([field, 'IN', values]) else: From ca2e9147151546390e1ff5dcd964b7842dc808c2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 24 Jun 2021 14:14:46 +0530 Subject: [PATCH 1092/1264] fix: Error while booking deferred revenue --- erpnext/accounts/deferred_revenue.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index dd346bc240..2f86c6c1de 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -263,6 +263,9 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): amount, base_amount = calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency) + if not amount: + return + if via_journal_entry: book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry) From 98e98d25e652bc114e2f59e58c3621887f6f7700 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 24 Jun 2021 14:24:28 +0530 Subject: [PATCH 1093/1264] fix(Work Order): added freeze when trying to stop work order (#26192) (#26196) * fix: added freeze when trying to stop work order * fix(ux): add freeze message Co-authored-by: Noah Jacob --- erpnext/manufacturing/doctype/work_order/work_order.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 3e5a72db9a..8088d930df 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -704,6 +704,8 @@ erpnext.work_order = { stop_work_order: function(frm, status) { frappe.call({ method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop", + freeze: true, + freeze_message: __("Updating Work Order status"), args: { work_order: frm.doc.name, status: status From 7fd44907ba382ef2cb6778183a0bd7801af1b7a2 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:26:36 +0530 Subject: [PATCH 1094/1264] feat: fetching of qty as per received qty from PR to PI (#26184) --- .../doctype/buying_settings/buying_settings.json | 14 +++++++++++--- erpnext/controllers/accounts_controller.py | 10 ++++++++-- erpnext/patches.txt | 1 + ...ll_for_rejected_quantity_in_purchase_invoice.py | 8 ++++++++ .../doctype/purchase_receipt/purchase_receipt.py | 8 ++++++-- .../purchase_receipt/test_purchase_receipt.py | 7 +++++++ 6 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 630a1dc8cd..b9c77d59b1 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -9,13 +9,14 @@ "supp_master_name", "supplier_group", "buying_price_list", + "maintain_same_rate_action", + "role_to_override_stop_action", "column_break_3", "po_required", "pr_required", "maintain_same_rate", - "maintain_same_rate_action", - "role_to_override_stop_action", "allow_multiple_items", + "bill_for_rejected_quantity_in_purchase_invoice", "subcontract", "backflush_raw_materials_of_subcontract_based_on", "column_break_11", @@ -108,6 +109,13 @@ "fieldtype": "Link", "label": "Role Allowed to Override Stop Action", "options": "Role" + }, + { + "default": "1", + "description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.", + "fieldname": "bill_for_rejected_quantity_in_purchase_invoice", + "fieldtype": "Check", + "label": "Bill for Rejected Quantity in Purchase Invoice" } ], "icon": "fa fa-cog", @@ -115,7 +123,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-04 20:01:44.087066", + "modified": "2021-06-24 10:38:28.934525", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 243939b275..1c086e9edc 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -828,8 +828,14 @@ class AccountsController(TransactionBase): role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles(): - frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") - .format(item.item_code, item.idx, max_allowed_amt)) + if self.doctype != "Purchase Invoice": + self.throw_overbill_exception(item, max_allowed_amt) + elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): + self.throw_overbill_exception(item, max_allowed_amt) + + def throw_overbill_exception(self, item, max_allowed_amt): + frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") + .format(item.item_code, item.idx, max_allowed_amt)) def get_company_default(self, fieldname): from erpnext.accounts.utils import get_company_default diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ed6fefdd87..dd0e33beba 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -288,3 +288,4 @@ execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True) erpnext.patches.v13_0.update_timesheet_changes erpnext.patches.v13_0.set_training_event_attendance erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold +erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice \ No newline at end of file diff --git a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py new file mode 100644 index 0000000000..be85cfdeef --- /dev/null +++ b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doctype("Buying Settings") + buying_settings = frappe.get_single("Buying Settings") + buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0 + buying_settings.save() \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index b8580f95a3..e488b695b5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -581,7 +581,6 @@ def update_billing_percentage(pr_doc, update_modified=True): @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): - from frappe.model.mapper import get_mapped_doc from erpnext.accounts.party import get_payment_terms_template doc = frappe.get_doc('Purchase Receipt', source_name) @@ -601,11 +600,16 @@ def make_purchase_invoice(source_name, target_doc=None): def update_item(source_doc, target_doc, source_parent): target_doc.qty, returned_qty = get_pending_qty(source_doc) + if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"): + target_doc.rejected_qty = 0 target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor")) returned_qty_map[source_doc.name] = returned_qty def get_pending_qty(item_row): - pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) + qty = item_row.qty + if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"): + qty = item_row.received_qty + pending_qty = qty - invoiced_qty_map.get(item_row.name, 0) returned_qty = flt(returned_qty_map.get(item_row.name, 0)) if returned_qty: if returned_qty >= pending_qty: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 95096d77d7..07c5da1dca 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -421,11 +421,18 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(return_pr_2.items[0].qty, -3) # Make PI against unreturned amount + buying_settings = frappe.get_single("Buying Settings") + buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0 + buying_settings.save() + pi = make_purchase_invoice(pr.name) pi.submit() self.assertEqual(pi.items[0].qty, 3) + buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 1 + buying_settings.save() + pr.load_from_db() # PR should be completed on billing all unreturned amount self.assertEqual(pr.items[0].billed_amt, 150) From 54cc1dedf2138a41fbd2d3a9247a4970fb69572c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 22 Jun 2021 21:18:20 +0530 Subject: [PATCH 1095/1264] refactor(pos): use pos invoice item name as unique identifier --- .../page/point_of_sale/pos_controller.js | 123 ++++++++++-------- .../page/point_of_sale/pos_item_cart.js | 29 +---- .../page/point_of_sale/pos_item_details.js | 86 +++++------- .../page/point_of_sale/pos_item_selector.js | 6 +- 4 files changed, 113 insertions(+), 131 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index ae3f9e3c9d..4c938756c7 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -241,8 +241,8 @@ erpnext.PointOfSale.Controller = class { events: { get_frm: () => this.frm, - cart_item_clicked: (item_code, batch_no, uom, rate) => { - const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); + cart_item_clicked: (item) => { + const item_row = this.get_item_from_frm(item); this.item_details.toggle_item_details_section(item_row); }, @@ -273,17 +273,16 @@ erpnext.PointOfSale.Controller = class { this.cart.toggle_numpad(minimize); }, - form_updated: (cdt, cdn, fieldname, value) => { - const item_row = frappe.model.get_doc(cdt, cdn); - if (item_row && item_row[fieldname] != value) { + form_updated: (item, field, value) => { + const item_row = frappe.model.get_doc(item.doctype, item.name); + if (item_row && item_row[field] != value) { - const { item_code, batch_no, uom, rate } = this.item_details.current_item; - const event = { - field: fieldname, + const args = { + field, value, - item: { item_code, batch_no, uom, rate } + item: this.item_details.current_item } - return this.on_cart_update(event) + return this.on_cart_update(args) } return Promise.resolve(); @@ -300,19 +299,18 @@ erpnext.PointOfSale.Controller = class { 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) => { + clone_new_batch_item_in_frm: (batch_serial_map, 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 item_to_clone = this.frm.doc.items.find(i => i.name == item.name); 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) { + if (item.item_code === row.item_code) { this.update_cart_html(row); } }); @@ -321,8 +319,8 @@ erpnext.PointOfSale.Controller = class { 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.item_details.toggle_item_details_section(null); + this.cart.prev_action = null; this.cart.toggle_item_highlight(); }, get_available_stock: (item_code, warehouse) => this.get_available_stock(item_code, warehouse) @@ -506,50 +504,47 @@ erpnext.PointOfSale.Controller = class { let item_row = undefined; try { let { field, value, item } = args; - const { item_code, batch_no, serial_no, uom, rate } = item; - item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); + item_row = this.get_item_from_frm(item); + const item_row_exists = !$.isEmptyObject(item_row); - const item_selected_from_selector = field === 'qty' && value === "+1" + const from_selector = field === 'qty' && value === "+1"; + if (from_selector) + value = flt(item_row.qty) + flt(value); - if (item_row) { - item_selected_from_selector && (value = item_row.qty + flt(value)) - - field === 'qty' && (value = flt(value)); + if (item_row_exists) { + if (field === 'qty') + value = flt(value); if (['qty', 'conversion_factor'].includes(field) && value > 0 && !this.allow_negative_stock) { const qty_needed = field === 'qty' ? value * item_row.conversion_factor : item_row.qty * value; await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse); } - if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) { + if (this.is_current_item_being_edited(item_row) || 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"); + if (!this.frm.doc.customer) + return this.raise_customer_selection_alert(); + + const { item_code, batch_no, serial_no, rate } = item; + + if (!item_code) return; - } - if (!item_code) return; - item_selected_from_selector && (value = flt(value)) - - const args = { item_code, batch_no, rate, [field]: value }; + const new_item = { item_code, batch_no, rate, [field]: value }; if (serial_no) { await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no); - args['serial_no'] = serial_no; + new_item['serial_no'] = serial_no; } - if (field === 'serial_no') args['qty'] = value.split(`\n`).length || 0; + if (field === 'serial_no') + new_item['qty'] = value.split(`\n`).length || 0; - item_row = this.frm.add_child('items', args); + item_row = this.frm.add_child('items', new_item); if (field === 'qty' && value !== 0 && !this.allow_negative_stock) await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); @@ -558,8 +553,11 @@ erpnext.PointOfSale.Controller = class { this.update_cart_html(item_row); - this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row); - this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row); + if (this.item_details.$component.is(':visible')) + this.edit_item_details_of(item_row); + + if (this.check_serial_batch_selection_needed(item_row)) + this.edit_item_details_of(item_row); } } catch (error) { @@ -570,14 +568,33 @@ erpnext.PointOfSale.Controller = class { } } - get_item_from_frm(item_code, batch_no, uom, rate) { - 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) - && (i.rate == rate) - ); + raise_customer_selection_alert() { + frappe.dom.unfreeze(); + frappe.show_alert({ + message: __('You must select a customer before adding an item.'), + indicator: 'orange' + }); + frappe.utils.play_sound("error"); + } + + get_item_from_frm({ name, item_code, batch_no, uom, rate }) { + let item_row = null; + if (name) { + item_row = this.frm.doc.items.find(i => i.name == name); + } else { + // if item is clicked twice from item selector + // then "item_code, batch_no, uom, rate" will help in getting the exact item + // to increase the qty by one + const has_batch_no = batch_no; + item_row = 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) + && (i.rate == rate) + ); + } + + return item_row || {}; } edit_item_details_of(item_row) { @@ -585,9 +602,7 @@ erpnext.PointOfSale.Controller = class { } 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; + return item_row.name == this.item_details.current_item.name; } update_cart_html(item_row, remove_item) { @@ -669,7 +684,7 @@ erpnext.PointOfSale.Controller = class { update_item_field(value, field_or_action) { if (field_or_action === 'checkout') { - this.item_details.toggle_item_details_section(undefined); + this.item_details.toggle_item_details_section(null); } else if (field_or_action === 'remove') { this.remove_item_from_cart(); } else { @@ -688,7 +703,7 @@ erpnext.PointOfSale.Controller = class { .then(() => { frappe.model.clear_doc(doctype, name); this.update_cart_html(current_item, true); - this.item_details.toggle_item_details_section(undefined); + this.item_details.toggle_item_details_section(null); frappe.dom.unfreeze(); }) .catch(e => console.log(e)); diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index f5019f5083..9de7beff46 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -181,11 +181,8 @@ erpnext.PointOfSale.ItemCart = class { 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')); - const rate = unescape($cart_item.attr('data-rate')); - me.events.cart_item_clicked(item_code, batch_no, uom, rate); + const item_row_name = unescape($cart_item.attr('data-row-name')); + me.events.cart_item_clicked({ name: item_row_name }); this.numpad_value = ''; }); @@ -521,25 +518,14 @@ erpnext.PointOfSale.ItemCart = class { } } - get_cart_item({ item_code, batch_no, uom, rate }) { - 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 rate_attr = `[data-rate="${escape(rate)}"]`; - - const item_selector = batch_no ? - `.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`; - + get_cart_item({ name }) { + const item_selector = `.cart-item-wrapper[data-row-name="${escape(name)}"]`; return this.$cart_items_wrapper.find(item_selector); } get_item_from_frm(item) { const doc = this.events.get_frm().doc; - const { item_code, batch_no, uom, rate } = item; - const search_field = batch_no ? 'batch_no' : 'item_code'; - const search_value = batch_no || item_code; - - return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate); + return doc.items.find(i => i.name == item.name); } update_item_html(item, remove_item) { @@ -564,10 +550,7 @@ erpnext.PointOfSale.ItemCart = class { if (!$item_to_update.length) { this.$cart_items_wrapper.append( - `
    -
    + `
    ` ) $item_to_update = this.get_cart_item(item_data); diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 5e09df8efe..43a29b9c75 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -54,36 +54,28 @@ erpnext.PointOfSale.ItemDetails = class { this.$dicount_section = this.$component.find('.discount-section'); } - has_item_has_changed(item) { - const { item_code, batch_no, uom, rate } = 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; - const rate_is_same = item && rate === item.rate; - - if (!item) - return false; - - if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same) - return false; - - return true; + compare_with_current_item(item) { + // returns true if `item` is currently being edited + return item && item.name == this.current_item.name } toggle_item_details_section(item) { - this.item_has_changed = this.has_item_has_changed(item); + const current_item_changed = !this.compare_with_current_item(item); - this.events.toggle_item_selector(this.item_has_changed); - this.toggle_component(this.item_has_changed); + // if item is null or highlighted cart item is clicked twice + const hide_item_details = !Boolean(item) || !current_item_changed; + + this.events.toggle_item_selector(!hide_item_details); + this.toggle_component(!hide_item_details); - if (this.item_has_changed) { + if (item && current_item_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, rate: item.rate }; + this.current_item = item this.render_dom(item); this.render_discount_dom(item); @@ -180,7 +172,7 @@ erpnext.PointOfSale.ItemDetails = class { df: { ...field_meta, onchange: function() { - me.events.form_updated(me.doctype, me.name, fieldname, this.value); + me.events.form_updated(me.current_item, fieldname, this.value); } }, parent: this.$form_container.find(`.${fieldname}-control`), @@ -218,22 +210,17 @@ erpnext.PointOfSale.ItemDetails = class { bind_custom_control_change_event() { const me = this; if (this.rate_control) { - if (this.allow_rate_change) { - this.rate_control.df.onchange = function() { - if (this.value || flt(this.value) === 0) { - me.events.set_value_in_current_cart_item('rate', 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); - }); - me.current_item.rate = this.value; - } - }; - } else { - this.rate_control.df.read_only = 1; - } + this.rate_control.df.onchange = function() { + if (this.value || flt(this.value) === 0) { + me.events.form_updated(me.current_item, '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); + }); + } + }; + this.rate_control.df.read_only = !this.allow_rate_change; this.rate_control.refresh(); } @@ -246,7 +233,7 @@ erpnext.PointOfSale.ItemDetails = class { 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.events.form_updated(me.current_item, '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) { @@ -278,7 +265,7 @@ erpnext.PointOfSale.ItemDetails = class { 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); + me.events.form_updated(me.current_item, 'serial_no', this.value); } this.serial_no_control.refresh(); } @@ -295,19 +282,12 @@ erpnext.PointOfSale.ItemDetails = class { } } }; - 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; + me.events.form_updated(me.current_item, 'uom', this.value); const item_row = frappe.get_doc(me.doctype, me.name); me.conversion_factor_control.df.read_only = (item_row.stock_uom == this.value); @@ -317,9 +297,9 @@ erpnext.PointOfSale.ItemDetails = class { frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { const field_control = this[`${fieldname}_control`]; - const item_is_same = !this.has_item_has_changed(item_row); + const item_row_is_being_edited = this.compare_with_current_item(item_row); - if (item_is_same && field_control && field_control.get_value() !== value) { + if (item_row_is_being_edited && field_control && field_control.get_value() !== value) { field_control.set_value(value); cur_pos.update_cart_html(item_row); } @@ -337,7 +317,9 @@ erpnext.PointOfSale.ItemDetails = class { fields: ["batch_no", "name"] }); const batch_serial_map = serials_with_batch_no.reduce((acc, r) => { - acc[r.batch_no] || (acc[r.batch_no] = []); + if (!acc[r.batch_no]) { + acc[r.batch_no] = []; + } acc[r.batch_no] = [...acc[r.batch_no], r.name]; return acc; }, {}); @@ -353,12 +335,10 @@ erpnext.PointOfSale.ItemDetails = class { 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) + delete batch_serial_map[batch_no]; this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item); + } } } diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 64c529ee4a..dd7f143c4c 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -232,7 +232,11 @@ erpnext.PointOfSale.ItemSelector = class { uom = uom === "undefined" ? undefined : uom; rate = rate === "undefined" ? undefined : rate; - me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }}); + me.events.item_selected({ + field: 'qty', + value: "+1", + item: { item_code, batch_no, serial_no, uom, rate } + }); me.set_search_value(''); }); From ea70f6f933b4ef6c1d1ec257244d67320e85cea7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 22 Jun 2021 21:18:44 +0530 Subject: [PATCH 1096/1264] fix: hide images from cart & details --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 2 +- erpnext/selling/page/point_of_sale/pos_item_details.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 9de7beff46..7cae0e4797 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -625,7 +625,7 @@ erpnext.PointOfSale.ItemCart = class { function get_item_image_html() { const { image, item_name } = item_data; - if (image) { + if (!me.hide_images && image) { return `
    Date: Thu, 24 Jun 2021 14:29:22 +0530 Subject: [PATCH 1097/1264] fix: add missing semicolons --- erpnext/selling/page/point_of_sale/pos_controller.js | 5 ++--- erpnext/selling/page/point_of_sale/pos_item_details.js | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 4c938756c7..f5c5a0ae09 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -276,14 +276,13 @@ erpnext.PointOfSale.Controller = class { form_updated: (item, field, value) => { const item_row = frappe.model.get_doc(item.doctype, item.name); if (item_row && item_row[field] != value) { - const args = { field, value, item: this.item_details.current_item - } + }; return this.on_cart_update(args) - } + }; return Promise.resolve(); }, diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 637fb908a8..6a4d3d5214 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -57,7 +57,7 @@ erpnext.PointOfSale.ItemDetails = class { compare_with_current_item(item) { // returns true if `item` is currently being edited - return item && item.name == this.current_item.name + return item && item.name == this.current_item.name; } toggle_item_details_section(item) { @@ -76,7 +76,7 @@ erpnext.PointOfSale.ItemDetails = class { this.item_row = item; this.currency = this.events.get_frm().doc.currency; - this.current_item = item + this.current_item = item; this.render_dom(item); this.render_discount_dom(item); From 3b126014613d2ecdc97493b47985bbd628e78451 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 24 Jun 2021 15:01:33 +0530 Subject: [PATCH 1098/1264] fix: sider issues --- erpnext/selling/page/point_of_sale/pos_controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index f5c5a0ae09..c827368dbf 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -281,8 +281,8 @@ erpnext.PointOfSale.Controller = class { value, item: this.item_details.current_item }; - return this.on_cart_update(args) - }; + return this.on_cart_update(args); + } return Promise.resolve(); }, From dbdf2515cd92669ed2ed0c6b71302d5ee6ad89a3 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 23 Jun 2021 09:54:12 +0530 Subject: [PATCH 1099/1264] fix: fetches correct preferred shipping address --- erpnext/accounts/custom/address.py | 2 ++ erpnext/public/js/utils/party.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py index 5e764037a7..c417a493c6 100644 --- a/erpnext/accounts/custom/address.py +++ b/erpnext/accounts/custom/address.py @@ -33,6 +33,8 @@ def get_shipping_address(company, address = None): if address and frappe.db.get_value('Dynamic Link', {'parent': address, 'link_name': company}): filters.append(["Address", "name", "=", address]) + if not address: + filters.append(["Address", "is_shipping_address", "=", 1]) address = frappe.get_all("Address", filters=filters, fields=fields) or {} diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 808dd5add0..99c8587391 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -274,9 +274,9 @@ erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) { return true; } -erpnext.utils.get_shipping_address = function(frm, callback){ +erpnext.utils.get_shipping_address = function(frm, callback) { if (frm.doc.company) { - if (!(frm.doc.inter_com_order_reference || frm.doc.internal_invoice_reference || + if ((frm.doc.inter_com_order_reference || frm.doc.internal_invoice_reference || frm.doc.internal_order_reference)) { if (callback) { return callback(); From da82bd4b51ff2670a5041bef3e28005adb39d2df Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 24 Jun 2021 17:23:21 +0530 Subject: [PATCH 1100/1264] refactor: update cost updates operation time and hour rates in BOM (#25891) * refactor: updates hour_rate and operation time on update cost * refactor: hour_rates are updated in routing when updated in workstations * test: test cases for updating hour_rates and operation time in linked bom --- erpnext/manufacturing/doctype/bom/bom.py | 45 +++++++++----- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../manufacturing/doctype/routing/routing.py | 14 ++++- .../doctype/routing/test_routing.py | 58 +++++++++++++++-- .../doctype/workstation/test_workstation.py | 62 ++++++++++++++++++- .../doctype/workstation/workstation.py | 5 +- 6 files changed, 157 insertions(+), 29 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index d1f63854c7..3f109d91b5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -81,7 +81,7 @@ class BOM(WebsiteGenerator): self.validate_operations() self.calculate_cost() self.update_stock_qty() - self.update_cost(update_parent=False, from_child_bom=True, save=False) + self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False) def get_context(self, context): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] @@ -213,7 +213,7 @@ class BOM(WebsiteGenerator): return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1) @frappe.whitelist() - def update_cost(self, update_parent=True, from_child_bom=False, save=True): + def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate = True, save=True): if self.docstatus == 2: return @@ -242,7 +242,7 @@ class BOM(WebsiteGenerator): if self.docstatus == 1: self.flags.ignore_validate_update_after_submit = True - self.calculate_cost() + self.calculate_cost(update_hour_rate) if save: self.db_update() @@ -403,32 +403,47 @@ class BOM(WebsiteGenerator): bom_list.reverse() return bom_list - def calculate_cost(self): + def calculate_cost(self, update_hour_rate = False): """Calculate bom totals""" - self.calculate_op_cost() + self.calculate_op_cost(update_hour_rate) self.calculate_rm_cost() self.calculate_sm_cost() self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost self.base_total_cost = self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost - def calculate_op_cost(self): + def calculate_op_cost(self, update_hour_rate = False): """Update workstation rate and calculates totals""" self.operating_cost = 0 self.base_operating_cost = 0 for d in self.get('operations'): if d.workstation: - if not d.hour_rate: - hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate")) - d.hour_rate = hour_rate / flt(self.conversion_rate) if self.conversion_rate else hour_rate - - if d.hour_rate and d.time_in_mins: - d.base_hour_rate = flt(d.hour_rate) * flt(self.conversion_rate) - d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0 - d.base_operating_cost = flt(d.operating_cost) * flt(self.conversion_rate) + self.update_rate_and_time(d, update_hour_rate) self.operating_cost += flt(d.operating_cost) self.base_operating_cost += flt(d.base_operating_cost) + def update_rate_and_time(self, row, update_hour_rate = False): + if not row.hour_rate or update_hour_rate: + hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate")) + row.hour_rate = (hour_rate / flt(self.conversion_rate) + if self.conversion_rate and hour_rate else hour_rate) + + if self.routing: + row.time_in_mins = flt(frappe.db.get_value("BOM Operation", { + "workstation": row.workstation, + "operation": row.operation, + "sequence_id": row.sequence_id, + "parent": self.routing + }, ["time_in_mins"])) + + if row.hour_rate and row.time_in_mins: + row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate) + row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 + row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate) + + if update_hour_rate: + row.db_update() + def calculate_rm_cost(self): """Fetch RM rate as per today's valuation rate and calculate totals""" total_rm_cost = 0 @@ -975,7 +990,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if filters and filters.get("is_stock_item"): query_filters["is_stock_item"] = 1 - + return frappe.get_all("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 42b23f223d..1f443fb95a 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -123,7 +123,7 @@ class TestBOM(unittest.TestCase): bom.items[0].conversion_factor = 5 bom.insert() - bom.update_cost() + bom.update_cost(update_hour_rate = False) # test amounts in selected currency self.assertEqual(bom.items[0].rate, 300) diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py index 8312d7436c..ece0db717a 100644 --- a/erpnext/manufacturing/doctype/routing/routing.py +++ b/erpnext/manufacturing/doctype/routing/routing.py @@ -4,14 +4,24 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint +from frappe.utils import cint, flt from frappe import _ from frappe.model.document import Document class Routing(Document): def validate(self): + self.calculate_operating_cost() self.set_routing_id() + def on_update(self): + self.calculate_operating_cost() + + def calculate_operating_cost(self): + for operation in self.operations: + if not operation.hour_rate: + operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, 'hour_rate') + operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60, 2) + def set_routing_id(self): sequence_id = 0 for row in self.operations: @@ -21,4 +31,4 @@ class Routing(Document): frappe.throw(_("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}") .format(row.idx, row.sequence_id, sequence_id)) - sequence_id = row.sequence_id \ No newline at end of file + sequence_id = row.sequence_id diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 6a38dcfa03..92f26946ab 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -7,9 +7,7 @@ import unittest import frappe from frappe.test_runner import make_test_records from erpnext.stock.doctype.item.test_item import make_item -from erpnext.manufacturing.doctype.operation.test_operation import make_operation from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError -from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record class TestRouting(unittest.TestCase): @@ -48,7 +46,53 @@ class TestRouting(unittest.TestCase): wo_doc.cancel() wo_doc.delete() + def test_update_bom_operation_time(self): + operations = [ + { + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "hour_rate_rent": 300, + "hour_rate_labour": 750 , + "time_in_mins": 30 + }, + { + "operation": "Test Operation B", + "workstation": "_Test Workstation B", + "hour_rate_labour": 200, + "hour_rate_rent": 1000, + "time_in_mins": 20 + } + ] + + test_routing_operations = [ + { + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "time_in_mins": 30 + }, + { + "operation": "Test Operation B", + "workstation": "_Test Workstation A", + "time_in_mins": 20 + } + ] + setup_operations(operations) + routing_doc = create_routing(routing_name="Routing Test", operations=test_routing_operations) + bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency = 'INR') + self.assertEqual(routing_doc.operations[0].time_in_mins, 30) + self.assertEqual(routing_doc.operations[1].time_in_mins, 20) + routing_doc.operations[0].time_in_mins = 90 + routing_doc.operations[1].time_in_mins = 42.2 + routing_doc.save() + bom_doc.update_cost() + bom_doc.reload() + self.assertEqual(bom_doc.operations[0].time_in_mins, 90) + self.assertEqual(bom_doc.operations[1].time_in_mins, 42.2) + + def setup_operations(rows): + from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation + from erpnext.manufacturing.doctype.operation.test_operation import make_operation for row in rows: make_workstation(row) make_operation(row) @@ -61,12 +105,14 @@ def create_routing(**args): if not args.do_not_save: try: - for operation in args.operations: - doc.append("operations", operation) - doc.insert() except frappe.DuplicateEntryError: doc = frappe.get_doc("Routing", args.routing_name) + doc.delete_key('operations') + for operation in args.operations: + doc.append("operations", operation) + + doc.save() return doc @@ -91,7 +137,7 @@ def setup_bom(**args): name = frappe.db.get_value('BOM', {'item': args.item_code}, 'name') if not name: bom_doc = make_bom(item = args.item_code, raw_materials = args.get("raw_materials"), - routing = args.routing, with_operations=1) + routing = args.routing, with_operations=1, currency = args.currency) else: bom_doc = frappe.get_doc("BOM", name) diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py index c6699bee48..9b73aca601 100644 --- a/erpnext/manufacturing/doctype/workstation/test_workstation.py +++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py @@ -1,16 +1,19 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt from __future__ import unicode_literals +from erpnext.manufacturing.doctype.operation.test_operation import make_operation import frappe import unittest from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours, NotInWorkingHoursError, WorkstationHolidayError +from erpnext.manufacturing.doctype.routing.test_routing import setup_bom, create_routing +from frappe.test_runner import make_test_records test_dependencies = ["Warehouse"] test_records = frappe.get_test_records('Workstation') +make_test_records('Workstation') class TestWorkstation(unittest.TestCase): - def test_validate_timings(self): check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00") check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00") @@ -21,6 +24,58 @@ class TestWorkstation(unittest.TestCase): self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours, "_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00") + def test_update_bom_operation_rate(self): + operations = [ + { + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "hour_rate_rent": 300, + "time_in_mins": 60 + }, + { + "operation": "Test Operation B", + "workstation": "_Test Workstation B", + "hour_rate_rent": 1000, + "time_in_mins": 60 + } + ] + + for row in operations: + make_workstation(row) + make_operation(row) + + test_routing_operations = [ + { + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "time_in_mins": 60 + }, + { + "operation": "Test Operation B", + "workstation": "_Test Workstation A", + "time_in_mins": 60 + } + ] + routing_doc = create_routing(routing_name = "Routing Test", operations=test_routing_operations) + bom_doc = setup_bom(item_code="_Testing Item", routing=routing_doc.name, currency="INR") + w1 = frappe.get_doc("Workstation", "_Test Workstation A") + #resets values + w1.hour_rate_rent = 300 + w1.hour_rate_labour = 0 + w1.save() + bom_doc.update_cost() + bom_doc.reload() + self.assertEqual(w1.hour_rate, 300) + self.assertEqual(bom_doc.operations[0].hour_rate, 300) + w1.hour_rate_rent = 250 + w1.save() + #updating after setting new rates in workstations + bom_doc.update_cost() + bom_doc.reload() + self.assertEqual(w1.hour_rate, 250) + self.assertEqual(bom_doc.operations[0].hour_rate, 250) + self.assertEqual(bom_doc.operations[1].hour_rate, 250) + def make_workstation(*args, **kwargs): args = args if args else kwargs if isinstance(args, tuple): @@ -34,9 +89,10 @@ def make_workstation(*args, **kwargs): "doctype": "Workstation", "workstation_name": workstation_name }) - + doc.hour_rate_rent = args.get("hour_rate_rent") + doc.hour_rate_labour = args.get("hour_rate_labour") doc.insert() return doc except frappe.DuplicateEntryError: - return frappe.get_doc("Workstation", workstation_name) \ No newline at end of file + return frappe.get_doc("Workstation", workstation_name) diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index 3512e59045..f4483f7547 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -39,7 +39,8 @@ class Workstation(Document): def update_bom_operation(self): bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation` - where workstation = %s""", self.name) + where workstation = %s and parenttype = 'routing' """, self.name) + for bom_no in bom_list: frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s where parent = %s and workstation = %s""", @@ -71,7 +72,7 @@ def check_if_within_operating_hours(workstation, operation, from_datetime, to_da def is_within_operating_hours(workstation, operation, from_datetime, to_datetime): operation_length = time_diff_in_seconds(to_datetime, from_datetime) workstation = frappe.get_doc("Workstation", workstation) - + if not workstation.working_hours: return From 755ebdf5828f31c0f8558bcceb20b4b1605586d7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 24 Jun 2021 17:35:14 +0530 Subject: [PATCH 1101/1264] Update party.js --- erpnext/public/js/utils/party.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 99c8587391..a79eadc761 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -276,7 +276,7 @@ erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) { erpnext.utils.get_shipping_address = function(frm, callback) { if (frm.doc.company) { - if ((frm.doc.inter_com_order_reference || frm.doc.internal_invoice_reference || + if ((frm.doc.inter_company_order_reference || frm.doc.internal_invoice_reference || frm.doc.internal_order_reference)) { if (callback) { return callback(); From 9dc625c1c9b841d950829e1b33024e596b742b45 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 24 Jun 2021 17:36:39 +0530 Subject: [PATCH 1102/1264] fix: validate product bundle for existing transactions before deletion (#25978) --- .../doctype/product_bundle/product_bundle.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index d3281f733f..ae3482f402 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -4,6 +4,8 @@ from __future__ import unicode_literals import frappe +from frappe.utils import get_link_to_form + from frappe import _ from frappe.model.document import Document @@ -18,6 +20,27 @@ class ProductBundle(Document): from erpnext.utilities.transaction_base import validate_uom_is_integer validate_uom_is_integer(self, "uom", "qty") + def on_trash(self): + linked_doctypes = ["Delivery Note", "Sales Invoice", "POS Invoice", "Purchase Receipt", "Purchase Invoice", + "Stock Entry", "Stock Reconciliation", "Sales Order", "Purchase Order", "Material Request"] + + invoice_links = [] + for doctype in linked_doctypes: + item_doctype = doctype + " Item" + + if doctype == "Stock Entry": + item_doctype = doctype + " Detail" + + invoices = frappe.db.get_all(item_doctype, {"item_code": self.new_item_code, "docstatus": 1}, ["parent"]) + + for invoice in invoices: + invoice_links.append(get_link_to_form(doctype, invoice['parent'])) + + if len(invoice_links): + frappe.throw( + "This Product Bundle is linked with {0}. You will have to cancel these documents in order to delete this Product Bundle" + .format(", ".join(invoice_links)), title=_("Not Allowed")) + def validate_main_item(self): """Validates, main Item is not a stock item""" if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"): From 1ca8f6a51d6ac6a374af3cf95a23b51d3e33f3ea Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 17 Jun 2021 13:05:43 +0530 Subject: [PATCH 1103/1264] fix: purchase receipt gl entries with same item code --- erpnext/accounts/general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 59009ae621..25d2cf10bd 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -101,7 +101,7 @@ def merge_similar_entries(gl_map, precision=None): def check_if_in_list(gle, gl_map, dimensions=None): account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type', - 'cost_center', 'project'] + 'cost_center', 'project', 'voucher_detail_no'] if dimensions: account_head_fieldnames = account_head_fieldnames + dimensions From f6dce4df73b2a61ca93e8c4119b2a2ce3ead39b6 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 21 Jun 2021 11:18:56 +0530 Subject: [PATCH 1104/1264] test: service item purchase with perpetual inventory enabled --- .../purchase_receipt/test_purchase_receipt.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 07c5da1dca..2eb8bfd5d2 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1011,6 +1011,47 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(pr.status, "To Bill") self.assertAlmostEqual(pr.per_billed, 50.0, places=2) + def test_service_item_purchase_with_perpetual_inventory(self): + company = '_Test Company with perpetual inventory' + service_item = '_Test Non Stock Item' + + before_test_value = frappe.db.get_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items') + frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', 1) + srbnb_account = 'Stock Received But Not Billed - TCP1' + frappe.db.set_value('Company', company, 'service_received_but_not_billed', srbnb_account) + + pr = make_purchase_receipt( + company=company, item=service_item, + warehouse='Finished Goods - TCP1', do_not_save=1 + ) + item_row_with_diff_rate = frappe.copy_doc(pr.items[0]) + item_row_with_diff_rate.rate = 100 + pr.append('items', item_row_with_diff_rate) + + pr.save() + pr.submit() + + item_one_gl_entry = frappe.db.get_all("GL Entry", { + 'voucher_type': pr.doctype, + 'voucher_no': pr.name, + 'account': srbnb_account, + 'voucher_detail_no': pr.items[0].name + }, pluck="name") + + item_two_gl_entry = frappe.db.get_all("GL Entry", { + 'voucher_type': pr.doctype, + 'voucher_no': pr.name, + 'account': srbnb_account, + 'voucher_detail_no': pr.items[1].name + }, pluck="name") + + # check if the entries are not merged into one + # seperate entries should be made since voucher_detail_no is different + self.assertEqual(len(item_one_gl_entry), 1) + self.assertEqual(len(item_two_gl_entry), 1) + + frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s From 6e8148909540f358f4cffc00dce29e3e70cf671d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 5 Jan 2021 14:18:26 +0530 Subject: [PATCH 1105/1264] feat: Job Card Enhancements --- .../doctype/bom_operation/bom_operation.json | 9 +- .../doctype/job_card/job_card.js | 136 +++++---- .../doctype/job_card/job_card.json | 64 ++-- .../doctype/job_card/job_card.py | 95 +++++- .../doctype/job_card_operation/__init__.py | 0 .../job_card_operation.json | 52 ++++ .../job_card_operation/job_card_operation.py | 10 + .../job_card_time_log/job_card_time_log.json | 24 +- .../manufacturing_settings.json | 19 +- .../doctype/operation/operation.js | 4 +- .../doctype/operation/operation.json | 274 ++++++++---------- .../doctype/operation/operation.py | 26 ++ .../doctype/sub_operation/__init__.py | 0 .../doctype/sub_operation/sub_operation.js | 8 + .../doctype/sub_operation/sub_operation.json | 51 ++++ .../doctype/sub_operation/sub_operation.py | 10 + .../sub_operation/test_sub_operation.py | 10 + .../doctype/work_order/work_order.js | 3 +- .../doctype/work_order/work_order.json | 55 ++++ .../doctype/work_order/work_order.py | 134 +++++++-- .../doctype/work_order_batch/__init__.py | 0 .../work_order_batch/work_order_batch.json | 49 ++++ .../work_order_batch/work_order_batch.py | 10 + erpnext/stock/doctype/batch/batch.py | 9 +- .../stock/doctype/stock_entry/stock_entry.py | 81 +++++- 25 files changed, 834 insertions(+), 299 deletions(-) create mode 100644 erpnext/manufacturing/doctype/job_card_operation/__init__.py create mode 100644 erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json create mode 100644 erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py create mode 100644 erpnext/manufacturing/doctype/sub_operation/__init__.py create mode 100644 erpnext/manufacturing/doctype/sub_operation/sub_operation.js create mode 100644 erpnext/manufacturing/doctype/sub_operation/sub_operation.json create mode 100644 erpnext/manufacturing/doctype/sub_operation/sub_operation.py create mode 100644 erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py create mode 100644 erpnext/manufacturing/doctype/work_order_batch/__init__.py create mode 100644 erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json create mode 100644 erpnext/manufacturing/doctype/work_order_batch/work_order_batch.py diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 07464e3e76..57062b8ca4 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -13,10 +13,10 @@ "col_break1", "hour_rate", "time_in_mins", - "batch_size", "operating_cost", "base_hour_rate", "base_operating_cost", + "batch_size", "image" ], "fields": [ @@ -61,6 +61,8 @@ }, { "description": "In minutes", + "fetch_from": "operation.total_operation_time", + "fetch_if_empty": 1, "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, @@ -104,7 +106,8 @@ "label": "Image" }, { - "default": "1", + "fetch_from": "operation.batch_size", + "fetch_if_empty": 1, "fieldname": "batch_size", "fieldtype": "Int", "label": "Batch Size" @@ -120,7 +123,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-13 18:14:10.018774", + "modified": "2020-12-14 15:01:33.142869", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 4e8dd41022..57ec20b42c 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -11,6 +11,16 @@ frappe.ui.form.on('Job Card', { } }; }); + + frm.set_indicator_formatter('sub_operation', + function(doc) { + if (doc.status == "Pending") { + return "red"; + } else { + return doc.status === "Complete" ? "green" : "orange"; + } + } + ); }, refresh: function(frm) { @@ -97,81 +107,76 @@ frappe.ui.form.on('Job Card', { prepare_timer_buttons: function(frm) { frm.trigger("make_dashboard"); - if (!frm.doc.job_started) { - frm.add_custom_button(__("Start"), () => { - if (!frm.doc.employee) { - frappe.prompt({fieldtype: 'Link', label: __('Employee'), options: "Employee", - fieldname: 'employee'}, d => { - if (d.employee) { - frm.set_value("employee", d.employee); - } else { - frm.events.start_job(frm); - } - }, __("Enter Value"), __("Start")); - } else { - frm.events.start_job(frm); - } + + if (!frm.doc.started_time && !frm.doc.current_time) { + frm.add_custom_button(__("Start Job"), () => { + frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Employee'), options: "Job Card Time Log", + fieldname: 'employee'}, d => { + debugger + frm.events.start_job(frm, "Work In Progress", d.employee); + }, __("Assign Job to Employee")); }).addClass("btn-primary"); } else if (frm.doc.status == "On Hold") { - frm.add_custom_button(__("Resume"), () => { - frappe.flags.resume_job = 1; - frm.events.start_job(frm); + frm.add_custom_button(__("Resume Job"), () => { + frm.events.start_job(frm, "Resume Job"); }).addClass("btn-primary"); } else { - frm.add_custom_button(__("Pause"), () => { - frappe.flags.pause_job = 1; - frm.set_value("status", "On Hold"); - frm.events.complete_job(frm); + frm.add_custom_button(__("Pause Job"), () => { + frm.events.complete_job(frm, "On Hold"); }); - frm.add_custom_button(__("Complete"), () => { - let completed_time = frappe.datetime.now_datetime(); - frm.trigger("hide_timer"); - - if (frm.doc.for_quantity) { - frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), - fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => { - frm.events.complete_job(frm, completed_time, data.qty); - }, __("Enter Value"), __("Complete")); - } else { - frm.events.complete_job(frm, completed_time, 0); - } + frm.add_custom_button(__("Complete Job"), () => { + frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), + fieldname: 'qty', default: frm.doc.for_quantity}, data => { + frm.events.complete_job(frm, "Complete", data.qty); + }, __("Enter Value")); }).addClass("btn-primary"); } }, - start_job: function(frm) { - let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs'); - row.from_time = frappe.datetime.now_datetime(); - frm.set_value('job_started', 1); - frm.set_value('started_time' , row.from_time); - frm.set_value("status", "Work In Progress"); - - if (!frappe.flags.resume_job) { - frm.set_value('current_time' , 0); - } - - frm.save(); + start_job: function(frm, status, employee) { + const args = { + job_card_id: frm.doc.name, + start_time: frappe.datetime.now_datetime(), + employee: employee, + status: status + }; + frm.events.make_time_log(frm, args); }, - complete_job: function(frm, completed_time, completed_qty) { - frm.doc.time_logs.forEach(d => { - if (d.from_time && !d.to_time) { - d.to_time = completed_time || frappe.datetime.now_datetime(); - d.completed_qty = completed_qty || 0; + complete_job: function(frm, status, completed_qty) { + const args = { + job_card_id: frm.doc.name, + complete_time: frappe.datetime.now_datetime(), + status: status, + completed_qty: completed_qty + }; + frm.events.make_time_log(frm, args); + }, - if(frappe.flags.pause_job) { - let currentIncrement = moment(d.to_time).diff(moment(d.from_time),"seconds") || 0; - frm.set_value('current_time' , currentIncrement + (frm.doc.current_time || 0)); - } else { - frm.set_value('started_time' , ''); - frm.set_value('job_started', 0); - frm.set_value('current_time' , 0); - } + make_time_log: function(frm, args) { + frm.events.update_sub_operation(frm, args); - frm.save(); + frappe.call({ + method: "erpnext.manufacturing.doctype.job_card.job_card.make_time_log", + args: { + args: args + }, + freeze: true, + callback: function (r) { + frm.reload_doc(); + frm.trigger("make_dashboard"); } - }); + }) + }, + + update_sub_operation: function(frm, args) { + if (frm.doc.sub_operations && frm.doc.sub_operations.length) { + let sub_operations = frm.doc.sub_operations.filter(d => d.status != 'Complete'); + if (sub_operations && sub_operations.length) { + args["sub_operation"] = sub_operations[0].sub_operation; + } + } }, validate: function(frm) { @@ -180,18 +185,8 @@ frappe.ui.form.on('Job Card', { } }, - employee: function(frm) { - if (frm.doc.job_started && !frm.doc.current_time) { - frm.trigger("reset_timer"); - } else { - frm.events.start_job(frm); - } - }, - reset_timer: function(frm) { frm.set_value('started_time' , ''); - frm.set_value('job_started', 0); - frm.set_value('current_time' , 0); }, make_dashboard: function(frm) { @@ -297,7 +292,6 @@ frappe.ui.form.on('Job Card Time Log', { }, to_time: function(frm) { - frm.set_value('job_started', 0); frm.set_value('started_time', ''); } }) \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 5713f697e9..c2fd8cc3f9 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -9,30 +9,30 @@ "naming_series", "work_order", "bom_no", - "workstation", - "operation", - "operation_row_number", "column_break_4", "posting_date", "company", - "remarks", "production_section", "production_item", "item_name", "for_quantity", - "quality_inspection", - "wip_warehouse", "column_break_12", - "employee", - "employee_name", - "status", + "wip_warehouse", + "quality_inspection", "project", + "operation_section_section", + "operation", + "operation_row_number", + "column_break_18", + "workstation", + "section_break_21", + "sub_operations", "timing_detail", "time_logs", "section_break_13", "total_completed_qty", - "total_time_in_mins", "column_break_15", + "total_time_in_mins", "section_break_8", "items", "more_information", @@ -40,7 +40,9 @@ "sequence_id", "transferred_qty", "requested_qty", + "status", "column_break_20", + "remarks", "barcode", "job_started", "started_time", @@ -117,13 +119,6 @@ "fieldtype": "Section Break", "label": "Timing Detail" }, - { - "fieldname": "employee", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Employee", - "options": "Employee" - }, { "allow_bulk_edit": 1, "fieldname": "time_logs", @@ -133,9 +128,11 @@ }, { "fieldname": "section_break_13", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { + "default": "0", "fieldname": "total_completed_qty", "fieldtype": "Float", "label": "Total Completed Qty", @@ -251,12 +248,7 @@ "reqd": 1 }, { - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Read Only", - "label": "Employee Name" - }, - { + "collapsible": 1, "fieldname": "production_section", "fieldtype": "Section Break", "label": "Production" @@ -314,11 +306,33 @@ "label": "Quality Inspection", "no_copy": 1, "options": "Quality Inspection" + }, + { + "allow_bulk_edit": 1, + "fieldname": "sub_operations", + "fieldtype": "Table", + "label": "Sub Operations", + "options": "Job Card Operation", + "read_only": 1 + }, + { + "fieldname": "operation_section_section", + "fieldtype": "Section Break", + "label": "Operation Section" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_21", + "fieldtype": "Section Break", + "hide_border": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-11-19 18:26:50.531664", + "modified": "2020-12-14 15:14:05.566271", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index cdc4518894..5c157d43ec 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -4,12 +4,13 @@ from __future__ import unicode_literals import frappe -import datetime +import datetime, json from frappe import _, bold +from six import string_types from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate, - get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form) + get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form, time_diff_in_seconds) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations @@ -25,9 +26,20 @@ class JobCard(Document): self.set_status() self.validate_operation_id() self.validate_sequence_id() + self.get_sub_operations() + self.update_sub_operation_status() + + def get_sub_operations(self): + if self.operation: + self.sub_operations = [] + for row in frappe.get_all("Sub Operation", + filters = {"parent": self.operation}, fields=["operation"]): + self.append("sub_operations", { + "sub_operation": row.operation, + "status": "Pending" + }) def validate_time_logs(self): - self.total_completed_qty = 0.0 self.total_time_in_mins = 0.0 if self.get('time_logs'): @@ -46,6 +58,8 @@ class JobCard(Document): if d.completed_qty: self.total_completed_qty += d.completed_qty + else: + self.total_completed_qty = 0.0 self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) @@ -57,7 +71,7 @@ class JobCard(Document): self.workstation, 'production_capacity') or 1 validate_overlap_for = " and jc.workstation = %(workstation)s " - if self.employee: + if args.get("employee"): # override capacity for employee production_capacity = 1 validate_overlap_for = " and jc.employee = %(employee)s " @@ -80,7 +94,7 @@ class JobCard(Document): "to_time": args.to_time, "name": args.name or "No Name", "parent": args.parent or "No Name", - "employee": self.employee, + "employee": args.get("employee"), "workstation": self.workstation }, as_dict=True) @@ -158,6 +172,66 @@ class JobCard(Document): row.planned_start_time = datetime.datetime.combine(start_date, get_time(workstation_doc.working_hours[0].start_time)) + def add_time_log(self, args): + last_row = [] + if self.time_logs and len(self.time_logs) > 0: + last_row = self.time_logs[-1] + + self.reset_timer_value(args) + if last_row and args.get("complete_time"): + last_row.update({ + "to_time": get_datetime(args.get("complete_time")), + "operation": args.get("sub_operation"), + "completed_qty": args.get("completed_qty") or 0.0 + }) + elif args.get("start_time"): + self.append("time_logs", { + "from_time": get_datetime(args.get("start_time")), + "employee": args.get("employee"), + "operation": args.get("sub_operation"), + "completed_qty": 0.0 + }) + + if self.status == "On Hold": + self.current_time = time_diff_in_seconds(last_row.to_time, last_row.from_time) + + self.save() + + def reset_timer_value(self, args): + self.started_time = None + + if args.get("status") in ["Work In Progress", "Complete"]: + self.current_time = 0.0 + + if args.get("status") == "Work In Progress": + self.started_time = get_datetime(args.get("start_time")) + + if args.get("status") == "Resume Job": + args["status"] = "Work In Progress" + + if args.get("status"): + self.status = args.get("status") + + def update_sub_operation_status(self): + if not (self.sub_operations and self.time_logs): return + + operation_wise_completed_time = {} + for time_log in self.time_logs: + if time_log.operation not in operation_wise_completed_time: + operation_wise_completed_time.setdefault(time_log.operation, + frappe._dict({"status": "Pending", "completed_time": 0.0})) + + op_row = operation_wise_completed_time[time_log.operation] + op_row.status = "Work In Progress" if not time_log.time_in_mins else "Complete" + if time_log.time_in_mins: + op_row.completed_time += time_log.time_in_mins + + for row in self.sub_operations: + operation_deatils = operation_wise_completed_time.get(row.sub_operation) + if operation_deatils: + row.status = operation_deatils.status + row.completed_time = operation_deatils.completed_time + def update_time_logs(self, row): self.append("time_logs", { "from_time": row.planned_start_time, @@ -376,6 +450,17 @@ class JobCard(Document): frappe.throw(_("{0}, complete the operation {1} before the operation {2}.") .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError) + +@frappe.whitelist() +def make_time_log(args): + if isinstance(args, string_types): + args = json.loads(args) + + args = frappe._dict(args) + doc = frappe.get_doc("Job Card", args.job_card_id) + doc.validate_sequence_id() + doc.add_time_log(args) + @frappe.whitelist() def get_operation_details(work_order, operation): if work_order and operation: diff --git a/erpnext/manufacturing/doctype/job_card_operation/__init__.py b/erpnext/manufacturing/doctype/job_card_operation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json new file mode 100644 index 0000000000..be8190236d --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "creation": "2020-12-07 16:58:38.449041", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sub_operation", + "completed_time", + "status" + ], + "fields": [ + { + "default": "Pending", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Complete\nPause\nPending\nWork In Progress", + "read_only": 1 + }, + { + "description": "In mins", + "fieldname": "completed_time", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Completed Time", + "read_only": 1 + }, + { + "fieldname": "sub_operation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Operation", + "options": "Operation", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-12-14 17:08:25.992957", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Operation", + "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/manufacturing/doctype/job_card_operation/job_card_operation.py b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.py new file mode 100644 index 0000000000..85d72982ed --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.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 JobCardOperation(Document): + pass diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json index 9dd54dd618..a7102d7d23 100644 --- a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json @@ -1,14 +1,17 @@ { + "actions": [], "creation": "2019-03-08 23:56:43.187569", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "employee", "from_time", "to_time", "column_break_2", "time_in_mins", - "completed_qty" + "completed_qty", + "operation" ], "fields": [ { @@ -41,10 +44,27 @@ "in_list_view": 1, "label": "Completed Qty", "reqd": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee" + }, + { + "fieldname": "operation", + "fieldtype": "Link", + "label": "Operation", + "no_copy": 1, + "options": "Operation", + "read_only": 1 } ], + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-12-03 12:56:02.285448", + "links": [], + "modified": "2020-12-23 14:30:00.970916", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Time Log", diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index b7634da87c..6647be54eb 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -26,7 +26,9 @@ "column_break_16", "overproduction_percentage_for_work_order", "other_settings_section", - "update_bom_costs_automatically" + "update_bom_costs_automatically", + "column_break_23", + "make_serial_no_batch_from_work_order" ], "fields": [ { @@ -155,13 +157,24 @@ { "fieldname": "column_break_5", "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_23", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "System will automatically create the serial numbers / batch for the Finished Good on submission of work order", + "fieldname": "make_serial_no_batch_from_work_order", + "fieldtype": "Check", + "label": "Make Serial No / Batch from Work Order" } ], "icon": "icon-wrench", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-13 10:55:43.996581", + "modified": "2020-12-08 13:37:40.325838", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", @@ -178,4 +191,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js index 5c2aba6f09..9bfcc6eedb 100644 --- a/erpnext/manufacturing/doctype/operation/operation.js +++ b/erpnext/manufacturing/doctype/operation/operation.js @@ -2,7 +2,5 @@ // For license information, please see license.txt frappe.ui.form.on('Operation', { - refresh: function(frm) { - } -}); +}); \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/operation/operation.json b/erpnext/manufacturing/doctype/operation/operation.json index c231fba2fa..9e6f8e1f5d 100644 --- a/erpnext/manufacturing/doctype/operation/operation.json +++ b/erpnext/manufacturing/doctype/operation/operation.json @@ -1,167 +1,133 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "Prompt", - "beta": 0, - "creation": "2014-11-07 16:20:30.683186", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2014-11-07 16:20:30.683186", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "workstation", + "data_2", + "cost_of_poor_quality_operation", + "job_card_section", + "create_job_card_based_on_batch_size", + "column_break_6", + "batch_size", + "sub_operations_section", + "sub_operations", + "total_operation_time", + "section_break_4", + "description" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "workstation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Default Workstation", - "length": 0, - "no_copy": 0, - "options": "Workstation", - "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 - }, + "fieldname": "workstation", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Default Workstation", + "options": "Workstation" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 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, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Operation Description" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "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, - "unique": 0 + "fieldname": "description", + "fieldtype": "Text", + "label": "Description" + }, + { + "collapsible": 1, + "fieldname": "sub_operations_section", + "fieldtype": "Section Break", + "label": "Sub Operations" + }, + { + "fieldname": "sub_operations", + "fieldtype": "Table", + "options": "Sub Operation" + }, + { + "description": "Time in mins.", + "fieldname": "total_operation_time", + "fieldtype": "Float", + "label": "Total Operation Time", + "read_only": 1 + }, + { + "fieldname": "data_2", + "fieldtype": "Column Break" + }, + { + "default": "1", + "depends_on": "create_job_card_based_on_batch_size", + "fieldname": "batch_size", + "fieldtype": "Int", + "label": "Batch Size", + "mandatory_depends_on": "create_job_card_based_on_batch_size" + }, + { + "default": "0", + "fieldname": "create_job_card_based_on_batch_size", + "fieldtype": "Check", + "label": "Create Job Card based on Batch Size" + }, + { + "default": "0", + "description": "Cost of poor quality operation", + "fieldname": "cost_of_poor_quality_operation", + "fieldtype": "Check", + "label": "Is COPQ Operation" + }, + { + "collapsible": 1, + "fieldname": "job_card_section", + "fieldtype": "Section Break", + "label": "Job Card" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-wrench", - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-11-07 05:28:27.462413", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Operation", - "name_case": "", - "owner": "Administrator", + ], + "icon": "fa fa-wrench", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-12-24 14:25:03.428303", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Operation", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 1, - "is_custom": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Manufacturing User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "export": 1, + "import": 1, + "read": 1, + "role": "Manufacturing User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 1, - "is_custom": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "export": 1, + "import": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "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/manufacturing/doctype/operation/operation.py b/erpnext/manufacturing/doctype/operation/operation.py index 69e83292ff..aaf0d5c01b 100644 --- a/erpnext/manufacturing/doctype/operation/operation.py +++ b/erpnext/manufacturing/doctype/operation/operation.py @@ -2,9 +2,35 @@ # For license information, please see license.txt from __future__ import unicode_literals + +import frappe +from frappe import _ +from frappe.utils import flt from frappe.model.document import Document class Operation(Document): def validate(self): if not self.description: self.description = self.name + + self.duplicate_sub_operation() + self.set_total_time() + + def duplicate_sub_operation(self): + operation_list = [] + for row in self.sub_operations: + if row.operation in operation_list: + frappe.throw(_("The operation {0} can not add multiple times") + .format(frappe.bold(row.operation))) + + if self.name == row.operation: + frappe.throw(_("The operation {0} can not be the sub operation") + .format(frappe.bold(row.operation))) + + operation_list.append(row.operation) + + def set_total_time(self): + self.total_operation_time = 0.0 + + for row in self.sub_operations: + self.total_operation_time += row.time_in_mins diff --git a/erpnext/manufacturing/doctype/sub_operation/__init__.py b/erpnext/manufacturing/doctype/sub_operation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.js b/erpnext/manufacturing/doctype/sub_operation/sub_operation.js new file mode 100644 index 0000000000..be9db6a408 --- /dev/null +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.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('Sub Operation', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json new file mode 100644 index 0000000000..f63d2b9864 --- /dev/null +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json @@ -0,0 +1,51 @@ +{ + "actions": [], + "creation": "2020-12-07 15:39:47.488519", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "operation", + "time_in_mins", + "column_break_5", + "description" + ], + "fields": [ + { + "fieldname": "operation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Operation", + "options": "Operation" + }, + { + "description": "Time in mins", + "fieldname": "time_in_mins", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Operation Time" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-12-07 18:09:18.005578", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Sub Operation", + "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/manufacturing/doctype/sub_operation/sub_operation.py b/erpnext/manufacturing/doctype/sub_operation/sub_operation.py new file mode 100644 index 0000000000..f4b27758e9 --- /dev/null +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.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 SubOperation(Document): + pass diff --git a/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py b/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py new file mode 100644 index 0000000000..d3410ca312 --- /dev/null +++ b/erpnext/manufacturing/doctype/sub_operation/test_sub_operation.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 TestSubOperation(unittest.TestCase): + pass diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 8088d930df..601734914d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -141,8 +141,7 @@ frappe.ui.form.on("Work Order", { } if (frm.doc.docstatus === 1 - && frm.doc.operations && frm.doc.operations.length - && frm.doc.qty != frm.doc.material_transferred_for_manufacturing) { + && frm.doc.operations && frm.doc.operations.length) { const not_completed = frm.doc.operations.filter(d => { if(d.status != 'Completed') { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index cd9edeeea8..cb3c942107 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -21,6 +21,13 @@ "produced_qty", "sales_order", "project", + "serial_no_and_batch_for_finished_good_section", + "has_serial_no", + "has_batch_no", + "column_break_17", + "serial_no", + "batch_size", + "batches", "settings_section", "allow_alternative_item", "use_multi_level_bom", @@ -488,6 +495,54 @@ "fieldtype": "Float", "label": "Lead Time", "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:!doc.__islocal", + "fieldname": "serial_no_and_batch_for_finished_good_section", + "fieldtype": "Section Break", + "label": "Serial No and Batch for Finished Good" + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fetch_from": "production_item.has_serial_no", + "fieldname": "has_serial_no", + "fieldtype": "Check", + "label": "Has Serial No", + "read_only": 1 + }, + { + "default": "0", + "fetch_from": "production_item.has_batch_no", + "fieldname": "has_batch_no", + "fieldtype": "Check", + "label": "Has Batch No", + "read_only": 1 + }, + { + "depends_on": "has_serial_no", + "fieldname": "serial_no", + "fieldtype": "Small Text", + "label": "Serial Nos" + }, + { + "default": "0", + "depends_on": "has_batch_no", + "fieldname": "batch_size", + "fieldtype": "Float", + "label": "Batch Size" + }, + { + "depends_on": "has_batch_no", + "fieldname": "batches", + "fieldtype": "Table", + "label": "Batches", + "options": "Work Order Batch", + "read_only": 1 } ], "icon": "fa fa-cogs", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 2600790a59..587204c341 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -19,6 +19,8 @@ from frappe.utils.csvutils import getlink from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty from erpnext.utilities.transaction_base import validate_uom_is_integer from frappe.model.mapper import get_mapped_doc +from erpnext.stock.doctype.batch.batch import make_batch +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_auto_serial_nos, auto_make_serial_nos class OverProductionError(frappe.ValidationError): pass class CapacityError(frappe.ValidationError): pass @@ -40,6 +42,7 @@ class WorkOrder(Document): self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order) def validate(self): + self.set("batches", []) self.validate_production_item() if self.bom_no: validate_bom_no(self.production_item, self.bom_no) @@ -235,6 +238,9 @@ class WorkOrder(Document): production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item) + def before_submit(self): + self.create_serial_no_batch_no() + def on_submit(self): if not self.wip_warehouse: frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) @@ -266,6 +272,67 @@ class WorkOrder(Document): self.update_planned_qty() self.update_ordered_qty() self.update_reserved_qty_for_production() + self.delete_auto_created_batch_and_serial_no() + + def create_serial_no_batch_no(self): + if not (self.has_serial_no or self.has_batch_no): return + + if not cint(frappe.db.get_single_value("Manufacturing Settings", + "make_serial_no_batch_from_work_order")): return + + if self.has_batch_no: + self.set("batches", []) + self.create_batch_for_finished_good() + + args = {"item_code": self.production_item} + + if self.has_serial_no: + self.make_serial_nos(args) + + def create_batch_for_finished_good(self): + total_qty = self.qty + if not self.batch_size: + self.batch_size = total_qty + + while total_qty > 0: + qty = self.batch_size + if self.batch_size >= total_qty: + qty = total_qty + + if total_qty > self.batch_size: + total_qty -= self.batch_size + else: + qty = total_qty + total_qty = 0 + + batch = make_batch(self.production_item) + self.append("batches", { + "batch_no": batch, + "qty": qty, + }) + + def delete_auto_created_batch_and_serial_no(self): + if self.serial_no: + for d in get_serial_nos(self.serial_no): + frappe.delete_doc("Serial No", d) + + for row in self.batches: + batch_no = row.batch_no + row.db_set("batch_no", None) + frappe.delete_doc("Batch", batch_no) + + def make_serial_nos(self, args): + serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series") + if serial_no_series: + self.serial_no = get_auto_serial_nos(serial_no_series, self.qty) + elif self.serial_no: + args.update({"serial_no": self.serial_no, "actual_qty": self.qty, "batch_no": self.batch_no}) + self.serial_no = auto_make_serial_nos(args) + + serial_nos_length = len(get_serial_nos(self.serial_no)) + if serial_nos_length != self.qty: + frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.") + .format(self.qty, self.production_item, serial_nos_length), SerialNoQtyError) def create_job_card(self): manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings") @@ -273,32 +340,51 @@ class WorkOrder(Document): enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning) plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 - for i, row in enumerate(self.operations): - self.set_operation_start_end_time(i, row) + for index, row in enumerate(self.operations): + qty = self.qty + i=0 + while qty > 0: + i += 1 + if not cint(frappe.db.get_value("Operation", + row.operation, "create_job_card_based_on_batch_size")): + row.batch_size = self.qty - if not row.workstation: - frappe.throw(_("Row {0}: select the workstation against the operation {1}") - .format(row.idx, row.operation)) + job_card_qty = row.batch_size + if row.batch_size and qty >= row.batch_size: + qty -= row.batch_size + elif qty > 0: + job_card_qty = qty - original_start_time = row.planned_start_time - job_card_doc = create_job_card(self, row, - enable_capacity_planning=enable_capacity_planning, auto_create=True) - - if enable_capacity_planning and job_card_doc: - row.planned_start_time = job_card_doc.time_logs[-1].from_time - row.planned_end_time = job_card_doc.time_logs[-1].to_time - - if date_diff(row.planned_start_time, original_start_time) > plan_days: - frappe.message_log.pop() - frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.") - .format(plan_days, row.operation), CapacityError) - - row.db_update() + if job_card_qty > 0: + self.prepare_data_for_job_card(row, job_card_qty, index, + plan_days, enable_capacity_planning) planned_end_date = self.operations and self.operations[-1].planned_end_time if planned_end_date: self.db_set("planned_end_date", planned_end_date) + def prepare_data_for_job_card(self, row, job_card_qty, index, plan_days, enable_capacity_planning): + self.set_operation_start_end_time(index, row) + + if not row.workstation: + frappe.throw(_("Row {0}: select the workstation against the operation {1}") + .format(row.idx, row.operation)) + + original_start_time = row.planned_start_time + job_card_doc = create_job_card(self, row, qty=job_card_qty, + enable_capacity_planning=enable_capacity_planning, auto_create=True) + + if enable_capacity_planning and job_card_doc: + row.planned_start_time = job_card_doc.time_logs[-1].from_time + row.planned_end_time = job_card_doc.time_logs[-1].to_time + + if date_diff(row.planned_start_time, original_start_time) > plan_days: + frappe.message_log.pop() + frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.") + .format(plan_days, row.operation), CapacityError) + + row.db_update() + def set_operation_start_end_time(self, idx, row): """Set start and end time for given operation. If first operation, set start as `planned_start_date`, else add time diff to end time of earlier operation.""" @@ -669,6 +755,15 @@ class WorkOrder(Document): bom.set_bom_material_details() return bom + def update_batch_qty(self): + if self.has_batch_no and self.batches: + for row in self.batches: + qty = frappe.get_all("Stock Entry Detail", fields = ["sum(transfer_qty)"], + filters = {"docstatus": 1, "batch_no": row.batch_no, "is_finished_item": 1}, as_list=1) + + if qty: + frappe.db.set_value("Work Order Batch", row.name, "produced_qty", flt(qty[0][0])) + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_bom_operations(doctype, txt, searchfield, start, page_len, filters): @@ -826,6 +921,7 @@ def make_stock_entry(work_order_id, purpose, qty=None): stock_entry.set_stock_entry_type() stock_entry.get_items() + stock_entry.set_serial_no_batch_for_finished_good() return stock_entry.as_dict() @frappe.whitelist() diff --git a/erpnext/manufacturing/doctype/work_order_batch/__init__.py b/erpnext/manufacturing/doctype/work_order_batch/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json b/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json new file mode 100644 index 0000000000..ad667b7c39 --- /dev/null +++ b/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-01-04 16:42:39.347528", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "batch_no", + "qty", + "produced_qty" + ], + "fields": [ + { + "fieldname": "batch_no", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Batch No", + "options": "Batch" + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "non_negative": 1 + }, + { + "default": "0", + "fieldname": "produced_qty", + "fieldtype": "Float", + "label": "Produced Qty", + "no_copy": 1, + "print_hide": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-01-05 10:57:07.278399", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Batch", + "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/manufacturing/doctype/work_order_batch/work_order_batch.py b/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.py new file mode 100644 index 0000000000..cf3ec475ca --- /dev/null +++ b/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 WorkOrderBatch(Document): + pass diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 508e17c340..07cf08a5bb 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -308,4 +308,11 @@ def validate_serial_no_with_batch(serial_nos, item_code): message = "Serial Nos" if len(serial_nos) > 1 else "Serial No" frappe.throw(_("There is no batch found against the {0}: {1}") - .format(message, serial_no_link)) \ No newline at end of file + .format(message, serial_no_link)) + +def make_batch(item_code): + if frappe.db.get_value("Item", item_code, "has_batch_no"): + doc = frappe.new_doc("Batch") + doc.item = item_code + doc.save() + return doc.name \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 66f8b63cb9..5fde35a811 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -498,6 +498,7 @@ class StockEntry(StockController): d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount")) if not d.t_warehouse: outgoing_items_cost += flt(d.basic_amount) + return outgoing_items_cost def get_args_for_incoming_rate(self, item): @@ -854,6 +855,7 @@ class StockEntry(StockController): pro_doc.run_method("update_work_order_qty") if self.purpose == "Manufacture": pro_doc.run_method("update_planned_qty") + pro_doc.update_batch_qty() if not pro_doc.operations: pro_doc.set_actual_dates() @@ -1076,18 +1078,45 @@ class StockEntry(StockController): # in case of BOM to_warehouse = item.get("default_warehouse") + args = { + "to_warehouse": to_warehouse, + "from_warehouse": "", + "qty": self.fg_completed_qty, + "item_name": item.item_name, + "description": item.description, + "stock_uom": item.stock_uom, + "expense_account": item.get("expense_account"), + "cost_center": item.get("buying_cost_center"), + "is_finished_item": 1 + } + + if self.work_order and self.pro_doc.batches: + self.set_batchwise_finished_goods(args, item) + else: + self.add_finisged_goods(args, item) + + def set_batchwise_finished_goods(self, args, item): + qty = flt(self.fg_completed_qty) + for row in self.pro_doc.batches: + batch_qty = flt(row.qty) - flt(row.produced_qty) + if not batch_qty: continue + + if qty <=0: + break + + fg_qty = batch_qty + if batch_qty >= qty: + fg_qty = qty + + qty -= batch_qty + args["qty"] = fg_qty + args["batch_no"] = row.batch_no + + self.add_finisged_goods(args, item) + + def add_finisged_goods(self, args, item): self.add_to_stock_entry_detail({ - item.name: { - "to_warehouse": to_warehouse, - "from_warehouse": "", - "qty": self.fg_completed_qty, - "item_name": item.item_name, - "description": item.description, - "stock_uom": item.stock_uom, - "expense_account": item.get("expense_account"), - "cost_center": item.get("buying_cost_center"), - "is_finished_item": 1 - } + item.name: args }, bom_no = self.bom_no) def get_bom_raw_materials(self, qty): @@ -1524,6 +1553,36 @@ class StockEntry(StockController): material_requests.append(material_request) frappe.db.set_value('Material Request', material_request, 'transfer_status', status) + def set_serial_no_batch_for_finished_good(self): + args = {} + if self.pro_doc.serial_no or self.pro_doc.batch_no: + self.get_serial_nos_for_fg(args) + + for row in self.items: + if row.is_finished_item and row.item_code == self.pro_doc.production_item: + if args.get("serial_no"): + row.serial_no = '\n'.join(args["serial_no"][0: cint(row.qty)]) + + def get_serial_nos_for_fg(self, args): + fields = ["`tabStock Entry`.`name`", "`tabStock Entry Detail`.`qty`", + "`tabStock Entry Detail`.`serial_no`", "`tabStock Entry Detail`.`batch_no`"] + + filters = [["Stock Entry","work_order","=",self.work_order], ["Stock Entry","purpose","=","Manufacture"], + ["Stock Entry","docstatus","=",1], ["Stock Entry Detail","item_code","=",self.pro_doc.production_item]] + + stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters) + + if self.pro_doc.serial_no: + args["serial_no"] = self.get_available_serial_nos(stock_entries) + + def get_available_serial_nos(self, stock_entries): + used_serial_nos = [] + for row in stock_entries: + if row.serial_no: + used_serial_nos.extend(get_serial_nos(row.serial_no)) + + return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos))) + @frappe.whitelist() def move_sample_to_retention_warehouse(company, items): if isinstance(items, string_types): From fcab53b238d2f6c1e0587ed309bf2765f63c72ec Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 5 Jan 2021 15:55:09 +0530 Subject: [PATCH 1106/1264] fix: skip job card --- .../doctype/bom_operation/bom_operation.json | 10 +++- .../doctype/work_order/work_order.js | 16 +++--- .../doctype/work_order/work_order.json | 9 --- .../doctype/work_order/work_order.py | 57 ++++++++++--------- .../work_order/work_order_dashboard.py | 7 +++ .../doctype/work_order_batch/__init__.py | 0 .../work_order_batch/work_order_batch.json | 49 ---------------- .../work_order_batch/work_order_batch.py | 10 ---- .../work_order_operation.json | 17 +++++- erpnext/stock/doctype/batch/batch.json | 31 +++++++++- erpnext/stock/doctype/batch/batch.py | 10 ++-- .../stock/doctype/serial_no/serial_no.json | 11 +++- erpnext/stock/doctype/serial_no/serial_no.py | 17 +++--- .../stock/doctype/stock_entry/stock_entry.py | 17 ++++-- 14 files changed, 132 insertions(+), 129 deletions(-) delete mode 100644 erpnext/manufacturing/doctype/work_order_batch/__init__.py delete mode 100644 erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json delete mode 100644 erpnext/manufacturing/doctype/work_order_batch/work_order_batch.py diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 57062b8ca4..1330636198 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -11,6 +11,7 @@ "workstation", "description", "col_break1", + "skip_job_card", "hour_rate", "time_in_mins", "operating_cost", @@ -117,13 +118,20 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "skip_job_card", + "fieldtype": "Check", + "label": "Skip Job Card" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-14 15:01:33.142869", + "modified": "2021-01-05 14:29:11.887888", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 601734914d..adf6453e2e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -242,13 +242,15 @@ frappe.ui.form.on("Work Order", { if(data.completed_qty != frm.doc.qty) { pending_qty = frm.doc.qty - flt(data.completed_qty); - dialog.fields_dict.operations.df.data.push({ - 'name': data.name, - 'operation': data.operation, - 'workstation': data.workstation, - 'qty': pending_qty, - 'pending_qty': pending_qty, - }); + if (pending_qty && !data.skip_job_card) { + dialog.fields_dict.operations.df.data.push({ + 'name': data.name, + 'operation': data.operation, + 'workstation': data.workstation, + 'qty': pending_qty, + 'pending_qty': pending_qty, + }); + } } }); dialog.fields_dict.operations.grid.refresh(); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index cb3c942107..c80decb92e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -27,7 +27,6 @@ "column_break_17", "serial_no", "batch_size", - "batches", "settings_section", "allow_alternative_item", "use_multi_level_bom", @@ -535,14 +534,6 @@ "fieldname": "batch_size", "fieldtype": "Float", "label": "Batch Size" - }, - { - "depends_on": "has_batch_no", - "fieldname": "batches", - "fieldtype": "Table", - "label": "Batches", - "options": "Work Order Batch", - "read_only": 1 } ], "icon": "fa fa-cogs", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 587204c341..23cc090427 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -27,6 +27,7 @@ class CapacityError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass class OperationTooLongError(frappe.ValidationError): pass class ItemHasVariantError(frappe.ValidationError): pass +class SerialNoQtyError(frappe.ValidationError): pass from six import string_types @@ -42,7 +43,6 @@ class WorkOrder(Document): self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order) def validate(self): - self.set("batches", []) self.validate_production_item() if self.bom_no: validate_bom_no(self.production_item, self.bom_no) @@ -281,10 +281,12 @@ class WorkOrder(Document): "make_serial_no_batch_from_work_order")): return if self.has_batch_no: - self.set("batches", []) self.create_batch_for_finished_good() - args = {"item_code": self.production_item} + args = { + "item_code": self.production_item, + "work_order": self.name + } if self.has_serial_no: self.make_serial_nos(args) @@ -305,29 +307,29 @@ class WorkOrder(Document): qty = total_qty total_qty = 0 - batch = make_batch(self.production_item) - self.append("batches", { - "batch_no": batch, - "qty": qty, - }) + make_batch(frappe._dict({ + "item": self.production_item, + "qty_to_produce": qty, + "reference_doctype": self.doctype, + "reference_name": self.name + })) def delete_auto_created_batch_and_serial_no(self): - if self.serial_no: - for d in get_serial_nos(self.serial_no): - frappe.delete_doc("Serial No", d) + for row in frappe.get_all("Serial No", filters = {"work_order": self.name}): + frappe.delete_doc("Serial No", row.name) + self.db_set("serial_no", "") - for row in self.batches: - batch_no = row.batch_no - row.db_set("batch_no", None) - frappe.delete_doc("Batch", batch_no) + for row in frappe.get_all("Batch", filters = {"reference_name": self.name}): + frappe.delete_doc("Batch", row.name) def make_serial_nos(self, args): serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series") if serial_no_series: self.serial_no = get_auto_serial_nos(serial_no_series, self.qty) - elif self.serial_no: - args.update({"serial_no": self.serial_no, "actual_qty": self.qty, "batch_no": self.batch_no}) - self.serial_no = auto_make_serial_nos(args) + + if self.serial_no: + args.update({"serial_no": self.serial_no, "actual_qty": self.qty}) + auto_make_serial_nos(args) serial_nos_length = len(get_serial_nos(self.serial_no)) if serial_nos_length != self.qty: @@ -341,6 +343,7 @@ class WorkOrder(Document): plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 for index, row in enumerate(self.operations): + if row.skip_job_card: continue qty = self.qty i=0 while qty > 0: @@ -493,7 +496,7 @@ class WorkOrder(Document): select operation, description, workstation, idx, base_hour_rate as hour_rate, time_in_mins, - "Pending" as status, parent as bom, batch_size, sequence_id + "Pending" as status, parent as bom, batch_size, sequence_id, skip_job_card from `tabBOM Operation` where @@ -755,14 +758,16 @@ class WorkOrder(Document): bom.set_bom_material_details() return bom - def update_batch_qty(self): - if self.has_batch_no and self.batches: - for row in self.batches: - qty = frappe.get_all("Stock Entry Detail", fields = ["sum(transfer_qty)"], - filters = {"docstatus": 1, "batch_no": row.batch_no, "is_finished_item": 1}, as_list=1) + def update_batch_produced_qty(self, stock_entry_doc): + if not cint(frappe.db.get_single_value("Manufacturing Settings", + "make_serial_no_batch_from_work_order")): return - if qty: - frappe.db.set_value("Work Order Batch", row.name, "produced_qty", flt(qty[0][0])) + for row in stock_entry_doc.items: + if row.batch_no and (row.is_finished_item or row.is_scrap_item): + qty = frappe.get_all("Stock Entry Detail", filters = {"batch_no": row.batch_no}, + or_conditions= {"is_finished_item": 1, "is_scrap_item": 1}, fields = ["sum(qty)"])[0][0] + + frappe.db.set_value("Batch", row.batch_no, "produced_qty", qty) @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py index 87c090f99c..9aa0715e7f 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py +++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py @@ -4,10 +4,17 @@ from frappe import _ def get_data(): return { 'fieldname': 'work_order', + 'non_standard_fieldnames': { + 'Batch': 'reference_name' + }, 'transactions': [ { 'label': _('Transactions'), 'items': ['Stock Entry', 'Job Card', 'Pick List'] + }, + { + 'label': _('Reference'), + 'items': ['Serial No', 'Batch'] } ] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order_batch/__init__.py b/erpnext/manufacturing/doctype/work_order_batch/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json b/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json deleted file mode 100644 index ad667b7c39..0000000000 --- a/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "actions": [], - "creation": "2021-01-04 16:42:39.347528", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "batch_no", - "qty", - "produced_qty" - ], - "fields": [ - { - "fieldname": "batch_no", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Batch No", - "options": "Batch" - }, - { - "fieldname": "qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Qty", - "non_negative": 1 - }, - { - "default": "0", - "fieldname": "produced_qty", - "fieldtype": "Float", - "label": "Produced Qty", - "no_copy": 1, - "print_hide": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-01-05 10:57:07.278399", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Work Order Batch", - "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/manufacturing/doctype/work_order_batch/work_order_batch.py b/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.py deleted file mode 100644 index cf3ec475ca..0000000000 --- a/erpnext/manufacturing/doctype/work_order_batch/work_order_batch.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, 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 WorkOrderBatch(Document): - pass diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 8c5cde9a13..b77690997c 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -8,8 +8,10 @@ "details", "operation", "bom", - "sequence_id", + "column_break_4", + "skip_job_card", "description", + "sequence_id", "col_break1", "completed_qty", "status", @@ -195,12 +197,23 @@ "label": "Sequence ID", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "skip_job_card", + "fieldtype": "Check", + "label": "Skip Job Card" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-14 12:58:49.241252", + "modified": "2021-01-08 17:42:05.372163", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json index 943cb3401f..e6d2e1330b 100644 --- a/erpnext/stock/doctype/batch/batch.json +++ b/erpnext/stock/doctype/batch/batch.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "field:batch_id", "creation": "2013-03-05 14:50:38", @@ -25,7 +26,11 @@ "reference_doctype", "reference_name", "section_break_7", - "description" + "description", + "manufacturing_section", + "qty_to_produce", + "column_break_23", + "produced_qty" ], "fields": [ { @@ -160,13 +165,35 @@ "label": "Batch UOM", "options": "UOM", "read_only": 1 + }, + { + "fieldname": "manufacturing_section", + "fieldtype": "Section Break", + "label": "Manufacturing" + }, + { + "fieldname": "qty_to_produce", + "fieldtype": "Float", + "label": "Qty To Produce", + "read_only": 1 + }, + { + "fieldname": "column_break_23", + "fieldtype": "Column Break" + }, + { + "fieldname": "produced_qty", + "fieldtype": "Float", + "label": "Produced Qty", + "read_only": 1 } ], "icon": "fa fa-archive", "idx": 1, "image_field": "image", + "links": [], "max_attachments": 5, - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-01-07 11:10:09.149170", "modified_by": "Administrator", "module": "Stock", "name": "Batch", diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 07cf08a5bb..bb5ad5c6fe 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -310,9 +310,7 @@ def validate_serial_no_with_batch(serial_nos, item_code): frappe.throw(_("There is no batch found against the {0}: {1}") .format(message, serial_no_link)) -def make_batch(item_code): - if frappe.db.get_value("Item", item_code, "has_batch_no"): - doc = frappe.new_doc("Batch") - doc.item = item_code - doc.save() - return doc.name \ No newline at end of file +def make_batch(args): + if frappe.db.get_value("Item", args.item, "has_batch_no"): + args.doctype = "Batch" + frappe.get_doc(args).insert().name \ No newline at end of file diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index 3acf3a9316..a3d44af494 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -57,7 +57,8 @@ "more_info", "serial_no_details", "company", - "status" + "status", + "work_order" ], "fields": [ { @@ -422,12 +423,18 @@ "label": "Status", "options": "\nActive\nInactive\nDelivered\nExpired", "read_only": 1 + }, + { + "fieldname": "work_order", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work Order" } ], "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2020-07-20 20:50:16.660433", + "modified": "2021-01-08 14:31:15.375996", "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 b236f6a999..bad7b608ac 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -473,16 +473,13 @@ def get_serial_nos(serial_no): if s.strip()] def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False): - serial_no_doc.update({ - "item_code": args.get("item_code"), - "company": args.get("company"), - "batch_no": args.get("batch_no"), - "via_stock_ledger": args.get("via_stock_ledger") or True, - "supplier": args.get("supplier"), - "location": args.get("location"), - "warehouse": (args.get("warehouse") - if args.get("actual_qty", 0) > 0 else None) - }) + for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]: + if args.get(field): + serial_no_doc.set(field, args.get(field)) + + serial_no_doc.via_stock_ledger = args.get("via_stock_ledger") or True + serial_no_doc.warehouse = (args.get("warehouse") + if args.get("actual_qty", 0) > 0 else None) if is_new: serial_no_doc.serial_no = serial_no diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 5fde35a811..83412c61d9 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -855,7 +855,7 @@ class StockEntry(StockController): pro_doc.run_method("update_work_order_qty") if self.purpose == "Manufacture": pro_doc.run_method("update_planned_qty") - pro_doc.update_batch_qty() + pro_doc.update_batch_produced_qty(self) if not pro_doc.operations: pro_doc.set_actual_dates() @@ -1090,14 +1090,21 @@ class StockEntry(StockController): "is_finished_item": 1 } - if self.work_order and self.pro_doc.batches: + if self.work_order and self.pro_doc.has_batch_no: self.set_batchwise_finished_goods(args, item) else: self.add_finisged_goods(args, item) def set_batchwise_finished_goods(self, args, item): qty = flt(self.fg_completed_qty) - for row in self.pro_doc.batches: + filters = {"reference_name": self.pro_doc.name, + "reference_doctype": self.pro_doc.doctype, + "qty_to_produce": (">", 0) + } + + fields = ["qty_to_produce as qty", "produced_qty", "name"] + + for row in frappe.get_all("Batch", filters = filters, fields = fields): batch_qty = flt(row.qty) - flt(row.produced_qty) if not batch_qty: continue @@ -1110,7 +1117,7 @@ class StockEntry(StockController): qty -= batch_qty args["qty"] = fg_qty - args["batch_no"] = row.batch_no + args["batch_no"] = row.name self.add_finisged_goods(args, item) @@ -1555,7 +1562,7 @@ class StockEntry(StockController): def set_serial_no_batch_for_finished_good(self): args = {} - if self.pro_doc.serial_no or self.pro_doc.batch_no: + if self.pro_doc.serial_no: self.get_serial_nos_for_fg(args) for row in self.items: From 6a9798f305d93a879be5264e6388b36b04b7ec43 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Thu, 24 Jun 2021 18:11:33 +0530 Subject: [PATCH 1107/1264] fix: update leave allocation after submit (#26191) * fix: update leave allocation after submit v13 * fix: test * fix: test --- .../leave_allocation/leave_allocation.json | 5 +- .../leave_allocation/leave_allocation.py | 40 +++++++++++++++- .../leave_allocation/test_leave_allocation.py | 46 +++++++++++++++++++ .../employee_leave_balance.py | 2 +- 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index ae02c512c2..3a6539ece9 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -110,6 +110,7 @@ "label": "Allocation" }, { + "allow_on_submit": 1, "bold": 1, "fieldname": "new_leaves_allocated", "fieldtype": "Float", @@ -235,7 +236,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-14 15:28:26.335104", + "modified": "2021-06-03 15:28:26.335104", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", @@ -277,4 +278,4 @@ "sort_field": "modified", "sort_order": "DESC", "timeline_field": "employee" -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 11302cad75..4757cd3b19 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry +from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period class OverlapError(frappe.ValidationError): pass class BackDatedAllocationError(frappe.ValidationError): pass @@ -55,6 +56,43 @@ class LeaveAllocation(Document): if self.carry_forward: self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True) + def on_update_after_submit(self): + if self.has_value_changed("new_leaves_allocated"): + self.validate_against_leave_applications() + leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count() + args = { + "leaves": leaves_to_be_added, + "from_date": self.from_date, + "to_date": self.to_date, + "is_carry_forward": 0 + } + create_leave_ledger_entry(self, args, True) + + def get_existing_leave_count(self): + ledger_entries = frappe.get_all("Leave Ledger Entry", + filters={ + "transaction_type": "Leave Allocation", + "transaction_name": self.name, + "employee": self.employee, + "company": self.company, + "leave_type": self.leave_type + }, + pluck="leaves") + total_existing_leaves = 0 + for entry in ledger_entries: + total_existing_leaves += entry + + return total_existing_leaves + + def validate_against_leave_applications(self): + leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type, + self.from_date, self.to_date) + if flt(leaves_taken) > flt(self.total_leaves_allocated): + if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): + frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken)) + else: + frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) + def update_leave_policy_assignments_when_no_allocations_left(self): allocations = frappe.db.get_list("Leave Allocation", filters = { "docstatus": 1, @@ -225,4 +263,4 @@ def get_unused_leaves(employee, leave_type, from_date, to_date): def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): - frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) \ No newline at end of file + frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 6e7ae87d08..bff06e6a91 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals import frappe +import erpnext import unittest from frappe.utils import nowdate, add_months, getdate, add_days from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type @@ -164,6 +165,51 @@ class TestLeaveAllocation(unittest.TestCase): leave_allocation.cancel() self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name})) + def test_leave_addition_after_submit(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + + leave_allocation = create_leave_allocation() + leave_allocation.submit() + self.assertTrue(leave_allocation.total_leaves_allocated, 15) + leave_allocation.new_leaves_allocated = 40 + leave_allocation.submit() + self.assertTrue(leave_allocation.total_leaves_allocated, 40) + + def test_leave_subtraction_after_submit(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + leave_allocation = create_leave_allocation() + leave_allocation.submit() + self.assertTrue(leave_allocation.total_leaves_allocated, 15) + leave_allocation.new_leaves_allocated = 10 + leave_allocation.submit() + self.assertTrue(leave_allocation.total_leaves_allocated, 10) + + def test_against_leave_application_validation_after_submit(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + + leave_allocation = create_leave_allocation() + leave_allocation.submit() + self.assertTrue(leave_allocation.total_leaves_allocated, 15) + employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) + leave_application = frappe.get_doc({ + "doctype": 'Leave Application', + "employee": employee.name, + "leave_type": "_Test Leave Type", + "from_date": add_months(nowdate(), 2), + "to_date": add_months(add_days(nowdate(), 10), 2), + "company": erpnext.get_default_company() or "_Test Company", + "docstatus": 1, + "status": "Approved", + "leave_approver": 'test@example.com' + }) + leave_application.submit() + leave_allocation.new_leaves_allocated = 8 + leave_allocation.total_leaves_allocated = 8 + self.assertRaises(frappe.ValidationError, leave_allocation.submit) + def create_leave_allocation(**args): args = frappe._dict(args) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 4dd4570e8c..b8953b3eaa 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -178,7 +178,7 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type): is_carry_forward, is_expired FROM `tabLeave Ledger Entry` WHERE employee=%(employee)s AND leave_type=%(leave_type)s - AND docstatus=1 AND leaves>0 + AND docstatus=1 AND (from_date between %(from_date)s AND %(to_date)s OR to_date between %(from_date)s AND %(to_date)s OR (from_date < %(from_date)s AND to_date > %(to_date)s)) From c878389050a45fa6cdebf057da1752242db0ad3f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 Jan 2021 19:47:38 +0530 Subject: [PATCH 1108/1264] fix: or condition filter in the get_all --- .../doctype/job_card/job_card.json | 8 +- .../doctype/job_card/job_card.py | 20 +-- .../doctype/job_card_item/job_card_item.json | 13 ++ .../doctype/work_order/work_order.py | 9 +- .../cost_of_poor_quality_report/__init__.py | 0 .../cost_of_poor_quality_report.js | 9 ++ .../cost_of_poor_quality_report.json | 33 +++++ .../cost_of_poor_quality_report.py | 136 ++++++++++++++++++ erpnext/patches.txt | 3 +- .../patches/v13_0/update_job_card_details.py | 16 +++ .../stock/doctype/stock_entry/stock_entry.py | 3 +- 11 files changed, 234 insertions(+), 16 deletions(-) create mode 100644 erpnext/manufacturing/report/cost_of_poor_quality_report/__init__.py create mode 100644 erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js create mode 100644 erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.json create mode 100644 erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py create mode 100644 erpnext/patches/v13_0/update_job_card_details.py diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index c2fd8cc3f9..0597cdb207 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -33,6 +33,7 @@ "total_completed_qty", "column_break_15", "total_time_in_mins", + "hour_rate", "section_break_8", "items", "more_information", @@ -328,11 +329,16 @@ "fieldname": "section_break_21", "fieldtype": "Section Break", "hide_border": 1 + }, + { + "fieldname": "hour_rate", + "fieldtype": "Currency", + "label": "Hour Rate" } ], "is_submittable": 1, "links": [], - "modified": "2020-12-14 15:14:05.566271", + "modified": "2021-01-11 12:09:00.452032", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 5c157d43ec..b2d5667368 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -41,6 +41,7 @@ class JobCard(Document): def validate_time_logs(self): self.total_time_in_mins = 0.0 + self.total_completed_qty = 0.0 if self.get('time_logs'): for d in self.get('time_logs'): @@ -58,8 +59,6 @@ class JobCard(Document): if d.completed_qty: self.total_completed_qty += d.completed_qty - else: - self.total_completed_qty = 0.0 self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) @@ -256,12 +255,14 @@ class JobCard(Document): if self.get('operation') == d.operation: self.append('items', { - 'item_code': d.item_code, - 'source_warehouse': d.source_warehouse, - 'uom': frappe.db.get_value("Item", d.item_code, 'stock_uom'), - 'item_name': d.item_name, - 'description': d.description, - 'required_qty': (d.required_qty * flt(self.for_quantity)) / doc.qty + "item_code": d.item_code, + "source_warehouse": d.source_warehouse, + "uom": frappe.db.get_value("Item", d.item_code, 'stock_uom'), + "item_name": d.item_name, + "description": d.description, + "required_qty": (d.required_qty * flt(self.for_quantity)) / doc.qty, + "rate": d.rate, + "amount": d.amount }) def on_submit(self): @@ -439,7 +440,8 @@ class JobCard(Document): data = frappe.get_all("Work Order Operation", fields = ["operation", "status", "completed_qty"], - filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id)}, + filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id), + "skip_job_card": 0}, order_by = "sequence_id, idx") message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(bold(self.name), diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json index 100ef4ca3a..60a2249442 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json @@ -17,6 +17,8 @@ "required_qty", "column_break_9", "transferred_qty", + "rate", + "amount", "allow_alternative_item" ], "fields": [ @@ -101,6 +103,17 @@ "label": "Transferred Qty", "no_copy": 1, "print_hide": 1, + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "read_only": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", "read_only": 1 } ], diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 23cc090427..06cafd2d04 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -764,10 +764,10 @@ class WorkOrder(Document): for row in stock_entry_doc.items: if row.batch_no and (row.is_finished_item or row.is_scrap_item): - qty = frappe.get_all("Stock Entry Detail", filters = {"batch_no": row.batch_no}, - or_conditions= {"is_finished_item": 1, "is_scrap_item": 1}, fields = ["sum(qty)"])[0][0] + qty = frappe.get_all("Stock Entry Detail", filters = {"batch_no": row.batch_no, "docstatus": 1}, + or_filters= {"is_finished_item": 1, "is_scrap_item": 1}, fields = ["sum(qty)"], as_list=1)[0][0] - frappe.db.set_value("Batch", row.batch_no, "produced_qty", qty) + frappe.db.set_value("Batch", row.batch_no, "produced_qty", flt(qty)) @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs @@ -1006,7 +1006,8 @@ def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto 'project': work_order.project, 'company': work_order.company, 'sequence_id': row.get("sequence_id"), - 'wip_warehouse': work_order.wip_warehouse + 'wip_warehouse': work_order.wip_warehouse, + "hour_rate": row.get("hour_rate") }) if work_order.transfer_material_against == 'Job Card' and not work_order.skip_transfer: diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/__init__.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js new file mode 100644 index 0000000000..7f5bc48f18 --- /dev/null +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js @@ -0,0 +1,9 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Cost of Poor Quality Report"] = { + "filters": [ + + ] +}; diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.json b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.json new file mode 100644 index 0000000000..ee63bc1c28 --- /dev/null +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-01-11 11:10:58.292896", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2021-01-11 11:11:03.594242", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Cost of Poor Quality Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Job Card", + "report_name": "Cost of Poor Quality Report", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Manufacturing Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py new file mode 100644 index 0000000000..21e7be7478 --- /dev/null +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -0,0 +1,136 @@ +# 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 _ +from frappe.utils import flt + +def execute(filters=None): + columns, data = [], [] + + columns = get_columns(filters) + data = get_data(filters) + + return columns, data + +def get_data(filters): + data = [] + operations = frappe.get_all("Operation", filters = {"cost_of_poor_quality_operation": 1}) + if operations: + operations = [d.name for d in operations] + fields = ["production_item as item_code", "item_name", "work_order", "operation", + "workstation", "total_time_in_mins", "name", "hour_rate"] + + job_cards = frappe.get_all("Job Card", fields = fields, + filters = {"docstatus": 1, "operation": ("in", operations)}) + + for row in job_cards: + row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) + update_raw_material_cost(row, filters) + update_time_details(row, filters, data) + + return data + +def update_raw_material_cost(row, filters): + row.rm_cost = 0.0 + for data in frappe.get_all("Job Card Item", fields = ["amount"], + filters={"parent": row.name, "docstatus": 1}): + row.rm_cost += data.amount + +def update_time_details(row, filters, data): + args = frappe._dict({"item_code": "", "item_name": "", "name": "", "work_order":"", + "operation": "", "workstation":"", "operating_cost": "", "rm_cost": "", "total_time_in_mins": ""}) + + i=0 + for time_log in frappe.get_all("Job Card Time Log", fields = ["from_time", "to_time", "time_in_mins"], + filters={"parent": row.name, "docstatus": 1}): + + if i==0: + i += 1 + row.update(time_log) + data.append(row) + else: + args.update(time_log) + data.append(args) + +def get_columns(filters): + return [ + { + "label": _("Job Card"), + "fieldtype": "Link", + "fieldname": "name", + "options": "Job Card", + "width": "100" + }, + { + "label": _("Work Order"), + "fieldtype": "Link", + "fieldname": "work_order", + "options": "Work Order", + "width": "100" + }, + { + "label": _("Item Code"), + "fieldtype": "Link", + "fieldname": "item_code", + "options": "Item", + "width": "100" + }, + { + "label": _("Item Name"), + "fieldtype": "Data", + "fieldname": "item_name", + "width": "100" + }, + { + "label": _("Operation"), + "fieldtype": "Link", + "fieldname": "operation", + "options": "Operation", + "width": "100" + }, + { + "label": _("Workstation"), + "fieldtype": "Link", + "fieldname": "workstation", + "options": "Workstation", + "width": "100" + }, + { + "label": _("Operating Cost"), + "fieldtype": "Currency", + "fieldname": "operating_cost", + "width": "100" + }, + { + "label": _("Raw Material Cost"), + "fieldtype": "Currency", + "fieldname": "rm_cost", + "width": "100" + }, + { + "label": _("Total Time (in Mins)"), + "fieldtype": "Float", + "fieldname": "total_time_in_mins", + "width": "100" + }, + { + "label": _("From Time"), + "fieldtype": "Datetime", + "fieldname": "from_time", + "width": "100" + }, + { + "label": _("To Time"), + "fieldtype": "Datetime", + "fieldname": "to_time", + "width": "100" + }, + { + "label": _("Time in Mins"), + "fieldtype": "Float", + "fieldname": "time_in_mins", + "width": "100" + }, + ] \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index dd0e33beba..2b1fc43a1c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -288,4 +288,5 @@ execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True) erpnext.patches.v13_0.update_timesheet_changes erpnext.patches.v13_0.set_training_event_attendance erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold -erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice \ No newline at end of file +erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice +erpnext.patches.v13_0.update_job_card_details diff --git a/erpnext/patches/v13_0/update_job_card_details.py b/erpnext/patches/v13_0/update_job_card_details.py new file mode 100644 index 0000000000..d4e65c6f2f --- /dev/null +++ b/erpnext/patches/v13_0/update_job_card_details.py @@ -0,0 +1,16 @@ +# 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("manufacturing", "doctype", "job_card") + frappe.reload_doc("manufacturing", "doctype", "job_card_item") + frappe.reload_doc("manufacturing", "doctype", "work_order_operation") + + frappe.db.sql(""" update `tabJob Card` jc, `tabWork Order Operation` wo + SET jc.hour_rate = wo.hour_rate + WHERE + jc.operation_id = wo.name and jc.docstatus < 2 and wo.hour_rate > 0 + """) \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 83412c61d9..4cc721badf 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -365,6 +365,7 @@ class StockEntry(StockController): "overproduction_percentage_for_work_order")) for d in prod_order.get("operations"): + if d.skip_job_card: continue total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty) completed_qty = d.completed_qty + (allowance_percentage/100 * d.completed_qty) if total_completed_qty > flt(completed_qty): @@ -1104,7 +1105,7 @@ class StockEntry(StockController): fields = ["qty_to_produce as qty", "produced_qty", "name"] - for row in frappe.get_all("Batch", filters = filters, fields = fields): + for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"): batch_qty = flt(row.qty) - flt(row.produced_qty) if not batch_qty: continue From 57307443f04c7645889e9e8f41670f18f9ba63ee Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 19 Jan 2021 18:32:33 +0530 Subject: [PATCH 1109/1264] is corrective job card --- .../doctype/bom_operation/bom_operation.json | 10 +-- .../doctype/job_card/job_card.js | 53 +++++++++++-- .../doctype/job_card/job_card.json | 43 +++++++++- .../doctype/job_card/job_card.py | 78 +++++++++++++++---- .../doctype/job_card_item/job_card_item.json | 2 +- .../doctype/operation/operation.json | 17 ++-- .../doctype/work_order/work_order.js | 4 +- .../doctype/work_order/work_order.json | 11 +++ .../doctype/work_order/work_order.py | 8 +- .../work_order_operation.json | 10 +-- .../cost_of_poor_quality_report.js | 62 ++++++++++++++- .../cost_of_poor_quality_report.py | 26 +++++-- .../stock/doctype/stock_entry/stock_entry.py | 1 - 13 files changed, 260 insertions(+), 65 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 1330636198..4458e6db23 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -11,7 +11,6 @@ "workstation", "description", "col_break1", - "skip_job_card", "hour_rate", "time_in_mins", "operating_cost", @@ -118,20 +117,13 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID" - }, - { - "allow_on_submit": 1, - "default": "0", - "fieldname": "skip_job_card", - "fieldtype": "Check", - "label": "Skip Job Card" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-05 14:29:11.887888", + "modified": "2021-01-12 14:48:09.596843", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 57ec20b42c..266d5f6058 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -41,6 +41,10 @@ frappe.ui.form.on('Job Card', { } } + if (frm.doc.docstatus == 1 && !frm.doc.is_corrective_job_card) { + frm.trigger('setup_corrective_job_card') + } + frm.set_query("quality_inspection", function() { return { query: "erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query", @@ -53,12 +57,50 @@ frappe.ui.form.on('Job Card', { frm.trigger("toggle_operation_number"); - if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity) + if (frm.doc.docstatus == 0 && !frm.is_new() && + (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity) && (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) { frm.trigger("prepare_timer_buttons"); } }, + setup_corrective_job_card: function(frm) { + frm.add_custom_button(__('Corrective Job Card'), () => { + let operations = frm.doc.sub_operations.map(d => d.sub_operation).concat(frm.doc.operation); + + let fields = [ + { + fieldtype: 'Link', label: __('Corrective Operation'), options: 'Operation', + fieldname: 'operation', get_query() { return { filters: { "is_corrective_operation": 1 }}} + }, { + fieldtype: 'Link', label: __('For Operation'), options: 'Operation', + fieldname: 'for_operation', get_query() { return { filters: { "name": ["in", operations] }}} + } + ]; + + frappe.prompt(fields, d => { + frm.events.make_corrective_job_card(frm, d.operation, d.for_operation); + }, __("Select Corrective Operation")); + }, __('Make')); + }, + + make_corrective_job_card: function(frm, operation, for_operation) { + frappe.call({ + method: 'erpnext.manufacturing.doctype.job_card.job_card.make_corrective_job_card', + args: { + source_name: frm.doc.name, + operation: operation, + for_operation: for_operation + }, + callback: function(r) { + if (r.message) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + }); + }, + operation: function(frm) { frm.trigger("toggle_operation_number"); @@ -110,10 +152,9 @@ frappe.ui.form.on('Job Card', { if (!frm.doc.started_time && !frm.doc.current_time) { frm.add_custom_button(__("Start Job"), () => { - frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Employee'), options: "Job Card Time Log", - fieldname: 'employee'}, d => { - debugger - frm.events.start_job(frm, "Work In Progress", d.employee); + frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Select Employees'), + options: "Job Card Time Log", fieldname: 'employees'}, d => { + frm.events.start_job(frm, "Work In Progress", d.employees); }, __("Assign Job to Employee")); }).addClass("btn-primary"); } else if (frm.doc.status == "On Hold") { @@ -138,7 +179,7 @@ frappe.ui.form.on('Job Card', { const args = { job_card_id: frm.doc.name, start_time: frappe.datetime.now_datetime(), - employee: employee, + employees: employee, status: status }; frm.events.make_time_log(frm, args); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 0597cdb207..be7a810173 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -33,9 +33,14 @@ "total_completed_qty", "column_break_15", "total_time_in_mins", - "hour_rate", "section_break_8", "items", + "corrective_operation_section", + "for_job_card", + "is_corrective_job_card", + "column_break_33", + "hour_rate", + "for_operation", "more_information", "operation_id", "sequence_id", @@ -331,14 +336,48 @@ "hide_border": 1 }, { + "depends_on": "is_corrective_job_card", "fieldname": "hour_rate", "fieldtype": "Currency", "label": "Hour Rate" + }, + { + "collapsible": 1, + "depends_on": "is_corrective_job_card", + "fieldname": "corrective_operation_section", + "fieldtype": "Section Break", + "label": "Corrective Operation" + }, + { + "default": "0", + "fieldname": "is_corrective_job_card", + "fieldtype": "Check", + "label": "Is Corrective Job Card", + "read_only": 1 + }, + { + "fieldname": "column_break_33", + "fieldtype": "Column Break" + }, + { + "fieldname": "for_job_card", + "fieldtype": "Link", + "label": "For Job Card", + "options": "Job Card", + "read_only": 1 + }, + { + "fetch_from": "for_job_card.operation", + "fetch_if_empty": 1, + "fieldname": "for_operation", + "fieldtype": "Link", + "label": "For Operation", + "options": "Operation" } ], "is_submittable": 1, "links": [], - "modified": "2021-01-11 12:09:00.452032", + "modified": "2021-02-03 20:36:51.826944", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index b2d5667368..b4202e158d 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -178,18 +178,27 @@ class JobCard(Document): self.reset_timer_value(args) if last_row and args.get("complete_time"): - last_row.update({ - "to_time": get_datetime(args.get("complete_time")), - "operation": args.get("sub_operation"), - "completed_qty": args.get("completed_qty") or 0.0 - }) + for row in self.time_logs: + if not row.to_time: + row.update({ + "to_time": get_datetime(args.get("complete_time")), + "operation": args.get("sub_operation"), + "completed_qty": args.get("completed_qty") or 0.0 + }) elif args.get("start_time"): - self.append("time_logs", { - "from_time": get_datetime(args.get("start_time")), - "employee": args.get("employee"), - "operation": args.get("sub_operation"), - "completed_qty": 0.0 - }) + employees = args.employees + print(args) + if isinstance(employees, string_types): + employees = json.loads(employees) + + for name in employees: + print(name.get('employee')) + self.append("time_logs", { + "from_time": get_datetime(args.get("start_time")), + "employee": name.get('employee'), + "operation": args.get("sub_operation"), + "completed_qty": 0.0 + }) if self.status == "On Hold": self.current_time = time_diff_in_seconds(last_row.to_time, last_row.from_time) @@ -300,10 +309,24 @@ class JobCard(Document): time_in_mins = flt(data[0].time_in_mins) wo = frappe.get_doc('Work Order', self.work_order) - if self.operation_id: + + if self.is_corrective_job_card: + self.update_corrective_in_work_order(wo) + + elif self.operation_id: self.validate_produced_quantity(for_quantity, wo) self.update_work_order_data(for_quantity, time_in_mins, wo) + def update_corrective_in_work_order(self, wo): + wo.corrective_operation_cost = 0.0 + for row in frappe.get_all('Job Card', fields = ['total_time_in_mins', 'hour_rate'], + filters = {'is_corrective_job_card': 1, 'docstatus': 1, 'work_order': self.work_order}): + wo.corrective_operation_cost += flt(row.total_time_in_mins) * flt(row.hour_rate) + + wo.calculate_operating_cost() + wo.flags.ignore_validate_update_after_submit = True + wo.save() + def validate_produced_quantity(self, for_quantity, wo): if self.docstatus < 2: return @@ -346,7 +369,8 @@ class JobCard(Document): def get_current_operation_data(self): return frappe.get_all('Job Card', fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], - filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id}) + filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id, + "is_corrective_job_card": 0}) def set_transferred_qty_in_job_card(self, ste_doc): for row in ste_doc.items: @@ -429,6 +453,8 @@ class JobCard(Document): .format(bold(self.operation), work_order), OperationMismatchError) def validate_sequence_id(self): + if self.is_corrective_job_card: return + if not (self.work_order and self.sequence_id): return current_operation_qty = 0.0 @@ -440,8 +466,7 @@ class JobCard(Document): data = frappe.get_all("Work Order Operation", fields = ["operation", "status", "completed_qty"], - filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id), - "skip_job_card": 0}, + filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id)}, order_by = "sequence_id, idx") message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(bold(self.name), @@ -598,3 +623,26 @@ def get_job_details(start, end, filters=None): events.append(job_card_data) return events + +@frappe.whitelist() +def make_corrective_job_card(source_name, operation=None, for_operation=None, target_doc=None): + def set_missing_values(source, target): + target.is_corrective_job_card = 1 + target.operation = operation + target.for_operation = for_operation + + target.set('time_logs', []) + target.get_sub_operations() + target.get_required_items() + target.validate_time_logs() + + doclist = get_mapped_doc("Job Card", source_name, { + "Job Card": { + "doctype": "Job Card", + "field_map": { + "name": "for_job_card", + }, + } + }, target_doc, set_missing_values) + + return doclist \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json index 60a2249442..a239a247e3 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json @@ -102,7 +102,7 @@ "fieldtype": "Float", "label": "Transferred Qty", "no_copy": 1, - "print_hide": 1, + "print_hide": 1 }, { "fieldname": "rate", diff --git a/erpnext/manufacturing/doctype/operation/operation.json b/erpnext/manufacturing/doctype/operation/operation.json index 9e6f8e1f5d..10a97eda76 100644 --- a/erpnext/manufacturing/doctype/operation/operation.json +++ b/erpnext/manufacturing/doctype/operation/operation.json @@ -10,7 +10,7 @@ "field_order": [ "workstation", "data_2", - "cost_of_poor_quality_operation", + "is_corrective_operation", "job_card_section", "create_job_card_based_on_batch_size", "column_break_6", @@ -77,13 +77,6 @@ "fieldtype": "Check", "label": "Create Job Card based on Batch Size" }, - { - "default": "0", - "description": "Cost of poor quality operation", - "fieldname": "cost_of_poor_quality_operation", - "fieldtype": "Check", - "label": "Is COPQ Operation" - }, { "collapsible": 1, "fieldname": "job_card_section", @@ -93,12 +86,18 @@ { "fieldname": "column_break_6", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_corrective_operation", + "fieldtype": "Check", + "label": "Is Corrective Operation" } ], "icon": "fa fa-wrench", "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-24 14:25:03.428303", + "modified": "2021-01-12 15:09:23.593338", "modified_by": "Administrator", "module": "Manufacturing", "name": "Operation", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index adf6453e2e..acb3407e2b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -242,13 +242,13 @@ frappe.ui.form.on("Work Order", { if(data.completed_qty != frm.doc.qty) { pending_qty = frm.doc.qty - flt(data.completed_qty); - if (pending_qty && !data.skip_job_card) { + if (pending_qty) { dialog.fields_dict.operations.df.data.push({ 'name': data.name, 'operation': data.operation, 'workstation': data.workstation, 'qty': pending_qty, - 'pending_qty': pending_qty, + 'pending_qty': pending_qty }); } } diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index c80decb92e..8e99c665f1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -58,6 +58,7 @@ "actual_operating_cost", "additional_operating_cost", "column_break_24", + "corrective_operation_cost", "total_operating_cost", "more_info", "description", @@ -534,6 +535,16 @@ "fieldname": "batch_size", "fieldtype": "Float", "label": "Batch Size" + }, + { + "allow_on_submit": 1, + "description": "From Corrective Job Card", + "fieldname": "corrective_operation_cost", + "fieldtype": "Currency", + "label": "Corrective Operation Cost", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-cogs", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 06cafd2d04..c83f539e03 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -130,7 +130,9 @@ class WorkOrder(Document): variable_cost = self.actual_operating_cost if self.actual_operating_cost \ else self.planned_operating_cost - self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost) + + self.total_operating_cost = (flt(self.additional_operating_cost) + + flt(variable_cost) + flt(self.corrective_operation_cost)) def validate_work_order_against_so(self): # already ordered qty @@ -343,7 +345,6 @@ class WorkOrder(Document): plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 for index, row in enumerate(self.operations): - if row.skip_job_card: continue qty = self.qty i=0 while qty > 0: @@ -357,6 +358,7 @@ class WorkOrder(Document): qty -= row.batch_size elif qty > 0: job_card_qty = qty + qty = 0 if job_card_qty > 0: self.prepare_data_for_job_card(row, job_card_qty, index, @@ -496,7 +498,7 @@ class WorkOrder(Document): select operation, description, workstation, idx, base_hour_rate as hour_rate, time_in_mins, - "Pending" as status, parent as bom, batch_size, sequence_id, skip_job_card + "Pending" as status, parent as bom, batch_size, sequence_id from `tabBOM Operation` where diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index b77690997c..6d8fb80e31 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -9,7 +9,6 @@ "operation", "bom", "column_break_4", - "skip_job_card", "description", "sequence_id", "col_break1", @@ -201,19 +200,12 @@ { "fieldname": "column_break_4", "fieldtype": "Column Break" - }, - { - "allow_on_submit": 1, - "default": "0", - "fieldname": "skip_job_card", - "fieldtype": "Check", - "label": "Skip Job Card" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-08 17:42:05.372163", + "modified": "2021-01-12 14:48:31.061286", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js index 7f5bc48f18..ef77566389 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js @@ -4,6 +4,66 @@ frappe.query_reports["Cost of Poor Quality Report"] = { "filters": [ - + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Datetime", + default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Datetime", + default: frappe.datetime.now_datetime(), + reqd: 1, + }, + { + label: __("Job Card"), + fieldname: "name", + fieldtype: "Link", + options: "Job Card", + get_query: function() { + return { + filters: { + is_corrective_job_card: 1, + docstatus: 1 + } + } + } + }, + { + label: __("Work Order"), + fieldname: "work_order", + fieldtype: "Link", + options: "Work Order" + }, + { + label: __("Operation"), + fieldname: "operation", + fieldtype: "Link", + options: "Operation", + get_query: function() { + return { + filters: { + is_corrective_operation: 1 + } + } + } + }, + { + label: __("Workstation"), + fieldname: "workstation", + fieldtype: "Link", + options: "Workstation" + }, ] }; diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index 21e7be7478..2e8c191c60 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -14,24 +14,34 @@ def execute(filters=None): return columns, data -def get_data(filters): +def get_data(report_filters): data = [] - operations = frappe.get_all("Operation", filters = {"cost_of_poor_quality_operation": 1}) + operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1}) if operations: operations = [d.name for d in operations] fields = ["production_item as item_code", "item_name", "work_order", "operation", "workstation", "total_time_in_mins", "name", "hour_rate"] + filters = get_filters(report_filters, operations) + job_cards = frappe.get_all("Job Card", fields = fields, - filters = {"docstatus": 1, "operation": ("in", operations)}) + filters = filters) for row in job_cards: row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) - update_raw_material_cost(row, filters) - update_time_details(row, filters, data) + update_raw_material_cost(row, report_filters) + update_time_details(row, report_filters, data) return data +def get_filters(report_filters, operations): + filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1} + for field in ["name", "work_order", "operation", "workstation", "company"]: + if report_filters.get(field): + filters[field] = report_filters.get(field) + + return filters + def update_raw_material_cost(row, filters): row.rm_cost = 0.0 for data in frappe.get_all("Job Card Item", fields = ["amount"], @@ -43,8 +53,10 @@ def update_time_details(row, filters, data): "operation": "", "workstation":"", "operating_cost": "", "rm_cost": "", "total_time_in_mins": ""}) i=0 - for time_log in frappe.get_all("Job Card Time Log", fields = ["from_time", "to_time", "time_in_mins"], - filters={"parent": row.name, "docstatus": 1}): + for time_log in frappe.get_all("Job Card Time Log", + fields = ["from_time", "to_time", "time_in_mins"], + filters={"parent": row.name, "docstatus": 1, + "from_time": (">=", filters.from_date), "to_time": ("<=", filters.to_date)}): if i==0: i += 1 diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4cc721badf..e49c9a57c3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -365,7 +365,6 @@ class StockEntry(StockController): "overproduction_percentage_for_work_order")) for d in prod_order.get("operations"): - if d.skip_job_card: continue total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty) completed_qty = d.completed_qty + (allowance_percentage/100 * d.completed_qty) if total_completed_qty > flt(completed_qty): From 2330c41ccae1777c063a14024c4cdd42aeb9c921 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Mar 2021 14:03:12 +0530 Subject: [PATCH 1110/1264] fix: total time calculation --- erpnext/manufacturing/doctype/bom/bom.js | 8 -- erpnext/manufacturing/doctype/bom/bom.json | 5 +- erpnext/manufacturing/doctype/bom/bom.py | 2 +- .../doctype/job_card/job_card.js | 56 ++++++++--- .../doctype/job_card/job_card.json | 25 ++++- .../doctype/job_card/job_card.py | 89 ++++++++++++----- .../doctype/job_card_item/job_card_item.json | 23 +---- .../job_card_operation.json | 11 ++- .../manufacturing_settings.json | 9 +- .../doctype/operation/operation.js | 10 +- .../doctype/operation/operation.py | 1 - .../doctype/work_order/work_order.js | 43 +++++--- .../doctype/work_order/work_order.json | 5 +- .../doctype/work_order/work_order.py | 97 ++++++++++++------- .../cost_of_poor_quality_report.js | 36 +++++++ .../cost_of_poor_quality_report.py | 61 ++++-------- .../stock/doctype/stock_entry/stock_entry.py | 10 +- .../stock_entry_detail.json | 4 +- 18 files changed, 324 insertions(+), 171 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index a09a5e3430..27019dbbae 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -71,7 +71,6 @@ frappe.ui.form.on("BOM", { refresh: function(frm) { frm.toggle_enable("item", frm.doc.__islocal); - toggle_operations(frm); frm.set_indicator_formatter('item_code', function(doc) { @@ -651,15 +650,8 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) { erpnext.bom.calculate_total(frm.doc); }); -var toggle_operations = function(frm) { - frm.toggle_display("operations_section", cint(frm.doc.with_operations) == 1); - frm.toggle_display("transfer_material_against", cint(frm.doc.with_operations) == 1); - frm.toggle_reqd("transfer_material_against", cint(frm.doc.with_operations) == 1); -}; - frappe.ui.form.on("BOM", "with_operations", function(frm) { if(!cint(frm.doc.with_operations)) { frm.set_value("operations", []); } - toggle_operations(frm); }); \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index f551b91597..f38d1b9892 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -193,6 +193,7 @@ }, { "default": "Work Order", + "depends_on": "with_operations", "fieldname": "transfer_material_against", "fieldtype": "Select", "label": "Transfer Material Against", @@ -235,6 +236,7 @@ { "fieldname": "operations_section", "fieldtype": "Section Break", + "hide_border": 1, "oldfieldtype": "Section Break" }, { @@ -245,6 +247,7 @@ "options": "Routing" }, { + "depends_on": "with_operations", "fieldname": "operations", "fieldtype": "Table", "label": "Operations", @@ -517,7 +520,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2020-05-21 12:29:32.634952", + "modified": "2021-03-16 12:25:09.081968", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 3f109d91b5..3e855603b4 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -590,7 +590,7 @@ class BOM(WebsiteGenerator): self.get_routing() def validate_operations(self): - if self.with_operations and not self.get('operations'): + if self.with_operations and not self.get('operations') and self.docstatus == 1: frappe.throw(_("Operations cannot be left blank")) if self.with_operations: diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 266d5f6058..81860c9fbc 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -42,7 +42,7 @@ frappe.ui.form.on('Job Card', { } if (frm.doc.docstatus == 1 && !frm.doc.is_corrective_job_card) { - frm.trigger('setup_corrective_job_card') + frm.trigger('setup_corrective_job_card'); } frm.set_query("quality_inspection", function() { @@ -71,15 +71,27 @@ frappe.ui.form.on('Job Card', { let fields = [ { fieldtype: 'Link', label: __('Corrective Operation'), options: 'Operation', - fieldname: 'operation', get_query() { return { filters: { "is_corrective_operation": 1 }}} + fieldname: 'operation', get_query() { + return { + filters: { + "is_corrective_operation": 1 + } + }; + } }, { fieldtype: 'Link', label: __('For Operation'), options: 'Operation', - fieldname: 'for_operation', get_query() { return { filters: { "name": ["in", operations] }}} + fieldname: 'for_operation', get_query() { + return { + filters: { + "name": ["in", operations] + } + }; + } } ]; frappe.prompt(fields, d => { - frm.events.make_corrective_job_card(frm, d.operation, d.for_operation); + frm.events.make_corrective_job_card(frm, d.operation, d.for_operation); }, __("Select Corrective Operation")); }, __('Make')); }, @@ -152,14 +164,18 @@ frappe.ui.form.on('Job Card', { if (!frm.doc.started_time && !frm.doc.current_time) { frm.add_custom_button(__("Start Job"), () => { - frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Select Employees'), - options: "Job Card Time Log", fieldname: 'employees'}, d => { + if ((frm.doc.employee && !frm.doc.employee.length) || !frm.doc.employee) { + frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Select Employees'), + options: "Job Card Time Log", fieldname: 'employees'}, d => { frm.events.start_job(frm, "Work In Progress", d.employees); - }, __("Assign Job to Employee")); + }, __("Assign Job to Employee")); + } else { + frm.events.start_job(frm, "Work In Progress", frm.doc.employee); + } }).addClass("btn-primary"); } else if (frm.doc.status == "On Hold") { frm.add_custom_button(__("Resume Job"), () => { - frm.events.start_job(frm, "Resume Job"); + frm.events.start_job(frm, "Resume Job", frm.doc.employee); }).addClass("btn-primary"); } else { frm.add_custom_button(__("Pause Job"), () => { @@ -167,10 +183,26 @@ frappe.ui.form.on('Job Card', { }); frm.add_custom_button(__("Complete Job"), () => { - frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), - fieldname: 'qty', default: frm.doc.for_quantity}, data => { + var sub_operations = frm.doc.sub_operations; + + let set_qty = true; + if (sub_operations && sub_operations.length > 1) { + set_qty = false; + let last_op_row = sub_operations[sub_operations.length - 2]; + + if (last_op_row.status == 'Complete') { + set_qty = true; + } + } + + if (set_qty) { + frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), + fieldname: 'qty', default: frm.doc.for_quantity}, data => { frm.events.complete_job(frm, "Complete", data.qty); }, __("Enter Value")); + } else { + frm.events.complete_job(frm, "Complete", 0.0); + } }).addClass("btn-primary"); } }, @@ -204,11 +236,11 @@ frappe.ui.form.on('Job Card', { args: args }, freeze: true, - callback: function (r) { + callback: function () { frm.reload_doc(); frm.trigger("make_dashboard"); } - }) + }); }, update_sub_operation: function(frm, args) { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index be7a810173..046e2fd182 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -16,15 +16,18 @@ "production_item", "item_name", "for_quantity", + "serial_no", "column_break_12", "wip_warehouse", "quality_inspection", "project", + "batch_no", "operation_section_section", "operation", "operation_row_number", "column_break_18", "workstation", + "employee", "section_break_21", "sub_operations", "timing_detail", @@ -163,8 +166,7 @@ "fieldname": "items", "fieldtype": "Table", "label": "Items", - "options": "Job Card Item", - "read_only": 1 + "options": "Job Card Item" }, { "collapsible": 1, @@ -373,11 +375,28 @@ "fieldtype": "Link", "label": "For Operation", "options": "Operation" + }, + { + "fieldname": "employee", + "fieldtype": "Table MultiSelect", + "label": "Employee", + "options": "Job Card Time Log" + }, + { + "fieldname": "serial_no", + "fieldtype": "Small Text", + "label": "Serial No" + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "options": "Batch" } ], "is_submittable": 1, "links": [], - "modified": "2021-02-03 20:36:51.826944", + "modified": "2021-03-16 15:59:32.766484", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index b4202e158d..7f8f2ef68d 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -4,9 +4,9 @@ from __future__ import unicode_literals import frappe -import datetime, json +import datetime +import json from frappe import _, bold -from six import string_types from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate, @@ -33,11 +33,10 @@ class JobCard(Document): if self.operation: self.sub_operations = [] for row in frappe.get_all("Sub Operation", - filters = {"parent": self.operation}, fields=["operation"]): - self.append("sub_operations", { - "sub_operation": row.operation, - "status": "Pending" - }) + filters = {"parent": self.operation}, fields=["operation", "idx"]): + row.status = "Pending" + row.sub_operation = row.operation + self.append("sub_operations", row) def validate_time_logs(self): self.total_time_in_mins = 0.0 @@ -57,11 +56,14 @@ class JobCard(Document): d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60 self.total_time_in_mins += d.time_in_mins - if d.completed_qty: + if d.completed_qty and not self.sub_operations: self.total_completed_qty += d.completed_qty self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) + for row in self.sub_operations: + self.total_completed_qty += row.completed_qty + def get_overlap_for(self, args, check_next_available_slot=False): production_capacity = 1 @@ -173,6 +175,10 @@ class JobCard(Document): def add_time_log(self, args): last_row = [] + employees = args.employees + if isinstance(employees, str): + employees = json.loads(employees) + if self.time_logs and len(self.time_logs) > 0: last_row = self.time_logs[-1] @@ -186,13 +192,7 @@ class JobCard(Document): "completed_qty": args.get("completed_qty") or 0.0 }) elif args.get("start_time"): - employees = args.employees - print(args) - if isinstance(employees, string_types): - employees = json.loads(employees) - for name in employees: - print(name.get('employee')) self.append("time_logs", { "from_time": get_datetime(args.get("start_time")), "employee": name.get('employee'), @@ -200,11 +200,21 @@ class JobCard(Document): "completed_qty": 0.0 }) + if not self.employee: + self.set_employees(employees) + if self.status == "On Hold": self.current_time = time_diff_in_seconds(last_row.to_time, last_row.from_time) self.save() + def set_employees(self, employees): + for name in employees: + self.append('employee', { + 'employee': name.get('employee'), + 'completed_qty': 0.0 + }) + def reset_timer_value(self, args): self.started_time = None @@ -221,24 +231,41 @@ class JobCard(Document): self.status = args.get("status") def update_sub_operation_status(self): - if not (self.sub_operations and self.time_logs): return + if not (self.sub_operations and self.time_logs): + return operation_wise_completed_time = {} for time_log in self.time_logs: if time_log.operation not in operation_wise_completed_time: operation_wise_completed_time.setdefault(time_log.operation, - frappe._dict({"status": "Pending", "completed_time": 0.0})) + frappe._dict({"status": "Pending", "completed_qty":0.0, "completed_time": 0.0, "employee": []})) op_row = operation_wise_completed_time[time_log.operation] op_row.status = "Work In Progress" if not time_log.time_in_mins else "Complete" + if self.status == 'On Hold': + op_row.status = 'Pause' + + op_row.employee.append(time_log.employee) if time_log.time_in_mins: op_row.completed_time += time_log.time_in_mins + op_row.completed_qty += time_log.completed_qty for row in self.sub_operations: operation_deatils = operation_wise_completed_time.get(row.sub_operation) if operation_deatils: - row.status = operation_deatils.status + if row.status != 'Complete': + row.status = operation_deatils.status + row.completed_time = operation_deatils.completed_time + if operation_deatils.employee: + row.completed_time = row.completed_time / len(set(operation_deatils.employee)) + + if operation_deatils.completed_qty: + row.completed_qty = operation_deatils.completed_qty / len(set(operation_deatils.employee)) + else: + row.status = 'Pending' + row.completed_time = 0.0 + row.completed_qty = 0.0 def update_time_logs(self, row): self.append("time_logs", { @@ -275,6 +302,7 @@ class JobCard(Document): }) def on_submit(self): + self.validate_transfer_qty() self.validate_job_card() self.update_work_order() self.set_transferred_qty() @@ -283,7 +311,16 @@ class JobCard(Document): self.update_work_order() self.set_transferred_qty() + def validate_transfer_qty(self): + if self.items and self.transferred_qty < self.for_quantity: + frappe.throw(_('Materials needs to be transferred to the work in progress warehouse for the job card {0}') + .format(self.name)) + def validate_job_card(self): + if self.work_order and frappe.get_cached_value('Work Order', self.work_order, 'status') == 'Stopped': + frappe.throw(_("Transaction not allowed against stopped Work Order {0}") + .format(get_link_to_form('Work Order', self.work_order))) + if not self.time_logs: frappe.throw(_("Time logs are required for {0} {1}") .format(bold("Job Card"), get_link_to_form("Job Card", self.name))) @@ -299,6 +336,10 @@ class JobCard(Document): if not self.work_order: return + if self.is_corrective_job_card and not cint(frappe.db.get_single_value('Manufacturing Settings', + 'add_corrective_operation_cost_in_finished_good_valuation')): + return + for_quantity, time_in_mins = 0, 0 from_time_list, to_time_list = [], [] @@ -346,8 +387,8 @@ class JobCard(Document): min(from_time) as start_time, max(to_time) as end_time FROM `tabJob Card` jc, `tabJob Card Time Log` jctl WHERE - jctl.parent = jc.name and jc.work_order = %s - and jc.operation_id = %s and jc.docstatus = 1 + jctl.parent = jc.name and jc.work_order = %s and jc.operation_id = %s + and jc.docstatus = 1 and IFNULL(jc.is_corrective_job_card, 0) = 0 """, (self.work_order, self.operation_id), as_dict=1) for data in wo.operations: @@ -453,9 +494,11 @@ class JobCard(Document): .format(bold(self.operation), work_order), OperationMismatchError) def validate_sequence_id(self): - if self.is_corrective_job_card: return + if self.is_corrective_job_card: + return - if not (self.work_order and self.sequence_id): return + if not (self.work_order and self.sequence_id): + return current_operation_qty = 0.0 data = self.get_current_operation_data() @@ -480,7 +523,7 @@ class JobCard(Document): @frappe.whitelist() def make_time_log(args): - if isinstance(args, string_types): + if isinstance(args, str): args = json.loads(args) args = frappe._dict(args) @@ -632,6 +675,8 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta target.for_operation = for_operation target.set('time_logs', []) + target.set('employee', []) + target.set('items', []) target.get_sub_operations() target.get_required_items() target.validate_time_logs() diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json index a239a247e3..d91530dd3b 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json @@ -17,8 +17,6 @@ "required_qty", "column_break_9", "transferred_qty", - "rate", - "amount", "allow_alternative_item" ], "fields": [ @@ -27,8 +25,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Item Code", - "options": "Item", - "read_only": 1 + "options": "Item" }, { "fieldname": "source_warehouse", @@ -69,8 +66,7 @@ "fieldname": "required_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Required Qty", - "read_only": 1 + "label": "Required Qty" }, { "fieldname": "column_break_9", @@ -102,25 +98,14 @@ "fieldtype": "Float", "label": "Transferred Qty", "no_copy": 1, - "print_hide": 1 - }, - { - "fieldname": "rate", - "fieldtype": "Currency", - "label": "Rate", - "read_only": 1 - }, - { - "fieldname": "amount", - "fieldtype": "Currency", - "label": "Amount", + "print_hide": 1, "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-11 13:50:13.804108", + "modified": "2021-04-22 18:50:00.003444", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Item", diff --git a/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json index be8190236d..9a8692b84d 100644 --- a/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json +++ b/erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json @@ -7,7 +7,8 @@ "field_order": [ "sub_operation", "completed_time", - "status" + "status", + "completed_qty" ], "fields": [ { @@ -34,12 +35,18 @@ "label": "Operation", "options": "Operation", "read_only": 1 + }, + { + "fieldname": "completed_qty", + "fieldtype": "Float", + "label": "Completed Qty", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-14 17:08:25.992957", + "modified": "2021-03-16 18:24:35.399593", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Operation", diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 6647be54eb..024f784725 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -27,6 +27,7 @@ "overproduction_percentage_for_work_order", "other_settings_section", "update_bom_costs_automatically", + "add_corrective_operation_cost_in_finished_good_valuation", "column_break_23", "make_serial_no_batch_from_work_order" ], @@ -168,13 +169,19 @@ "fieldname": "make_serial_no_batch_from_work_order", "fieldtype": "Check", "label": "Make Serial No / Batch from Work Order" + }, + { + "default": "0", + "fieldname": "add_corrective_operation_cost_in_finished_good_valuation", + "fieldtype": "Check", + "label": "Add Corrective Operation Cost in Finished Good Valuation" } ], "icon": "icon-wrench", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-12-08 13:37:40.325838", + "modified": "2021-03-16 15:54:38.967341", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js index 9bfcc6eedb..102b6780e5 100644 --- a/erpnext/manufacturing/doctype/operation/operation.js +++ b/erpnext/manufacturing/doctype/operation/operation.js @@ -2,5 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('Operation', { - + setup: function(frm) { + frm.set_query('operation', 'sub_operations', function() { + return { + filters: { + 'name': ['not in', [frm.doc.name]] + } + }; + }); + } }); \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/operation/operation.py b/erpnext/manufacturing/doctype/operation/operation.py index aaf0d5c01b..374f32019b 100644 --- a/erpnext/manufacturing/doctype/operation/operation.py +++ b/erpnext/manufacturing/doctype/operation/operation.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt from frappe.model.document import Document class Operation(Document): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index acb3407e2b..512048512e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -189,35 +189,41 @@ frappe.ui.form.on("Work Order", { const dialog = frappe.prompt({fieldname: 'operations', fieldtype: 'Table', label: __('Operations'), fields: [ { - fieldtype:'Link', - fieldname:'operation', + fieldtype: 'Link', + fieldname: 'operation', label: __('Operation'), - read_only:1, - in_list_view:1 + read_only: 1, + in_list_view: 1 }, { - fieldtype:'Link', - fieldname:'workstation', + fieldtype: 'Link', + fieldname: 'workstation', label: __('Workstation'), - read_only:1, - in_list_view:1 + read_only: 1, + in_list_view: 1 }, { - fieldtype:'Data', - fieldname:'name', + fieldtype: 'Data', + fieldname: 'name', label: __('Operation Id') }, { - fieldtype:'Float', - fieldname:'pending_qty', + fieldtype: 'Float', + fieldname: 'pending_qty', label: __('Pending Qty'), }, { - fieldtype:'Float', - fieldname:'qty', + fieldtype: 'Float', + fieldname: 'qty', label: __('Quantity to Manufacture'), - read_only:0, - in_list_view:1, + read_only: 0, + in_list_view: 1, + }, + { + fieldtype: 'Float', + fieldname: 'batch_size', + label: __('Batch Size'), + read_only: 1 }, ], data: operations_data, @@ -228,9 +234,13 @@ frappe.ui.form.on("Work Order", { }, function(data) { frappe.call({ method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card", + freeze: true, args: { work_order: frm.doc.name, operations: data.operations, + }, + callback: function() { + frm.reload_doc(); } }); }, __("Job Card"), __("Create")); @@ -247,6 +257,7 @@ frappe.ui.form.on("Work Order", { 'name': data.name, 'operation': data.operation, 'workstation': data.workstation, + 'batch_size': data.batch_size, 'qty': pending_qty, 'pending_qty': pending_qty }); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 8e99c665f1..44d76d2b01 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -527,7 +527,8 @@ "depends_on": "has_serial_no", "fieldname": "serial_no", "fieldtype": "Small Text", - "label": "Serial Nos" + "label": "Serial Nos", + "no_copy": 1 }, { "default": "0", @@ -552,7 +553,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-03-16 13:27:51.116484", + "modified": "2021-06-20 15:19:14.902699", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index c83f539e03..e343ed2dd3 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -27,9 +27,8 @@ class CapacityError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass class OperationTooLongError(frappe.ValidationError): pass class ItemHasVariantError(frappe.ValidationError): pass -class SerialNoQtyError(frappe.ValidationError): pass - -from six import string_types +class SerialNoQtyError(frappe.ValidationError): + pass form_grid_templates = { "operations": "templates/form_grid/work_order_grid.html" @@ -248,7 +247,7 @@ class WorkOrder(Document): frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) - + if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}): self.update_work_order_qty_in_combined_so() else: @@ -268,7 +267,7 @@ class WorkOrder(Document): self.update_work_order_qty_in_combined_so() else: self.update_work_order_qty_in_so() - + self.delete_job_card() self.update_completed_qty_in_material_request() self.update_planned_qty() @@ -277,10 +276,11 @@ class WorkOrder(Document): self.delete_auto_created_batch_and_serial_no() def create_serial_no_batch_no(self): - if not (self.has_serial_no or self.has_batch_no): return + if not (self.has_serial_no or self.has_batch_no): + return - if not cint(frappe.db.get_single_value("Manufacturing Settings", - "make_serial_no_batch_from_work_order")): return + if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")): + return if self.has_batch_no: self.create_batch_for_finished_good() @@ -346,29 +346,17 @@ class WorkOrder(Document): for index, row in enumerate(self.operations): qty = self.qty - i=0 while qty > 0: - i += 1 - if not cint(frappe.db.get_value("Operation", - row.operation, "create_job_card_based_on_batch_size")): - row.batch_size = self.qty - - job_card_qty = row.batch_size - if row.batch_size and qty >= row.batch_size: - qty -= row.batch_size - elif qty > 0: - job_card_qty = qty - qty = 0 - - if job_card_qty > 0: - self.prepare_data_for_job_card(row, job_card_qty, index, + qty = split_qty_based_on_batch_size(self, row, qty) + if row.job_card_qty > 0: + self.prepare_data_for_job_card(row, index, plan_days, enable_capacity_planning) planned_end_date = self.operations and self.operations[-1].planned_end_time if planned_end_date: self.db_set("planned_end_date", planned_end_date) - def prepare_data_for_job_card(self, row, job_card_qty, index, plan_days, enable_capacity_planning): + def prepare_data_for_job_card(self, row, index, plan_days, enable_capacity_planning): self.set_operation_start_end_time(index, row) if not row.workstation: @@ -376,8 +364,8 @@ class WorkOrder(Document): .format(row.idx, row.operation)) original_start_time = row.planned_start_time - job_card_doc = create_job_card(self, row, qty=job_card_qty, - enable_capacity_planning=enable_capacity_planning, auto_create=True) + job_card_doc = create_job_card(self, row, auto_create=True, + enable_capacity_planning=enable_capacity_planning) if enable_capacity_planning and job_card_doc: row.planned_start_time = job_card_doc.time_logs[-1].from_time @@ -456,7 +444,7 @@ class WorkOrder(Document): work_order_qty = qty[0][0] if qty and qty[0][0] else 0 frappe.db.set_value('Sales Order Item', self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) - + def update_work_order_qty_in_combined_so(self): total_bundle_qty = 1 if self.product_bundle_item: @@ -469,7 +457,7 @@ class WorkOrder(Document): prod_plan = frappe.get_doc('Production Plan', self.production_plan) item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item') - + for plan_reference in prod_plan.prod_plan_references: work_order_qty = 0.0 if plan_reference.item_reference == item_reference: @@ -477,7 +465,7 @@ class WorkOrder(Document): work_order_qty = flt(plan_reference.qty) / total_bundle_qty frappe.db.set_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty', work_order_qty) - + def update_completed_qty_in_material_request(self): if self.material_request: frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item]) @@ -761,8 +749,8 @@ class WorkOrder(Document): return bom def update_batch_produced_qty(self, stock_entry_doc): - if not cint(frappe.db.get_single_value("Manufacturing Settings", - "make_serial_no_batch_from_work_order")): return + if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")): + return for row in stock_entry_doc.items: if row.batch_no and (row.is_finished_item or row.is_scrap_item): @@ -848,7 +836,7 @@ def make_work_order(bom_no, item, qty=0, project=None, variant_items=None): return wo_doc def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"): - if isinstance(variant_items, string_types): + if isinstance(variant_items, str): variant_items = json.loads(variant_items) for item in variant_items: @@ -970,13 +958,47 @@ def query_sales_order(production_item): @frappe.whitelist() def make_job_card(work_order, operations): - if isinstance(operations, string_types): + if isinstance(operations, str): operations = json.loads(operations) work_order = frappe.get_doc('Work Order', work_order) for row in operations: + row = frappe._dict(row) validate_operation_data(row) - create_job_card(work_order, row, row.get("qty"), auto_create=True) + qty = row.get("qty") + while qty > 0: + qty = split_qty_based_on_batch_size(work_order, row, qty) + if row.job_card_qty > 0: + create_job_card(work_order, row, auto_create=True) + +def split_qty_based_on_batch_size(wo_doc, row, qty): + if not cint(frappe.db.get_value("Operation", + row.operation, "create_job_card_based_on_batch_size")): + row.batch_size = row.get("qty") or wo_doc.qty + + row.job_card_qty = row.batch_size + if row.batch_size and qty >= row.batch_size: + qty -= row.batch_size + elif qty > 0: + row.job_card_qty = qty + qty = 0 + + get_serial_nos_for_job_card(row, wo_doc) + + return qty + +def get_serial_nos_for_job_card(row, wo_doc): + if not wo_doc.serial_no: + return + + serial_nos = get_serial_nos(wo_doc.serial_no) + used_serial_nos = [] + for d in frappe.get_all('Job Card', fields=['serial_no'], + filters={'docstatus': ('<', 2), 'work_order': wo_doc.name, 'operation_id': row.name}): + used_serial_nos.extend(get_serial_nos(d.serial_no)) + + serial_nos = sorted(list(set(serial_nos) - set(used_serial_nos))) + row.serial_no = '\n'.join(serial_nos[0:row.job_card_qty]) def validate_operation_data(row): if row.get("qty") <= 0: @@ -995,21 +1017,22 @@ def validate_operation_data(row): ) ) -def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False): +def create_job_card(work_order, row, enable_capacity_planning=False, auto_create=False): doc = frappe.new_doc("Job Card") doc.update({ 'work_order': work_order.name, 'operation': row.get("operation"), 'workstation': row.get("workstation"), 'posting_date': nowdate(), - 'for_quantity': qty or work_order.get('qty', 0), + 'for_quantity': row.job_card_qty or work_order.get('qty', 0), 'operation_id': row.get("name"), 'bom_no': work_order.bom_no, 'project': work_order.project, 'company': work_order.company, 'sequence_id': row.get("sequence_id"), 'wip_warehouse': work_order.wip_warehouse, - "hour_rate": row.get("hour_rate") + 'hour_rate': row.get("hour_rate"), + 'serial_no': row.get("serial_no") }) if work_order.transfer_material_against == 'Job Card' and not work_order.skip_transfer: diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js index ef77566389..97e7e0a7d2 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js @@ -65,5 +65,41 @@ frappe.query_reports["Cost of Poor Quality Report"] = { fieldtype: "Link", options: "Workstation" }, + { + label: __("Item"), + fieldname: "production_item", + fieldtype: "Link", + options: "Item" + }, + { + label: __("Serial No"), + fieldname: "serial_no", + fieldtype: "Link", + options: "Serial No", + depends_on: "eval: doc.production_item", + get_query: function() { + var item_code = frappe.query_report.get_filter_value('production_item'); + return { + filters: { + item_code: item_code + } + } + } + }, + { + label: __("Batch No"), + fieldname: "batch_no", + fieldtype: "Link", + options: "Batch No", + depends_on: "eval: doc.production_item", + get_query: function() { + var item_code = frappe.query_report.get_filter_value('production_item'); + return { + filters: { + item: item_code + } + } + } + }, ] }; diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index 2e8c191c60..9f81e7d26a 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -20,7 +20,7 @@ def get_data(report_filters): if operations: operations = [d.name for d in operations] fields = ["production_item as item_code", "item_name", "work_order", "operation", - "workstation", "total_time_in_mins", "name", "hour_rate"] + "workstation", "total_time_in_mins", "name", "hour_rate", "serial_no", "batch_no"] filters = get_filters(report_filters, operations) @@ -30,15 +30,18 @@ def get_data(report_filters): for row in job_cards: row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) update_raw_material_cost(row, report_filters) - update_time_details(row, report_filters, data) + data.append(row) return data def get_filters(report_filters, operations): filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1} - for field in ["name", "work_order", "operation", "workstation", "company"]: + for field in ["name", "work_order", "operation", "workstation", "company", "serial_no", "batch_no", "production_item"]: if report_filters.get(field): - filters[field] = report_filters.get(field) + if field != 'serial_no': + filters[field] = report_filters.get(field) + else: + filters[field] = ('like', '% {} %'.format(report_filters.get(field))) return filters @@ -48,24 +51,6 @@ def update_raw_material_cost(row, filters): filters={"parent": row.name, "docstatus": 1}): row.rm_cost += data.amount -def update_time_details(row, filters, data): - args = frappe._dict({"item_code": "", "item_name": "", "name": "", "work_order":"", - "operation": "", "workstation":"", "operating_cost": "", "rm_cost": "", "total_time_in_mins": ""}) - - i=0 - for time_log in frappe.get_all("Job Card Time Log", - fields = ["from_time", "to_time", "time_in_mins"], - filters={"parent": row.name, "docstatus": 1, - "from_time": (">=", filters.from_date), "to_time": ("<=", filters.to_date)}): - - if i==0: - i += 1 - row.update(time_log) - data.append(row) - else: - args.update(time_log) - data.append(args) - def get_columns(filters): return [ { @@ -102,6 +87,18 @@ def get_columns(filters): "options": "Operation", "width": "100" }, + { + "label": _("Serial No"), + "fieldtype": "Data", + "fieldname": "serial_no", + "width": "100" + }, + { + "label": _("Batch No"), + "fieldtype": "Data", + "fieldname": "batch_no", + "width": "100" + }, { "label": _("Workstation"), "fieldtype": "Link", @@ -126,23 +123,5 @@ def get_columns(filters): "fieldtype": "Float", "fieldname": "total_time_in_mins", "width": "100" - }, - { - "label": _("From Time"), - "fieldtype": "Datetime", - "fieldname": "from_time", - "width": "100" - }, - { - "label": _("To Time"), - "fieldtype": "Datetime", - "fieldname": "to_time", - "width": "100" - }, - { - "label": _("Time in Mins"), - "fieldtype": "Float", - "fieldname": "time_in_mins", - "width": "100" - }, + } ] \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e49c9a57c3..8f27ef4356 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1097,7 +1097,8 @@ class StockEntry(StockController): def set_batchwise_finished_goods(self, args, item): qty = flt(self.fg_completed_qty) - filters = {"reference_name": self.pro_doc.name, + filters = { + "reference_name": self.pro_doc.name, "reference_doctype": self.pro_doc.doctype, "qty_to_produce": (">", 0) } @@ -1106,7 +1107,8 @@ class StockEntry(StockController): for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"): batch_qty = flt(row.qty) - flt(row.produced_qty) - if not batch_qty: continue + if not batch_qty: + continue if qty <=0: break @@ -1701,6 +1703,10 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None): if bom.quantity: operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity) + if work_order and work_order.produced_qty and cint(frappe.db.get_single_value('Manufacturing Settings', + 'add_corrective_operation_cost_in_finished_good_valuation')): + operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(work_order.produced_qty) + return operating_cost_per_unit def get_used_alternative_items(purchase_order=None, work_order=None): diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 864ff488b2..a178283904 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -18,6 +18,7 @@ "col_break2", "is_finished_item", "is_scrap_item", + "quality_inspection", "subcontracted_item", "section_break_8", "description", @@ -69,7 +70,6 @@ "putaway_rule", "column_break_51", "reference_purchase_receipt", - "quality_inspection", "job_card_item" ], "fields": [ @@ -548,7 +548,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-11 13:47:50.158754", + "modified": "2021-04-22 20:08:23.799715", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", From 9382b1f154502cdfc50be75042bc270d7aae3eb8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 24 Jun 2021 19:03:22 +0530 Subject: [PATCH 1111/1264] fix: Flaky test --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index ff433b962f..2f5d36c8fa 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -966,7 +966,7 @@ class TestPurchaseInvoice(unittest.TestCase): update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate()) # Create Purchase Order with TDS applied - po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000) + po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item') po.apply_tds = 1 po.tax_withholding_category = 'TDS - 194 - Dividends - Individual' po.save() @@ -1002,6 +1002,7 @@ class TestPurchaseInvoice(unittest.TestCase): # Create Purchase Invoice against Purchase Order purchase_invoice = get_mapped_purchase_invoice(po.name) purchase_invoice.allocate_advances_automatically = 1 + purchase_invoice.items[0].item_code = '_Test Non Stock Item' purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC' purchase_invoice.save() purchase_invoice.submit() From e21e435a0d5b2aa6c6433233d499e09b63139f03 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 24 Jun 2021 19:17:58 +0530 Subject: [PATCH 1112/1264] fix: Add python 3 compatible string types --- erpnext/public/js/controllers/taxes_and_totals.js | 4 ++-- erpnext/stock/get_item_details.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 4a14a665cd..3f76a3e927 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -288,8 +288,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ company: me.frm.doc.company, tax_category: cstr(me.frm.doc.tax_category), item_codes: item_codes, - item_tax_templates: item_tax_templates, - item_rates: item_rates + item_rates: item_rates, + item_tax_templates: item_tax_templates }, callback: function(r) { if (!r.exc) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 37850350ab..c64084fe34 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -436,15 +436,15 @@ def get_barcode_data(items_list): return itemwise_barcode @frappe.whitelist() -def get_item_tax_info(company, tax_category, item_codes, item_tax_templates=None, item_rates=None): +def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_tax_templates=None): out = {} - if isinstance(item_codes, string_types): + if isinstance(item_codes, (str,)): item_codes = json.loads(item_codes) - if isinstance(item_rates, string_types): + if isinstance(item_rates, (str,)): item_rates = json.loads(item_rates) - if isinstance(item_tax_templates, string_types): + if isinstance(item_tax_templates, (str,)): item_tax_templates = json.loads(item_tax_templates) for item_code in item_codes: @@ -514,7 +514,7 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): return None # do not change if already a valid template - if args.get('item_tax_template') in [t.item_tax_template for t in taxes]: + if args.get('item_tax_template') in {t.item_tax_template for t in taxes}: out["item_tax_template"] = args.get('item_tax_template') return args.get('item_tax_template') From 1658107a926ee1880a79581158e0dd8205ae5f9f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 24 Jun 2021 19:18:50 +0530 Subject: [PATCH 1113/1264] fix: Linting fixes --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 3f76a3e927..1de9ec1a7d 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -277,7 +277,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ // Use combination of name and item code in case same item is added multiple times item_codes.push([item.item_code, item.name]); item_rates[item.name] = item.net_rate; - item_tax_templates[item.name] = item.item_tax_template + item_tax_templates[item.name] = item.item_tax_template; } }); From b3a0a7b4329aa1574a6d42abf61bf8691b8f8145 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 24 Jun 2021 19:30:10 +0530 Subject: [PATCH 1114/1264] fix: too many writes while renaming company abbreviation (#26203) --- erpnext/setup/doctype/company/company.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 27e023c1e5..0427abe558 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -407,8 +407,6 @@ def replace_abbr(company, old, new): frappe.only_for("System Manager") - frappe.db.set_value("Company", company, "abbr", new) - def _rename_record(doc): parts = doc[0].rsplit(" - ", 1) if len(parts) == 1 or parts[1].lower() == old.lower(): @@ -419,11 +417,18 @@ def replace_abbr(company, old, new): doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company)) for d in doc: _rename_record(d) + try: + frappe.db.auto_commit_on_many_writes = 1 + frappe.db.set_value("Company", company, "abbr", new) + for dt in ["Warehouse", "Account", "Cost Center", "Department", + "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]: + _rename_records(dt) + frappe.db.commit() - for dt in ["Warehouse", "Account", "Cost Center", "Department", - "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]: - _rename_records(dt) - frappe.db.commit() + except Exception: + frappe.log_error(title=_('Abbreviation Rename Error')) + finally: + frappe.db.auto_commit_on_many_writes = 0 def get_name_with_abbr(name, company): From 5708b7140b45db45d3239f9cf1407cdaf89b6eae Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 24 Jun 2021 19:38:37 +0530 Subject: [PATCH 1115/1264] fix: batch nos in packed items (bp #26105) * test: batch info in packed_items * fix(ux): make packed items editable * refactor: allow custom table name for set_batch In some doctypes there are multiple child tables requiring batched items. This change makes the function a bit more flexible. * fix: Auto fetch batch_nos in packed_item table --- erpnext/stock/doctype/batch/batch.py | 4 +-- .../doctype/delivery_note/delivery_note.js | 3 ++ .../doctype/delivery_note/delivery_note.json | 5 ++-- .../doctype/delivery_note/delivery_note.py | 7 +++-- .../delivery_note/test_delivery_note.py | 29 +++++++++++++++---- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index bb5ad5c6fe..cd441b5958 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -226,9 +226,9 @@ def split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None): return batch.name -def set_batch_nos(doc, warehouse_field, throw=False): +def set_batch_nos(doc, warehouse_field, throw=False, child_table="items"): """Automatically select `batch_no` for outgoing items in item table""" - for d in doc.items: + for d in doc.get(child_table): qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0 has_batch_no = frappe.db.get_value('Item', d.item_code, 'has_batch_no') warehouse = d.get(warehouse_field, None) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 7875b9cd87..74cb3fcb1f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -78,6 +78,9 @@ frappe.ui.form.on("Delivery Note", { }); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); + + frm.set_df_property('packed_items', 'cannot_add_rows', true); + frm.set_df_property('packed_items', 'cannot_delete_rows', true); }, print_without_amount: function(frm) { diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 280fde158f..f20e76f5bf 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -554,8 +554,7 @@ "oldfieldname": "packing_details", "oldfieldtype": "Table", "options": "Packed Item", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "product_bundle_help", @@ -1289,7 +1288,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-04-15 23:55:49.620641", + "modified": "2021-06-11 19:27:30.901112", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index dd31965fac..fcdb5f3b19 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -129,12 +129,13 @@ class DeliveryNote(SellingController): self.validate_uom_is_integer("uom", "qty") self.validate_with_previous_doc() - if self._action != 'submit' and not self.is_return: - set_batch_nos(self, 'warehouse', True) - from erpnext.stock.doctype.packed_item.packed_item import make_packing_list make_packing_list(self) + if self._action != 'submit' and not self.is_return: + set_batch_nos(self, 'warehouse', throw=True) + set_batch_nos(self, 'warehouse', throw=True, child_table="packed_items") + self.update_current_stock() if not self.installation_status: self.installation_status = 'Not Installed' diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 0c63df0e22..f981aeb13b 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -7,7 +7,7 @@ import unittest import frappe import json import frappe.defaults -from frappe.utils import cint, nowdate, nowtime, cstr, add_days, flt, today +from frappe.utils import nowdate, nowtime, cstr, flt from erpnext.stock.stock_ledger import get_previous_sle from erpnext.accounts.utils import get_balance_on from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries @@ -18,9 +18,11 @@ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoWa from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \ import create_stock_reconciliation, set_valuation_method from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so -from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account +from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse -from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + class TestDeliveryNote(unittest.TestCase): def test_over_billing_against_dn(self): @@ -277,8 +279,6 @@ class TestDeliveryNote(unittest.TestCase): dn.cancel() def test_sales_return_for_non_bundled_items_full(self): - from erpnext.stock.doctype.item.test_item import make_item - company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') make_item("Box", {'is_stock_item': 1}) @@ -741,6 +741,25 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(si2.items[0].qty, 2) self.assertEqual(si2.items[1].qty, 1) + + def test_delivery_note_bundle_with_batched_item(self): + batched_bundle = make_item("_Test Batched bundle", {"is_stock_item": 0}) + batched_item = make_item("_Test Batched Item", + {"is_stock_item": 1, "has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "TESTBATCH.#####"} + ) + make_product_bundle(parent=batched_bundle.name, items=[batched_item.name]) + make_stock_entry(item_code=batched_item.name, target="_Test Warehouse - _TC", qty=10, basic_rate=42) + + try: + dn = create_delivery_note(item_code=batched_bundle.name, qty=1) + except frappe.ValidationError as e: + if "batch" in str(e).lower(): + self.fail("Batch numbers not getting added to bundled items in DN.") + raise e + + self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item") + + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") args = frappe._dict(args) From bdba29bab56491eefb26281a804974739d240e85 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 24 Jun 2021 19:48:11 +0530 Subject: [PATCH 1116/1264] fix: Account filter not working with accounting dimension filter --- .../doctype/accounting_dimension/accounting_dimension.py | 2 +- erpnext/accounts/report/general_ledger/general_ledger.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 7cd1e7736c..fac28c9239 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -19,7 +19,7 @@ class AccountingDimension(Document): def validate(self): if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project', - 'Cost Center', 'Accounting Dimension Detail', 'Company') : + 'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') : msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) frappe.throw(msg) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 03808c3640..914058e633 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -211,7 +211,7 @@ def get_gl_entries(filters, accounting_dimensions): dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, order_by_statement=order_by_statement ), - filters, as_dict=1) + filters, as_dict=1, debug=1) if filters.get('presentation_currency'): return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company')) @@ -222,7 +222,7 @@ def get_gl_entries(filters, accounting_dimensions): def get_conditions(filters): conditions = [] - if filters.get("account") and not filters.get("include_dimensions"): + if filters.get("account"): filters.account = get_accounts_with_children(filters.account) conditions.append("account in %(account)s") From e3ca1778281c3e6d409741d39f641ea2f7cbbb16 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 24 Jun 2021 19:51:23 +0530 Subject: [PATCH 1117/1264] fix: Remove debug flag --- erpnext/accounts/report/general_ledger/general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 914058e633..744ada9e55 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -211,7 +211,7 @@ def get_gl_entries(filters, accounting_dimensions): dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, order_by_statement=order_by_statement ), - filters, as_dict=1, debug=1) + filters, as_dict=1) if filters.get('presentation_currency'): return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company')) From 21e44b19f0459286d3750cbb561339af3de0050f Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 24 Jun 2021 20:58:07 +0530 Subject: [PATCH 1118/1264] perf: don't query unless required (bp #26175) re-order conditionals so queries are not evaluated unless required. # Conflicts: # erpnext/stock/doctype/batch/batch.py --- erpnext/controllers/sales_and_purchase_return.py | 9 +++++---- erpnext/stock/doctype/batch/batch.py | 5 ++--- erpnext/stock/doctype/delivery_note/delivery_note.py | 5 ++--- .../stock/doctype/material_request/material_request.py | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 5f759b43bc..80ccc6d75b 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -99,9 +99,10 @@ def validate_returned_items(doc): frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}") .format(d.idx, s, doc.doctype, doc.return_against)) - if warehouse_mandatory and frappe.db.get_value("Item", d.item_code, "is_stock_item") \ - and not d.get("warehouse"): - frappe.throw(_("Warehouse is mandatory")) + if (warehouse_mandatory and not d.get("warehouse") and + frappe.db.get_value("Item", d.item_code, "is_stock_item") + ): + frappe.throw(_("Warehouse is mandatory")) items_returned = True @@ -462,4 +463,4 @@ def get_returned_serial_nos(child_doc, parent_doc): for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters): serial_nos.extend(get_serial_nos(row.serial_no)) - return serial_nos \ No newline at end of file + return serial_nos diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index cd441b5958..b6eef6ca48 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -230,9 +230,8 @@ def set_batch_nos(doc, warehouse_field, throw=False, child_table="items"): """Automatically select `batch_no` for outgoing items in item table""" for d in doc.get(child_table): qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0 - has_batch_no = frappe.db.get_value('Item', d.item_code, 'has_batch_no') warehouse = d.get(warehouse_field, None) - if has_batch_no and warehouse and qty > 0: + if warehouse and qty > 0 and frappe.db.get_value('Item', d.item_code, 'has_batch_no'): if not d.batch_no: d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw, d.serial_no) else: @@ -313,4 +312,4 @@ def validate_serial_no_with_batch(serial_nos, item_code): def make_batch(args): if frappe.db.get_value("Item", args.item, "has_batch_no"): args.doctype = "Batch" - frappe.get_doc(args).insert().name \ No newline at end of file + frappe.get_doc(args).insert().name diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index fcdb5f3b19..4808e948fc 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -182,9 +182,8 @@ class DeliveryNote(SellingController): super(DeliveryNote, self).validate_warehouse() for d in self.get_item_list(): - if frappe.db.get_value("Item", d['item_code'], "is_stock_item") == 1: - if not d['warehouse']: - frappe.throw(_("Warehouse required for stock Item {0}").format(d["item_code"])) + if not d['warehouse'] and frappe.db.get_value("Item", d['item_code'], "is_stock_item") == 1: + frappe.throw(_("Warehouse required for stock Item {0}").format(d["item_code"])) def update_current_stock(self): diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 335175f21d..3ad9909ad0 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -189,7 +189,7 @@ class MaterialRequest(BuyingController): item_wh_list = [] for d in self.get("items"): if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \ - and frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and d.warehouse: + and d.warehouse and frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 : item_wh_list.append([d.item_code, d.warehouse]) for item_code, warehouse in item_wh_list: From 8958f5589023284b963ad45e66e7930de74e6128 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Thu, 24 Jun 2021 21:01:55 +0530 Subject: [PATCH 1119/1264] fix: add validation for 'for_qty' else throws errors (#25829) * fix: add validation for 'for_qty' else throws errors * fix: check if for_qty is None * fix: check purpose * fix: add purpose to pick list get_doc * fix: set as read only to prevent se from picking up value Co-authored-by: Ankush * chore: undo changes to doctype modified timestamp Co-authored-by: Ankush --- erpnext/stock/doctype/pick_list/pick_list.json | 2 +- erpnext/stock/doctype/pick_list/pick_list.py | 9 +++++++++ erpnext/stock/doctype/pick_list/test_pick_list.py | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index c01388dcd2..2146793537 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -184,4 +184,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 6ab68e292a..e795742ea4 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -17,6 +17,9 @@ from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note a # TODO: Prioritize SO or WO group warehouse class PickList(Document): + def validate(self): + self.validate_for_qty() + def before_save(self): self.set_item_locations() @@ -35,6 +38,7 @@ class PickList(Document): @frappe.whitelist() def set_item_locations(self, save=False): + self.validate_for_qty() items = self.aggregate_item_qty() self.item_location_map = frappe._dict() @@ -107,6 +111,11 @@ class PickList(Document): return item_map.values() + def validate_for_qty(self): + if self.purpose == "Material Transfer for Manufacture" \ + and (self.for_qty is None or self.for_qty == 0): + frappe.throw(_("Qty of Finished Goods Item should be greater than 0.")) + def validate_item_locations(pick_list): if not pick_list.locations: diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index c4da05a6d4..84566b8d8c 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -37,6 +37,7 @@ class TestPickList(unittest.TestCase): 'company': '_Test Company', 'customer': '_Test Customer', 'items_based_on': 'Sales Order', + 'purpose': 'Delivery', 'locations': [{ 'item_code': '_Test Item', 'qty': 5, @@ -90,6 +91,7 @@ class TestPickList(unittest.TestCase): 'company': '_Test Company', 'customer': '_Test Customer', 'items_based_on': 'Sales Order', + 'purpose': 'Delivery', 'locations': [{ 'item_code': '_Test Item Warehouse Group Wise Reorder', 'qty': 1000, @@ -135,6 +137,7 @@ class TestPickList(unittest.TestCase): 'company': '_Test Company', 'customer': '_Test Customer', 'items_based_on': 'Sales Order', + 'purpose': 'Delivery', 'locations': [{ 'item_code': '_Test Serialized Item', 'qty': 1000, @@ -264,6 +267,7 @@ class TestPickList(unittest.TestCase): 'company': '_Test Company', 'customer': '_Test Customer', 'items_based_on': 'Sales Order', + 'purpose': 'Delivery', 'locations': [{ 'item_code': '_Test Item', 'qty': 5, @@ -319,6 +323,7 @@ class TestPickList(unittest.TestCase): 'company': '_Test Company', 'customer': '_Test Customer', 'items_based_on': 'Sales Order', + 'purpose': 'Delivery', 'locations': [{ 'item_code': '_Test Item', 'qty': 1, From 43f85a2877eddd6f1920038e07ba477ab4c5b4c6 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 25 Jun 2021 11:38:11 +0530 Subject: [PATCH 1120/1264] fix: changed profitability analysis report width (#26165) --- .../profitability_analysis/profitability_analysis.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index 60e675f2f1..48bd7308bc 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -168,21 +168,24 @@ def get_columns(filters): "label": _("Income"), "fieldtype": "Currency", "options": "currency", - "width": 120 + "width": 305 + }, { "fieldname": "expense", "label": _("Expense"), "fieldtype": "Currency", "options": "currency", - "width": 120 + "width": 305 + }, { "fieldname": "gross_profit_loss", "label": _("Gross Profit / Loss"), "fieldtype": "Currency", "options": "currency", - "width": 120 + "width": 307 + } ] From 58daf5f91652056027ae19990e0f20407775d518 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 25 Jun 2021 12:52:17 +0530 Subject: [PATCH 1121/1264] fix: precision rate for packed items (#26046) --- erpnext/controllers/selling_controller.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 7f28289760..da2765dede 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -330,9 +330,15 @@ class SellingController(StockController): # For internal transfers use incoming rate as the valuation rate if self.is_internal_transfer(): - rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate')) - if d.rate != rate: - d.rate = rate + if d.doctype == "Packed Item": + incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision('incoming_rate')) + if d.incoming_rate != incoming_rate: + d.incoming_rate = incoming_rate + else: + rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate')) + if d.rate != rate: + d.rate = rate + d.discount_percentage = 0 d.discount_amount = 0 frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer") From 2ca9b765efb6f96c86fbf1cd5e284c54b77ae4ba Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 25 Jun 2021 13:34:00 +0530 Subject: [PATCH 1122/1264] fix: Error while fetching item taxes --- erpnext/stock/get_item_details.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index c64084fe34..e0a0c4a472 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -438,6 +438,10 @@ def get_barcode_data(items_list): @frappe.whitelist() def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_tax_templates=None): out = {} + + if not item_tax_templates: + item_tax_templates = {} + if isinstance(item_codes, (str,)): item_codes = json.loads(item_codes) From e955a4ee72d2f20041c966defc3389b3b98483e7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 25 Jun 2021 13:38:06 +0530 Subject: [PATCH 1123/1264] fix: Check for is None --- erpnext/stock/get_item_details.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e0a0c4a472..ca174a3f63 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -439,8 +439,11 @@ def get_barcode_data(items_list): def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_tax_templates=None): out = {} - if not item_tax_templates: + if item_tax_templates is None: item_tax_templates = {} + + if item_rates is None: + item_rates = {} if isinstance(item_codes, (str,)): item_codes = json.loads(item_codes) @@ -457,7 +460,7 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t out[item_code[1]] = {} item = frappe.get_cached_doc("Item", item_code[0]) - args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]]} + args = {"company": company, "tax_category": tax_category, "net_rate": item_rates.get(item_code[1])} if item_tax_templates: args.update({"item_tax_template": item_tax_templates.get(item_code[1])}) From ff4aadc6577b2f97e0e4fbe8413ca404a195ed36 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 May 2021 17:40:59 +0530 Subject: [PATCH 1124/1264] chore: remove dead and py2 compatibility code form_grid_template doesn't exist --- erpnext/manufacturing/doctype/work_order/work_order.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index e343ed2dd3..302753214b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1,7 +1,6 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals import frappe import json import math @@ -30,9 +29,6 @@ class ItemHasVariantError(frappe.ValidationError): pass class SerialNoQtyError(frappe.ValidationError): pass -form_grid_templates = { - "operations": "templates/form_grid/work_order_grid.html" -} class WorkOrder(Document): def onload(self): From 6ec8875434a18f6b3a0dbb2698e53f7c06c5abdc Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 30 May 2021 14:47:44 +0530 Subject: [PATCH 1125/1264] fix(ux): show bom in operations child table --- .../work_order_operation/work_order_operation.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 6d8fb80e31..f7b8787a0b 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -2,7 +2,6 @@ "actions": [], "creation": "2014-10-16 14:35:41.950175", "doctype": "DocType", - "editable_grid": 1, "engine": "InnoDB", "field_order": [ "details", @@ -49,6 +48,7 @@ { "fieldname": "bom", "fieldtype": "Link", + "in_list_view": 1, "label": "BOM", "no_copy": 1, "options": "BOM", @@ -68,6 +68,7 @@ "fieldtype": "Column Break" }, { + "columns": 1, "description": "Operation completed for how many finished goods?", "fieldname": "completed_qty", "fieldtype": "Float", @@ -77,6 +78,7 @@ "read_only": 1 }, { + "columns": 1, "default": "Pending", "fieldname": "status", "fieldtype": "Select", @@ -119,6 +121,7 @@ "fieldtype": "Column Break" }, { + "columns": 1, "description": "in Minutes", "fieldname": "time_in_mins", "fieldtype": "Float", @@ -205,7 +208,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-12 14:48:31.061286", + "modified": "2021-06-24 14:36:12.835543", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", @@ -214,4 +217,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 9e43445f3602ab322f629e800c4677b1a0da7f55 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 30 May 2021 12:22:50 +0530 Subject: [PATCH 1126/1264] fix: order and time of operations for multilevel bom - Order of operations was being sorted by idx of individual operations in BOM table, which made the ordering useless. - This adds ordering that's sorted from lowest level item to top level item. - chore: remove dead functionality. There's no `items` table. Required item level operations get overwritten on fetching of items / operations e.g. when clicking on multi-level BOM checkbox. - test: add test for tree representation - feat: BOMTree class to get complete representation of a tree --- erpnext/manufacturing/doctype/bom/bom.py | 85 ++++++++++++++++++- erpnext/manufacturing/doctype/bom/test_bom.py | 82 +++++++++++++++++- .../doctype/work_order/work_order.py | 57 +++++++------ 3 files changed, 189 insertions(+), 35 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 3e855603b4..c58f017258 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1,7 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from typing import List +from collections import deque import frappe, erpnext from frappe.utils import cint, cstr, flt, today from frappe import _ @@ -16,14 +17,85 @@ from frappe.model.mapper import get_mapped_doc import functools -from six import string_types - from operator import itemgetter form_grid_templates = { "items": "templates/form_grid/item_grid.html" } + +class BOMTree: + """Full tree representation of a BOM""" + + # specifying the attributes to save resources + # ref: https://docs.python.org/3/reference/datamodel.html#slots + __slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"] + + def __init__(self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1) -> None: + self.name = name # name of node, BOM number if is_bom else item_code + self.child_items: List["BOMTree"] = [] # list of child items + self.is_bom = is_bom # true if the node is a BOM and not a leaf item + self.item_code: str = None # item_code associated with node + self.qty = qty # required unit quantity to make one unit of parent item. + self.exploded_qty = exploded_qty # total exploded qty required for making root of tree. + if not self.is_bom: + self.item_code = self.name + else: + self.__create_tree() + + def __create_tree(self): + bom = frappe.get_cached_doc("BOM", self.name) + self.item_code = bom.item + + for item in bom.get("items", []): + qty = item.qty / bom.quantity # quantity per unit + exploded_qty = self.exploded_qty * qty + if item.bom_no: + child = BOMTree(item.bom_no, exploded_qty=exploded_qty, qty=qty) + self.child_items.append(child) + else: + self.child_items.append( + BOMTree(item.item_code, is_bom=False, exploded_qty=exploded_qty, qty=qty) + ) + + def level_order_traversal(self) -> List["BOMTree"]: + """Get level order traversal of tree. + E.g. for following tree the traversal will return list of nodes in order from top to bottom. + BOM: + - SubAssy1 + - item1 + - item2 + - SubAssy2 + - item3 + - item4 + + returns = [SubAssy1, item1, item2, SubAssy2, item3, item4] + """ + traversal = [] + q = deque() + q.append(self) + + while q: + node = q.popleft() + + for child in node.child_items: + traversal.append(child) + q.append(child) + + return traversal + + def __str__(self) -> str: + return ( + f"{self.item_code}{' - ' + self.name if self.is_bom else ''} qty(per unit): {self.qty}" + f" exploded_qty: {self.exploded_qty}" + ) + + def __repr__(self, level: int = 0) -> str: + rep = "┃ " * (level - 1) + "┣━ " * (level > 0) + str(self) + "\n" + for child in self.child_items: + rep += child.__repr__(level=level + 1) + return rep + class BOM(WebsiteGenerator): website = frappe._dict( # page_title_field = "item_name", @@ -152,7 +224,7 @@ class BOM(WebsiteGenerator): if not args: args = frappe.form_dict.get('args') - if isinstance(args, string_types): + if isinstance(args, str): import json args = json.loads(args) @@ -600,6 +672,11 @@ class BOM(WebsiteGenerator): if not d.batch_size or d.batch_size <= 0: d.batch_size = 1 + def get_tree_representation(self) -> BOMTree: + """Get a complete tree representation preserving order of child items.""" + return BOMTree(self.name) + + def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 1f443fb95a..57a5458726 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -2,14 +2,13 @@ # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from collections import deque import unittest import frappe from frappe.utils import cstr, flt from frappe.test_runner import make_test_records from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost -from six import string_types from erpnext.stock.doctype.item.test_item import make_item from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.tests.test_subcontracting import set_backflush_based_on @@ -227,11 +226,88 @@ class TestBOM(unittest.TestCase): supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) self.assertEqual(bom_items, supplied_items) + def test_bom_tree_representation(self): + bom_tree = { + "Assembly": { + "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},}, + "SubAssembly2": {"ChildPart3": {}}, + "SubAssembly3": {"SubSubAssy1": {"ChildPart4": {}}}, + "ChildPart5": {}, + "ChildPart6": {}, + "SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}}, + } + } + parent_bom = create_nested_bom(bom_tree, prefix="") + created_tree = parent_bom.get_tree_representation() + + reqd_order = level_order_traversal(bom_tree)[1:] # skip first item + created_order = created_tree.level_order_traversal() + + self.assertEqual(len(reqd_order), len(created_order)) + + for reqd_item, created_item in zip(reqd_order, created_order): + self.assertEqual(reqd_item, created_item.item_code) + + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) + + + +def level_order_traversal(node): + traversal = [] + q = deque() + q.append(node) + + while q: + node = q.popleft() + + for node_name, subtree in node.items(): + traversal.append(node_name) + q.append(subtree) + + return traversal + +def create_nested_bom(tree, prefix="_Test bom "): + """ Helper function to create a simple nested bom from tree describing item names. (along with required items) + """ + + def create_items(bom_tree): + for item_code, subtree in bom_tree.items(): + bom_item_code = prefix + item_code + if not frappe.db.exists("Item", bom_item_code): + frappe.get_doc(doctype="Item", item_code=bom_item_code, item_group="_Test Item Group").insert() + create_items(subtree) + create_items(tree) + + def dfs(tree, node): + """naive implementation for searching right subtree""" + for node_name, subtree in tree.items(): + if node_name == node: + return subtree + else: + result = dfs(subtree, node) + if result is not None: + return result + + order_of_creating_bom = reversed(level_order_traversal(tree)) + + for item in order_of_creating_bom: + child_items = dfs(tree, item) + if child_items: + bom_item_code = prefix + item + bom = frappe.get_doc(doctype="BOM", item=bom_item_code) + for child_item in child_items.keys(): + bom.append("items", {"item_code": prefix + child_item}) + bom.insert() + bom.submit() + + return bom # parent bom is last bom + + def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=None): - if warehouse_list and isinstance(warehouse_list, string_types): + if warehouse_list and isinstance(warehouse_list, str): warehouse_list = [warehouse_list] if not warehouse_list: diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 302753214b..180815d80e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -468,46 +468,47 @@ class WorkOrder(Document): def set_work_order_operations(self): """Fetch operations from BOM and set in 'Work Order'""" - self.set('operations', []) + def _get_operations(bom_no, qty=1): + return frappe.db.sql( + f"""select + operation, description, workstation, idx, + base_hour_rate as hour_rate, time_in_mins * {qty} as time_in_mins, + "Pending" as status, parent as bom, batch_size, sequence_id + from + `tabBOM Operation` + where + parent = %s order by idx + """, bom_no, as_dict=1) + + + self.set('operations', []) if not self.bom_no: return - if self.use_multi_level_bom: - bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree() + operations = [] + if not self.use_multi_level_bom: + bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") + operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) else: - bom_list = [self.bom_no] + bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation() + bom_traversal = list(reversed(bom_tree.level_order_traversal())) + bom_traversal.append(bom_tree) # add operation on top level item last + + for d in bom_traversal: + if d.is_bom: + operations.extend(_get_operations(d.name, qty=d.exploded_qty)) + + for correct_index, operation in enumerate(operations, start=1): + operation.idx = correct_index - operations = frappe.db.sql(""" - select - operation, description, workstation, idx, - base_hour_rate as hour_rate, time_in_mins, - "Pending" as status, parent as bom, batch_size, sequence_id - from - `tabBOM Operation` - where - parent in (%s) order by idx - """ % ", ".join(["%s"]*len(bom_list)), tuple(bom_list), as_dict=1) self.set('operations', operations) - - if self.use_multi_level_bom and self.get('operations') and self.get('items'): - raw_material_operations = [d.operation for d in self.get('items')] - operations = [d.operation for d in self.get('operations')] - - for operation in raw_material_operations: - if operation not in operations: - self.append('operations', { - 'operation': operation - }) - self.calculate_time() def calculate_time(self): - bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") - for d in self.get("operations"): - d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * (flt(self.qty) / flt(d.batch_size)) + d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size)) self.calculate_operating_cost() From 98c9b0e9edf8031b1afdb1647f4a3d48b3b39834 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 25 Jun 2021 16:11:17 +0530 Subject: [PATCH 1127/1264] refactor: remove unused func, sider fixes --- .../cogs_by_item_group/cogs_by_item_group.js | 18 +---------- .../cogs_by_item_group/cogs_by_item_group.py | 30 +++++++++---------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js index bb780e50b2..d7c50a6697 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -11,24 +11,8 @@ frappe.query_reports["COGS By Item Group"] = { fieldtype: "Link", options: "Company", mandatory: true, - default: frappe.defaults.get_user_default("Company"), + default: frappe.defaults.get_user_default("Company"), }, - // { - // label: __("Account"), - // fieldname: "account", - // fieldtype: "Link", - // options: "Account", - // mandatory: true, - // get_query() { - // const company = frappe.query_report.get_filter_value('company'); - // return { - // "doctype": "Account", - // "filters": { - // "company": company, - // } - // } - // }, - // }, { label: __("From Date"), fieldname: "from_date", diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index e2c6f7928c..0d601738ff 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -52,13 +52,13 @@ def get_data(filters): assign_agg_values(leveled_dict) data = [] - for _, i in leveled_dict.items(): + for item in leveled_dict.items(): + i = item[1] if i['agg_value'] == 0: continue data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level'])) if i['self_value'] < i['agg_value'] and i['self_value'] > 0: data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1)) - # append_blank() return data @@ -76,9 +76,10 @@ def get_filtered_entries(filters): def get_stock_value_difference_list(filtered_entries): voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] - svd_list = frappe.get_list('Stock Ledger Entry', - fields=['item_code','stock_value_difference'], - filters=[('voucher_no', 'in', voucher_nos)]) + svd_list = frappe.get_list( + 'Stock Ledger Entry', fields=['item_code','stock_value_difference'], + filters=[('voucher_no', 'in', voucher_nos)] + ) assign_item_groups_to_svd_list(svd_list) return svd_list @@ -155,22 +156,19 @@ def assign_item_groups_to_svd_list(svd_list): def get_item_groups_map(svd_list): # for items in svd_list: [{'item_code':'item_group'}] item_codes = set([i['item_code'] for i in svd_list]) - ig_list = frappe.get_list('Item', - fields=['item_code','item_group'], - filters=[('item_code', 'in', item_codes)]) + ig_list = frappe.get_list( + 'Item', fields=['item_code','item_group'], + filters=[('item_code', 'in', item_codes)] + ) return {i['item_code']:i['item_group'] for i in ig_list} -def append_blank(data): - if len(data) == 0: - data.append(get_row("", 0, 0, 0)) - - def get_item_groups_dict(): item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) - return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} - for i in item_groups_list } + return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} + for i in item_groups_list} def update_leveled_dict(leveled_dict): - for k in leveled_dict: leveled_dict[k].update({'self_value':0, 'agg_value':0}) + for k in leveled_dict: + leveled_dict[k].update({'self_value':0, 'agg_value':0}) From e5c47f8957a0439b303684f49a963c308e2fad0e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 24 Jun 2021 23:13:54 +0530 Subject: [PATCH 1128/1264] fix: fetch batch items in stock reco --- .../doctype/work_order/test_work_order.py | 9 +- .../stock_reconciliation.js | 67 +++++++---- .../stock_reconciliation.py | 108 +++++++++++++----- 3 files changed, 125 insertions(+), 59 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index cb1ee92196..68de0b29d3 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -389,17 +389,12 @@ class TestWorkOrder(unittest.TestCase): ste.submit() stock_entries.append(ste) - job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) + job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}, order_by='creation asc') self.assertEqual(len(job_cards), len(bom.operations)) for i, job_card in enumerate(job_cards): doc = frappe.get_doc("Job Card", job_card) - doc.append("time_logs", { - "from_time": add_to_date(None, i), - "hours": 1, - "to_time": add_to_date(None, i + 1), - "completed_qty": doc.for_quantity - }) + doc.time_logs[0].completed_qty = 1 doc.submit() ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index ac4ed5e75d..a01db80da4 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -48,37 +48,54 @@ frappe.ui.form.on("Stock Reconciliation", { }, get_items: function(frm) { - frappe.prompt({label:"Warehouse", fieldname: "warehouse", fieldtype:"Link", options:"Warehouse", reqd: 1, + let fields = [{ + label: 'Warehouse', fieldname: 'warehouse', fieldtype: 'Link', options: 'Warehouse', reqd: 1, "get_query": function() { return { "filters": { "company": frm.doc.company, } - } - }}, - function(data) { - frappe.call({ - method:"erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_items", - args: { - warehouse: data.warehouse, - posting_date: frm.doc.posting_date, - posting_time: frm.doc.posting_time, - company:frm.doc.company - }, - callback: function(r) { - var items = []; - frm.clear_table("items"); - for(var i=0; i< r.message.length; i++) { - var d = frm.add_child("items"); - $.extend(d, r.message[i]); - if(!d.qty) d.qty = null; - if(!d.valuation_rate) d.valuation_rate = null; - } - frm.refresh_field("items"); - } - }); + }; } - , __("Get Items"), __("Update")); + }, { + label: "Item Code", fieldname: "item_code", fieldtype: "Link", options: "Item", + "get_query": function() { + return { + "filters": { + "disabled": 0, + } + }; + } + }]; + + frappe.prompt(fields, function(data) { + frappe.call({ + method: "erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_items", + args: { + warehouse: data.warehouse, + posting_date: frm.doc.posting_date, + posting_time: frm.doc.posting_time, + company: frm.doc.company, + item_code: data.item_code + }, + callback: function(r) { + frm.clear_table("items"); + for (var i=0; i= %s and rgt <= %s and name=bin.warehouse) - """, (lft, rgt)) + where i.name=bin.item_code and IFNULL(i.disabled, 0) = 0 and i.is_stock_item = 1 + and i.has_variants = 0 and exists( + select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse + ) + """, (lft, rgt), as_dict=1) items += frappe.db.sql(""" - select i.name, i.item_name, id.default_warehouse, i.has_serial_no + select i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no from tabItem i, `tabItem Default` id where i.name = id.parent and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse) - and i.is_stock_item = 1 and i.has_batch_no = 0 - and i.has_variants = 0 and i.disabled = 0 and id.company=%s + and i.is_stock_item = 1 and i.has_variants = 0 and IFNULL(i.disabled, 0) = 0 and id.company=%s group by i.name - """, (lft, rgt, company)) + """, (lft, rgt, company), as_dict=1) - res = [] - for d in set(items): - stock_bal = get_stock_balance(d[0], d[2], posting_date, posting_time, - with_valuation_rate=True , with_serial_no=cint(d[3])) + return items - if frappe.db.get_value("Item", d[0], "disabled") == 0: - res.append({ - "item_code": d[0], - "warehouse": d[2], - "qty": stock_bal[0], - "item_name": d[1], - "valuation_rate": stock_bal[1], - "current_qty": stock_bal[0], - "current_valuation_rate": stock_bal[1], - "current_serial_no": stock_bal[2] if cint(d[3]) else '', - "serial_no": stock_bal[2] if cint(d[3]) else '' - }) +def get_item_data(row, qty, valuation_rate, serial_no=None): + return { + 'item_code': row.item_code, + 'warehouse': row.warehouse, + 'qty': qty, + 'item_name': row.item_name, + 'valuation_rate': valuation_rate, + 'current_qty': qty, + 'current_valuation_rate': valuation_rate, + 'current_serial_no': serial_no, + 'serial_no': serial_no, + 'batch_no': row.get('batch_no') + } - return res +def get_itemwise_batch(warehouse, posting_date, company, item_code=None): + from erpnext.stock.report.batch_wise_balance_history.batch_wise_balance_history import execute + itemwise_batch_data = {} + + filters = frappe._dict({ + 'warehouse': warehouse, + 'from_date': posting_date, + 'to_date': posting_date, + 'company': company + }) + + if item_code: + filters.item_code = item_code + + columns, data = execute(filters) + + for row in data: + itemwise_batch_data.setdefault(row[0], []).append(frappe._dict({ + 'item_code': row[0], + 'warehouse': warehouse, + 'qty': row[8], + 'item_name': row[1], + 'batch_no': row[4] + })) + + return itemwise_batch_data @frappe.whitelist() def get_stock_balance_for(item_code, warehouse, From 170f2ad05619f8bf92d3ecf5acaad4ca7c7cf356 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 22 Jun 2021 15:23:04 +0530 Subject: [PATCH 1129/1264] fix: removed values out of sync validation from stock transactions --- erpnext/controllers/stock_controller.py | 5 +- .../incorrect_stock_value_report/__init__.py | 0 .../incorrect_stock_value_report.js | 36 +++++ .../incorrect_stock_value_report.json | 29 ++++ .../incorrect_stock_value_report.py | 141 ++++++++++++++++++ 5 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 erpnext/stock/report/incorrect_stock_value_report/__init__.py create mode 100644 erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js create mode 100644 erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.json create mode 100644 erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 35097b97b9..8196cff849 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -11,7 +11,7 @@ from frappe.utils import cint, cstr, flt, get_link_to_form, getdate import erpnext from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map -from erpnext.accounts.utils import check_if_stock_and_account_balance_synced, get_fiscal_year +from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock import get_warehouse_account_map from erpnext.stock.stock_ledger import get_valuation_rate @@ -497,9 +497,6 @@ class StockController(AccountsController): }) if future_sle_exists(args): create_repost_item_valuation_entry(args) - elif not is_reposting_pending(): - check_if_stock_and_account_balance_synced(self.posting_date, - self.company, self.doctype, self.name) @frappe.whitelist() def make_quality_inspections(doctype, docname, items): diff --git a/erpnext/stock/report/incorrect_stock_value_report/__init__.py b/erpnext/stock/report/incorrect_stock_value_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js new file mode 100644 index 0000000000..ff424807e3 --- /dev/null +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js @@ -0,0 +1,36 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Incorrect Stock Value Report"] = { + "filters": [ + { + "label": __("Company"), + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "label": __("Account"), + "fieldname": "account", + "fieldtype": "Link", + "options": "Account", + get_query: function() { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + "account_type": "Stock", + "company": company + } + } + } + }, + { + "label": __("From Date"), + "fieldname": "from_date", + "fieldtype": "Date" + } + ] +}; diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.json b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.json new file mode 100644 index 0000000000..a7e9f203f7 --- /dev/null +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-06-22 15:35:05.148177", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-06-22 15:35:05.148177", + "modified_by": "Administrator", + "module": "Stock", + "name": "Incorrect Stock Value Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Incorrect Stock Value Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Accounts Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py new file mode 100644 index 0000000000..a7243878eb --- /dev/null +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -0,0 +1,141 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import erpnext +from frappe import _ +from six import iteritems +from frappe.utils import add_days, today, getdate +from erpnext.stock.utils import get_stock_value_on +from erpnext.accounts.utils import get_stock_and_account_balance + +def execute(filters=None): + if not erpnext.is_perpetual_inventory_enabled(filters.company): + frappe.throw(_("Perpetual inventory required for the company {0} to view this report.") + .format(filters.company)) + + data = get_data(filters) + columns = get_columns(filters) + + return columns, data + +def get_unsync_date(filters): + date = filters.from_date + if not date: + date = frappe.db.sql(""" SELECT min(posting_date) from `tabStock Ledger Entry`""") + date = date[0][0] + + if not date: + return + + while getdate(date) < getdate(today()): + account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(posting_date=date, + company=filters.company, account = filters.account) + + if abs(account_bal - stock_bal) > 0.1: + return date + + date = add_days(date, 1) + +def get_data(report_filters): + from_date = get_unsync_date(report_filters) + + if not from_date: + return [] + + result = [] + + voucher_wise_dict = {} + data = frappe.db.sql(''' + SELECT + name, posting_date, posting_time, voucher_type, voucher_no, + stock_value_difference, stock_value, warehouse, item_code + FROM + `tabStock Ledger Entry` + WHERE + posting_date + = %s and company = %s + and is_cancelled = 0 + ORDER BY timestamp(posting_date, posting_time) asc, creation asc + ''', (from_date, report_filters.company), as_dict=1) + + for d in data: + voucher_wise_dict.setdefault((d.item_code, d.warehouse), []).append(d) + + closing_date = add_days(from_date, -1) + for key, stock_data in iteritems(voucher_wise_dict): + prev_stock_value = get_stock_value_on(posting_date = closing_date, item_code=key[0], warehouse =key[1]) + for data in stock_data: + expected_stock_value = prev_stock_value + data.stock_value_difference + if abs(data.stock_value - expected_stock_value) > 0.1: + data.difference_value = abs(data.stock_value - expected_stock_value) + data.expected_stock_value = expected_stock_value + result.append(data) + + return result + +def get_columns(filters): + return [ + { + "label": _("Stock Ledger ID"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Stock Ledger Entry", + "width": "80" + }, + { + "label": _("Posting Date"), + "fieldname": "posting_date", + "fieldtype": "Date" + }, + { + "label": _("Posting Time"), + "fieldname": "posting_time", + "fieldtype": "Time" + }, + { + "label": _("Voucher Type"), + "fieldname": "voucher_type", + "width": "110" + }, + { + "label": _("Voucher No"), + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "options": "voucher_type", + "width": "110" + }, + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": "110" + }, + { + "label": _("Warehouse"), + "fieldname": "warehouse", + "fieldtype": "Link", + "options": "Warehouse", + "width": "110" + }, + { + "label": _("Expected Stock Value"), + "fieldname": "expected_stock_value", + "fieldtype": "Currency", + "width": "150" + }, + { + "label": _("Stock Value"), + "fieldname": "stock_value", + "fieldtype": "Currency", + "width": "120" + }, + { + "label": _("Difference Value"), + "fieldname": "difference_value", + "fieldtype": "Currency", + "width": "150" + } + ] \ No newline at end of file From 1e5482cedd605f2398f6dbeff4efe40a8cbc1848 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 26 Jun 2021 23:49:32 +0530 Subject: [PATCH 1130/1264] fix: Revert Changes --- erpnext/public/js/setup_wizard.js | 26 +++++++ .../setup_wizard/data/country_wise_tax.json | 72 ++++++++++++------- .../setup_wizard/operations/taxes_setup.py | 15 ++-- 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index a3045724fe..6f5d67c746 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -139,10 +139,36 @@ erpnext.setup.slides_settings = [ }, validate: function () { + let me = this; + let exist; + if (!this.validate_fy_dates()) { return false; } + // Validate bank name + if(me.values.bank_account) { + frappe.call({ + async: false, + method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account", + args: { + "coa": me.values.chart_of_accounts, + "bank_account": me.values.bank_account + }, + callback: function (r) { + if(r.message){ + exist = r.message; + me.get_field("bank_account").set_value(""); + let message = __('Account {0} already exists. Please enter a different name for your bank account.', + [me.values.bank_account] + ); + frappe.msgprint(message); + } + } + }); + return !exist; // Return False if exist = true + } + return true; }, diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index e36bf5cbe0..34af093a23 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1218,37 +1218,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 18.00 + "tax_rate": 18.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 18.00 + "tax_rate": 18.00, + "root_type": "Asset" } } ] @@ -1277,37 +1283,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 2.5 + "tax_rate": 2.5, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 2.5 + "tax_rate": 2.5, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 5.0 + "tax_rate": 5.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 2.50 + "tax_rate": 2.50, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 2.50 + "tax_rate": 2.50, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 5.00 + "tax_rate": 5.00, + "root_type": "Asset" } } ] @@ -1336,37 +1348,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 6.0 + "tax_rate": 6.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 6.0 + "tax_rate": 6.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 12.0 + "tax_rate": 12.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 6.00 + "tax_rate": 6.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 6.00 + "tax_rate": 6.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 12.00 + "tax_rate": 12.00, + "root_type": "Asset" } } ] @@ -1395,37 +1413,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 14.0 + "tax_rate": 14.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 14.0 + "tax_rate": 14.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 28.0 + "tax_rate": 28.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 14.00 + "tax_rate": 14.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 14.00 + "tax_rate": 14.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 28.00 + "tax_rate": 28.00, + "root_type": "Asset" } } ] diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index d5682b6f4c..9f73f214bd 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -203,16 +203,15 @@ def get_or_create_account(company_name, account): existing_accounts = frappe.get_list('Account', filters={ - 'account_name': account.get('account_name'), - 'account_number': account.get('account_number', ''), - 'company': company_name + 'company': company_name, + 'root_type': root_type }, or_filters={ - 'company': company_name, - 'root_type': root_type, - 'is_group': 0 - } - ) + 'account_name': account.get('account_name'), + 'account_number': account.get('account_number') + }) + + print(company_name, account, existing_accounts) if existing_accounts: return frappe.get_doc('Account', existing_accounts[0].name) From cf445eb7b4f057dfb57c0861745051d5e65dccb9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 26 Jun 2021 23:59:47 +0530 Subject: [PATCH 1131/1264] fix: Add validate bank account method back --- .../chart_of_accounts/chart_of_accounts.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 9b6842d896..927adc7086 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -188,6 +188,24 @@ def build_account_tree(tree, parent, all_accounts): # call recursively to build a subtree for current account build_account_tree(tree[child.account_name], child, all_accounts) +@frappe.whitelist() +def validate_bank_account(coa, bank_account): + accounts = [] + chart = get_chart(coa) + + if chart: + def _get_account_names(account_master): + for account_name, child in iteritems(account_master): + if account_name not in ["account_number", "account_type", + "root_type", "is_group", "tax_rate"]: + accounts.append(account_name) + + _get_account_names(child) + + _get_account_names(chart) + + return (bank_account in accounts) + @frappe.whitelist() def build_tree_from_json(chart_template, chart_data=None): ''' get chart template from its folder and parse the json to be rendered as tree ''' From ebc46c1e09b26fc6a26ad386272affd78fa2948e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 28 Jun 2021 10:52:38 +0530 Subject: [PATCH 1132/1264] fix: Update account heads in GST test cases --- .../sales_invoice/test_sales_invoice.py | 63 ++++++++++--------- .../gstr_3b_report/test_gstr_3b_report.py | 28 ++++----- .../setup_wizard/operations/taxes_setup.py | 2 - 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 114b7d2d35..dbc7f8632f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1957,6 +1957,33 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) + def test_item_tax_net_range(self): + item = create_item("T Shirt") + + item.set('taxes', []) + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", + "minimum_net_rate": 0, + "maximum_net_rate": 500 + }) + + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", + "minimum_net_rate": 501, + "maximum_net_rate": 1000 + }) + + item.save() + + sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True) + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") + + # Apply discount + sales_invoice.apply_discount_on = 'Net Total' + sales_invoice.discount_amount = 300 + sales_invoice.save() + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' @@ -1985,32 +2012,6 @@ def get_sales_invoice_for_e_invoice(): return si - def test_item_tax_net_range(self): - item = create_item("T Shirt") - - item.set('taxes', []) - item.append("taxes", { - "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", - "minimum_net_rate": 0, - "maximum_net_rate": 500 - }) - - item.append("taxes", { - "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", - "minimum_net_rate": 501, - "maximum_net_rate": 1000 - }) - - item.save() - - sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True) - self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") - - # Apply discount - sales_invoice.apply_discount_on = 'Net Total' - sales_invoice.discount_amount = 300 - sales_invoice.save() - self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): @@ -2087,9 +2088,9 @@ def make_sales_invoice_for_ewaybill(): if not gst_account: gst_settings.append("gst_accounts", { "company": "_Test Company", - "cgst_account": "CGST - _TC", - "sgst_account": "SGST - _TC", - "igst_account": "IGST - _TC", + "cgst_account": "Output Tax CGST - _TC", + "sgst_account": "Output Tax SGST - _TC", + "igst_account": "Output Tax IGST - _TC", }) gst_settings.save() @@ -2106,7 +2107,7 @@ def make_sales_invoice_for_ewaybill(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "CGST - _TC", + "account_head": "Output Tax CGST - _TC", "cost_center": "Main - _TC", "description": "CGST @ 9.0", "rate": 9 @@ -2114,7 +2115,7 @@ def make_sales_invoice_for_ewaybill(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "SGST - _TC", + "account_head": "Output Tax SGST - _TC", "cost_center": "Main - _TC", "description": "SGST @ 9.0", "rate": 9 diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 3857ce1cdb..065f80d610 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -46,14 +46,14 @@ class TestGSTR3BReport(unittest.TestCase): make_sales_invoice() create_purchase_invoices() - if frappe.db.exists("GSTR 3B Report", "GSTR3B-March-2019-_Test Address-Billing"): - report = frappe.get_doc("GSTR 3B Report", "GSTR3B-March-2019-_Test Address-Billing") + if frappe.db.exists("GSTR 3B Report", "GSTR3B-March-2019-_Test Address GST-Billing"): + report = frappe.get_doc("GSTR 3B Report", "GSTR3B-March-2019-_Test Address GST-Billing") report.save() else: report = frappe.get_doc({ "doctype": "GSTR 3B Report", "company": "_Test Company GST", - "company_address": "_Test Address-Billing", + "company_address": "_Test Address GST-Billing", "year": getdate().year, "month": month_number_mapping.get(getdate().month) }).insert() @@ -89,7 +89,7 @@ class TestGSTR3BReport(unittest.TestCase): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -117,7 +117,7 @@ def make_sales_invoice(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -138,7 +138,7 @@ def make_sales_invoice(): si1.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -159,7 +159,7 @@ def make_sales_invoice(): si2.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -195,7 +195,7 @@ def create_purchase_invoices(): pi.append("taxes", { "charge_type": "On Net Total", - "account_head": "CGST - _GST", + "account_head": "Input Tax CGST - _GST", "cost_center": "Main - _GST", "description": "CGST @ 9.0", "rate": 9 @@ -203,7 +203,7 @@ def create_purchase_invoices(): pi.append("taxes", { "charge_type": "On Net Total", - "account_head": "SGST - _GST", + "account_head": "Input Tax SGST - _GST", "cost_center": "Main - _GST", "description": "SGST @ 9.0", "rate": 9 @@ -410,10 +410,10 @@ def make_company(): company.country = "India" company.insert() - if not frappe.db.exists('Address', '_Test Address-Billing'): + if not frappe.db.exists('Address', '_Test Address GST-Billing'): address = frappe.get_doc({ + "address_title": "_Test Address GST", "address_line1": "_Test Address Line 1", - "address_title": "_Test Address", "address_type": "Billing", "city": "_Test City", "state": "Test State", @@ -444,9 +444,9 @@ def set_account_heads(): if not gst_account: gst_settings.append("gst_accounts", { "company": "_Test Company GST", - "cgst_account": "CGST - _GST", - "sgst_account": "SGST - _GST", - "igst_account": "IGST - _GST", + "cgst_account": "Output Tax CGST - _GST", + "sgst_account": "Output Tax SGST - _GST", + "igst_account": "Output Tax IGST - _GST" }) gst_settings.save() diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 9f73f214bd..c7cc000518 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -211,8 +211,6 @@ def get_or_create_account(company_name, account): 'account_number': account.get('account_number') }) - print(company_name, account, existing_accounts) - if existing_accounts: return frappe.get_doc('Account', existing_accounts[0].name) From 7d7d797ffc80956e13493d98d92097cc8d280863 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 28 Jun 2021 11:24:32 +0530 Subject: [PATCH 1133/1264] fix: Do not consider cancelled entries in party dashboard --- erpnext/accounts/party.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e025fc6905..b97dc401e6 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -542,6 +542,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None): select company, sum(debit_in_account_currency) - sum(credit_in_account_currency) from `tabGL Entry` where party_type = %s and party=%s + and is_cancelled = 0 group by company""", (party_type, party))) for d in companies: From 2d1c4fee1d79e06e90effe283cdf8ab47c98d9de Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 29 Jun 2021 00:31:14 +0530 Subject: [PATCH 1134/1264] fix: allow to changes to date in the blanket order --- .../manufacturing/doctype/blanket_order/blanket_order.js | 2 +- .../manufacturing/doctype/blanket_order/blanket_order.json | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index 4c31bd0b7d..f19a1b0868 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Blanket Order', { refresh: function(frm) { erpnext.hide_company(); - if (frm.doc.customer && frm.doc.docstatus === 1) { + if (frm.doc.customer && frm.doc.docstatus === 1 && frm.doc.to_date > frappe.datetime.get_today()) { frm.add_custom_button(__("Sales Order"), function() { frappe.model.open_mapped_doc({ method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_order", diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index 0330e5c85c..a63fc4da69 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2018-05-24 07:18:08.256060", "doctype": "DocType", @@ -79,6 +80,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "to_date", "fieldtype": "Date", "label": "To Date", @@ -129,8 +131,10 @@ "label": "Terms and Conditions Details" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, - "modified": "2019-11-18 19:37:37.151686", + "links": [], + "modified": "2021-06-29 00:30:30.621636", "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order", From 91dcc07e26c647708b5317623ec984d9fa1671d1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 29 Jun 2021 15:58:46 +0530 Subject: [PATCH 1135/1264] fix: Employee Inactive status implications (#26244) --- erpnext/hr/doctype/attendance/attendance.js | 2 +- erpnext/hr/doctype/attendance/attendance.py | 5 +++++ erpnext/hr/doctype/attendance/attendance_list.js | 3 +++ erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/attendance/attendance.js b/erpnext/hr/doctype/attendance/attendance.js index c3c3cb82f9..7964078c7f 100644 --- a/erpnext/hr/doctype/attendance/attendance.js +++ b/erpnext/hr/doctype/attendance/attendance.js @@ -11,5 +11,5 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) { return{ query: "erpnext.controllers.queries.employee_query" - } + } } diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index f3b8a799b3..3412675d81 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -15,6 +15,7 @@ class Attendance(Document): validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"]) self.validate_attendance_date() self.validate_duplicate_record() + self.validate_employee_status() self.check_leave_record() def validate_attendance_date(self): @@ -38,6 +39,10 @@ class Attendance(Document): frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format( frappe.bold(self.employee), frappe.bold(self.attendance_date))) + def validate_employee_status(self): + if frappe.db.get_value("Employee", self.employee, "status") == "Inactive": + frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee)) + def check_leave_record(self): leave_record = frappe.db.sql(""" select leave_type, half_day, half_day_date diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 0c7eafe9c6..9a3bac0eb2 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -21,6 +21,9 @@ frappe.listview_settings['Attendance'] = { label: __('For Employee'), fieldtype: 'Link', options: 'Employee', + get_query: () => { + return {query: "erpnext.controllers.queries.employee_query"} + }, reqd: 1, onchange: function() { dialog.set_df_property("unmarked_days", "hidden", 1); diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index e71d81f323..5c7c0a3b09 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -459,6 +459,7 @@ def get_emp_list(sal_struct, cond, end_date, payroll_payable_account): where t1.name = t2.employee and t2.docstatus = 1 + and t1.status != 'Inactive' %s order by t2.from_date desc """ % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True) From 9181dde86a9ccabdfb2c075383dc1daaa8f4662f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 29 Jun 2021 17:18:39 +0530 Subject: [PATCH 1136/1264] fix: Cancelation of Loan Security Pledges --- .../loan_security_pledge.json | 84 ++++++++++++++----- .../loan_security_pledge.py | 18 +++- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json index 18bd4aea78..68bac8ed8c 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json @@ -35,7 +35,9 @@ "no_copy": 1, "options": "Loan Security Pledge", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "loan_application.applicant", @@ -45,47 +47,63 @@ "in_standard_filter": 1, "label": "Applicant", "options": "applicant_type", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_security_details_section", "fieldtype": "Section Break", - "label": "Loan Security Details" + "label": "Loan Security Details", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_3", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan", "fieldtype": "Link", "label": "Loan", - "options": "Loan" + "options": "Loan", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_application", "fieldtype": "Link", "label": "Loan Application", - "options": "Loan Application" + "options": "Loan Application", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_security_value", "fieldtype": "Currency", "label": "Total Security Value", "options": "Company:company:default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "maximum_loan_value", "fieldtype": "Currency", "label": "Maximum Loan Value", "options": "Company:company:default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_details_section", "fieldtype": "Section Break", - "label": "Loan Details" + "label": "Loan Details", + "show_days": 1, + "show_seconds": 1 }, { "default": "Requested", @@ -94,37 +112,49 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "options": "Requested\nUnpledged\nPledged\nPartially Pledged", - "read_only": 1 + "options": "Requested\nUnpledged\nPledged\nPartially Pledged\nCancelled", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pledge_time", "fieldtype": "Datetime", "label": "Pledge Time", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "securities", "fieldtype": "Table", "label": "Securities", "options": "Pledge", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_10", "fieldtype": "Section Break", - "label": "Totals" + "label": "Totals", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", "options": "Company", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "loan.applicant_type", @@ -132,35 +162,45 @@ "fieldtype": "Select", "label": "Applicant Type", "options": "Employee\nMember\nCustomer", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "more_information_section", "fieldtype": "Section Break", - "label": "More Information" + "label": "More Information", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "reference_no", "fieldtype": "Data", - "label": "Reference No" + "label": "Reference No", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_18", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "description", "fieldtype": "Text", - "label": "Description" + "label": "Description", + "show_days": 1, + "show_seconds": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:23:16.953305", + "modified": "2021-06-29 17:15:16.082256", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Pledge", diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index cbc8376aa5..c390b6c526 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -23,6 +23,12 @@ class LoanSecurityPledge(Document): update_shortfall_status(self.loan, self.total_security_value) update_loan(self.loan, self.maximum_loan_value) + def on_cancel(self): + if self.loan: + self.db_set("status", "Cancelled") + self.db_set("pledge_time", None) + update_loan(self.loan, self.maximum_loan_value, cancel=1) + def validate_duplicate_securities(self): security_list = [] for security in self.securities: @@ -36,7 +42,7 @@ class LoanSecurityPledge(Document): existing_pledge = '' if self.loan: - existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan}, ['name']) + existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan, 'docstatus': 1}, ['name']) if existing_pledge: loan_security_type = frappe.db.get_value('Pledge', {'parent': existing_pledge}, ['loan_security_type']) @@ -77,8 +83,12 @@ class LoanSecurityPledge(Document): self.total_security_value = total_security_value self.maximum_loan_value = maximum_loan_value -def update_loan(loan, maximum_value_against_pledge): +def update_loan(loan, maximum_value_against_pledge, cancel=0): maximum_loan_value = frappe.db.get_value('Loan', {'name': loan}, ['maximum_loan_amount']) - frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1 - WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan)) + if cancel: + frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s + WHERE name=%s""", (maximum_loan_value - maximum_value_against_pledge, loan)) + else: + frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1 + WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan)) From 61690775a836be80d19d842b7cf7a6e6d56e1dba Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 28 Jun 2021 15:42:39 +0530 Subject: [PATCH 1137/1264] feat: provision to make subcontracted purchase order from the production plan --- .../purchase_order_item.json | 29 ++- erpnext/manufacturing/doctype/bom/bom.json | 21 +- erpnext/manufacturing/doctype/bom/bom.py | 19 +- .../doctype/bom/bom_item_preview.html | 36 +++- erpnext/manufacturing/doctype/bom/bom_tree.js | 2 +- .../production_plan/production_plan.js | 41 +++- .../production_plan/production_plan.json | 31 ++- .../production_plan/production_plan.py | 180 +++++++++++----- .../production_plan_dashboard.py | 4 + .../production_plan/test_production_plan.py | 10 +- .../production_plan_item.json | 19 +- .../__init__.py | 0 .../production_plan_sub_assembly_item.json | 202 ++++++++++++++++++ .../production_plan_sub_assembly_item.py | 10 + .../doctype/work_order/work_order.json | 18 +- .../doctype/work_order/work_order.py | 3 +- .../work_order/work_order_preview.html | 33 +++ .../report/bom_explorer/bom_explorer.py | 11 +- .../job_card_summary/job_card_summary.js | 12 ++ .../job_card_summary/job_card_summary.json | 8 +- .../production_plan_summary/__init__.py | 0 .../production_plan_summary.js | 32 +++ .../production_plan_summary.json | 26 +++ .../production_plan_summary.py | 136 ++++++++++++ .../work_order_summary/work_order_summary.py | 5 +- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_level_in_bom.py | 30 +++ .../stock/doctype/stock_entry/stock_entry.py | 2 +- 28 files changed, 809 insertions(+), 112 deletions(-) create mode 100644 erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py create mode 100644 erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json create mode 100644 erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py create mode 100644 erpnext/manufacturing/doctype/work_order/work_order_preview.html create mode 100644 erpnext/manufacturing/report/production_plan_summary/__init__.py create mode 100644 erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js create mode 100644 erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json create mode 100644 erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py create mode 100644 erpnext/patches/v13_0/update_level_in_bom.py diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 1dbd7c60c3..132dd1769c 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -97,6 +97,9 @@ "is_fixed_asset", "item_tax_rate", "section_break_72", + "production_plan", + "production_plan_item", + "production_plan_sub_assembly_item", "page_break" ], "fields": [ @@ -803,13 +806,37 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "production_plan", + "fieldtype": "Link", + "label": "Production Plan", + "options": "Production Plan", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "production_plan_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Item", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "production_plan_sub_assembly_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Sub Assembly Item", + "no_copy": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-22 11:46:12.357435", + "modified": "2021-06-28 19:22:22.715365", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index f38d1b9892..7e539183b0 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -36,6 +36,9 @@ "materials_section", "inspection_required", "quality_inspection_template", + "column_break_31", + "bom_level", + "section_break_33", "items", "scrap_section", "scrap_items", @@ -513,6 +516,22 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "bom_level", + "fieldtype": "Int", + "label": "BOM Level", + "read_only": 1 + }, + { + "fieldname": "section_break_33", + "fieldtype": "Section Break", + "hide_border": 1 } ], "icon": "fa fa-sitemap", @@ -520,7 +539,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-03-16 12:25:09.081968", + "modified": "2021-05-16 12:25:09.081968", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c58f017258..c31b1bd3e9 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -154,6 +154,7 @@ class BOM(WebsiteGenerator): self.calculate_cost() self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False) + self.set_bom_level() def get_context(self, context): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] @@ -676,6 +677,19 @@ class BOM(WebsiteGenerator): """Get a complete tree representation preserving order of child items.""" return BOMTree(self.name) + def set_bom_level(self, update=False): + levels = [] + + self.bom_level = 0 + for row in self.items: + if row.bom_no: + levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0) + + if levels: + self.bom_level = max(levels) + 1 + + if update: + self.db_set("bom_level", self.bom_level) def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': @@ -860,7 +874,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): frappe.form_dict.parent = parent if frappe.form_dict.parent: - bom_doc = frappe.get_doc("BOM", frappe.form_dict.parent) + bom_doc = frappe.get_cached_doc("BOM", frappe.form_dict.parent) frappe.has_permission("BOM", doc=bom_doc, throw=True) bom_items = frappe.get_all('BOM Item', @@ -871,7 +885,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): item_names = tuple(d.get('item_code') for d in bom_items) items = frappe.get_list('Item', - fields=['image', 'description', 'name', 'stock_uom', 'item_name'], + fields=['image', 'description', 'name', 'stock_uom', 'item_name', 'is_sub_contracted_item'], filters=[['name', 'in', item_names]]) # to get only required item dicts for bom_item in bom_items: @@ -884,6 +898,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): bom_item.parent_bom_qty = bom_doc.quantity bom_item.expandable = 0 if bom_item.value in ('', None) else 1 + bom_item.image = frappe.db.escape(bom_item.image) return bom_items diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index 6cd5f8cb3c..6088e46265 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -1,13 +1,31 @@
    - {% if data.image %} - -
    - {% endif %} -

    - {{ __("Description") }} -

    -
    - {{ data.description }} +
    +
    + {% if data.image %} +
    + +
    + {% endif %} +
    +
    +

    + {{ __("Description") }} +

    +
    + {{ data.description }} +
    +
    +

    + {% if data.value %} + + {{ __("Open BOM {0}", [data.value.bold()]) }} + {% endif %} + {% if data.item_code %} + + {{ __("Open Item {0}", [data.item_code.bold()]) }} + {% endif %} +

    +

    diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index 185b9ed4bc..60fb377f47 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -64,7 +64,7 @@ frappe.treeview_settings["BOM"] = { if(node.is_root && node.data.value!="BOM") { frappe.model.with_doc("BOM", node.data.value, function() { var bom = frappe.model.get_doc("BOM", node.data.value); - node.data.image = bom.image || ""; + node.data.image = escape(bom.image) || ""; node.data.description = bom.description || ""; }); } diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 450aa04a73..d198a6962a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Production Plan', { setup: function(frm) { frm.custom_make_buttons = { - 'Work Order': 'Work Order', + 'Work Order': 'Work Order / Subcontract PO', 'Material Request': 'Material Request', }; @@ -68,17 +68,13 @@ frappe.ui.form.on('Production Plan', { frm.trigger("show_progress"); if (frm.doc.status !== "Completed") { - if (frm.doc.po_items && frm.doc.status !== "Closed") { - frm.add_custom_button(__("Work Order"), ()=> { - frm.trigger("make_work_order"); - }, __('Create')); - } + frm.add_custom_button(__("Work Order Tree"), ()=> { + frappe.set_route('Tree', 'Work Order', {production_plan: frm.doc.name}); + }, __('View')); - if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) { - frm.add_custom_button(__("Material Request"), ()=> { - frm.trigger("make_material_request"); - }, __('Create')); - } + frm.add_custom_button(__("Production Plan Summary"), ()=> { + frappe.set_route('query-report', 'Production Plan Summary', {production_plan: frm.doc.name}); + }, __('View')); if (frm.doc.status === "Closed") { frm.add_custom_button(__("Re-open"), function() { @@ -89,6 +85,18 @@ frappe.ui.form.on('Production Plan', { frm.events.close_open_production_plan(frm, true); }, __("Status")); } + + if (frm.doc.po_items && frm.doc.status !== "Closed") { + frm.add_custom_button(__("Work Order / Subcontract PO"), ()=> { + frm.trigger("make_work_order"); + }, __('Create')); + } + + if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) { + frm.add_custom_button(__("Material Request"), ()=> { + frm.trigger("make_material_request"); + }, __('Create')); + } } } @@ -233,6 +241,17 @@ frappe.ui.form.on('Production Plan', { }); }, + get_sub_assembly_items: function(frm) { + frappe.call({ + method: "get_sub_assembly_items", + freeze: true, + doc: frm.doc, + callback: function() { + refresh_field("sub_assembly_items"); + } + }); + }, + get_items_for_mr: function(frm) { if (!frm.doc.for_warehouse) { frappe.throw(__("Select warehouse for material requests")); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 1c0dde227c..84378956c6 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -32,6 +32,9 @@ "po_items", "section_break_25", "prod_plan_references", + "section_break_24", + "get_sub_assembly_items", + "sub_assembly_items", "material_request_planning", "include_non_stock_items", "include_subcontracted_items", @@ -187,7 +190,7 @@ "depends_on": "get_items_from", "fieldname": "get_items", "fieldtype": "Button", - "label": "Get Items For Work Order" + "label": "Get Finished Goods for Manufacture" }, { "fieldname": "po_items", @@ -199,7 +202,7 @@ { "fieldname": "material_request_planning", "fieldtype": "Section Break", - "label": "Material Request Planning" + "label": "Material Requirement Planning" }, { "default": "1", @@ -237,12 +240,13 @@ }, { "fieldname": "section_break_27", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "mr_items", "fieldtype": "Table", - "label": "Material Request Plan Item", + "label": "Raw Materials", "no_copy": 1, "options": "Material Request Plan Item" }, @@ -337,13 +341,30 @@ "hidden": 1, "label": "Production Plan Item Reference", "options": "Production Plan Item Reference" + }, + { + "fieldname": "section_break_24", + "fieldtype": "Section Break", + "hide_border": 1 + }, + { + "fieldname": "sub_assembly_items", + "fieldtype": "Table", + "label": "Sub Assembly Items", + "no_copy": 1, + "options": "Production Plan Sub Assembly Item" + }, + { + "fieldname": "get_sub_assembly_items", + "fieldtype": "Button", + "label": "Get Sub Assembly Items" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-24 16:59:03.643211", + "modified": "2021-06-28 20:00:33.905114", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 0ede1bd4ab..38a0ee77ad 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -5,10 +5,11 @@ from __future__ import unicode_literals import frappe, json, copy from frappe import msgprint, _ -from six import string_types, iteritems +from six import iteritems from frappe.model.document import Document -from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil +from frappe.utils import (flt, cint, nowdate, add_days, comma_and, now_datetime, + ceil, get_link_to_form, getdate) from frappe.utils.csvutils import build_csv_response from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_children from erpnext.manufacturing.doctype.work_order.work_order import get_item_details @@ -349,49 +350,88 @@ class ProductionPlan(Document): @frappe.whitelist() def make_work_order(self): - wo_list = [] + wo_list, po_list = [], [] + subcontracted_po = {} + self.validate_data() + self.make_work_order_for_finished_goods(wo_list) + self.make_work_order_for_subassembly_items(wo_list, subcontracted_po) + self.make_subcontracted_purchase_order(subcontracted_po, po_list) + self.show_list_created_message('Work Order', wo_list) + self.show_list_created_message('Purchase Order', po_list) + + def make_work_order_for_finished_goods(self, wo_list): items_data = self.get_production_items() for key, item in items_data.items(): + if self.sub_assembly_items: + item['use_multi_level_bom'] = 0 + work_order = self.create_work_order(item) if work_order: wo_list.append(work_order) - if item.get("make_work_order_for_sub_assembly_items"): - work_orders = self.make_work_order_for_sub_assembly_items(item) - wo_list.extend(work_orders) + def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po): + for row in self.sub_assembly_items: + if row.type_of_manufacturing == 'Subcontract': + subcontracted_po.setdefault(row.supplier, []).append(row) + continue + + args = {} + self.prepare_args_for_sub_assembly_items(row, args) + work_order = self.create_work_order(args) + if work_order: + wo_list.append(work_order) + + def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders): + if not subcontracted_po: + return + + for supplier, po_list in subcontracted_po.items(): + po = frappe.new_doc('Purchase Order') + po.supplier = supplier + po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate() + po.is_subcontracted_item = 'Yes' + for row in po_list: + args = { + 'item_code': row.production_item, + 'warehouse': row.fg_warehouse, + 'production_plan_sub_assembly_item': row.name, + 'bom': row.bom_no, + 'production_plan': self.name + } + + for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name', + 'description', 'production_plan_item']: + args[field] = row.get(field) + + po.append('items', args) + + po.set_missing_values() + po.flags.ignore_mandatory = True + po.flags.ignore_validate = True + po.insert() + purchase_orders.append(po.name) + + def show_list_created_message(self, doctype, doc_list=None): + if not doc_list: + return frappe.flags.mute_messages = False + if doc_list: + doc_list = [get_link_to_form(doctype, p) for p in doc_list] + msgprint(_("{0} created").format(comma_and(doc_list))) - if wo_list: - wo_list = ["""%s""" % \ - (p, p) for p in wo_list] - msgprint(_("{0} created").format(comma_and(wo_list))) - else : - msgprint(_("No Work Orders created")) + def prepare_args_for_sub_assembly_items(self, row, args): + for field in ["production_item", "item_name", "qty", "fg_warehouse", + "description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]: + args[field] = row.get(field) - def make_work_order_for_sub_assembly_items(self, item): - work_orders = [] - bom_data = {} - - get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty")) - - for key, data in bom_data.items(): - data.update({ - 'qty': data.get("stock_qty"), - 'production_plan': self.name, - 'use_multi_level_bom': item.get("use_multi_level_bom"), - 'company': self.company, - 'fg_warehouse': item.get("fg_warehouse"), - 'update_consumed_material_cost_in_project': 0 - }) - - work_order = self.create_work_order(data) - if work_order: - work_orders.append(work_order) - - return work_orders + args.update({ + "use_multi_level_bom": 0, + "production_plan": self.name, + "production_plan_sub_assembly_item": row.name + }) def create_work_order(self, item): from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse @@ -476,9 +516,32 @@ class ProductionPlan(Document): else : msgprint(_("No material request created")) + @frappe.whitelist() + def get_sub_assembly_items(self, manufacturing_type=None): + self.sub_assembly_items = [] + for row in self.po_items: + bom_data = [] + get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) + self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) + + self.save() + + def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): + bom_data = sorted(bom_data, key = lambda i: i.bom_level) + + for data in bom_data: + data.qty = data.stock_qty + data.production_plan_item = row.name + data.fg_warehouse = row.warehouse + data.schedule_date = row.planned_start_date + data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item + else "In House") + + self.append("sub_assembly_items", data) + @frappe.whitelist() def download_raw_materials(doc, warehouses=None): - if isinstance(doc, string_types): + if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM', @@ -660,7 +723,7 @@ def get_sales_orders(self): @frappe.whitelist() def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): - if isinstance(row, string_types): + if isinstance(row, str): row = frappe._dict(json.loads(row)) company = frappe.db.escape(company) @@ -684,8 +747,11 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) -def get_warehouse_list(warehouses, warehouse_list=[]): - if isinstance(warehouses, string_types): +def get_warehouse_list(warehouses, warehouse_list=None): + if not warehouse_list: + warehouse_list = [] + + if isinstance(warehouses, str): warehouses = json.loads(warehouses) for row in warehouses: @@ -697,7 +763,7 @@ def get_warehouse_list(warehouses, warehouse_list=[]): @frappe.whitelist() def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): - if isinstance(doc, string_types): + if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) warehouse_list = [] @@ -726,6 +792,9 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d so_item_details = frappe._dict() for data in po_items: + if not data.get("include_exploded_items") and doc.get("sub_assembly_items"): + data["include_exploded_items"] = 1 + planned_qty = data.get('required_qty') or data.get('planned_qty') ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty warehouse = doc.get('for_warehouse') @@ -857,23 +926,28 @@ def get_item_data(item_code): # "description": item_details.get("description") } -def get_sub_assembly_items(bom_no, bom_data, to_produce_qty): +def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0): data = get_children('BOM', parent = bom_no) for d in data: if d.expandable: - key = (d.name, d.value) - if key not in bom_data: - bom_data.setdefault(key, { - 'stock_qty': 0, - 'description': d.description, - 'production_item': d.item_code, - 'item_name': d.item_name, - 'stock_uom': d.stock_uom, - 'uom': d.stock_uom, - 'bom_no': d.value - }) + parent_item_code = frappe.get_cached_value("BOM", bom_no, "item") + bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level") + if d.value else 0) - bom_item = bom_data.get(key) - bom_item["stock_qty"] += (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) + stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) + bom_data.append(frappe._dict({ + 'parent_item_code': parent_item_code, + 'description': d.description, + 'production_item': d.item_code, + 'item_name': d.item_name, + 'stock_uom': d.stock_uom, + 'uom': d.stock_uom, + 'bom_no': d.value, + 'is_sub_contracted_item': d.is_sub_contracted_item, + 'bom_level': bom_level, + 'indent': indent, + 'stock_qty': stock_qty + })) - get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"]) + if d.value: + get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py index 09ec24a67a..ca597f6327 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py @@ -9,5 +9,9 @@ def get_data(): 'label': _('Transactions'), 'items': ['Work Order', 'Material Request'] }, + { + 'label': _('Subcontract'), + 'items': ['Purchase Order'] + }, ] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 768f99eb43..cce1bb61b6 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -169,7 +169,7 @@ class TestProductionPlan(unittest.TestCase): pln.get_items() pln.submit() - self.assertTrue(pln.po_items[0].planned_qty, 3) + self.assertTrue(pln.po_items[0].planned_qty, 3) pln.make_work_order() work_order = frappe.db.get_value('Work Order', { @@ -193,10 +193,10 @@ class TestProductionPlan(unittest.TestCase): for so_item in so_items: so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty') self.assertEqual(so_wo_qty, 0.0) - + latest_plan = frappe.get_doc('Production Plan', pln.name) latest_plan.cancel() - + def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) @@ -236,10 +236,10 @@ class TestProductionPlan(unittest.TestCase): pln.append("po_items", { "item_code": item_code, "bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}), - "planned_qty": 3, - "make_work_order_for_sub_assembly_items": 1 + "planned_qty": 3 }) + pln.get_sub_assembly_items('In House') pln.submit() pln.make_work_order() diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 89ab7aa0a0..f829d57475 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -9,18 +9,17 @@ "include_exploded_items", "item_code", "bom_no", - "planned_qty", "column_break_6", - "make_work_order_for_sub_assembly_items", + "planned_qty", "warehouse", "planned_start_date", "section_break_9", "pending_qty", "ordered_qty", - "produced_qty", "column_break_17", "description", "stock_uom", + "produced_qty", "reference_section", "sales_order", "sales_order_item", @@ -32,11 +31,10 @@ ], "fields": [ { - "columns": 2, - "default": "0", + "columns": 1, + "default": "1", "fieldname": "include_exploded_items", "fieldtype": "Check", - "in_list_view": 1, "label": "Include Exploded Items" }, { @@ -80,13 +78,6 @@ "fieldname": "column_break_6", "fieldtype": "Column Break" }, - { - "default": "0", - "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", - "fieldname": "make_work_order_for_sub_assembly_items", - "fieldtype": "Check", - "label": "Make Work Order for Sub Assembly Items" - }, { "fieldname": "warehouse", "fieldtype": "Link", @@ -218,7 +209,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-28 19:14:57.772123", + "modified": "2021-06-28 18:31:06.822168", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json new file mode 100644 index 0000000000..657ee35a85 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -0,0 +1,202 @@ +{ + "actions": [], + "creation": "2020-12-27 16:08:36.127199", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "production_item", + "item_name", + "fg_warehouse", + "parent_item_code", + "schedule_date", + "column_break_3", + "qty", + "bom_no", + "bom_level", + "type_of_manufacturing", + "supplier", + "work_order_details_section", + "work_order", + "purchase_order", + "production_plan_item", + "column_break_7", + "produced_qty", + "received_qty", + "indent", + "section_break_19", + "uom", + "stock_uom", + "column_break_22", + "description" + ], + "fields": [ + { + "fetch_from": "sub_assembly_item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.type_of_manufacturing == \"In House\"", + "fieldname": "work_order_details_section", + "fieldtype": "Section Break", + "label": "Reference" + }, + { + "fieldname": "work_order", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work Order", + "read_only": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "columns": 1, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty", + "read_only": 1 + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "options": "Purchase Order", + "read_only": 1 + }, + { + "fieldname": "received_qty", + "fieldtype": "Float", + "label": "Received Qty" + }, + { + "fieldname": "bom_no", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Bom No", + "options": "BOM" + }, + { + "fieldname": "production_plan_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Item", + "read_only": 1 + }, + { + "fieldname": "parent_item_code", + "fieldtype": "Link", + "label": "Finished Good", + "options": "Item", + "read_only": 1 + }, + { + "columns": 1, + "fetch_from": "bom_no.bom_level", + "fieldname": "bom_level", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Level (BOM)", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_19", + "fieldtype": "Section Break", + "label": "Item Details" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "description", + "read_only": 1 + }, + { + "fieldname": "production_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sub Assembly Item Code", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "indent", + "fieldtype": "Int", + "label": "Indent" + }, + { + "fieldname": "fg_warehouse", + "fieldtype": "Link", + "label": "Target Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "produced_qty", + "fieldtype": "Data", + "label": "Produced Quantity", + "read_only": 1 + }, + { + "default": "In House", + "fieldname": "type_of_manufacturing", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Manufacturing Type", + "options": "In House\nSubcontract" + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "mandatory_depends_on": "eval:doc.type_of_manufacturing == 'Subcontract'", + "options": "Supplier" + }, + { + "fieldname": "schedule_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Schedule Date" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-28 20:10:56.296410", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Sub Assembly Item", + "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/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py new file mode 100644 index 0000000000..6850a2eb4e --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.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 ProductionPlanSubAssemblyItem(Document): + pass diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 44d76d2b01..3b56854aaf 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -64,11 +64,16 @@ "description", "stock_uom", "column_break2", + "references_section", "material_request", "material_request_item", "sales_order_item", + "column_break_61", "production_plan", "production_plan_item", + "production_plan_sub_assembly_item", + "parent_work_order", + "bom_level", "product_bundle_item", "amended_from" ], @@ -546,17 +551,26 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 - } + }, + { + "fieldname": "production_plan_sub_assembly_item", + "fieldtype": "Data", + "label": "Production Plan Sub-assembly Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } ], "icon": "fa fa-cogs", "idx": 1, "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-06-20 15:19:14.902699", + "modified": "2021-06-28 16:19:14.902699", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", + "nsm_parent_field": "parent_work_order", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 180815d80e..779ae42d65 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -483,7 +483,7 @@ class WorkOrder(Document): self.set('operations', []) - if not self.bom_no: + if not self.bom_no or not frappe.get_cached_value('BOM', self.bom_no, 'with_operations'): return operations = [] @@ -590,6 +590,7 @@ class WorkOrder(Document): def validate_operation_time(self): for d in self.operations: if not d.time_in_mins > 0: + print(self.bom_no, self.production_item) frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) def update_required_items(self): diff --git a/erpnext/manufacturing/doctype/work_order/work_order_preview.html b/erpnext/manufacturing/doctype/work_order/work_order_preview.html new file mode 100644 index 0000000000..a4bf93edef --- /dev/null +++ b/erpnext/manufacturing/doctype/work_order/work_order_preview.html @@ -0,0 +1,33 @@ +

    +
    +
    + {% if data.image %} +
    + +
    + {% endif %} +
    +
    +
    + Status {{ data.status }} +
    +
    + Qty to Produce {{ data.qty }} +
    +
    + Produced Qty {{ data.produced_qty }} +
    +
    +

    + {% if data.value %} + + {{ __("Open Work Order {0}", [data.value.bold()]) }} + {% endif %} + {% if data.item_code %} + + {{ __("Open Item {0}", [data.item_code.bold()]) }} + {% endif %} +

    +
    +
    +
    \ No newline at end of file diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index 48907adc5f..858b5546b0 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -20,17 +20,20 @@ def get_exploded_items(bom, data, indent=0, qty=1): fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom']) for item in exploded_items: + print(item.bom_no, indent) item["indent"] = indent data.append({ 'item_code': item.item_code, 'item_name': item.item_name, 'indent': indent, + 'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level") + if item.bom_no else ""), 'bom': item.bom_no, 'qty': item.qty * qty, 'uom': item.uom, 'description': item.description, 'scrap': item.scrap - }) + }) if item.bom_no: get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty) @@ -68,6 +71,12 @@ def get_columns(): "fieldname": "uom", "width": 100 }, + { + "label": "BOM Level", + "fieldtype": "Data", + "fieldname": "bom_level", + "width": 100 + }, { "label": "Standard Description", "fieldtype": "data", diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js index bd68db190e..cb771e4994 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js @@ -68,6 +68,18 @@ frappe.query_reports["Job Card Summary"] = { get_data: function(txt) { return frappe.db.get_link_options('Item', txt); } + }, + { + label: __("Workstation"), + fieldname: "workstation", + fieldtype: "Link", + options: "Workstation" + }, + { + label: __("Operation"), + fieldname: "operation", + fieldtype: "Link", + options: "Operation" } ] }; diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json index 9f08fc34cb..ecf2b74bbe 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json @@ -1,14 +1,16 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2020-04-20 12:00:21.436619", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "letter_head": "Gadgets International", - "modified": "2020-04-20 12:00:21.436619", + "letter_head": "", + "modified": "2020-12-30 11:49:21.713561", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Summary", diff --git a/erpnext/manufacturing/report/production_plan_summary/__init__.py b/erpnext/manufacturing/report/production_plan_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js new file mode 100644 index 0000000000..59396fef16 --- /dev/null +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js @@ -0,0 +1,32 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Production Plan Summary"] = { + "filters": [ + { + fieldname: "production_plan", + label: __("Production Plan"), + fieldtype: "Link", + options: "Production Plan", + reqd: 1, + get_query: function() { + return { + filters: { + "docstatus": 1 + } + }; + } + } + ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "document_name") { + var color = data.pending_qty > 0 ? 'red': 'green'; + value = `${data['document_name']}`; + } + + return value; + }, +}; diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json new file mode 100644 index 0000000000..33aca21a6e --- /dev/null +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json @@ -0,0 +1,26 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2020-12-27 11:43:39.781793", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2020-12-27 11:43:42.677584", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Production Plan", + "report_name": "Production Plan Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py new file mode 100644 index 0000000000..81b1791ae8 --- /dev/null +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -0,0 +1,136 @@ +# 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.utils import flt + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_column(filters) + + return columns, data + +def get_data(filters): + data = [] + + order_details = {} + get_work_order_details(filters, order_details) + get_purchase_order_details(filters, order_details) + get_production_plan_item_details(filters, data, order_details) + + return data + +def get_production_plan_item_details(filters, data, order_details): + itemwise_indent = {} + + production_plan_doc = frappe.get_cached_doc("Production Plan", filters.get("production_plan")) + for row in production_plan_doc.po_items: + work_order = frappe.get_cached_value("Work Order", {"production_plan_item": row.name, + "bom_no": row.bom_no, "production_item": row.item_code}, "name") + + if row.item_code not in itemwise_indent: + itemwise_indent.setdefault(row.item_code, {}) + + data.append({ + "indent": 0, + "item_code": row.item_code, + "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"), + "qty": row.planned_qty, + "document_type": "Work Order", + "document_name": work_order, + "bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"), + "produced_qty": order_details.get((work_order, row.item_code)).get("produced_qty"), + "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code)).get("produced_qty")) + }) + + get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details) + +def get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details): + for item in production_plan_doc.sub_assembly_items: + if row.name == item.production_plan_item: + subcontracted_item = (item.type_of_manufacturing == 'Subcontract') + + if subcontracted_item: + docname = frappe.get_cached_value("Purchase Order Item", + {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "parent") + else: + docname = frappe.get_cached_value("Work Order", + {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name") + + data.append({ + "indent": 1, + "item_code": item.production_item, + "item_name": item.item_name, + "qty": item.qty, + "document_type": "Work Order" if not subcontracted_item else "Purchase Order", + "document_name": docname, + "bom_level": item.bom_level, + "produced_qty": order_details.get((docname, item.production_item)).get("produced_qty"), + "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item)).get("produced_qty")) + }) + +def get_work_order_details(filters, order_details): + for row in frappe.get_all("Work Order", filters = {"production_plan": filters.get("production_plan")}, + fields=["name", "produced_qty", "production_plan", "production_item"]): + order_details.setdefault((row.name, row.production_item), row) + +def get_purchase_order_details(filters, order_details): + for row in frappe.get_all("Purchase Order Item", filters = {"production_plan": filters.get("production_plan")}, + fields=["parent", "received_qty as produced_qty", "item_code"]): + order_details.setdefault((row.parent, row.item_code), row) + +def get_column(filters): + return [ + { + "label": "Finished Good", + "fieldtype": "Link", + "fieldname": "item_code", + "width": 300, + "options": "Item" + }, + { + "label": "Item Name", + "fieldtype": "data", + "fieldname": "item_name", + "width": 100 + }, + { + "label": "Document Type", + "fieldtype": "Link", + "fieldname": "document_type", + "width": 150, + "options": "DocType" + }, + { + "label": "Document Name", + "fieldtype": "Dynamic Link", + "fieldname": "document_name", + "width": 150 + }, + { + "label": "BOM Level", + "fieldtype": "Int", + "fieldname": "bom_level", + "width": 100 + }, + { + "label": "Order Qty", + "fieldtype": "Float", + "fieldname": "qty", + "width": 120 + }, + { + "label": "Received Qty", + "fieldtype": "Float", + "fieldname": "produced_qty", + "width": 160 + }, + { + "label": "Pending Qty", + "fieldtype": "Float", + "fieldname": "pending_qty", + "width": 110 + } + ] diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index fb047b230c..612dad0bf5 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -19,7 +19,7 @@ def execute(filters=None): return columns, data, None, chart_data def get_data(filters): - query_filters = {"docstatus": 1} + query_filters = {"docstatus": ("<", 2)} fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty", "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"] @@ -62,7 +62,8 @@ def get_chart_based_on_status(data): "Not Started": 0, "In Process": 0, "Stopped": 0, - "Completed": 0 + "Completed": 0, + "Draft": 0 } for d in data: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2b1fc43a1c..29376f00a1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -290,3 +290,4 @@ erpnext.patches.v13_0.set_training_event_attendance erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details +erpnext.patches.v13_0.update_level_in_bom #1234sswef diff --git a/erpnext/patches/v13_0/update_level_in_bom.py b/erpnext/patches/v13_0/update_level_in_bom.py new file mode 100644 index 0000000000..0d03c42e98 --- /dev/null +++ b/erpnext/patches/v13_0/update_level_in_bom.py @@ -0,0 +1,30 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + for document in ["bom", "bom_item", "bom_explosion_item"]: + frappe.reload_doc('manufacturing', 'doctype', document) + + frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1") + + bom_list = frappe.db.sql_list("""select name from `tabBOM` bom + where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item` + where parent=bom.name and ifnull(bom_no, '')!='')""") + + count = 0 + while(count < len(bom_list)): + for parent_bom in get_parent_boms(bom_list[count]): + bom_doc = frappe.get_cached_doc("BOM", parent_bom) + bom_doc.set_bom_level(update=True) + bom_list.append(parent_bom) + count += 1 + +def get_parent_boms(bom_no): + return frappe.db.sql_list(""" + select distinct bom_item.parent from `tabBOM Item` bom_item + where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM' + and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1) + """, bom_no) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8f27ef4356..e21a80030a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -72,7 +72,7 @@ class StockEntry(StockController): self.validate_with_material_request() self.validate_batch() self.validate_inspection() - self.validate_fg_completed_qty() + # self.validate_fg_completed_qty() self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() From 46b67b901b55f296bef953f85059bfc9c788ddbb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Jun 2021 00:03:32 +0530 Subject: [PATCH 1138/1264] fix: incorrect valuation rate in stock reconciliation --- .../stock_reconciliation.py | 7 +-- .../test_stock_reconciliation.py | 44 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e646600752..dd94e7c752 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -404,17 +404,18 @@ class StockReconciliation(StockController): key = (d.item_code, d.warehouse) if key not in merge_similar_entries: + d.total_amount = (d.actual_qty * d.valuation_rate) merge_similar_entries[key] = d elif d.serial_no: data = merge_similar_entries[key] data.actual_qty += d.actual_qty data.qty_after_transaction += d.qty_after_transaction - data.valuation_rate = (data.valuation_rate + d.valuation_rate) / data.actual_qty + data.total_amount += (d.actual_qty * d.valuation_rate) + data.valuation_rate = (data.total_amount) / data.actual_qty data.serial_no += '\n' + d.serial_no - if data.incoming_rate: - data.incoming_rate = (data.incoming_rate + d.incoming_rate) / data.actual_qty + data.incoming_rate = (data.total_amount) / data.actual_qty for key, value in merge_similar_entries.items(): new_sl_entries.append(value) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 36380b838b..ce4cbd259c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, unittest -from frappe.utils import flt, nowdate, nowtime +from frappe.utils import flt, nowdate, nowtime, random_string from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items @@ -150,6 +150,42 @@ class TestStockReconciliation(unittest.TestCase): stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel() + + def test_stock_reco_for_merge_serialized_item(self): + to_delete_records = [] + + # Add new serial nos + serial_item_code = "Stock-Reco-Serial-Item-2" + serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC" + + sr = create_stock_reconciliation(item_code=serial_item_code, serial_no=random_string(6), + warehouse = serial_warehouse, qty=1, rate=100, do_not_submit=True, purpose='Opening Stock') + + for i in range(3): + sr.append('items', { + 'item_code': serial_item_code, + 'warehouse': serial_warehouse, + 'qty': 1, + 'valuation_rate': 100, + 'serial_no': random_string(6) + }) + + sr.save() + sr.submit() + + sle_entries = frappe.get_all('Stock Ledger Entry', filters= {'voucher_no': sr.name}, + fields = ['name', 'incoming_rate']) + + self.assertEqual(len(sle_entries), 1) + self.assertEqual(sle_entries[0].incoming_rate, 100) + + to_delete_records.append(sr.name) + to_delete_records.reverse() + + for d in to_delete_records: + stock_doc = frappe.get_doc("Stock Reconciliation", d) + stock_doc.cancel() + def test_stock_reco_for_batch_item(self): to_delete_records = [] to_delete_serial_nos = [] @@ -231,6 +267,12 @@ def create_batch_or_serial_no_items(): serial_item_doc.serial_no_series = "SRSI.####" serial_item_doc.save(ignore_permissions=True) + serial_item_doc = create_item("Stock-Reco-Serial-Item-2", is_stock_item=1) + if not serial_item_doc.has_serial_no: + serial_item_doc.has_serial_no = 1 + serial_item_doc.serial_no_series = "SRSII.####" + serial_item_doc.save(ignore_permissions=True) + batch_item_doc = create_item("Stock-Reco-batch-Item-1", is_stock_item=1) if not batch_item_doc.has_batch_no: batch_item_doc.has_batch_no = 1 From 815e6ec846799449bf15b4c70b95bf3532e34a4f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Jun 2021 11:35:50 +0530 Subject: [PATCH 1139/1264] fix: minor removed unused file --- .../work_order/work_order_preview.html | 33 ------------------- .../stock/doctype/stock_entry/stock_entry.py | 2 +- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 erpnext/manufacturing/doctype/work_order/work_order_preview.html diff --git a/erpnext/manufacturing/doctype/work_order/work_order_preview.html b/erpnext/manufacturing/doctype/work_order/work_order_preview.html deleted file mode 100644 index a4bf93edef..0000000000 --- a/erpnext/manufacturing/doctype/work_order/work_order_preview.html +++ /dev/null @@ -1,33 +0,0 @@ -
    -
    -
    - {% if data.image %} -
    - -
    - {% endif %} -
    -
    -
    - Status {{ data.status }} -
    -
    - Qty to Produce {{ data.qty }} -
    -
    - Produced Qty {{ data.produced_qty }} -
    -
    -

    - {% if data.value %} - - {{ __("Open Work Order {0}", [data.value.bold()]) }} - {% endif %} - {% if data.item_code %} - - {{ __("Open Item {0}", [data.item_code.bold()]) }} - {% endif %} -

    -
    -
    -
    \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e21a80030a..8f27ef4356 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -72,7 +72,7 @@ class StockEntry(StockController): self.validate_with_material_request() self.validate_batch() self.validate_inspection() - # self.validate_fg_completed_qty() + self.validate_fg_completed_qty() self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() From 68c697b354367e394e00a451487cd24528514d40 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Jul 2021 09:31:31 +0530 Subject: [PATCH 1140/1264] fix: Auto process deferred accounting for multi-company setup --- erpnext/accounts/deferred_revenue.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 2f86c6c1de..335e8a15ab 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -301,17 +301,21 @@ def process_deferred_accounting(posting_date=None): start_date = add_months(today(), -1) end_date = add_days(today(), -1) - for record_type in ('Income', 'Expense'): - doc = frappe.get_doc(dict( - doctype='Process Deferred Accounting', - posting_date=posting_date, - start_date=start_date, - end_date=end_date, - type=record_type - )) + companies = frappe.get_all('Company') - doc.insert() - doc.submit() + for company in companies: + for record_type in ('Income', 'Expense'): + doc = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + company=company.name, + posting_date=posting_date, + start_date=start_date, + end_date=end_date, + type=record_type + )) + + doc.insert() + doc.submit() def make_gl_entries(doc, credit_account, debit_account, against, amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None): From 0bfd56e615f0e9f9053f572d945bbc15f3948dd6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 1 Jul 2021 11:50:48 +0530 Subject: [PATCH 1141/1264] fix: update cost not working in the draft bom --- erpnext/manufacturing/doctype/bom/bom.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 27019dbbae..15a7c316c9 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -325,8 +325,7 @@ frappe.ui.form.on("BOM", { freeze: true, args: { update_parent: true, - from_child_bom:false, - save: frm.doc.docstatus === 1 ? true : false + from_child_bom:false }, callback: function(r) { refresh_field("items"); From a856624ccb9c20b3cf4428204a0da6840df767c2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Jun 2021 23:27:24 +0530 Subject: [PATCH 1142/1264] fix: employee selection not working in payroll entry --- .../doctype/payroll_entry/payroll_entry.js | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index f2892600d1..496c37b2fa 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -135,10 +135,26 @@ frappe.ui.form.on('Payroll Entry', { }); frm.set_query('employee', 'employees', () => { - if (!frm.doc.company) { - frappe.msgprint(__("Please set a Company")); - return []; + let error_fields = []; + let mandatory_fields = ['company', 'payroll_frequency', 'start_date', 'end_date']; + + let message = __('Mandatory fields required in {0}', [__(frm.doc.doctype)]); + + mandatory_fields.forEach(field => { + if (!frm.doc[field]) { + error_fields.push(frappe.unscrub(field)); + } + }); + + if (error_fields && error_fields.length) { + message = message + '

    • ' + error_fields.join('
    • ') + "
    "; + frappe.throw({ + message: message, + indicator: 'red', + title: __('Missing Fields') + }); } + return { query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query", filters: frm.events.get_employee_filters(frm) @@ -148,25 +164,22 @@ frappe.ui.form.on('Payroll Entry', { get_employee_filters: function (frm) { let filters = {}; - filters['company'] = frm.doc.company; - filters['start_date'] = frm.doc.start_date; - filters['end_date'] = frm.doc.end_date; filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet; - filters['payroll_frequency'] = frm.doc.payroll_frequency; - filters['payroll_payable_account'] = frm.doc.payroll_payable_account; - filters['currency'] = frm.doc.currency; - if (frm.doc.department) { - filters['department'] = frm.doc.department; - } - if (frm.doc.branch) { - filters['branch'] = frm.doc.branch; - } - if (frm.doc.designation) { - filters['designation'] = frm.doc.designation; - } + let fields = ['company', 'start_date', 'end_date', 'payroll_frequency', 'payroll_payable_account', + 'currency', 'department', 'branch', 'designation']; + + fields.forEach(field => { + if (frm.doc[field]) { + filters[field] = frm.doc[field]; + } + }); + if (frm.doc.employees) { - filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee); + let employees = frm.doc.employees.filter(d => d.employee).map(d => d.employee); + if (employees && employees.length) { + filters['employees'] = employees; + } } return filters; }, From d8bc51422656f6446584d7d7d75a7d0d209b7993 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 1 Jul 2021 14:06:01 +0530 Subject: [PATCH 1143/1264] fix: to fetch the correct field in Tax Rule (#25927) --- erpnext/accounts/doctype/tax_rule/tax_rule.js | 18 - .../accounts/doctype/tax_rule/tax_rule.json | 1273 +++-------------- .../doctype/tax_rule/test_tax_rule.py | 2 +- 3 files changed, 211 insertions(+), 1082 deletions(-) diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.js b/erpnext/accounts/doctype/tax_rule/tax_rule.js index 370890e4d8..bc497163e8 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.js +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.js @@ -1,24 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch("customer", "customer_group", "customer_group" ); -cur_frm.add_fetch("supplier", "supplier_group_name", "supplier_group" ); - -frappe.ui.form.on("Tax Rule", "tax_type", function(frm) { - frm.toggle_reqd("sales_tax_template", frm.doc.tax_type=="Sales"); - frm.toggle_reqd("purchase_tax_template", frm.doc.tax_type=="Purchase"); -}) - -frappe.ui.form.on("Tax Rule", "onload", function(frm) { - if(frm.doc.__islocal) { - frm.set_value("use_for_shopping_cart", 1); - } -}) - -frappe.ui.form.on("Tax Rule", "refresh", function(frm) { - frappe.ui.form.trigger("Tax Rule", "tax_type"); -}) - frappe.ui.form.on("Tax Rule", "customer", function(frm) { if(frm.doc.customer) { frappe.call({ diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.json b/erpnext/accounts/doctype/tax_rule/tax_rule.json index ef155381c0..2746748432 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.json +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.json @@ -1,1103 +1,250 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "ACC-TAX-RULE-.YYYY.-.#####", - "beta": 0, - "creation": "2015-08-07 02:33:52.670866", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "actions": [], + "allow_import": 1, + "autoname": "ACC-TAX-RULE-.YYYY.-.#####", + "creation": "2015-08-07 02:33:52.670866", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "tax_type", + "use_for_shopping_cart", + "column_break_1", + "sales_tax_template", + "purchase_tax_template", + "filters", + "customer", + "supplier", + "item", + "billing_city", + "billing_county", + "billing_state", + "billing_zipcode", + "billing_country", + "tax_category", + "column_break_2", + "customer_group", + "supplier_group", + "item_group", + "shipping_city", + "shipping_county", + "shipping_state", + "shipping_zipcode", + "shipping_country", + "section_break_4", + "from_date", + "column_break_7", + "to_date", + "section_break_6", + "priority", + "column_break_20", + "company" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Sales", - "fieldname": "tax_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Tax Type", - "length": 0, - "no_copy": 0, - "options": "Sales\nPurchase", - "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": "Sales", + "fieldname": "tax_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Tax Type", + "options": "Sales\nPurchase" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "use_for_shopping_cart", - "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": "Use for Shopping 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 - }, + "default": "1", + "fieldname": "use_for_shopping_cart", + "fieldtype": "Check", + "label": "Use for Shopping Cart" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_1", - "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_1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "sales_tax_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": "Sales Tax Template", - "length": 0, - "no_copy": 0, - "options": "Sales Taxes and Charges 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 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fieldname": "sales_tax_template", + "fieldtype": "Link", + "label": "Sales Tax Template", + "options": "Sales Taxes and Charges Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "purchase_tax_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": "Purchase Tax Template", - "length": 0, - "no_copy": 0, - "options": "Purchase Taxes and Charges 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 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fieldname": "purchase_tax_template", + "fieldtype": "Link", + "label": "Purchase Tax Template", + "options": "Purchase Taxes and Charges Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "filters", - "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": "Filters", - "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": "filters", + "fieldtype": "Section Break", + "label": "Filters" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "customer", - "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": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "supplier", - "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": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "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": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "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": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_city", - "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": "Billing City", - "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": "billing_city", + "fieldtype": "Data", + "label": "Billing City" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_county", - "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": "Billing County", - "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": "billing_county", + "fieldtype": "Data", + "label": "Billing County" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_state", - "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": "Billing State", - "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": "billing_state", + "fieldtype": "Data", + "label": "Billing State" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_zipcode", - "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": "Billing Zipcode", - "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": "billing_zipcode", + "fieldtype": "Data", + "label": "Billing Zipcode" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_country", - "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": "Billing Country", - "length": 0, - "no_copy": 0, - "options": "Country", - "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": "billing_country", + "fieldtype": "Link", + "label": "Billing Country", + "options": "Country" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_category", - "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": "Tax Category", - "length": 0, - "no_copy": 0, - "options": "Tax Category", - "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": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category" + }, { - "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 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "customer_group", - "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": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fetch_from": "customer.customer_group", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "supplier_group", - "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": "Supplier Group", - "length": 0, - "no_copy": 0, - "options": "Supplier 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fetch_from": "supplier.supplier_group", + "fieldname": "supplier_group", + "fieldtype": "Link", + "label": "Supplier Group", + "options": "Supplier Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_group", - "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": "Item Group", - "length": 0, - "no_copy": 0, - "options": "Item 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_city", - "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": "Shipping City", - "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": "shipping_city", + "fieldtype": "Data", + "label": "Shipping City" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_county", - "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": "Shipping County", - "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": "shipping_county", + "fieldtype": "Data", + "label": "Shipping County" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_state", - "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": "Shipping State", - "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": "shipping_state", + "fieldtype": "Data", + "label": "Shipping State" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_zipcode", - "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": "Shipping Zipcode", - "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": "shipping_zipcode", + "fieldtype": "Data", + "label": "Shipping Zipcode" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_country", - "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": "Shipping Country", - "length": 0, - "no_copy": 0, - "options": "Country", - "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": "shipping_country", + "fieldtype": "Link", + "label": "Shipping Country", + "options": "Country" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "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": "Validity", - "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": "section_break_4", + "fieldtype": "Section Break", + "label": "Validity" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_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": "From 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 - }, + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, { - "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 - }, + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_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": "To 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 - }, + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date" + }, { - "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 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "priority", - "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": "Priority", - "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": "priority", + "fieldtype": "Int", + "label": "Priority" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_20", - "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_20", + "fieldtype": "Column Break" + }, { - "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": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 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": "2018-12-27 01:22:17.721636", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Rule", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2021-06-04 23:14:27.186879", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Rule", + "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": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index ac1ffd9e75..cf7226822e 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -50,7 +50,7 @@ class TestTaxRule(unittest.TestCase): tax_rule1 = make_tax_rule(customer_group= "All Customer Groups", sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01") tax_rule1.save() - self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":0}), + self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":1}), "_Test Sales Taxes and Charges Template - _TC") def test_conflict_with_overlapping_dates(self): From 865900fd2d634491e61f4f9191faac7fc880b07f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 28 Jun 2021 12:52:22 +0530 Subject: [PATCH 1144/1264] refactor: add type hints, remove comment, sort imports --- .../cogs_by_item_group/cogs_by_item_group.py | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index 0d601738ff..9e5e63e37e 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -1,14 +1,28 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +from collections import OrderedDict +import datetime +from typing import Dict, List, Tuple, Union + import frappe from frappe import _ from frappe.utils import date_diff -from collections import OrderedDict + from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries -def execute(filters=None): +Filters = frappe._dict +Row = frappe._dict +Data = List[Row] +Columns = List[Dict[str, str]] +DateTime = Union[datetime.date, datetime.datetime] +FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]] +ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]] +SVDList = List[frappe._dict] + + +def execute(filters: Filters) -> Tuple[Columns, Data]: update_filters_with_account(filters) validate_filters(filters) columns = get_columns() @@ -16,17 +30,17 @@ def execute(filters=None): return columns, data -def update_filters_with_account(filters): +def update_filters_with_account(filters: Filters) -> None: account = frappe.get_value("Company", filters.get("company"), "default_expense_account") filters.update(dict(account=account)) -def validate_filters(filters): +def validate_filters(filters: Filters) -> None: if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) -def get_columns(): +def get_columns() -> Columns: return [ { 'label': 'Item Group', @@ -43,7 +57,7 @@ def get_columns(): ] -def get_data(filters): +def get_data(filters: Filters) -> Data: filtered_entries = get_filtered_entries(filters) svd_list = get_stock_value_difference_list(filtered_entries) leveled_dict = get_leveled_dict() @@ -62,7 +76,7 @@ def get_data(filters): return data -def get_filtered_entries(filters): +def get_filtered_entries(filters: Filters) -> FilteredEntries: gl_entries = get_gl_entries(filters, []) filtered_entries = [] for entry in gl_entries: @@ -74,7 +88,7 @@ def get_filtered_entries(filters): return filtered_entries -def get_stock_value_difference_list(filtered_entries): +def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList: voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] svd_list = frappe.get_list( 'Stock Ledger Entry', fields=['item_code','stock_value_difference'], @@ -84,7 +98,7 @@ def get_stock_value_difference_list(filtered_entries): return svd_list -def get_leveled_dict(): +def get_leveled_dict() -> OrderedDict: item_groups_dict = get_item_groups_dict() lr_list = sorted(item_groups_dict, key=lambda x : int(x[0])) leveled_dict = OrderedDict() @@ -109,14 +123,14 @@ def get_leveled_dict(): return leveled_dict -def assign_self_values(leveled_dict, svd_list): +def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None: key_dict = {v['name']:k for k, v in leveled_dict.items()} for item in svd_list: key = key_dict[item.get("item_group")] leveled_dict[key]['self_value'] += -item.get("stock_value_difference") -def assign_agg_values(leveled_dict): +def assign_agg_values(leveled_dict: OrderedDict) -> None: keys = list(leveled_dict.keys())[::-1] prev_level = leveled_dict[keys[-1]]['level'] accu = [0] @@ -141,21 +155,21 @@ def assign_agg_values(leveled_dict): leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value'] -def get_row(name:str, value:float, is_bold:int, indent:int): +def get_row(name:str, value:float, is_bold:int, indent:int) -> Row: item_group = name if is_bold: item_group = frappe.bold(item_group) return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent) -def assign_item_groups_to_svd_list(svd_list): +def assign_item_groups_to_svd_list(svd_list: SVDList) -> None: ig_map = get_item_groups_map(svd_list) for item in svd_list: item.item_group = ig_map[item.get("item_code")] -def get_item_groups_map(svd_list): - # for items in svd_list: [{'item_code':'item_group'}] - item_codes = set([i['item_code'] for i in svd_list]) + +def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]: + item_codes = set(i['item_code'] for i in svd_list) ig_list = frappe.get_list( 'Item', fields=['item_code','item_group'], filters=[('item_code', 'in', item_codes)] @@ -163,12 +177,12 @@ def get_item_groups_map(svd_list): return {i['item_code']:i['item_group'] for i in ig_list} -def get_item_groups_dict(): +def get_item_groups_dict() -> ItemGroupsDict: item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} for i in item_groups_list} -def update_leveled_dict(leveled_dict): +def update_leveled_dict(leveled_dict: OrderedDict) -> None: for k in leveled_dict: leveled_dict[k].update({'self_value':0, 'agg_value':0}) From 752f099e9dd5187669d11b6420dece399d072aff Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Jul 2021 17:20:24 +0530 Subject: [PATCH 1145/1264] fix: Order Items by weightage in the web items query --- erpnext/shopping_cart/product_query.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index d96d803416..6c92d967d0 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -71,7 +71,8 @@ class ProductQuery: ], or_filters=self.or_filters, start=start, - limit=self.page_length + limit=self.page_length, + order_by="weightage desc" ) items_dict = {item.name: item for item in items} @@ -86,7 +87,8 @@ class ProductQuery: filters=self.filters, or_filters=self.or_filters, start=start, - limit=self.page_length + limit=self.page_length, + order_by="weightage desc" ) # Combine results having context of website item groups into item results From ba2c3c776f15394ed287454dd9ccce60acd6f228 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Jul 2021 18:56:51 +0530 Subject: [PATCH 1146/1264] fix: Bank statement import --- .../doctype/bank_statement_import/bank_statement_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 5f110e2727..ffc9d1c465 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -51,7 +51,7 @@ class BankStatementImport(DataImport): self.import_file, self.google_sheets_url ) - if 'Bank Account' not in json.dumps(preview): + if 'Bank Account' not in json.dumps(preview['columns']): frappe.throw(_("Please add the Bank Account column")) from frappe.core.page.background_jobs.background_jobs import get_info From 991d3cdd76a0f4c20f405799ddc54d98951b28e7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Jul 2021 21:17:17 +0530 Subject: [PATCH 1147/1264] fix: Incorrect discount amount on amended document --- erpnext/public/js/controllers/taxes_and_totals.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 1de9ec1a7d..52efbb5f6c 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -67,6 +67,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ calculate_discount_amount: function(){ if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) { + this.calculate_item_values(); + this.calculate_net_total(); this.set_discount_amount(); this.apply_discount_amount(); } From 5173e74a041d33d87d4ab3172a4bfc4b64d66381 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 2 Jul 2021 11:48:46 +0530 Subject: [PATCH 1148/1264] fix: Project Portal Enhancements (#26290) * fix: project portal enhancements * fix: condition for pills --- erpnext/hooks.py | 1 + .../includes/projects/project_row.html | 80 ++++--- .../includes/projects/project_tasks.html | 33 +-- .../includes/projects/project_timesheets.html | 54 +++-- erpnext/templates/pages/projects.html | 215 ++++++++++++------ erpnext/templates/pages/projects.py | 41 +--- 6 files changed, 250 insertions(+), 174 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 8ad77a1524..52daec9180 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -157,6 +157,7 @@ website_route_rules = [ "parents": [{"label": _("Material Request"), "route": "material-requests"}] } }, + {"from_route": "/project", "to_route": "Project"} ] standard_portal_menu_items = [ diff --git a/erpnext/templates/includes/projects/project_row.html b/erpnext/templates/includes/projects/project_row.html index 4c8c40db00..bfd63494c3 100644 --- a/erpnext/templates/includes/projects/project_row.html +++ b/erpnext/templates/includes/projects/project_row.html @@ -1,28 +1,54 @@ -{% if doc.status=="Open" %} - +{% if doc.status == "Open" %} +
    +
    +
    + Link + {{ doc.name }} +
    +
    + {{ doc.project_name }} +
    +
    + {% if doc.percent_complete %} + {% set pill_class = "green" if doc.percent_complete | round == 100 else + "orange" %} +
    + + {{ frappe.utils.cint(doc.percent_complete) }} + % + +
    + {% else %} + + {{ doc.status }} + {% endif %} +
    + {% if doc["_assign"] %} + {% set assigned_users = json.loads(doc["_assign"])%} +
    + {% for user in assigned_users %} + {% set user_details = frappe + .db + .get_value("User", user, [ + "full_name", "user_image" + ], as_dict = True) %} + {% if user_details.user_image %} + + + + {% else %} + +
    + {{ frappe.utils.get_abbr(user_details.full_name) }} +
    +
    + {% endif %} + {% endfor %} +
    + {% endif %} +
    + {{ frappe.utils.pretty_date(doc.modified) }} +
    +
    +
    {% endif %} diff --git a/erpnext/templates/includes/projects/project_tasks.html b/erpnext/templates/includes/projects/project_tasks.html index 50b9f4b259..2b07a5f0d0 100644 --- a/erpnext/templates/includes/projects/project_tasks.html +++ b/erpnext/templates/includes/projects/project_tasks.html @@ -1,32 +1,5 @@ {% for task in doc.tasks %} - +
    + {{ task_row(task, 0) }} +
    {% endfor %} diff --git a/erpnext/templates/includes/projects/project_timesheets.html b/erpnext/templates/includes/projects/project_timesheets.html index 05a07c12e8..850c5e9863 100644 --- a/erpnext/templates/includes/projects/project_timesheets.html +++ b/erpnext/templates/includes/projects/project_timesheets.html @@ -1,23 +1,33 @@ {% for timesheet in doc.timesheets %} - -{% endfor %} \ No newline at end of file +
    +
    +
    {{ timesheet.name }}
    + Link +
    {{ timesheet.status }}
    +
    {{ frappe.utils.format_date(timesheet.from_time, "medium") }}
    +
    {{ frappe.utils.format_date(timesheet.to_time, "medium") }}
    +
    + {% set user_details = frappe + .db + .get_value("User", timesheet.modified_by, [ + "full_name", "user_image" + ], as_dict = True) + %} + {% if user_details.user_image %} + + + + {% else %} + +
    + {{ frappe.utils.get_abbr(user_details.full_name) }} +
    +
    + {% endif %} +
    +
    + {{ frappe.utils.pretty_date(timesheet.modified) }} +
    +
    +
    +{% endfor %} diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index 7e294e076b..76eaf75cf3 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -1,90 +1,173 @@ {% extends "templates/web.html" %} -{% block title %}{{ doc.project_name }}{% endblock %} +{% block title %} + {{ doc.project_name }} +{% endblock %} + +{% block head_include %} + +{% endblock %} {% block header %} -

    {{ doc.project_name }}

    +

    {{ doc.project_name }}

    {% endblock %} {% block style %} - + {% endblock %} - {% block page_content %} -{% if doc.percent_complete %} -
    -
    -
    -
    -{% endif %} -
    -

    {{ _("Tasks") }}

    - {{ _("New task") }} -
    + {{ progress_bar(doc.percent_complete) }} -

    - -

    +
    +

    Status:

    +

    Progress: + {{ doc.percent_complete }} + % +

    +

    Hours Spent: + {{ doc.actual_time }} +

    +
    -{% if doc.tasks %} -
    -
    - {% include "erpnext/templates/includes/projects/project_tasks.html" %} -
    -

    -

    -{% else %} -

    {{ _("No tasks") }}

    -{% endif %} + {{ progress_bar(doc.percent_complete) }} + {% if doc.tasks %} +
    +
    +
    +
    +

    Tasks

    +

    Status

    +

    End Date

    +

    Assigned To

    + +
    +
    + {% include "erpnext/templates/includes/projects/project_tasks.html" %} +
    +
    + {% else %} +

    {{ _("No Tasks") }}

    + {% endif %} -
    + {% if doc.timesheets %} +
    +
    +
    +
    +

    Timesheets

    +

    Status

    +

    From

    +

    To

    +

    Modified By

    +

    Modified On

    +
    +
    + {% include "erpnext/templates/includes/projects/project_timesheets.html" %} +
    +
    + {% else %} +

    {{ _("No Timesheets") }}

    + {% endif %} -

    {{ _("Timesheets") }}

    + {% if doc.attachments %} +
    -{% if doc.timesheets %} -
    - {% include "erpnext/templates/includes/projects/project_timesheets.html" %} -
    - {% if doc.timesheets|length > 9 %} -

    {{ _("More") }}

    - {% endif %} -{% else %} -

    {{ _("No time sheets") }}

    -{% endif %} - -{% if doc.attachments %} -
    - -

    {{ _("Attachments") }}

    -
    - {% for attachment in doc.attachments %} - - {% endfor %} -
    -{% endif %} +

    {{ _("Attachments") }}

    +
    + {% for attachment in doc.attachments %} + + {% endfor %} +
    + {% endif %}
    {% endblock %} + +{% macro progress_bar(percent_complete) %} +{% if percent_complete %} +
    +
    +
    +{% else %} +
    +{% endif %} +{% endmacro %} + +{% macro task_row(task, indent) %} +
    + +
    {{ task.status }}
    +
    + {% if task.exp_end_date %} + {{ task.exp_end_date }} + {% else %} + -- + {% endif %} +
    +
    + {% if task["_assign"] %} + {% set assigned_users = json.loads(task["_assign"])%} + {% for user in assigned_users %} + {% set user_details = frappe.db.get_value("User", user, + ["full_name", "user_image"], + as_dict = True)%} + {% if user_details.user_image %} + + + + {% else %} + +
    + {{ frappe.utils.get_abbr(user_details.full_name) }} +
    +
    + {% endif %} + {% endfor %} + {% endif %} +
    +
    + {{ frappe.utils.pretty_date(task.modified) }} +
    +
    +{% if task.children %} + {% for child in task.children %} + {{ task_row(child, indent + 30) }} + {% endfor %} +{% endif %} +{% endmacro %} diff --git a/erpnext/templates/pages/projects.py b/erpnext/templates/pages/projects.py index d23fed9e7d..b369cb6a99 100644 --- a/erpnext/templates/pages/projects.py +++ b/erpnext/templates/pages/projects.py @@ -35,26 +35,16 @@ def get_tasks(project, start=0, search=None, item_status=None): # if item_status: # filters["status"] = item_status tasks = frappe.get_all("Task", filters=filters, - fields=["name", "subject", "status", "_seen", "_comments", "modified", "description"], + fields=["name", "subject", "status", "modified", "_assign", "exp_end_date", "is_group", "parent_task"], limit_start=start, limit_page_length=10) - + task_nest = [] for task in tasks: - task.todo = frappe.get_all('ToDo',filters={'reference_name':task.name, 'reference_type':'Task'}, - fields=["assigned_by", "owner", "modified", "modified_by"]) - - if task.todo: - task.todo=task.todo[0] - task.todo.user_image = frappe.db.get_value('User', task.todo.owner, 'user_image') - - - task.comment_count = len(json.loads(task._comments or "[]")) - - task.css_seen = '' - if task._seen: - if frappe.session.user in json.loads(task._seen): - task.css_seen = 'seen' - - return tasks + if task.is_group: + child_tasks = list(filter(lambda x: x.parent_task == task.name, tasks)) + if len(child_tasks): + task.children = child_tasks + task_nest.append(task) + return list(filter(lambda x: not x.parent_task, tasks)) @frappe.whitelist() def get_task_html(project, start=0, item_status=None): @@ -74,19 +64,12 @@ def get_timesheets(project, start=0, search=None): fields=['project','activity_type','from_time','to_time','parent'], limit_start=start, limit_page_length=10) for timesheet in timesheets: - timesheet.infos = frappe.get_all('Timesheet', filters={"name": timesheet.parent}, - fields=['name','_comments','_seen','status','modified','modified_by'], + info = frappe.get_all('Timesheet', filters={"name": timesheet.parent}, + fields=['name','status','modified','modified_by'], limit_start=start, limit_page_length=10) - for timesheet.info in timesheet.infos: - timesheet.info.user_image = frappe.db.get_value('User', timesheet.info.modified_by, 'user_image') - - timesheet.info.comment_count = len(json.loads(timesheet.info._comments or "[]")) - - timesheet.info.css_seen = '' - if timesheet.info._seen: - if frappe.session.user in json.loads(timesheet.info._seen): - timesheet.info.css_seen = 'seen' + if len(info): + timesheet.update(info[0]) return timesheets @frappe.whitelist() From ad6f20c5c778fc2377f4febf8389d575d6c87185 Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Fri, 2 Jul 2021 12:32:22 +0530 Subject: [PATCH 1149/1264] fix: Added permission for employee to book appointment (#26255) --- erpnext/crm/doctype/appointment/appointment.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 8517ddec32..306be7faa7 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -102,7 +102,7 @@ } ], "links": [], - "modified": "2020-01-28 16:16:45.447213", + "modified": "2021-06-29 18:27:02.832979", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", @@ -153,6 +153,18 @@ "role": "Sales User", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "write": 1 } ], "quick_entry": 1, From 18533e381a832bfb78f08d34eb51731edaeacb05 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 2 Jul 2021 12:57:06 +0530 Subject: [PATCH 1150/1264] fix: lms progress issue (#26253) --- erpnext/education/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 9db8a4a90d..3070e6a3e8 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -355,11 +355,11 @@ def get_or_create_course_enrollment(course, program): student = get_current_student() course_enrollment = get_enrollment("course", course, student.name) if not course_enrollment: - program_enrollment = get_enrollment('program', program, student.name) + program_enrollment = get_enrollment('program', program.name, student.name) if not program_enrollment: frappe.throw(_("You are not enrolled in program {0}").format(program)) return - return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name)) + return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program.name, student.name)) else: return frappe.get_doc('Course Enrollment', course_enrollment) From 877597bc16c061a68e4577e434df6b1c041033c5 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 2 Jul 2021 13:10:18 +0530 Subject: [PATCH 1151/1264] fix: feating employee in payroll entry (#26271) --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 5c7c0a3b09..36e728fc99 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -680,6 +680,10 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] include_employees = [] emp_cond = '' + + if not filters.payroll_frequency: + frappe.throw(_('Select Payroll Frequency.')) + if filters.start_date and filters.end_date: employee_list = get_employee_list(filters) emp = filters.get('employees') From c0817838d951a56d8ea38f268a6e20d43ef05c71 Mon Sep 17 00:00:00 2001 From: Ashish Shah Date: Fri, 2 Jul 2021 15:16:42 +0530 Subject: [PATCH 1152/1264] fix: when lead is created with mobile_no, mobile_no value gets lost (#26298) Summary: When a Lead is created with mobile_no, mobile_no value gets lost (mobile_no value is overwritten by phone value) It is backport of https://github.com/frappe/erpnext/pull/26116 Steps to reproduce [1]Create a Lead. [2]Enter Person Name(lead_name): before_fix Under Contact section, enter Phone(phone): 11 and Mobile No.(mobile_no):22 [3]Save it [4] F12, cur_frm.doc.phone : 11 (correct) cur_frm.doc.mobile_no : 11 (incorrect, it should be 22) [5]Under Address & Contact section ,check contact_html it shows before_fix Phone: 11 (Primary label is missing) Phone: 22 (incorrect, it should be Mobile No:22, also Primary label is missing) Actual: mobile_no value is lost. it is overwritten by phone value following is image with error (before fix) ![image](https://user-images.githubusercontent.com/29812965/122664017-54b2e880-d1bc-11eb-8e4c-767a23ed7eb7.png) Expected: mobile_no value should be retained following is image after fix ![image](https://user-images.githubusercontent.com/29812965/122664037-64323180-d1bc-11eb-8f6f-7628cdaa7adc.png) --- erpnext/crm/doctype/lead/lead.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d1d096843b..ce3de40fc3 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -168,12 +168,13 @@ class Lead(SellingController): if self.phone: contact.append("phone_nos", { "phone": self.phone, - "is_primary": 1 + "is_primary_phone": 1 }) if self.mobile_no: contact.append("phone_nos", { - "phone": self.mobile_no + "phone": self.mobile_no, + "is_primary_mobile_no":1 }) contact.insert(ignore_permissions=True) From b6076f772d6fd9928ddadb8572e391a7beb33c92 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 2 Jul 2021 15:39:16 +0530 Subject: [PATCH 1153/1264] fix: only "Tax" type accounts should be shown for selection in GST Settings --- erpnext/regional/doctype/gst_settings/gst_settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.js b/erpnext/regional/doctype/gst_settings/gst_settings.js index 808f9bc078..cd682c5403 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.js +++ b/erpnext/regional/doctype/gst_settings/gst_settings.js @@ -35,6 +35,7 @@ frappe.ui.form.on('GST Settings', { return { filters: { company: row.company, + account_type: "Tax", is_group: 0 } }; From 4503a3836128784541ef14a575f9067db182438c Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 2 Jul 2021 17:13:45 +0530 Subject: [PATCH 1154/1264] fix: Handle Stock Reco cancellation and limit reposting - Handled cancellation of reco with and without prior SLE - Repost / Recalculate balance qty only till next stock reco --- .../stock_reconciliation.py | 1 + erpnext/stock/stock_ledger.py | 84 ++++++++++++++++--- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 2b51c1a5c3..7b84c4c2d9 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -357,6 +357,7 @@ class StockReconciliation(StockController): if row.current_qty: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) + data.previous_qty_after_transaction = flt(row.qty) data.valuation_rate = flt(row.current_valuation_rate) data.stock_value = data.qty_after_transaction * data.valuation_rate data.stock_value_difference = -1 * flt(row.amount_difference) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 94bd3077a7..7425473f9d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -55,6 +55,11 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher) args = sle_doc.as_dict() + + if sle.get("voucher_type") == "Stock Reconciliation": + # preserve previous_qty_after_transaction for qty reposting + args.previous_qty_after_transaction = sle.get("previous_qty_after_transaction") + update_bin(args, allow_negative_stock, via_landed_cost_voucher) def get_args_for_future_sle(row): @@ -869,19 +874,21 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, def update_qty_in_future_sle(args, allow_negative_stock=None): """Recalculate Qty after Transaction in future SLEs based on current SLE.""" + datetime_limit_condition = "" + last_balance = None + qty_shift = args.actual_qty # find difference/shift in qty caused by stock reconciliation if args.voucher_type == "Stock Reconciliation": - last_balance = get_previous_sle_of_current_voucher( - args, - exclude_current_voucher=True - ).get("qty_after_transaction") - if last_balance is not None: - stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) - else: - stock_reco_qty_shift = args.qty_after_transaction - qty_shift = stock_reco_qty_shift + qty_shift = get_stock_reco_qty_shift(args) + + # find the next nearest stock reco so that we only recalculate SLEs till that point + next_stock_reco_detail = get_next_stock_reco(args) + if next_stock_reco_detail: + detail = next_stock_reco_detail[0] + # add condition to update SLEs before this date & time + datetime_limit_condition = get_datetime_limit_condition(detail) frappe.db.sql(""" update `tabStock Ledger Entry` @@ -897,10 +904,67 @@ def update_qty_in_future_sle(args, allow_negative_stock=None): and creation > %(creation)s ) ) - """.format(qty_shift=qty_shift), args) + {datetime_limit_condition} + """.format(qty_shift=qty_shift, datetime_limit_condition=datetime_limit_condition), args) validate_negative_qty_in_future_sle(args, allow_negative_stock) +def get_stock_reco_qty_shift(args): + stock_reco_qty_shift = 0 + if args.get("is_cancelled"): + if args.get("previous_qty_after_transaction"): + # get qty (balance) that was set at submission + last_balance = args.get("previous_qty_after_transaction") + stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) + else: + stock_reco_qty_shift = flt(args.actual_qty) + else: + # reco is being submitted + last_balance = get_previous_sle_of_current_voucher(args, + exclude_current_voucher=True).get("qty_after_transaction") + + if last_balance is not None: + stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) + else: + stock_reco_qty_shift = args.qty_after_transaction + + return stock_reco_qty_shift + +def get_next_stock_reco(args): + """Returns next nearest stock reconciliaton's details.""" + + return frappe.db.sql(""" + select + name, posting_date, posting_time, creation, voucher_no + from + `tabStock Ledger Entry` + where + item_code = %(item_code)s + and warehouse = %(warehouse)s + and voucher_type = 'Stock Reconciliation' + and voucher_no != %(voucher_no)s + and is_cancelled = 0 + and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) + or ( + timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) + and creation > %(creation)s + ) + ) + limit 1 + """, args, as_dict=1) + +def get_datetime_limit_condition(detail): + if not detail: return None + + return f""" + and + (timestamp(posting_date, posting_time) < timestamp('{detail.posting_date}', '{detail.posting_time}') + or ( + timestamp(posting_date, posting_time) = timestamp('{detail.posting_date}', '{detail.posting_time}') + and creation < '{detail.creation}' + ) + )""" + def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): allow_negative_stock = allow_negative_stock \ or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) From 73db919c99d376abbea9d98b2f6e53a8e46ff4ed Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 2 Jul 2021 17:55:42 +0530 Subject: [PATCH 1155/1264] fix: set query for training events (#26303) * fix: set query * fix: remove whitespace between function and params Co-authored-by: Rucha Mahabal --- .../hr/doctype/training_event/training_event.js | 14 ++++++++++---- .../training_event_employee.json | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js index b7d34b178a..064dfb2455 100644 --- a/erpnext/hr/doctype/training_event/training_event.js +++ b/erpnext/hr/doctype/training_event/training_event.js @@ -20,11 +20,10 @@ frappe.ui.form.on('Training Event', { frappe.set_route("List", "Training Feedback"); }); } - } -}); + frm.events.set_employee_query(frm); + }, -frappe.ui.form.on("Training Event Employee", { - employee: function (frm) { + set_employee_query: function(frm) { let emp = []; for (let d in frm.doc.employees) { if (frm.doc.employees[d].employee) { @@ -40,3 +39,10 @@ frappe.ui.form.on("Training Event Employee", { }); } }); + +frappe.ui.form.on("Training Event Employee", { + employee: function(frm) { + frm.events.set_employee_query(frm); + } +}); + diff --git a/erpnext/hr/doctype/training_event_employee/training_event_employee.json b/erpnext/hr/doctype/training_event_employee/training_event_employee.json index 2d313e9fac..bcb7d5e5bc 100644 --- a/erpnext/hr/doctype/training_event_employee/training_event_employee.json +++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.json @@ -19,6 +19,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Employee", + "no_copy": 1, "options": "Employee" }, { @@ -68,7 +69,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-21 12:41:59.336237", + "modified": "2021-07-02 17:20:27.630176", "modified_by": "Administrator", "module": "HR", "name": "Training Event Employee", From 311e277204a6aa28a6a132a888787385018e546f Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 2 Jul 2021 17:46:05 +0530 Subject: [PATCH 1156/1264] fix: Sider --- erpnext/stock/stock_ledger.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 7425473f9d..4e9c7689ae 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -875,8 +875,6 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, def update_qty_in_future_sle(args, allow_negative_stock=None): """Recalculate Qty after Transaction in future SLEs based on current SLE.""" datetime_limit_condition = "" - last_balance = None - qty_shift = args.actual_qty # find difference/shift in qty caused by stock reconciliation @@ -937,7 +935,7 @@ def get_next_stock_reco(args): select name, posting_date, posting_time, creation, voucher_no from - `tabStock Ledger Entry` + `tabStock Ledger Entry` where item_code = %(item_code)s and warehouse = %(warehouse)s @@ -954,8 +952,6 @@ def get_next_stock_reco(args): """, args, as_dict=1) def get_datetime_limit_condition(detail): - if not detail: return None - return f""" and (timestamp(posting_date, posting_time) < timestamp('{detail.posting_date}', '{detail.posting_time}') From 3105332e3c707af1360e529ec77e438897cc9e2f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 3 Jul 2021 17:22:09 +0530 Subject: [PATCH 1157/1264] fix: allow to make job card without employee --- .../doctype/job_card/job_card.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 7f8f2ef68d..420bb00803 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -192,15 +192,20 @@ class JobCard(Document): "completed_qty": args.get("completed_qty") or 0.0 }) elif args.get("start_time"): - for name in employees: - self.append("time_logs", { - "from_time": get_datetime(args.get("start_time")), - "employee": name.get('employee'), - "operation": args.get("sub_operation"), - "completed_qty": 0.0 - }) + new_args = { + "from_time": get_datetime(args.get("start_time")), + "operation": args.get("sub_operation"), + "completed_qty": 0.0 + } - if not self.employee: + if employees: + for name in employees: + new_args.employee = name.get('employee') + self.add_start_time_log(new_args) + else: + self.add_start_time_log(new_args) + + if not self.employee and employees: self.set_employees(employees) if self.status == "On Hold": @@ -208,6 +213,9 @@ class JobCard(Document): self.save() + def add_start_time_log(self, args): + self.append("time_logs", args) + def set_employees(self, employees): for name in employees: self.append('employee', { From a0599e5ac2d4da456256ab00bbe2105e03d3e821 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 10:09:42 +0530 Subject: [PATCH 1158/1264] fix: Test cases for M-pesa --- .../doctype/mpesa_settings/test_mpesa_settings.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 3c2e59ab82..2dfd5aad5d 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -9,13 +9,17 @@ from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import p from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice class TestMpesaSettings(unittest.TestCase): + def setUp(self): + # create payment gateway in setup + create_mpesa_settings(payment_gateway_name="_Test") + create_mpesa_settings(payment_gateway_name="_Account Balance") + create_mpesa_settings(payment_gateway_name="Payment") + def tearDown(self): frappe.db.sql('delete from `tabMpesa Settings`') frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"') def test_creation_of_payment_gateway(self): - create_mpesa_settings(payment_gateway_name="_Test") - mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test") self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"})) self.assertTrue(mode_of_payment.name) @@ -47,7 +51,6 @@ class TestMpesaSettings(unittest.TestCase): integration_request.delete() def test_processing_of_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") @@ -90,7 +93,6 @@ class TestMpesaSettings(unittest.TestCase): pos_invoice.delete() def test_processing_of_multiple_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") @@ -141,7 +143,6 @@ class TestMpesaSettings(unittest.TestCase): pos_invoice.delete() def test_processing_of_only_one_succes_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") From 75fdf79376ccec55e34cf0ff1911f297360a431d Mon Sep 17 00:00:00 2001 From: Richard Case <64409021+casesolved-co-uk@users.noreply.github.com> Date: Mon, 5 Jul 2021 06:26:34 +0100 Subject: [PATCH 1159/1264] fix: incorrect bom no. added for non-variant items on variant boms (#26320) --- erpnext/manufacturing/doctype/bom/bom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c31b1bd3e9..c32a8a95a1 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1115,6 +1115,8 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None): }, 'BOM Item': { 'doctype': 'BOM Item', + # stop get_mapped_doc copying parent bom_no to children + 'field_no_map': ['bom_no'], 'condition': lambda doc: doc.has_variants == 0 }, }, target_doc, postprocess) From db682d9e4cdea680f2ab0d4e589000e54baf6a20 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 13:46:03 +0530 Subject: [PATCH 1160/1264] fix: Create mode of payment if doesn't exists --- .../doctype/mpesa_settings/test_mpesa_settings.py | 3 ++- erpnext/erpnext_integrations/utils.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 2dfd5aad5d..f592c180a3 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -7,6 +7,7 @@ import frappe import unittest from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import process_balance_info, verify_transaction from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice +from erpnext.erpnext_integrations.utils import create_mode_of_payment class TestMpesaSettings(unittest.TestCase): def setUp(self): @@ -20,7 +21,7 @@ class TestMpesaSettings(unittest.TestCase): frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"') def test_creation_of_payment_gateway(self): - mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test") + mode_of_payment = create_mode_of_payment('Mpesa-_Test', payment_type="Phone") self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"})) self.assertTrue(mode_of_payment.name) self.assertEqual(mode_of_payment.type, "Phone") diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 3840e781b4..b764701103 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -52,7 +52,8 @@ def create_mode_of_payment(gateway, payment_type="General"): "payment_gateway": gateway }, ['payment_account']) - if not frappe.db.exists("Mode of Payment", gateway) and payment_gateway_account: + mode_of_payment = frappe.db.exists("Mode of Payment", gateway) + if not mode_of_payment and payment_gateway_account: mode_of_payment = frappe.get_doc({ "doctype": "Mode of Payment", "mode_of_payment": gateway, @@ -66,6 +67,10 @@ def create_mode_of_payment(gateway, payment_type="General"): }) mode_of_payment.insert(ignore_permissions=True) + return mode_of_payment + else: + return frappe.get_doc("Mode of Payment", mode_of_payment) + def get_tracking_url(carrier, tracking_number): # Return the formatted Tracking URL. tracking_url = '' From 5638fbb1aac81bc1196c9a942e1abb9310983ce6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 5 Jul 2021 13:54:05 +0530 Subject: [PATCH 1161/1264] fix: bom stock report not working --- .../manufacturing/report/bom_stock_report/bom_stock_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 1c6758e6f3..ed8b93929a 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -70,12 +70,12 @@ def get_bom_stock(filters): ON bom_item.item_code = ledger.item_code {conditions} WHERE - bom_item.parent = '{bom}' and bom_item.parenttype='BOM' + bom_item.parent = {bom} and bom_item.parenttype='BOM' GROUP BY bom_item.item_code""".format( qty_field=qty_field, table=table, conditions=conditions, - bom=bom, + bom=frappe.db.escape(bom), qty_to_produce=qty_to_produce or 1) ) From c69bc54297314f952458b4d1bd30b8524b61cad2 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 5 Jul 2021 14:24:38 +0530 Subject: [PATCH 1162/1264] fix: Validate LCV for Invoices without Update Stock --- .../landed_cost_voucher/landed_cost_voucher.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 5df4d8743f..1f78867bef 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -60,8 +60,19 @@ class LandedCostVoucher(Document): receipt_documents = [] for d in self.get("purchase_receipts"): - if frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus") != 1: - frappe.throw(_("Receipt document must be submitted")) + doc_data = frappe.db.get_values( + d.receipt_document_type, + d.receipt_document, + ["docstatus", "update_stock"], + as_dict=1 + )[0] + if doc_data.get("docstatus") != 1: + msg = f"Row {d.idx}: Receipt Document {frappe.bold(d.receipt_document)} must be submitted" + frappe.throw(_(msg), title=_("Invalid Document")) + elif d.receipt_document_type == "Purchase Invoice" and not doc_data.get("update_stock"): + msg = _(f"Row {d.idx}: Purchase Invoice {frappe.bold(d.receipt_document)} has no stock impact.") + msg += "
    " + _("Please create Landed Cost Vouchers against Invoices with 'Update Stock' enabled.") + frappe.throw(msg, title=_("Incorrect Invoice")) else: receipt_documents.append(d.receipt_document) From 15b336df28c6a8746bea4edb362400bd6c1f8c3d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 14:45:33 +0530 Subject: [PATCH 1163/1264] fix: Test cases --- erpnext/erpnext_integrations/utils.py | 2 +- erpnext/hr/doctype/expense_claim/test_expense_claim.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index b764701103..a5e162f8b5 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -68,7 +68,7 @@ def create_mode_of_payment(gateway, payment_type="General"): mode_of_payment.insert(ignore_permissions=True) return mode_of_payment - else: + elif mode_of_payment: return frappe.get_doc("Mode of Payment", mode_of_payment) def get_tracking_url(carrier, tracking_number): diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 578eccf787..141561fcdc 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -72,7 +72,8 @@ class TestExpenseClaim(unittest.TestCase): def test_expense_claim_gl_entry(self): payable_account = get_payable_account(company_name) taxes = generate_taxes() - expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", do_not_submit=True, taxes=taxes) + expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", + do_not_submit=True, taxes=taxes) expense_claim.submit() gl_entries = frappe.db.sql("""select account, debit, credit @@ -145,7 +146,7 @@ def generate_taxes(): parent_account = frappe.db.get_value('Account', {'company': company_name, 'is_group':1, 'account_type': 'Tax'}, 'name') - account = create_account(company=company_name, account_name="CGST", account_type="Tax", parent_account=parent_account) + account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account) return {'taxes':[{ "account_head": account, "rate": 0, From 9b6d9a41f4a66fefcb02e7ec0c4f3a4100c4d3ac Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 17:08:27 +0530 Subject: [PATCH 1164/1264] fix: Test Cases --- .../doctype/mpesa_settings/test_mpesa_settings.py | 1 + erpnext/hr/doctype/expense_claim/test_expense_claim.py | 2 +- erpnext/setup/setup_wizard/operations/install_fixtures.py | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index f592c180a3..b0e662d3f3 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -204,6 +204,7 @@ def create_mpesa_settings(payment_gateway_name="Express"): doc = frappe.get_doc(dict( #nosec doctype="Mpesa Settings", + sandbox=1, payment_gateway_name=payment_gateway_name, consumer_key="5sMu9LVI1oS3oBGPJfh3JyvLHwZOdTKn", consumer_secret="VI1oS3oBGPJfh3JyvLHw", diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 141561fcdc..96ea686706 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -83,7 +83,7 @@ class TestExpenseClaim(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - ['CGST - _TC4',18.0, 0.0], + ['Output Tax CGST - _TC4',18.0, 0.0], [payable_account, 0.0, 218.0], ["Travel Expenses - _TC4", 200.0, 0.0] ]) diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 7dfb9f4d3c..3dcb63867c 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -449,6 +449,8 @@ def install_defaults(args=None): set_active_domains(args) update_stock_settings() update_shopping_cart_settings(args) + + args.update({"set_default": 1}) create_bank_account(args) def set_global_defaults(args): @@ -499,7 +501,10 @@ def create_bank_account(args): try: doc = bank_account.insert() - frappe.db.set_value("Company", args.get('company_name'), "default_bank_account", bank_account.name, update_modified=False) + if args.get('set_default'): + frappe.db.set_value("Company", args.get('company_name'), "default_bank_account", bank_account.name, update_modified=False) + + return doc except RootNotEditable: frappe.throw(_("Bank account cannot be named as {0}").format(args.get('bank_account'))) From fa9e67502cf538e56b42e89e03878a9766b034f6 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 5 Jul 2021 20:23:00 +0530 Subject: [PATCH 1165/1264] chore: Test for backdated reco qty reposting --- .../test_stock_reconciliation.py | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 36380b838b..f7b243221a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, unittest -from frappe.utils import flt, nowdate, nowtime +from frappe.utils import flt, nowdate, nowtime, add_days from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items @@ -14,6 +14,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class TestStockReconciliation(unittest.TestCase): @classmethod @@ -204,6 +205,74 @@ class TestStockReconciliation(unittest.TestCase): self.assertEqual(sr.get("items")[0].valuation_rate, 0) self.assertEqual(sr.get("items")[0].amount, 0) + def test_backdated_stock_reco_qty_reposting(self): + """ + Test if a backdated stock reco recalculates future qty until next reco. + ------------------------------------------- + Var | Doc | Qty | Balance + ------------------------------------------- + SR5 | Reco | 0 | 8 (posting date: today-4) [backdated] + PR1 | PR | 10 | 18 (posting date: today-3) + PR2 | PR | 1 | 19 (posting date: today-2) + SR4 | Reco | 0 | 6 (posting date: today-1) [backdated] + PR3 | PR | 1 | 7 (posting date: today) # can't post future PR + """ + item_code = "Backdated-Reco-Item" + warehouse = "_Test Warehouse - _TC" + create_item(item_code) + + pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100, + posting_date=add_days(nowdate(), -3)) + pr2 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100, + posting_date=add_days(nowdate(), -2)) + pr3 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100, + posting_date=nowdate()) + + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 10) + self.assertEqual(pr3_balance, 12) + + # post backdated stock reco in between + sr4 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=6, rate=100, + posting_date=add_days(nowdate(), -1)) + pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr3_balance, 7) + + # post backdated stock reco at the start + sr5 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=8, rate=100, + posting_date=add_days(nowdate(), -4)) + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, + "qty_after_transaction") + sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 18) + self.assertEqual(pr2_balance, 19) + self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected + + # cancel backdated stock reco and check future impact + sr5.cancel() + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, + "qty_after_transaction") + sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 10) + self.assertEqual(pr2_balance, 11) + self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected + + # teardown + sr4.cancel() + pr3.cancel() + pr2.cancel() + pr1.cancel() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 57d06a86f8b2ab481cf5c2cb6d8a3f7c40c05ec5 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 5 Jul 2021 21:56:03 +0530 Subject: [PATCH 1166/1264] chore: Test to block backdated reco causing future scarcity --- .../test_stock_reconciliation.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index cd891c0ed6..84cdc49128 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -309,6 +309,49 @@ class TestStockReconciliation(unittest.TestCase): pr2.cancel() pr1.cancel() + def test_backdated_stock_reco_future_negative_stock(self): + """ + Test if a backdated stock reco causes future negative stock and is blocked. + ------------------------------------------- + Var | Doc | Qty | Balance + ------------------------------------------- + PR1 | PR | 10 | 10 (posting date: today-2) + SR3 | Reco | 0 | 1 (posting date: today-1) [backdated & blocked] + DN2 | DN | -2 | 8(-1) (posting date: today) + """ + from erpnext.stock.stock_ledger import NegativeStockError + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item_code = "Backdated-Reco-Item" + warehouse = "_Test Warehouse - _TC" + create_item(item_code) + + negative_stock_setting = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0) + + pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100, + posting_date=add_days(nowdate(), -2)) + dn2 = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=2, rate=120, + posting_date=nowdate()) + + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + dn2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn2.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 10) + self.assertEqual(dn2_balance, 8) + + # check if stock reco is blocked + sr3 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=1, rate=100, + posting_date=add_days(nowdate(), -1), do_not_submit=True) + self.assertRaises(NegativeStockError, sr3.submit) + + # teardown + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting) + sr3.cancel() + dn2.cancel() + pr1.cancel() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 842674ce79c8f2130d60b90b05afd9feb9c05bb0 Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Tue, 6 Jul 2021 13:34:32 +0530 Subject: [PATCH 1167/1264] fix: Added a message to enable appontment booking if disabled (#26334) --- erpnext/www/book_appointment/index.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py index 7bfac89f30..ccfa97bc62 100644 --- a/erpnext/www/book_appointment/index.py +++ b/erpnext/www/book_appointment/index.py @@ -2,6 +2,7 @@ import frappe import datetime import json import pytz +from frappe import _ WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] @@ -14,7 +15,8 @@ def get_context(context): if is_enabled: return context else: - frappe.local.flags.redirect_location = '/404' + frappe.redirect_to_message(_("Appointment Scheduling Disabled"), _("Appointment Scheduling has been disabled for this site"), + http_status_code=302, indicator_color="red") raise frappe.Redirect @frappe.whitelist(allow_guest=True) From f0b62f70d5bfdfe1d29d286122368d0d988cafc4 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 6 Jul 2021 13:36:23 +0530 Subject: [PATCH 1168/1264] fix: payroll-entry minor fix --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 36e728fc99..388a44d895 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -686,7 +686,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): if filters.start_date and filters.end_date: employee_list = get_employee_list(filters) - emp = filters.get('employees') + emp = filters.get('employees') or [] include_employees = [employee.employee for employee in employee_list if employee.employee not in emp] filters.pop('start_date') filters.pop('end_date') From c8eca8a448de4e4654c16141fc61f047d563cf55 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 6 Jul 2021 14:37:57 +0530 Subject: [PATCH 1169/1264] fix: remove cancelled entries in consolidated financial statements (#26331) --- .../consolidated_financial_statement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 7793af737f..56a67bb098 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -380,7 +380,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company, gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency, acc.account_name, acc.account_number - from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s + from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0 {additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions), { From 7f794cc0ea062ed8c3a799400cd9d2b044a164af Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 6 Jul 2021 17:58:44 +0530 Subject: [PATCH 1170/1264] fix: Purchase Invoice advance test case --- .../purchase_invoice/test_purchase_invoice.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2f5d36c8fa..311745d3cd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1010,21 +1010,21 @@ class TestPurchaseInvoice(unittest.TestCase): # Check GLE for Purchase Invoice # Zero net effect on final TDS Payable on invoice expected_gle = [ - ['_Test Account Cost for Goods Sold - _TC', 30000, 0], - ['_Test Account Excise Duty - _TC', 0, 3000], - ['Creditors - _TC', 0, 27000], - ['TDS Payable - _TC', 3000, 3000] + ['_Test Account Cost for Goods Sold - _TC', 30000], + ['_Test Account Excise Duty - _TC', -3000], + ['Creditors - _TC', -27000], + ['TDS Payable - _TC', 0] ] - gl_entries = frappe.db.sql("""select account, debit, credit + gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + group by account order by account asc""", (purchase_invoice.name), as_dict=1) for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.debit) - self.assertEqual(expected_gle[i][2], gle.credit) + self.assertEqual(expected_gle[i][1], gle.amount) def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year From 5e99aa7f65ea424159b6122b50064023427f89c8 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 6 Jul 2021 18:00:35 +0530 Subject: [PATCH 1171/1264] fix: stock_rbnb not defined (#26354) --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e488b695b5..82c87a83a5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -386,6 +386,7 @@ class PurchaseReceipt(BuyingController): against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked + stock_rbnb = self.get_company_default("stock_received_but_not_billed") i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): From 422325bb74ff39f6e4ebe7367d57ecf3622d56b2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 6 Jul 2021 18:09:21 +0530 Subject: [PATCH 1172/1264] test: fetching of previous sle (#26352) --- .../test_stock_ledger_entry.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index ba31ad7b06..af2ada8c9a 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -54,7 +54,7 @@ class TestStockLedgerEntry(unittest.TestCase): ) # _Test Item for Reposting transferred from Stores to FG warehouse on 30-04-2020 - make_stock_entry( + se = make_stock_entry( item_code="_Test Item for Reposting", source="Stores - _TC", target="Finished Goods - _TC", @@ -64,29 +64,29 @@ class TestStockLedgerEntry(unittest.TestCase): posting_date='2020-04-30', posting_time='14:00' ) - target_wh_sle = get_previous_sle({ + target_wh_sle = frappe.db.get_value('Stock Ledger Entry', { "item_code": "_Test Item for Reposting", "warehouse": "Finished Goods - _TC", - "posting_date": '2020-04-30', - "posting_time": '14:00' - }) + "voucher_type": "Stock Entry", + "voucher_no": se.name + }, ["valuation_rate"], as_dict=1) self.assertEqual(target_wh_sle.get("valuation_rate"), 150) # Repack entry on 5-5-2020 repack = create_repack_entry(company=company, posting_date='2020-05-05', posting_time='14:00') - finished_item_sle = get_previous_sle({ + finished_item_sle = frappe.db.get_value('Stock Ledger Entry', { "item_code": "_Test Finished Item for Reposting", "warehouse": "Finished Goods - _TC", - "posting_date": '2020-05-05', - "posting_time": '14:00' - }) + "voucher_type": "Stock Entry", + "voucher_no": repack.name + }, ["incoming_rate", "valuation_rate"], as_dict=1) self.assertEqual(finished_item_sle.get("incoming_rate"), 540) self.assertEqual(finished_item_sle.get("valuation_rate"), 540) # Reconciliation for _Test Item for Reposting at Stores on 12-04-2020: Qty = 50, Rate = 150 - create_stock_reconciliation( + sr = create_stock_reconciliation( item_code="_Test Item for Reposting", warehouse="Stores - _TC", qty=50, @@ -109,12 +109,12 @@ class TestStockLedgerEntry(unittest.TestCase): self.assertEqual(target_wh_sle.get("valuation_rate"), 175) # Check valuation rate of repacked item after back-dated entry at Stores - finished_item_sle = get_previous_sle({ + finished_item_sle = frappe.db.get_value('Stock Ledger Entry', { "item_code": "_Test Finished Item for Reposting", "warehouse": "Finished Goods - _TC", - "posting_date": '2020-05-05', - "posting_time": '14:00' - }) + "voucher_type": "Stock Entry", + "voucher_no": repack.name + }, ["incoming_rate", "valuation_rate"], as_dict=1) self.assertEqual(finished_item_sle.get("incoming_rate"), 790) self.assertEqual(finished_item_sle.get("valuation_rate"), 790) From 0bd190b88592faeae6783fa7cfd9f9b70bd93fb4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 6 Jul 2021 14:24:42 +0530 Subject: [PATCH 1173/1264] fix: stock entry with putaway rule not working --- erpnext/stock/doctype/putaway_rule/putaway_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py index ea26caced0..0f50bcd6ea 100644 --- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py @@ -97,7 +97,7 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None): at_capacity, rules = get_ordered_putaway_rules(item_code, company, source_warehouse=source_warehouse) if not rules: - warehouse = source_warehouse or item.warehouse + warehouse = source_warehouse or item.get('warehouse') if at_capacity: # rules available, but no free space items_not_accomodated.append([item_code, pending_qty]) From 00f90c50c0577d86c530197810dfca28c683345b Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 7 Jul 2021 12:10:02 +0530 Subject: [PATCH 1174/1264] chore: add product listing link in settings (#26026) * chore: add product listing link in settings * chore: add icon in workspace card Co-authored-by: Ankush --- .../workspace/erpnext_settings/erpnext_settings.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 014f4095c1..6ca3d637da 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -11,10 +11,11 @@ "hide_custom": 0, "icon": "settings", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "ERPNext Settings", "links": [], - "modified": "2020-12-01 13:38:37.759596", + "modified": "2021-06-12 01:58:11.399566", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", @@ -109,6 +110,13 @@ "label": "Domain Settings", "link_to": "Domain Settings", "type": "DocType" + }, + { + "doc_view": "", + "icon": "retail", + "label": "Products Settings", + "link_to": "Products Settings", + "type": "DocType" } ] -} \ No newline at end of file +} From 8f945a9852281083a0861f2fdb193112fb7bb236 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 8 Jul 2021 13:05:14 +0530 Subject: [PATCH 1175/1264] fix: Removed un-used flag --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 388a44d895..13cc423fc2 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -117,7 +117,6 @@ class PayrollEntry(Document): Creates salary slip for selected employees if already not created """ self.check_permission('write') - self.created = 1 employees = [emp.employee for emp in self.employees] if employees: args = frappe._dict({ From a82e9e42e1746d42ecf32b6ff4ee7e3fb7823d62 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 8 Jul 2021 17:25:37 +0530 Subject: [PATCH 1176/1264] fix: query for training Event --- erpnext/hr/doctype/training_event/training_event.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js index b7d34b178a..a20f0b70af 100644 --- a/erpnext/hr/doctype/training_event/training_event.js +++ b/erpnext/hr/doctype/training_event/training_event.js @@ -34,7 +34,8 @@ frappe.ui.form.on("Training Event Employee", { frm.set_query("employee", "employees", function () { return { filters: { - name: ["NOT IN", emp] + name: ["NOT IN", emp], + status: "Active" } }; }); From 257cbd3b92b8fd78855b9c6ae98c40c5708f5fe7 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Thu, 8 Jul 2021 18:44:30 +0530 Subject: [PATCH 1177/1264] fix: track changes on batch (#26382) --- erpnext/stock/doctype/batch/batch.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json index e6d2e1330b..fc4cf1dbdb 100644 --- a/erpnext/stock/doctype/batch/batch.json +++ b/erpnext/stock/doctype/batch/batch.json @@ -193,7 +193,7 @@ "image_field": "image", "links": [], "max_attachments": 5, - "modified": "2021-01-07 11:10:09.149170", + "modified": "2021-07-08 16:22:01.343105", "modified_by": "Administrator", "module": "Stock", "name": "Batch", @@ -217,5 +217,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "batch_id" + "title_field": "batch_id", + "track_changes": 1 } \ No newline at end of file From 3888488b3628df39c152fb48c3a8b17b3af6cc35 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 8 Jul 2021 19:27:53 +0530 Subject: [PATCH 1178/1264] fix: precision for expected values in payment entry test --- .../accounts/doctype/payment_entry/test_payment_entry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 4641d6b5ff..d1302f5ae7 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -589,9 +589,9 @@ class TestPaymentEntry(unittest.TestCase): party_account_balance = get_balance_on(account=pe.paid_from, cost_center=pe.cost_center) 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) + self.assertEqual(flt(expected_account_balance), account_balance) + self.assertEqual(flt(expected_party_balance), party_balance) + self.assertEqual(flt(expected_party_account_balance), party_account_balance) def create_payment_terms_template(): From 8f3c7ab4029dafb1d5e1c3384eb48d02b50f18a1 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 9 Jul 2021 10:35:55 +0530 Subject: [PATCH 1179/1264] fix: escape quotes while fetching customer emails (#26329) (#26376) --- .../process_statement_of_accounts.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 0b0ee904ff..500952e38a 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -207,10 +207,9 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): @frappe.whitelist() def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True): billing_email = frappe.db.sql(""" - SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \ - WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \ - c.is_billing_contact=1 \ - order by c.creation desc""") + SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent + WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1 + order by c.creation desc""", customer_name) if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: From bf462abb00798a73549e71f1113ee9ed8a8b18f0 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 9 Jul 2021 13:06:38 +0530 Subject: [PATCH 1180/1264] fix: Rename function and tweak logic - Dont validate PI on `else` --- .../landed_cost_voucher.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 1f78867bef..bf969f99f8 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -41,7 +41,7 @@ class LandedCostVoucher(Document): def validate(self): self.check_mandatory() - self.validate_purchase_receipts() + self.validate_receipt_documents() init_landed_taxes_and_totals(self) self.set_total_taxes_and_charges() if not self.get("items"): @@ -56,25 +56,23 @@ class LandedCostVoucher(Document): frappe.throw(_("Please enter Receipt Document")) - def validate_purchase_receipts(self): + def validate_receipt_documents(self): receipt_documents = [] for d in self.get("purchase_receipts"): - doc_data = frappe.db.get_values( - d.receipt_document_type, - d.receipt_document, - ["docstatus", "update_stock"], - as_dict=1 - )[0] - if doc_data.get("docstatus") != 1: - msg = f"Row {d.idx}: Receipt Document {frappe.bold(d.receipt_document)} must be submitted" + docstatus = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus") + if docstatus != 1: + msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted" frappe.throw(_(msg), title=_("Invalid Document")) - elif d.receipt_document_type == "Purchase Invoice" and not doc_data.get("update_stock"): - msg = _(f"Row {d.idx}: Purchase Invoice {frappe.bold(d.receipt_document)} has no stock impact.") - msg += "
    " + _("Please create Landed Cost Vouchers against Invoices with 'Update Stock' enabled.") - frappe.throw(msg, title=_("Incorrect Invoice")) - else: - receipt_documents.append(d.receipt_document) + + if d.receipt_document_type == "Purchase Invoice": + update_stock = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "update_stock") + if not update_stock: + msg = _("Row {0}: Purchase Invoice {1} has no stock impact.").format(d.idx, frappe.bold(d.receipt_document)) + msg += "
    " + _("Please create Landed Cost Vouchers against Invoices that have 'Update Stock' enabled.") + frappe.throw(msg, title=_("Incorrect Invoice")) + + receipt_documents.append(d.receipt_document) for item in self.get("items"): if not item.receipt_document: From d53991857c59ab41888e9fe0f28964f346f84ec7 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 9 Jul 2021 14:33:00 +0530 Subject: [PATCH 1181/1264] fix: Fixed Budget Variance Graph color from all black to default (#26368) --- .../report/budget_variance_report/budget_variance_report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 9c9ada871c..f1b231b690 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -397,6 +397,7 @@ def get_chart_data(filters, columns, data): {'name': 'Budget', 'chartType': 'bar', 'values': budget_values}, {'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values} ] - } + }, + 'type' : 'bar' } From 9ac63da457ec7e168e3a8862bdb0a15dbf149902 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 9 Jul 2021 14:35:11 +0530 Subject: [PATCH 1182/1264] fix: value fetching for custom field in POS (#26367) --- erpnext/selling/page/point_of_sale/pos_payment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index c484873d3e..f1a166b523 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -56,7 +56,7 @@ erpnext.PointOfSale.Payment = class { ); let df_events = { onchange: function() { - frm.set_value(this.df.fieldname, this.value); + frm.set_value(this.df.fieldname, this.get_value()); } }; if (df.fieldtype == "Button") { From 8e8434a78a66178652bdb850c582399b51e22382 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 9 Jul 2021 15:32:28 +0530 Subject: [PATCH 1183/1264] fix: omit item discount amount for e-invoicing (#26353) (#26407) --- erpnext/regional/india/e_invoice/einvoice.js | 4 +++- erpnext/regional/india/e_invoice/utils.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 23d4fe9030..8ad30fa910 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -1,6 +1,8 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.ui.form.on(doctype, { async refresh(frm) { + if (frm.doc.docstatus == 2) return; + const res = await frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility', args: { doc: frm.doc } @@ -111,7 +113,7 @@ erpnext.setup_einvoice_actions = (doctype) => { if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { const action = () => { - let message = __('Cancellation of e-way bill is currently not supported. '); + let message = __('Cancellation of e-way bill is currently not supported.') + ' '; message += '

    '; message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 11ebef724c..405b10ff54 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -188,9 +188,10 @@ def get_item_list(invoice): item.qty = abs(item.qty) - item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty) - item.gross_amount = abs(item.taxable_value) + item.discount_amount + item.unit_rate = abs(item.taxable_value / item.qty) + item.gross_amount = abs(item.taxable_value) item.taxable_value = abs(item.taxable_value) + item.discount_amount = 0 item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None From fe4f58d0f62fe2b9b05e112da3d8e5f85676fe6f Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 9 Jul 2021 15:32:54 +0530 Subject: [PATCH 1184/1264] fix(e-invoicing): allow export invoice even if no taxes applied (#26405) --- erpnext/regional/india/e_invoice/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 405b10ff54..ea600d9097 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -42,7 +42,10 @@ def validate_eligibility(doc): invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') - no_taxes_applied = not doc.get('taxes') + + # if export invoice, then taxes can be empty + # invoice can only be ineligible if no taxes applied and is not an export invoice + no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas' has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst')) if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: From 13d70434510f19a422f41ee77f4e3d39f13bde05 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 9 Jul 2021 15:33:14 +0530 Subject: [PATCH 1185/1264] fix: column 'outstanding_amount' cannot be null (#26404) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index adaf99a790..0c21aae944 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1318,9 +1318,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre return frappe._dict({ "due_date": ref_doc.get("due_date"), - "total_amount": total_amount, - "outstanding_amount": outstanding_amount, - "exchange_rate": exchange_rate, + "total_amount": flt(total_amount), + "outstanding_amount": flt(outstanding_amount), + "exchange_rate": flt(exchange_rate), "bill_no": bill_no }) From 38fa3a3f8927abf7242481d61c2d72e6879af608 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 18:04:24 +0530 Subject: [PATCH 1186/1264] fix: Unallocated amount in Payment Entry after taxes --- .../doctype/payment_entry/payment_entry.py | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index adaf99a790..889c59762c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -404,9 +404,15 @@ class PaymentEntry(AccountsController): if not self.advance_tax_account: frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction")) - reference_doclist = [] net_total = self.paid_amount - included_in_paid_amount = 0 + + for reference in self.get("references"): + net_total_for_tds = 0 + if reference.reference_doctype == 'Purchase Order': + net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total')) + + if net_total_for_tds: + net_total = net_total_for_tds # Adding args as purchase invoice to get TDS amount args = frappe._dict({ @@ -423,7 +429,6 @@ class PaymentEntry(AccountsController): return tax_withholding_details.update({ - 'included_in_paid_amount': included_in_paid_amount, 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) }) @@ -509,18 +514,17 @@ class PaymentEntry(AccountsController): self.base_total_allocated_amount = abs(base_total_allocated_amount) def set_unallocated_amount(self): - self.unallocated_amount = 0 if self.party: total_deductions = sum(flt(d.amount) for d in self.get("deductions")) if self.payment_type == "Receive" \ - and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \ - and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate): - self.unallocated_amount = (self.received_amount_after_tax + total_deductions - + and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ + and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): + self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate elif self.payment_type == "Pay" \ - and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \ - and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate): - self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions + + and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ + and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): + self.unallocated_amount = (self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate def set_difference_amount(self): @@ -530,11 +534,11 @@ class PaymentEntry(AccountsController): base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount) if self.payment_type == "Receive": - self.difference_amount = base_party_amount - self.base_received_amount_after_tax + self.difference_amount = base_party_amount - self.base_received_amount elif self.payment_type == "Pay": - self.difference_amount = self.base_paid_amount_after_tax - base_party_amount + self.difference_amount = self.base_paid_amount - base_party_amount else: - self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax) + self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) total_deductions = sum(flt(d.amount) for d in self.get("deductions")) @@ -683,8 +687,8 @@ class PaymentEntry(AccountsController): "account": self.paid_from, "account_currency": self.paid_from_account_currency, "against": self.party if self.payment_type=="Pay" else self.paid_to, - "credit_in_account_currency": self.paid_amount_after_tax, - "credit": self.base_paid_amount_after_tax, + "credit_in_account_currency": self.paid_amount, + "credit": self.base_paid_amount, "cost_center": self.cost_center }, item=self) ) @@ -694,8 +698,8 @@ class PaymentEntry(AccountsController): "account": self.paid_to, "account_currency": self.paid_to_account_currency, "against": self.party if self.payment_type=="Receive" else self.paid_from, - "debit_in_account_currency": self.received_amount_after_tax, - "debit": self.base_received_amount_after_tax, + "debit_in_account_currency": self.received_amount, + "debit": self.base_received_amount, "cost_center": self.cost_center }, item=self) ) @@ -708,15 +712,17 @@ class PaymentEntry(AccountsController): if self.payment_type in ('Pay', 'Internal Transfer'): dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" + against = self.party or self.paid_from elif self.payment_type == 'Receive': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" + against = self.party or self.paid_to payment_or_advance_account = self.get_party_account_for_taxes() gl_entries.append( self.get_gl_dict({ "account": d.account_head, - "against": self.party if self.payment_type=="Receive" else self.paid_from, + "against": against, dr_or_cr: d.base_tax_amount, dr_or_cr + "_in_account_currency": d.base_tax_amount if account_currency==self.company_currency @@ -728,14 +734,12 @@ class PaymentEntry(AccountsController): gl_entries.append( self.get_gl_dict({ "account": payment_or_advance_account, - "against": self.party if self.payment_type=="Receive" else self.paid_from, + "against": against, dr_or_cr: -1 * d.base_tax_amount, dr_or_cr + "_in_account_currency": -1*d.base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, - "party_type": self.party_type, - "party": self.party }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): @@ -760,9 +764,9 @@ class PaymentEntry(AccountsController): if self.advance_tax_account: return self.advance_tax_account elif self.payment_type == 'Receive': - return self.paid_from - elif self.payment_type in ('Pay', 'Internal Transfer'): return self.paid_to + elif self.payment_type in ('Pay', 'Internal Transfer'): + return self.paid_from def update_advance_paid(self): if self.payment_type in ("Receive", "Pay") and self.party: @@ -1634,12 +1638,6 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta if dt == "Employee Advance": paid_amount = received_amount * doc.get('exchange_rate', 1) - if dt == "Purchase Order" and doc.apply_tds: - if party_account_currency == bank.account_currency: - paid_amount = received_amount = doc.base_net_total - else: - paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1) - return paid_amount, received_amount def apply_early_payment_discount(paid_amount, received_amount, doc): From 171ee515074e183ff3a59c120ea29b2ea84b96e9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 18:52:12 +0530 Subject: [PATCH 1187/1264] fix: Hide amount after tax fields --- .../accounts/doctype/payment_entry/payment_entry.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 51f18a5a4e..6f362c1fbb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -667,6 +667,7 @@ { "fieldname": "base_paid_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Paid Amount After Tax (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 @@ -693,21 +694,25 @@ "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'", "fieldname": "received_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Received Amount After Tax", - "options": "paid_to_account_currency" + "options": "paid_to_account_currency", + "read_only": 1 }, { "depends_on": "doc.received_amount", "fieldname": "base_received_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Received Amount After Tax (Company Currency)", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-22 20:37:06.154206", + "modified": "2021-07-09 08:58:15.008761", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From c13ac4ab11495b5067e41355c14140dc19c8782c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 20:00:55 +0530 Subject: [PATCH 1188/1264] fix: Remove unintentional changes --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 889c59762c..e3dbc22ca0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -521,6 +521,7 @@ class PaymentEntry(AccountsController): and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate + print(self.unallocated_amount, "#@#@#@#@#") elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): From eae7c1891fa434f380487e7f51db68a92b04596d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 20:08:29 +0530 Subject: [PATCH 1189/1264] fix: Remove unintentional changes --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e3dbc22ca0..85b98843ee 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -514,6 +514,7 @@ class PaymentEntry(AccountsController): self.base_total_allocated_amount = abs(base_total_allocated_amount) def set_unallocated_amount(self): + self.unallocated_amount = 0 if self.party: total_deductions = sum(flt(d.amount) for d in self.get("deductions")) if self.payment_type == "Receive" \ @@ -521,7 +522,6 @@ class PaymentEntry(AccountsController): and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate - print(self.unallocated_amount, "#@#@#@#@#") elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): From 77f2d2d01ea19c53cb3e726cbfaaa0b2fceb0eb4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Jul 2021 10:06:38 +0530 Subject: [PATCH 1190/1264] fix: Unable to download GSTR-1 json --- erpnext/regional/report/gstr_1/gstr_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 10961593e1..cfcb8c3444 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -584,7 +584,7 @@ class Gstr1Report(object): def get_json(filters, report_name, data): filters = json.loads(filters) report_data = json.loads(data) - gstin = get_company_gstin_number(filters["company"], filters["company_address"]) + gstin = get_company_gstin_number(filters.get("company"), filters.get("company_address")) fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) From c8a825c4783ebef33ed89f5cab5cdfe4d70fd326 Mon Sep 17 00:00:00 2001 From: marination Date: Sat, 10 Jul 2021 18:24:24 +0530 Subject: [PATCH 1191/1264] chore: Test case for QI Rejection in Stock Entry - Use `get_single_value` instead of `get_doc` in validation - Test Case to check impact of stock settings on SE with rejected qi --- erpnext/controllers/stock_controller.py | 4 +- .../test_quality_inspection.py | 48 +++++++++++++++++-- .../doctype/stock_entry/stock_entry_utils.py | 2 + .../stock_settings/stock_settings.json | 2 +- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1749297ce3..2526e6df0e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -395,7 +395,7 @@ class StockController(AccountsController): def validate_qi_submission(self, row): """Check if QI is submitted on row level, during submission""" - action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted or "Stop" + action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted") qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus") if not qa_docstatus == 1: @@ -408,7 +408,7 @@ class StockController(AccountsController): def validate_qi_rejection(self, row): """Check if QI is rejected on row level, during submission""" - action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_rejected or "Stop" + action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_rejected") qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status") if qa_status == "Rejected": diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 7f3d701034..f5d076a077 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -14,7 +14,7 @@ from erpnext.controllers.stock_controller import ( ) from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item -from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry # test_records = frappe.get_test_records('Quality Inspection') @@ -159,6 +159,47 @@ class TestQualityInspection(unittest.TestCase): frappe.delete_doc("Quality Inspection", qi) dn.delete() + def test_rejected_qi_validation(self): + """Test if rejected QI blocks Stock Entry as per Stock Settings.""" + se = make_stock_entry( + item_code="_Test Item with QA", + target="_Test Warehouse - _TC", + qty=1, + basic_rate=100, + inspection_required=True, + do_not_submit=True + ) + + readings = [ + { + "specification": "Iron Content", + "min_value": 0.1, + "max_value": 0.9, + "reading_1": "0.4" + } + ] + + qa = create_quality_inspection( + reference_type="Stock Entry", + reference_name=se.name, + readings=readings, + status="Rejected" + ) + + frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop") + se.reload() + self.assertRaises(QualityInspectionRejectedError, se.submit) # when blocked in Stock settings, block rejected QI + + frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Warn") + se.reload() + se.submit() # when allowed in Stock settings, allow rejected QI + + # teardown + qa.reload() + qa.cancel() + se.reload() + se.cancel() + frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop") def create_quality_inspection(**args): args = frappe._dict(args) @@ -175,12 +216,11 @@ def create_quality_inspection(**args): if not args.readings: create_quality_inspection_parameter("Size") readings = {"specification": "Size", "min_value": 0, "max_value": 10} + if args.status == "Rejected": + readings["reading_1"] = "12" # status is auto set in child on save else: readings = args.readings - if args.status == "Rejected": - readings["reading_1"] = "12" # status is auto set in child on save - if isinstance(readings, list): for entry in readings: create_quality_inspection_parameter(entry["specification"]) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py index b12a8547fe..563fcb0397 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py @@ -45,6 +45,8 @@ def make_stock_entry(**args): s.posting_date = args.posting_date if args.posting_time: s.posting_time = args.posting_time + if args.inspection_required: + s.inspection_required = args.inspection_required # map names if args.from_warehouse: diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index d07e26b536..2a9dcfb67e 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -290,7 +290,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-21 16:17:42.159829", + "modified": "2021-07-10 16:17:42.159829", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From e282effaed59e27e82875f90c6237ae4f99ba668 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Jul 2021 20:23:52 +0530 Subject: [PATCH 1192/1264] fix: Error on creation of company for India --- erpnext/regional/india/setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 5f9d5ed0d6..5ef04b66c7 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -122,10 +122,12 @@ def add_print_formats(): def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] + sales_invoice_series = frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n") + ['SINV-.YY.-', 'SRET-.YY.-', ''] + purchase_invoice_series = frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n") + ['PINV-.YY.-', 'PRET-.YY.-', ''] if not patch: - make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') - make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '') + make_property_setter('Purchase Invoice', 'naming_series', 'options', '\n'.join(purchase_invoice_series), '') make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') def make_custom_fields(update=True): From caacd0ad2c68f8dc0f14b96d904ac552c745b74a Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 12 Jul 2021 10:20:19 +0530 Subject: [PATCH 1193/1264] fix: stock levels disapperaing on refresh (bp #26305) refresh_section removes all sections with `custom` class, added different class to avoid this behaviour. # Conflicts: # erpnext/stock/doctype/item/item.js --- erpnext/stock/doctype/item/item.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 8aec89381a..b55374b8d8 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -93,7 +93,7 @@ frappe.ui.form.on("Item", { erpnext.item.edit_prices_button(frm); erpnext.item.toggle_attributes(frm); - + if (!frm.doc.is_fixed_asset) { erpnext.item.make_dashboard(frm); } @@ -381,7 +381,8 @@ $.extend(erpnext.item, { // Show Stock Levels only if is_stock_item if (frm.doc.is_stock_item) { frappe.require('assets/js/item-dashboard.min.js', function() { - const section = frm.dashboard.add_section('', __("Stock Levels")); + frm.dashboard.parent.find('.stock-levels').remove(); + const section = frm.dashboard.add_section('', __("Stock Levels"), 'stock-levels'); erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({ parent: section, item_code: frm.doc.name, From 432d8efa3d61dc489be45553ce8f2ab1da8efbb7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Jul 2021 10:47:40 +0530 Subject: [PATCH 1194/1264] fix(pos): taxes amount in pos item cart (#26411) --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 7cae0e4797..38508c219b 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -472,12 +472,7 @@ erpnext.PointOfSale.ItemCart = class { const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total; this.render_grand_total(grand_total); - const taxes = frm.doc.taxes.map(t => { - return { - description: t.description, rate: t.rate - }; - }); - this.render_taxes(frm.doc.total_taxes_and_charges, taxes); + this.render_taxes(frm.doc.taxes); } render_net_total(value) { @@ -502,14 +497,14 @@ erpnext.PointOfSale.ItemCart = class { ); } - render_taxes(value, taxes) { + render_taxes(taxes) { if (taxes.length) { const currency = this.events.get_frm().doc.currency; const taxes_html = taxes.map(t => { const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; return `
    ${description}
    -
    ${format_currency(value, currency)}
    +
    ${format_currency(t.tax_amount_after_discount_amount, currency)}
    `; }).join(''); this.$totals_section.find('.taxes-container').css('display', 'flex').html(taxes_html); From f60c3f06554206aec236690ae0ea975ddba981ff Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 12 Jul 2021 11:07:30 +0530 Subject: [PATCH 1195/1264] fix: error popup for COA errors (#26358) --- .../chart_of_accounts_importer.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 3b764aab10..4fd8413d83 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -13,7 +13,8 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file class ChartofAccountsImporter(Document): - pass + def validate(self): + validate_accounts(self.import_file) @frappe.whitelist() def validate_company(company): @@ -301,28 +302,27 @@ def validate_accounts(file_name): if account["parent_account"] and accounts_dict.get(account["parent_account"]): accounts_dict[account["parent_account"]]["is_group"] = 1 - message = validate_root(accounts_dict) - if message: return message - message = validate_account_types(accounts_dict) - if message: return message + validate_root(accounts_dict) + + validate_account_types(accounts_dict) return [True, len(accounts)] def validate_root(accounts): roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')] if len(roots) < 4: - return _("Number of root accounts cannot be less than 4") + frappe.throw(_("Number of root accounts cannot be less than 4")) error_messages = [] for account in roots: if not account.get("root_type") and account.get("account_name"): - error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name"))) + error_messages.append(_("Please enter Root Type for account- {0}").format(account.get("account_name"))) elif account.get("root_type") not in get_root_types() and account.get("account_name"): - error_messages.append("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity".format(account.get("account_name"))) + error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name"))) if error_messages: - return "
    ".join(error_messages) + frappe.throw("
    ".join(error_messages)) def get_root_types(): return ('Asset', 'Liability', 'Expense', 'Income', 'Equity') @@ -356,7 +356,7 @@ def validate_account_types(accounts): missing = list(set(account_types_for_ledger) - set(account_types)) if missing: - return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)) + frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))) account_types_for_group = ["Bank", "Cash", "Stock"] # fix logic bug @@ -364,7 +364,7 @@ def validate_account_types(accounts): missing = list(set(account_types_for_group) - set(account_groups)) if missing: - return _("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)) + frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing))) def unset_existing_data(company): linked = frappe.db.sql('''select fieldname from tabDocField From bf03671a334e65c3151d11a9c92e6d73f6edac97 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 12 Jul 2021 11:10:28 +0530 Subject: [PATCH 1196/1264] fix(report): iterate on accounts only when accounts exist (#26391) --- erpnext/accounts/report/general_ledger/general_ledger.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 744ada9e55..e724e9b51b 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -48,13 +48,12 @@ def validate_filters(filters, account_details): if not filters.get("from_date") and not filters.get("to_date"): frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) - - for account in filters.account: - if not account_details.get(account): - frappe.throw(_("Account {0} does not exists").format(account)) if filters.get('account'): filters.account = frappe.parse_json(filters.get('account')) + for account in filters.account: + if not account_details.get(account): + frappe.throw(_("Account {0} does not exists").format(account)) if (filters.get("account") and filters.get("group_by") == _('Group by Account') and account_details[filters.account].is_group == 0): From 10473b1195f8bb2d68c524a95bc66e87eea07862 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 12 Jul 2021 11:11:29 +0530 Subject: [PATCH 1197/1264] fix: dunning calculation of grand total when rate of interest is 0% (#26285) --- erpnext/accounts/doctype/dunning/dunning.py | 8 +-- .../accounts/doctype/dunning/test_dunning.py | 49 ++++++++++++++++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index c6c689212b..1ef512a489 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -25,7 +25,7 @@ class Dunning(AccountsController): 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) + self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days) if self.interest_amount != amounts.get('interest_amount'): self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount')) if self.dunning_amount != amounts.get('dunning_amount'): @@ -91,13 +91,13 @@ def resolve_dunning(doc, state): 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): +def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days): interest_amount = 0 - grand_total = 0 + grand_total = flt(outstanding_amount) + flt(dunning_fee) if rate_of_interest: interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100 interest_amount = (interest_per_year * cint(overdue_days)) / 365 - grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee) + grand_total += flt(interest_amount) dunning_amount = flt(interest_amount) + flt(dunning_fee) return { 'interest_amount': interest_amount, diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index e2d4d82e41..ed50f784b2 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -16,6 +16,7 @@ class TestDunning(unittest.TestCase): @classmethod def setUpClass(self): create_dunning_type() + create_dunning_type_with_zero_interest_rate() unlink_payment_on_cancel_of_invoice() @classmethod @@ -25,11 +26,20 @@ class TestDunning(unittest.TestCase): 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) + 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_dunning_with_zero_interest_rate(self): + dunning = create_dunning_with_zero_interest_rate() + amounts = calculate_interest_and_amount( + dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days) + self.assertEqual(round(amounts.get('interest_amount'), 2), 0) + self.assertEqual(round(amounts.get('dunning_amount'), 2), 20) + self.assertEqual(round(amounts.get('grand_total'), 2), 120) + + def test_gl_entries(self): dunning = create_dunning() dunning.submit() @@ -83,6 +93,27 @@ def create_dunning(): dunning.save() return dunning +def create_dunning_with_zero_interest_rate(): + 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 with 0% Rate of Interest') + 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 with 0% Rate of Interest' + 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' @@ -98,3 +129,19 @@ def create_dunning_type(): } ) dunning_type.save() + +def create_dunning_type_with_zero_interest_rate(): + dunning_type = frappe.new_doc("Dunning Type") + dunning_type.dunning_type = 'First Notice with 0% Rate of Interest' + dunning_type.start_day = 10 + dunning_type.end_day = 20 + dunning_type.dunning_fee = 20 + dunning_type.rate_of_interest = 0 + 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, and late fees.' + } + ) + dunning_type.save() \ No newline at end of file From 38994bd49480c55484c07e71aa52eb30ca91b485 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 12 Jul 2021 13:01:31 +0530 Subject: [PATCH 1198/1264] fix: Added Company filters for Loan (#26294) * fix: loan validations * fix: added company filter while fetching loans * fix: tests --- erpnext/loan_management/doctype/loan/loan.js | 3 +- .../loan_application/loan_application.js | 7 ++++ .../doctype/salary_slip/salary_slip.py | 1 + .../doctype/salary_slip/test_salary_slip.py | 15 +++++--- .../salary_structure/test_salary_structure.py | 37 +++++++++---------- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index 28af3a9c41..f9c201ab60 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -28,7 +28,8 @@ frappe.ui.form.on('Loan', { frm.set_query("loan_type", function () { return { "filters": { - "docstatus": 1 + "docstatus": 1, + "company": frm.doc.company } }; }); diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.js b/erpnext/loan_management/doctype/loan_application/loan_application.js index 1365274971..eccbdc3e91 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.js +++ b/erpnext/loan_management/doctype/loan_application/loan_application.js @@ -14,6 +14,13 @@ frappe.ui.form.on('Loan Application', { refresh: function(frm) { frm.trigger("toggle_fields"); frm.trigger("add_toolbar_buttons"); + frm.set_query('loan_type', () => { + return { + filters: { + company: frm.doc.company + } + }; + }); }, repayment_method: function(frm) { frm.doc.repayment_amount = frm.doc.repayment_periods = "" diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 877503b41c..bead880ef7 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1091,6 +1091,7 @@ class SalarySlip(TransactionBase): "applicant": self.employee, "docstatus": 1, "repay_from_salary": 1, + "company": self.company }) def make_loan_repayment_entry(self): diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index ce88cc3f1e..6e8d3b3f30 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -482,14 +482,19 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" - employee = frappe.db.get_value("Employee", {"user_id": user}) - salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee) + employee = frappe.db.get_value("Employee", + { + "user_id": user + }, + ["name", "company", "employee_name"], + as_dict=True) + + salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee.name, company=employee.company) salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}) if not salary_slip_name: - salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee) - salary_slip.employee_name = frappe.get_value("Employee", - {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name") + salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee.name) + salary_slip.employee_name = employee.employee_name salary_slip.payroll_frequency = payroll_frequency salary_slip.posting_date = nowdate() salary_slip.insert() diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index e7d123c996..3957d834d3 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -119,26 +119,25 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, if test_tax: frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) - if not frappe.db.exists('Salary Structure', salary_structure): - details = { - "doctype": "Salary Structure", - "name": salary_structure, - "company": company or erpnext.get_default_company(), - "earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), - "deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), - "payroll_frequency": payroll_frequency, - "payment_account": get_random("Account", filters={'account_currency': currency}), - "currency": currency - } - if other_details and isinstance(other_details, dict): - details.update(other_details) - salary_structure_doc = frappe.get_doc(details) - salary_structure_doc.insert() - if not dont_submit: - salary_structure_doc.submit() + if frappe.db.exists("Salary Structure", salary_structure): + frappe.db.delete("Salary Structure", salary_structure) - else: - salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure) + details = { + "doctype": "Salary Structure", + "name": salary_structure, + "company": company or erpnext.get_default_company(), + "earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), + "deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), + "payroll_frequency": payroll_frequency, + "payment_account": get_random("Account", filters={'account_currency': currency}), + "currency": currency + } + if other_details and isinstance(other_details, dict): + details.update(other_details) + salary_structure_doc = frappe.get_doc(details) + salary_structure_doc.insert() + if not dont_submit: + salary_structure_doc.submit() filters = {'employee':employee, 'docstatus': 1} if not from_date and payroll_period: From 45e6cffa4f9eacd4d299e4a5bf220b970a6c9105 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 12 Jul 2021 13:24:43 +0530 Subject: [PATCH 1199/1264] refactor: Optimized code for reposting item valuation --- .../stock/doctype/stock_entry/stock_entry.py | 2 +- .../stock_ledger_entry/stock_ledger_entry.py | 1 + erpnext/stock/stock_ledger.py | 61 +++++++++++++++---- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8f27ef4356..90b81ddb1d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -529,7 +529,7 @@ class StockEntry(StockController): scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item]) # Get raw materials cost from BOM if multiple material consumption entries - if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"): + if frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True): bom_items = self.get_bom_raw_materials(finished_item_qty) outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()]) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 0febcb6891..cb939e63c2 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -178,3 +178,4 @@ def on_doctype_update(): frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"]) + frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"]) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 4e9c7689ae..c15d1eda7d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -6,13 +6,14 @@ import frappe import erpnext import copy from frappe import _ -from frappe.utils import cint, flt, cstr, now, get_link_to_form +from frappe.utils import cint, flt, cstr, now, get_link_to_form, getdate from frappe.model.meta import get_field_precision from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel from erpnext.stock.utils import get_bin import json from six import iteritems + # future reposting class NegativeStockError(frappe.ValidationError): pass class SerialNoExistsInFutureTransaction(frappe.ValidationError): @@ -130,7 +131,13 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat if not args and voucher_type and voucher_no: args = get_args_for_voucher(voucher_type, voucher_no) - distinct_item_warehouses = [(d.item_code, d.warehouse) for d in args] + distinct_item_warehouses = {} + for i, d in enumerate(args): + distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({ + "reposting_status": False, + "sle": d, + "args_idx": i + })) i = 0 while i < len(args): @@ -139,13 +146,21 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat "warehouse": args[i].warehouse, "posting_date": args[i].posting_date, "posting_time": args[i].posting_time, - "creation": args[i].get("creation") + "creation": args[i].get("creation"), + "distinct_item_warehouses": distinct_item_warehouses }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - for item_wh, new_sle in iteritems(obj.new_items): - if item_wh not in distinct_item_warehouses: - args.append(new_sle) + distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True + if obj.new_items_found: + for item_wh, data in iteritems(distinct_item_warehouses): + if ('args_idx' not in data and not data.reposting_status) or (data.sle_changed and data.reposting_status): + data.args_idx = len(args) + args.append(data.sle) + elif data.sle_changed and not data.reposting_status: + args[data.args_idx] = data.sle + + data.sle_changed = False i += 1 def get_args_for_voucher(voucher_type, voucher_no): @@ -186,11 +201,12 @@ class update_entries_after(object): self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") self.get_precision() self.valuation_method = get_valuation_method(self.item_code) - self.new_items = {} + + self.new_items_found = False + self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict()) self.data = frappe._dict() self.initialize_previous_data(self.args) - self.build() def get_precision(self): @@ -296,11 +312,29 @@ class update_entries_after(object): elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse: return entries_to_fix elif dependant_sle.item_code != self.item_code: - if (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items: - self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle + self.update_distinct_item_warehouses(dependant_sle) return entries_to_fix elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data: return entries_to_fix + else: + return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix) + + def update_distinct_item_warehouses(self, dependant_sle): + key = (dependant_sle.item_code, dependant_sle.warehouse) + val = frappe._dict({ + "sle": dependant_sle + }) + if key not in self.distinct_item_warehouses: + self.distinct_item_warehouses[key] = val + self.new_items_found = True + else: + existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date") + if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date): + val.sle_changed = True + self.distinct_item_warehouses[key] = val + self.new_items_found = True + + def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix): self.initialize_previous_data(dependant_sle) args = self.data[dependant_sle.warehouse].previous_sle \ @@ -393,6 +427,7 @@ class update_entries_after(object): rate = 0 # Material Transfer, Repack, Manufacturing if sle.voucher_type == "Stock Entry": + self.recalculate_amounts_in_stock_entry(sle.voucher_no) rate = frappe.db.get_value("Stock Entry Detail", sle.voucher_detail_no, "valuation_rate") # Sales and Purchase Return elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"): @@ -442,7 +477,11 @@ class update_entries_after(object): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True) + if not sle.dependant_sle_voucher_detail_no: + self.recalculate_amounts_in_stock_entry(sle.voucher_no) + + def recalculate_amounts_in_stock_entry(self, voucher_no): + stock_entry = frappe.get_doc("Stock Entry", voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: From b75b556bbbe3760d3bcd75c378cb081f349836e2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Jul 2021 14:32:37 +0530 Subject: [PATCH 1200/1264] fix: move the rename abbreviation job to long queue (#26435) --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 915e6a4f31..36a7d20a8f 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -395,7 +395,7 @@ class Company(NestedSet): @frappe.whitelist() def enqueue_replace_abbr(company, old, new): - kwargs = dict(company=company, old=old, new=new) + kwargs = dict(queue="long", company=company, old=old, new=new) frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs) From 1298956482515f6067781f7eb0b404fa25f512a9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Jul 2021 18:29:52 +0530 Subject: [PATCH 1201/1264] fix: Use update flag for company dependant fixtures --- erpnext/regional/india/setup.py | 11 +++++++---- erpnext/setup/doctype/company/company.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 5ef04b66c7..92654608da 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -12,7 +12,10 @@ from erpnext.accounts.utils import get_fiscal_year, FiscalYearError from frappe.utils import today def setup(company=None, patch=True): - setup_company_independent_fixtures(patch=patch) + # Company independent fixtures should be called only once at the first company setup + if frappe.db.count('Company', {'country': 'India'}) <=1: + setup_company_independent_fixtures(patch=patch) + if not patch: make_fixtures(company) @@ -122,8 +125,8 @@ def add_print_formats(): def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] - sales_invoice_series = frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n") + ['SINV-.YY.-', 'SRET-.YY.-', ''] - purchase_invoice_series = frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n") + ['PINV-.YY.-', 'PRET-.YY.-', ''] + sales_invoice_series = ['SINV-.YY.-', 'SRET-.YY.-', ''] + frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n") + purchase_invoice_series = ['PINV-.YY.-', 'PRET-.YY.-', ''] + frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n") if not patch: make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '') @@ -788,7 +791,7 @@ def set_tax_withholding_category(company): doc.flags.ignore_mandatory = True doc.insert() else: - doc = frappe.get_doc("Tax Withholding Category", d.get("name")) + doc = frappe.get_doc("Tax Withholding Category", d.get("name"), for_update=True) if accounts: doc.append("accounts", accounts[0]) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 915e6a4f31..382510d0be 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -291,7 +291,7 @@ class Company(NestedSet): cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name') if cash and self.default_cash_account \ and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}): - mode_of_payment = frappe.get_doc('Mode of Payment', cash) + mode_of_payment = frappe.get_doc('Mode of Payment', cash, for_update=True) mode_of_payment.append('accounts', { 'company': self.name, 'default_account': self.default_cash_account From 7fb64d1645f65c4b1789cb0ed4e41ecd8893bd3d Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Jul 2021 18:33:16 +0530 Subject: [PATCH 1202/1264] fix: exchange gain loss not set for advances linked with invoices (#26436) --- .../doctype/payment_entry/payment_entry.py | 18 +- .../payment_entry_reference.json | 12 +- .../purchase_invoice/purchase_invoice.py | 1 + .../purchase_invoice/test_purchase_invoice.py | 103 ++++++ .../purchase_invoice_advance.json | 330 ++++++----------- .../doctype/sales_invoice/sales_invoice.py | 1 + .../sales_invoice_advance.json | 331 ++++++------------ erpnext/accounts/utils.py | 14 +- erpnext/controllers/accounts_controller.py | 86 ++++- 9 files changed, 441 insertions(+), 455 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0c21aae944..ff00fde523 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -183,6 +183,13 @@ class PaymentEntry(AccountsController): d.reference_name, self.party_account_currency) for field, value in iteritems(ref_details): + if d.exchange_gain_loss: + # for cases where gain/loss is booked into invoice + # exchange_gain_loss is calculated from invoice & populated + # and row.exchange_rate is already set to payment entry's exchange rate + # refer -> `update_reference_in_payment_entry()` in utils.py + continue + if field == 'exchange_rate' or not d.get(field) or force: d.db_set(field, value) @@ -664,8 +671,8 @@ class PaymentEntry(AccountsController): gl_entries.append(gle) if self.unallocated_amount: - base_unallocated_amount = self.unallocated_amount * \ - (self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate) + exchange_rate = self.get_exchange_rate() + base_unallocated_amount = (self.unallocated_amount * exchange_rate) gle = party_gl_dict.copy() @@ -806,10 +813,17 @@ class PaymentEntry(AccountsController): if account_details: row.update(account_details) + + if not row.get('amount'): + # if no difference amount + return self.append('deductions', row) self.set_unallocated_amount() + def get_exchange_rate(self): + return self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate + def initialize_taxes(self): for tax in self.get("taxes"): validate_taxes_and_charges(tax) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 912ad0977a..43eb0b6e2a 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -14,7 +14,8 @@ "total_amount", "outstanding_amount", "allocated_amount", - "exchange_rate" + "exchange_rate", + "exchange_gain_loss" ], "fields": [ { @@ -90,12 +91,19 @@ "fieldtype": "Link", "label": "Payment Term", "options": "Payment Term" + }, + { + "fieldname": "exchange_gain_loss", + "fieldtype": "Currency", + "label": "Exchange Gain/Loss", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-10 11:25:47.144392", + "modified": "2021-04-21 13:30:11.605388", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 45d89ad1c8..f7992797ed 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -451,6 +451,7 @@ class PurchaseInvoice(BuyingController): self.get_asset_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) + self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) self.allocate_advance_taxes(gl_entries) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 311745d3cd..c9384be6eb 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -953,6 +953,109 @@ class TestPurchaseInvoice(unittest.TestCase): acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.save() + def test_gain_loss_with_advance_entry(self): + unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice") + frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) + pay = frappe.get_doc({ + 'doctype': 'Payment Entry', + 'company': '_Test Company', + 'payment_type': 'Pay', + 'party_type': 'Supplier', + 'party': '_Test Supplier USD', + 'paid_to': '_Test Payable USD - _TC', + 'paid_from': 'Cash - _TC', + 'paid_amount': 70000, + 'target_exchange_rate': 70, + 'received_amount': 1000, + }) + pay.insert() + pay.submit() + + pi = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD", + conversion_rate=75, rate=500, do_not_save=1, qty=1) + pi.cost_center = "_Test Cost Center - _TC" + pi.advances = [] + pi.append("advances", { + "reference_type": "Payment Entry", + "reference_name": pay.name, + "advance_amount": 1000, + "remarks": pay.remarks, + "allocated_amount": 500, + "ref_exchange_rate": 70 + }) + pi.save() + pi.submit() + + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 37500.0], + ["_Test Payable USD - _TC", -40000.0], + ["Exchange Gain/Loss - _TC", 2500.0] + ] + + gl_entries = frappe.db.sql(""" + select account, sum(debit - credit) as balance from `tabGL Entry` + where voucher_no=%s + group by account order by account asc""", (pi.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.balance) + + pi_2 = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD", + conversion_rate=73, rate=500, do_not_save=1, qty=1) + pi_2.cost_center = "_Test Cost Center - _TC" + pi_2.advances = [] + pi_2.append("advances", { + "reference_type": "Payment Entry", + "reference_name": pay.name, + "advance_amount": 500, + "remarks": pay.remarks, + "allocated_amount": 500, + "ref_exchange_rate": 70 + }) + pi_2.save() + pi_2.submit() + + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 36500.0], + ["_Test Payable USD - _TC", -38000.0], + ["Exchange Gain/Loss - _TC", 1500.0] + ] + + gl_entries = frappe.db.sql(""" + select account, sum(debit - credit) as balance from `tabGL Entry` + where voucher_no=%s + group by account order by account asc""", (pi_2.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.balance) + + expected_gle = [ + ["_Test Payable USD - _TC", 70000.0], + ["Cash - _TC", -70000.0] + ] + + gl_entries = frappe.db.sql(""" + select account, sum(debit - credit) as balance from `tabGL Entry` + where voucher_no=%s and is_cancelled=0 + group by account order by account asc""", (pay.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.balance) + + pi.reload() + pi.cancel() + + pi_2.reload() + pi_2.cancel() + + pay.reload() + pay.cancel() + + frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled) + def test_purchase_invoice_advance_taxes(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index 5801b17f66..63dfff8921 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -1,235 +1,127 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-03-08 15:36:46", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "actions": [], + "creation": "2013-03-08 15:36:46", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_type", + "reference_name", + "remarks", + "reference_row", + "col_break1", + "advance_amount", + "allocated_amount", + "exchange_gain_loss", + "ref_exchange_rate" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Type", - "length": 0, - "no_copy": 1, - "oldfieldname": "journal_voucher", - "oldfieldtype": "Link", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "180px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "no_copy": 1, + "oldfieldname": "journal_voucher", + "oldfieldtype": "Link", + "options": "DocType", + "print_width": "180px", + "read_only": 1, "width": "180px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Reference Name", - "length": 0, - "no_copy": 1, - "options": "reference_type", - "permlevel": 0, - "precision": "", - "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 - }, + "columns": 3, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "no_copy": 1, + "options": "reference_type", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "remarks", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Remarks", - "length": 0, - "no_copy": 1, - "oldfieldname": "remarks", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 3, + "fieldname": "remarks", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Remarks", + "no_copy": 1, + "oldfieldname": "remarks", + "oldfieldtype": "Small Text", + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_row", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Row", - "length": 0, - "no_copy": 1, - "oldfieldname": "jv_detail_no", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "80px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row", + "no_copy": 1, + "oldfieldname": "jv_detail_no", + "oldfieldtype": "Date", + "print_hide": 1, + "print_width": "80px", + "read_only": 1, "width": "80px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "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, - "columns": 2, - "fieldname": "advance_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Advance Amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "advance_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "advance_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Advance Amount", + "no_copy": 1, + "oldfieldname": "advance_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "100px", + "read_only": 1, "width": "100px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "allocated_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Allocated Amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "allocated_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Allocated Amount", + "no_copy": 1, + "oldfieldname": "allocated_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "100px", "width": "100px" + }, + { + "fieldname": "exchange_gain_loss", + "fieldtype": "Currency", + "label": "Exchange Gain/Loss", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "ref_exchange_rate", + "fieldtype": "Float", + "label": "Reference Exchange Rate", + "non_negative": 1, + "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "menu_index": 0, - "modified": "2016-08-26 02:30:54.407138", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Purchase Invoice Advance", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "DESC", - "track_seen": 0 + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-20 16:26:53.820530", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Purchase Invoice Advance", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 55a5b99907..6d1f6249c1 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -840,6 +840,7 @@ class SalesInvoice(SellingController): self.make_customer_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) + self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) self.allocate_advance_taxes(gl_entries) diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json index 14bf4d8133..29422d68cf 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json @@ -1,235 +1,128 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:27:41", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "actions": [], + "creation": "2013-02-22 01:27:41", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_type", + "reference_name", + "remarks", + "reference_row", + "col_break1", + "advance_amount", + "allocated_amount", + "exchange_gain_loss", + "ref_exchange_rate" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Type", - "length": 0, - "no_copy": 1, - "oldfieldname": "journal_voucher", - "oldfieldtype": "Link", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "250px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "no_copy": 1, + "oldfieldname": "journal_voucher", + "oldfieldtype": "Link", + "options": "DocType", + "print_width": "250px", + "read_only": 1, "width": "250px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Reference Name", - "length": 0, - "no_copy": 1, - "options": "reference_type", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 3, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "no_copy": 1, + "options": "reference_type", + "print_hide": 1, + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "remarks", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Remarks", - "length": 0, - "no_copy": 1, - "oldfieldname": "remarks", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 3, + "fieldname": "remarks", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Remarks", + "no_copy": 1, + "oldfieldname": "remarks", + "oldfieldtype": "Small Text", + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_row", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Row", - "length": 0, - "no_copy": 1, - "oldfieldname": "jv_detail_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row", + "no_copy": 1, + "oldfieldname": "jv_detail_no", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "120px", + "read_only": 1, "width": "120px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "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, - "columns": 2, - "fieldname": "advance_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Advance amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "advance_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "advance_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Advance amount", + "no_copy": 1, + "oldfieldname": "advance_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "120px", + "read_only": 1, "width": "120px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "allocated_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Allocated amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "allocated_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Allocated amount", + "no_copy": 1, + "oldfieldname": "allocated_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "120px", "width": "120px" + }, + { + "fieldname": "exchange_gain_loss", + "fieldtype": "Currency", + "label": "Exchange Gain/Loss", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "ref_exchange_rate", + "fieldtype": "Float", + "label": "Reference Exchange Rate", + "non_negative": 1, + "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "menu_index": 0, - "modified": "2016-08-26 02:36:10.718057", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Invoice Advance", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "DESC", - "track_seen": 0 + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-04 20:25:49.832052", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Advance", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ed6e28da1e..1cdbd8d38a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -472,7 +472,8 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): "total_amount": d.grand_total, "outstanding_amount": d.outstanding_amount, "allocated_amount": d.allocated_amount, - "exchange_rate": d.exchange_rate + "exchange_rate": d.exchange_rate if not d.exchange_gain_loss else payment_entry.get_exchange_rate(), + "exchange_gain_loss": d.exchange_gain_loss # only populated from invoice in case of advance allocation } if d.voucher_detail_no: @@ -498,12 +499,15 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): payment_entry.set_amounts() if d.difference_amount and d.difference_account: - payment_entry.set_gain_or_loss(account_details={ + account_details = { 'account': d.difference_account, 'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company', - payment_entry.company, "cost_center"), - 'amount': d.difference_amount - }) + payment_entry.company, "cost_center") + } + if d.difference_amount: + account_details['amount'] = d.difference_amount + + payment_entry.set_gain_or_loss(account_details=account_details) if not do_not_save: payment_entry.save(ignore_permissions=True) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1c086e9edc..a9860ed2f0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -124,6 +124,8 @@ class AccountsController(TransactionBase): if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() + self.set_advance_gain_or_loss() + if self.is_return: self.validate_qty() else: @@ -584,15 +586,18 @@ class AccountsController(TransactionBase): allocated_amount = min(amount - advance_allocated, d.amount) advance_allocated += flt(allocated_amount) - self.append("advances", { + advance_row = { "doctype": self.doctype + " Advance", "reference_type": d.reference_type, "reference_name": d.reference_name, "reference_row": d.reference_row, "remarks": d.remarks, "advance_amount": flt(d.amount), - "allocated_amount": allocated_amount - }) + "allocated_amount": allocated_amount, + "ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry + } + + self.append("advances", advance_row) def get_advance_entries(self, include_unallocated=True): if self.doctype == "Sales Invoice": @@ -650,6 +655,66 @@ class AccountsController(TransactionBase): "Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.") .format(d.reference_name, d.against_order)) + def set_advance_gain_or_loss(self): + if not self.get("advances"): + return + + for d in self.get("advances"): + advance_exchange_rate = d.ref_exchange_rate + if (d.allocated_amount and self.conversion_rate != 1 + and self.conversion_rate != advance_exchange_rate): + + base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount + base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount + difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate + + d.exchange_gain_loss = difference + + def make_exchange_gain_loss_gl_entries(self, gl_entries): + if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']: + for d in self.get("advances"): + if d.exchange_gain_loss: + party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer + party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to + party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer" + + gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account') + account_currency = get_account_currency(gain_loss_account) + if account_currency != self.company_currency: + frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency)) + + # for purchase + dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit' + # just reverse for sales? + dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' + + gl_entries.append( + self.get_gl_dict({ + "account": gain_loss_account, + "account_currency": account_currency, + "against": party, + dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss), + dr_or_cr: abs(d.exchange_gain_loss), + "cost_center": self.cost_center, + "project": self.project + }, item=d) + ) + + dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' + + gl_entries.append( + self.get_gl_dict({ + "account": party_account, + "party_type": party_type, + "party": party, + "against": gain_loss_account, + dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate), + dr_or_cr: abs(d.exchange_gain_loss), + "cost_center": self.cost_center, + "project": self.project + }, self.party_account_currency, item=self) + ) + def update_against_document_in_jv(self): """ Links invoice and advance voucher: @@ -690,7 +755,9 @@ class AccountsController(TransactionBase): if self.party_account_currency != self.company_currency else 1), 'grand_total': (self.base_grand_total if self.party_account_currency == self.company_currency else self.grand_total), - 'outstanding_amount': self.outstanding_amount + 'outstanding_amount': self.outstanding_amount, + 'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'), + 'exchange_gain_loss': flt(d.get('exchange_gain_loss')) }) lst.append(args) @@ -1289,6 +1356,8 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, 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" + exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate" + payment_entries_against_order, unallocated_payment_entries = [], [] limit_cond = "limit %s" % limit if limit else "" @@ -1305,27 +1374,28 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, "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, - t1.{0} as currency + t1.{0} as currency, t1.{4} as exchange_rate from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 where 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 {2} order by t1.posting_date {3} - """.format(currency_field, party_account_field, reference_condition, limit_cond), + """.format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field), [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1) if include_unallocated: unallocated_payment_entries = frappe.db.sql(""" select "Payment Entry" as reference_type, name as reference_name, - remarks, unallocated_amount as amount + remarks, unallocated_amount as amount, {2} as exchange_rate from `tabPayment Entry` where {0} = %s and party_type = %s and party = %s and payment_type = %s and docstatus = 1 and unallocated_amount > 0 order by posting_date {1} - """.format(party_account_field, limit_cond), (party_account, party_type, party, payment_type), as_dict=1) + """.format(party_account_field, limit_cond, exchange_rate_field), + (party_account, party_type, party, payment_type), as_dict=1) return list(payment_entries_against_order) + list(unallocated_payment_entries) From 855e9030f2097c77ebc7c6114d2da48d9bc878c6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Jul 2021 22:11:57 +0530 Subject: [PATCH 1203/1264] fix: Deduct included taxes from unallocated amount --- .../doctype/payment_entry/payment_entry.py | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 85b98843ee..cf40e9cf2f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -517,16 +517,19 @@ class PaymentEntry(AccountsController): self.unallocated_amount = 0 if self.party: total_deductions = sum(flt(d.amount) for d in self.get("deductions")) + included_taxes = self.get_included_taxes() if self.payment_type == "Receive" \ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate + self.unallocated_amount -= included_taxes elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): self.unallocated_amount = (self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate + self.unallocated_amount -= included_taxes def set_difference_amount(self): base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) @@ -542,10 +545,22 @@ class PaymentEntry(AccountsController): self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) total_deductions = sum(flt(d.amount) for d in self.get("deductions")) + included_taxes = self.get_included_taxes() - self.difference_amount = flt(self.difference_amount - total_deductions, + self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")) + def get_included_taxes(self): + included_taxes = 0 + for tax in self.get('taxes'): + if tax.included_in_paid_amount: + if tax.add_deduct_tax == 'Add': + included_taxes += tax.base_tax_amount + else: + included_taxes -= tax.base_tax_amount + + return included_taxes + # Paid amount is auto allocated in the reference document by default. # Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast def clear_unallocated_reference_document_rows(self): @@ -719,6 +734,10 @@ class PaymentEntry(AccountsController): against = self.party or self.paid_to payment_or_advance_account = self.get_party_account_for_taxes() + tax_amount = d.tax_amount + + if self.advance_tax_account: + tax_amount = -1* tax_amount gl_entries.append( self.get_gl_dict({ @@ -732,16 +751,17 @@ class PaymentEntry(AccountsController): }, account_currency, item=d)) #Intentionally use -1 to get net values in party account - gl_entries.append( - self.get_gl_dict({ - "account": payment_or_advance_account, - "against": against, - dr_or_cr: -1 * d.base_tax_amount, - dr_or_cr + "_in_account_currency": -1*d.base_tax_amount - if account_currency==self.company_currency - else d.tax_amount, - "cost_center": self.cost_center, - }, account_currency, item=d)) + if not d.included_in_paid_amount or self.advance_tax_account: + gl_entries.append( + self.get_gl_dict({ + "account": payment_or_advance_account, + "against": against, + dr_or_cr: -1 * d.base_tax_amount, + dr_or_cr + "_in_account_currency": -1*d.base_tax_amount + if account_currency==self.company_currency + else d.tax_amount, + "cost_center": self.cost_center, + }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): for d in self.get("deductions"): From 4a2e4748ac8a815214a6de9c5b1ce54abda5d807 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Jul 2021 11:22:55 +0530 Subject: [PATCH 1204/1264] fix: Unallocated amount for inclusive charges --- .../accounts/doctype/payment_entry/payment_entry.py | 13 ++++++++----- erpnext/controllers/accounts_controller.py | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0bc3d94d2c..46904f7c57 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -436,6 +436,7 @@ class PaymentEntry(AccountsController): return tax_withholding_details.update({ + 'add_deduct_tax': 'Add', 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) }) @@ -742,16 +743,18 @@ class PaymentEntry(AccountsController): payment_or_advance_account = self.get_party_account_for_taxes() tax_amount = d.tax_amount + base_tax_amount = d.base_tax_amount if self.advance_tax_account: - tax_amount = -1* tax_amount + tax_amount = -1 * tax_amount + base_tax_amount = -1 * base_tax_amount gl_entries.append( self.get_gl_dict({ "account": d.account_head, "against": against, - dr_or_cr: d.base_tax_amount, - dr_or_cr + "_in_account_currency": d.base_tax_amount + dr_or_cr: tax_amount, + dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": d.cost_center @@ -763,8 +766,8 @@ class PaymentEntry(AccountsController): self.get_gl_dict({ "account": payment_or_advance_account, "against": against, - dr_or_cr: -1 * d.base_tax_amount, - dr_or_cr + "_in_account_currency": -1*d.base_tax_amount + dr_or_cr: -1 * tax_amount, + dr_or_cr + "_in_account_currency": -1 * base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a9860ed2f0..4c313c43a7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -818,11 +818,11 @@ class AccountsController(TransactionBase): account_currency = get_account_currency(tax.account_head) if self.doctype == "Purchase Invoice": - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - else: dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + else: + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" party = self.supplier if self.doctype == "Purchase Invoice" else self.customer unallocated_amount = tax.tax_amount - tax.allocated_amount From 0d190bb930a990932c6537c0c56fddb7d5d0eb87 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 13 Jul 2021 11:45:41 +0530 Subject: [PATCH 1205/1264] fix: multi-currency issue --- erpnext/manufacturing/doctype/bom/bom.py | 3 ++- erpnext/stock/get_item_details.py | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c32a8a95a1..9da461f497 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -713,7 +713,8 @@ def get_bom_item_rate(args, bom_doc): "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function "conversion_factor": args.get("conversion_factor") or 1, "plc_conversion_rate": 1, - "ignore_party": True + "ignore_party": True, + "ignore_conversion_rate": True }) item_doc = frappe.get_cached_doc("Item", args.get("item_code")) out = frappe._dict() diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index ca174a3f63..4657700dbb 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -441,7 +441,7 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t if item_tax_templates is None: item_tax_templates = {} - + if item_rates is None: item_rates = {} @@ -807,10 +807,14 @@ def check_packing_list(price_list_rate_name, desired_qty, item_code): def validate_conversion_rate(args, meta): from erpnext.controllers.accounts_controller import validate_conversion_rate - if (not args.conversion_rate - and args.currency==frappe.get_cached_value('Company', args.company, "default_currency")): + company_currency = frappe.get_cached_value('Company', args.company, "default_currency") + if (not args.conversion_rate and args.currency==company_currency): args.conversion_rate = 1.0 + if (not args.ignore_conversion_rate and args.conversion_rate == 1 and args.currency!=company_currency): + args.conversion_rate = get_exchange_rate(args.currency, + company_currency, args.transaction_date, "for_buying") or 1.0 + # validate currency conversion rate validate_conversion_rate(args.currency, args.conversion_rate, meta.get_label("conversion_rate"), args.company) From adfdc71844a94a68884c50f45c5f90f3d1640a95 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 14 Jul 2021 09:59:41 +0530 Subject: [PATCH 1206/1264] fix: Tax calculation for Recurring additional salary (#24206) * fix: Tax calculation for Recurring additional salary * fix: conflicts --- .../additional_salary/additional_salary.py | 6 +++--- .../doctype/salary_detail/salary_detail.json | 11 +++++++++- .../doctype/salary_slip/salary_slip.py | 21 ++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index ebeddf97f9..7db4b8686a 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -110,11 +110,11 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days +@frappe.whitelist() def get_additional_salaries(employee, start_date, end_date, component_type): additional_salary_list = frappe.db.sql(""" - select name, salary_component as component, type, amount, - overwrite_salary_structure_amount as overwrite, - deduct_full_tax_on_selected_payroll_date + select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite, + deduct_full_tax_on_selected_payroll_date, is_recurring from `tabAdditional Salary` where employee=%(employee)s and docstatus = 1 diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index 393f647cc8..97608d72f3 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.json +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json @@ -12,6 +12,7 @@ "year_to_date", "section_break_5", "additional_salary", + "is_recurring_additional_salary", "statistical_component", "depends_on_payment_days", "exempted_from_income_tax", @@ -235,11 +236,19 @@ "label": "Year To Date", "options": "currency", "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='earnings' && doc.additional_salary", + "fieldname": "is_recurring_additional_salary", + "fieldtype": "Check", + "label": "Is Recurring Additional Salary", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-01-14 13:39:15.847158", + "modified": "2021-03-14 13:39:15.847158", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Detail", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index bead880ef7..81e5dc9f87 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -7,12 +7,12 @@ import datetime, math from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day from frappe.model.naming import make_autoname +from frappe.utils.background_jobs import enqueue from frappe import msgprint, _ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase -from frappe.utils.background_jobs import enqueue from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount @@ -616,7 +616,8 @@ class SalarySlip(TransactionBase): get_salary_component_data(additional_salary.component), additional_salary.amount, component_type, - additional_salary + additional_salary, + is_recurring = additional_salary.is_recurring ) def add_tax_components(self, payroll_period): @@ -637,7 +638,7 @@ class SalarySlip(TransactionBase): tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, component_data, amount, component_type, additional_salary=None): + def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0): component_row = None for d in self.get(component_type): if d.salary_component != component_data.salary_component: @@ -678,6 +679,7 @@ class SalarySlip(TransactionBase): component_row.set('abbr', abbr) if additional_salary: + component_row.is_recurring_additional_salary = is_recurring component_row.default_amount = 0 component_row.additional_amount = amount component_row.additional_salary = additional_salary.name @@ -711,6 +713,7 @@ class SalarySlip(TransactionBase): # get remaining numbers of sub-period (period for which one salary is processed) remaining_sub_periods = get_period_factor(self.employee, self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1] + # get taxable_earnings, paid_taxes for previous period previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date, self.start_date, tax_slab.allow_tax_exemption) @@ -870,8 +873,16 @@ class SalarySlip(TransactionBase): if earning.is_tax_applicable: if additional_amount: - taxable_earnings += (amount - additional_amount) - additional_income += additional_amount + if not earning.is_recurring_additional_salary: + taxable_earnings += (amount - additional_amount) + additional_income += additional_amount + else: + to_date = frappe.db.get_value("Additional Salary", earning.additional_salary, 'to_date') + period = (getdate(to_date).month - getdate(self.start_date).month) + 1 + if period > 0: + taxable_earnings += (amount - additional_amount) * period + additional_income += additional_amount * period + if earning.deduct_full_tax_on_selected_payroll_date: additional_income_with_full_tax += additional_amount continue From cbf7e1b676d5f89f1be85c6633fe083c31ebe690 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Jul 2021 11:40:47 +0530 Subject: [PATCH 1207/1264] fix: pos item cart dom updates (#26460) --- .../selling/page/point_of_sale/pos_item_cart.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 38508c219b..f7b2c1d93c 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -965,8 +965,23 @@ erpnext.PointOfSale.ItemCart = class { }); } + attach_refresh_field_event(frm) { + $(frm.wrapper).off('refresh-fields'); + $(frm.wrapper).on('refresh-fields', () => { + if (frm.doc.items.length) { + frm.doc.items.forEach(item => { + this.update_item_html(item); + }); + } + this.update_totals_section(frm); + }); + } + load_invoice() { const frm = this.events.get_frm(); + + this.attach_refresh_field_event(frm); + this.fetch_customer_details(frm.doc.customer).then(() => { this.events.customer_details_updated(this.customer_info); this.update_customer_section(); From 9168bb369a3186b9efc09c0039e6b38a624d1359 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 14 Jul 2021 13:57:14 +0530 Subject: [PATCH 1208/1264] fix: filter by accounts with group by accounts (#26439) --- erpnext/accounts/report/general_ledger/general_ledger.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index e724e9b51b..1759fa3a48 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -55,9 +55,11 @@ def validate_filters(filters, account_details): if not account_details.get(account): frappe.throw(_("Account {0} does not exists").format(account)) - if (filters.get("account") and filters.get("group_by") == _('Group by Account') - and account_details[filters.account].is_group == 0): - frappe.throw(_("Can not filter based on Account, if grouped by Account")) + if (filters.get("account") and filters.get("group_by") == _('Group by Account')): + filters.account = frappe.parse_json(filters.get('account')) + for account in filters.account: + if account_details[account].is_group == 0: + frappe.throw(_("Can not filter based on Child Account, if grouped by Account")) if (filters.get("voucher_no") and filters.get("group_by") in [_('Group by Voucher')]): From 9c04079d04962607b9a8bdaebd0cbe907f1fd28e Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Jul 2021 14:45:11 +0530 Subject: [PATCH 1209/1264] fix: test fails due to improper gain loss account set (#26482) (#26484) --- .../purchase_invoice/test_purchase_invoice.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c9384be6eb..ca4d009956 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -954,8 +954,17 @@ class TestPurchaseInvoice(unittest.TestCase): acc_settings.save() def test_gain_loss_with_advance_entry(self): - unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice") - frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) + unlink_enabled = frappe.db.get_value( + "Accounts Settings", "Accounts Settings", + "unlink_payment_on_cancel_of_invoice") + + frappe.db.set_value( + "Accounts Settings", "Accounts Settings", + "unlink_payment_on_cancel_of_invoice", 1) + + original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account") + frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC") + pay = frappe.get_doc({ 'doctype': 'Payment Entry', 'company': '_Test Company', @@ -995,7 +1004,8 @@ class TestPurchaseInvoice(unittest.TestCase): gl_entries = frappe.db.sql(""" select account, sum(debit - credit) as balance from `tabGL Entry` where voucher_no=%s - group by account order by account asc""", (pi.name), as_dict=1) + group by account + order by account asc""", (pi.name), as_dict=1) for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) @@ -1055,6 +1065,7 @@ class TestPurchaseInvoice(unittest.TestCase): pay.cancel() frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled) + frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) def test_purchase_invoice_advance_taxes(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order From ac721ae1470923eb4c1a551ff0034998c31c4df9 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Jul 2021 15:20:14 +0530 Subject: [PATCH 1210/1264] fix: tds computation summary shows cancelled invoices (#26485) --- .../report/tds_computation_summary/tds_computation_summary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index e15715dccd..6b9df41f54 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -75,7 +75,8 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f select voucher_no, credit from `tabGL Entry` where party in (%s) and credit > 0 - and company=%s and posting_date between %s and %s + and company=%s and is_cancelled = 0 + and posting_date between %s and %s """, (supplier, company, from_date, to_date), as_dict=1) supplier_credit_amount = flt(sum(d.credit for d in entries)) From 7a890331631533521c03e3991911d09f5158f454 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Wed, 14 Jul 2021 16:02:49 +0530 Subject: [PATCH 1211/1264] fix: update integration links in help.js (#26483) --- erpnext/public/js/help_links.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index aa9bba17c7..140c9da2ee 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -54,7 +54,7 @@ frappe.help.help_links["permission-manager"] = [ frappe.help.help_links["Form/System Settings"] = [ { - label: "Naming Series", + label: "System Settings", url: docsUrl + "user/manual/en/setting-up/settings/system-settings", }, ]; @@ -206,7 +206,7 @@ frappe.help.help_links["Form/PayPal Settings"] = [ label: "PayPal Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/paypal-integration", + "user/manual/en/erpnext_integration/paypal-integration", }, ]; @@ -215,14 +215,14 @@ frappe.help.help_links["Form/Razorpay Settings"] = [ label: "Razorpay Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/razorpay-integration", + "user/manual/en/erpnext_integration/razorpay-integration", }, ]; frappe.help.help_links["Form/Dropbox Settings"] = [ { label: "Dropbox Settings", - url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup", + url: docsUrl + "user/manual/en/erpnext_integration/dropbox-backup", }, ]; @@ -230,7 +230,7 @@ frappe.help.help_links["Form/LDAP Settings"] = [ { label: "LDAP Settings", url: - docsUrl + "user/manual/en/setting-up/integrations/ldap-integration", + docsUrl + "user/manual/en/erpnext_integration/ldap-integration", }, ]; @@ -239,7 +239,7 @@ frappe.help.help_links["Form/Stripe Settings"] = [ label: "Stripe Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/stripe-integration", + "user/manual/en/erpnext_integration/stripe-integration", }, ]; From 513375f264034b2eaa5e0b563811aa1c12aca790 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Fri, 9 Jul 2021 21:52:50 +0530 Subject: [PATCH 1212/1264] fix: Nested/Multi-level BOM help link (#26409) Updated the link for multi-level boms. Current link is broken. --- 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 140c9da2ee..d0c935f488 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -991,7 +991,7 @@ frappe.help.help_links["Form/BOM"] = [ label: "Nested BOM Structure", url: docsUrl + - "user/manual/en/manufacturing/articles/nested-bom-structure", + "user/manual/en/manufacturing/articles/managing-multi-level-bom", }, ]; From 2c67894135544c04dddeab014ab0c854f8aaef9e Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 14 Jul 2021 16:28:40 +0530 Subject: [PATCH 1213/1264] fix: validation check for batch for stock reconciliation type in stock entry(bp #26370 ) (#26487) * fix(ux): added filter for valid batch nos. * fix: not validating batch no if entry type stock reconciliation * test: validate batch_no --- .../stock_ledger_entry/stock_ledger_entry.py | 19 ++++++++--------- .../stock_reconciliation.js | 8 +++++++ .../test_stock_reconciliation.py | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index cb939e63c2..93482e8bea 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -89,17 +89,16 @@ class StockLedgerEntry(Document): if item_det.is_stock_item != 1: frappe.throw(_("Item {0} must be a stock Item").format(self.item_code)) - # check if batch number is required - if self.voucher_type != 'Stock Reconciliation': - if item_det.has_batch_no == 1: - batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name - if not self.batch_no: - frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item)) - elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): - frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) + # check if batch number is valid + if item_det.has_batch_no == 1: + batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name + if not self.batch_no: + frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item)) + elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): + frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) - elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0: - frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) + elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0: + frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) if item_det.has_variants: frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code), diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index a01db80da4..349e59f31d 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -17,6 +17,14 @@ frappe.ui.form.on("Stock Reconciliation", { } } }); + frm.set_query("batch_no", "items", function(doc, cdt, cdn) { + var item = locals[cdt][cdn]; + return { + filters: { + 'item': item.item_code + } + }; + }); if (frm.doc.company) { erpnext.queries.setup_queries(frm, "Warehouse", function() { diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 84cdc49128..c192582531 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -16,6 +16,7 @@ from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valua from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + class TestStockReconciliation(unittest.TestCase): @classmethod def setUpClass(self): @@ -352,6 +353,26 @@ class TestStockReconciliation(unittest.TestCase): dn2.cancel() pr1.cancel() + def test_valid_batch(self): + create_batch_item_with_batch("Testing Batch Item 1", "001") + create_batch_item_with_batch("Testing Batch Item 2", "002") + sr = create_stock_reconciliation(item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002" + , do_not_submit=True) + self.assertRaises(frappe.ValidationError, sr.submit) + +def create_batch_item_with_batch(item_name, batch_id): + batch_item_doc = create_item(item_name, is_stock_item=1) + if not batch_item_doc.has_batch_no: + batch_item_doc.has_batch_no = 1 + batch_item_doc.create_new_batch = 1 + batch_item_doc.save(ignore_permissions=True) + + if not frappe.db.exists('Batch', batch_id): + b = frappe.new_doc('Batch') + b.item = item_name + b.batch_id = batch_id + b.save() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 7558e7f1157db7456521b8071b93aa4c0e8970c7 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 13 Jul 2021 15:34:25 +0530 Subject: [PATCH 1214/1264] fix: show child item group items on portal --- erpnext/setup/doctype/item_group/item_group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 1c72cebfa9..5fcad00af1 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -87,8 +87,8 @@ class ItemGroup(NestedSet, WebsiteGenerator): if not field_filters: field_filters = {} - # Ensure the query remains within current item group - field_filters['item_group'] = self.name + # Ensure the query remains within current item group & sub group + field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)] engine = ProductQuery() context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name) From e244560fb96b66899fe123dcb5799c7fefe053da Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 13 Jul 2021 17:27:55 +0530 Subject: [PATCH 1215/1264] fix: set item group as a persistent filter --- erpnext/portal/product_configurator/utils.py | 6 ++++++ erpnext/templates/generators/item_group.html | 2 +- erpnext/www/all-products/index.js | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index d77eb2c396..211b94a9cf 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -2,6 +2,7 @@ import frappe from frappe.utils import cint from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager from erpnext.shopping_cart.product_info import get_product_info_for_website +from erpnext.setup.doctype.item_group.item_group import get_child_groups def get_field_filter_data(): product_settings = get_product_settings() @@ -89,6 +90,7 @@ def get_products_for_website(field_filters=None, attribute_filters=None, search= def get_products_html_for_website(field_filters=None, attribute_filters=None): field_filters = frappe.parse_json(field_filters) attribute_filters = frappe.parse_json(attribute_filters) + set_item_group_filters(field_filters) items = get_products_for_website(field_filters, attribute_filters) html = ''.join(get_html_for_items(items)) @@ -98,6 +100,10 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None): return html +def set_item_group_filters(field_filters): + if 'item_group' in field_filters: + field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])] + def get_item_codes_by_attributes(attribute_filters, template_item_code=None): items = [] diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html index 393c3a43af..95eb8f493f 100644 --- a/erpnext/templates/generators/item_group.html +++ b/erpnext/templates/generators/item_group.html @@ -9,7 +9,7 @@ {% endblock %} {% block page_content %} -
    +
    {% if slideshow %} {{ web_block( diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js index 0721056816..1c641b59ad 100644 --- a/erpnext/www/all-products/index.js +++ b/erpnext/www/all-products/index.js @@ -124,6 +124,10 @@ $(() => { attribute_filters: if_key_exists(attribute_filters) }; + const item_group = $(".item-group-content").data('item-group'); + if (item_group) { + Object.assign(field_filters, { item_group }); + } return new Promise((resolve, reject) => { frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args) .then(r => { From 219623279ffd50f3410382de6ca255b6743f30dc Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 14 Jul 2021 20:01:36 +0530 Subject: [PATCH 1216/1264] fix: Paging buttons not working on item group portal page --- erpnext/templates/generators/item_group.html | 29 +++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html index 95eb8f493f..9050cc388a 100644 --- a/erpnext/templates/generators/item_group.html +++ b/erpnext/templates/generators/item_group.html @@ -127,15 +127,36 @@
    -
    -
    +
    +
    +
    +
    {% if frappe.form_dict.start|int > 0 %} - + {% endif %} {% if items|length >= page_length %} - + {% endif %}
    + + {% endblock %} \ No newline at end of file From 12f7befa13882f04cfcb23afec0c363e7103420b Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 15 Jul 2021 12:03:41 +0530 Subject: [PATCH 1217/1264] fix: check if field_filters is None --- erpnext/portal/product_configurator/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 211b94a9cf..d60b1a2b05 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -101,7 +101,7 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None): return html def set_item_group_filters(field_filters): - if 'item_group' in field_filters: + if field_filters is not None and 'item_group' in field_filters: field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])] From 74b97b5ec9a16ae10a988b782c1818ce7f823ebd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jul 2021 14:08:58 +0530 Subject: [PATCH 1218/1264] fix: FG item not fetched in manufacture entry --- .../doctype/work_order/test_work_order.py | 54 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.py | 22 +++++--- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 68de0b29d3..bf1ccb7159 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -513,6 +513,60 @@ class TestWorkOrder(unittest.TestCase): work_order1.save() self.assertEqual(work_order1.operations[0].time_in_mins, 40.0) + def test_batch_size_for_fg_item(self): + fg_item = "Test Batch Size Item For BOM 3" + rm1 = "Test Batch Size Item RM 1 For BOM 3" + + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0) + for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]: + item_args = { + "include_item_in_manufacturing": 1, + "is_stock_item": 1 + } + + if item == fg_item: + item_args['has_batch_no'] = 1 + item_args['create_new_batch'] = 1 + item_args['batch_number_series'] = 'TBSI3.#####' + + make_item(item, item_args) + + bom_name = frappe.db.get_value("BOM", + {"item": fg_item, "is_active": 1, "with_operations": 1}, "name") + + if not bom_name: + bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True) + bom.save() + bom.submit() + bom_name = bom.name + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1) + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), + qty=30, do_not_save = True) + work_order.batch_size = 10 + work_order.insert() + work_order.submit() + self.assertEqual(work_order.has_batch_no, 1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + self.assertEqual(row.qty, 10) + + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0) + def test_partial_material_consumption(self): frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1) wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 90b81ddb1d..c9838d75f1 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1090,13 +1090,13 @@ class StockEntry(StockController): "is_finished_item": 1 } - if self.work_order and self.pro_doc.has_batch_no: + if self.work_order and self.pro_doc.has_batch_no and cint(frappe.db.get_single_value('Manufacturing Settings', + 'make_serial_no_batch_from_work_order', cache=True)): self.set_batchwise_finished_goods(args, item) else: - self.add_finisged_goods(args, item) + self.add_finished_goods(args, item) def set_batchwise_finished_goods(self, args, item): - qty = flt(self.fg_completed_qty) filters = { "reference_name": self.pro_doc.name, "reference_doctype": self.pro_doc.doctype, @@ -1105,7 +1105,17 @@ class StockEntry(StockController): fields = ["qty_to_produce as qty", "produced_qty", "name"] - for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"): + data = frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc") + + if not data: + self.add_finished_goods(args, item) + else: + self.add_batchwise_finished_good(data, args, item) + + def add_batchwise_finished_good(self, data, args, item): + qty = flt(self.fg_completed_qty) + + for row in data: batch_qty = flt(row.qty) - flt(row.produced_qty) if not batch_qty: continue @@ -1121,9 +1131,9 @@ class StockEntry(StockController): args["qty"] = fg_qty args["batch_no"] = row.name - self.add_finisged_goods(args, item) + self.add_finished_goods(args, item) - def add_finisged_goods(self, args, item): + def add_finished_goods(self, args, item): self.add_to_stock_entry_detail({ item.name: args }, bom_no = self.bom_no) From d319e1088352cc911b54b57b4e3c89c8200a52fe Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 15 Jul 2021 16:49:55 +0530 Subject: [PATCH 1219/1264] fix: set default operation time to 0 (#26511) --- erpnext/manufacturing/doctype/sub_operation/sub_operation.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json index f63d2b9864..10cee32398 100644 --- a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json @@ -19,6 +19,7 @@ "options": "Operation" }, { + "default": "0", "description": "Time in mins", "fieldname": "time_in_mins", "fieldtype": "Float", @@ -38,7 +39,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-07 18:09:18.005578", + "modified": "2021-07-15 16:39:41.635362", "modified_by": "Administrator", "module": "Manufacturing", "name": "Sub Operation", From 26a9d385472d90dccf067ded8b0929447fad8033 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 15 Jul 2021 16:50:41 +0530 Subject: [PATCH 1220/1264] fix: WIP needs to be set before submit on skip_transfer (#26500) --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 779ae42d65..0a8e5329c1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -239,7 +239,7 @@ class WorkOrder(Document): self.create_serial_no_batch_no() def on_submit(self): - if not self.wip_warehouse: + if not self.wip_warehouse and not self.skip_transfer: frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) From 9b9b18c28622a84795e656a203ae6470ab8c7ba7 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 15 Jul 2021 18:11:22 +0530 Subject: [PATCH 1221/1264] fix: improving ux for additional discount field (#26502) --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index f7b2c1d93c..6e36d2809a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -367,15 +367,16 @@ erpnext.PointOfSale.ItemCart = class { `
    ` ); const me = this; + const frm = me.events.get_frm(); + let discount = frm.doc.additional_discount_percentage; this.discount_field = frappe.ui.form.make_control({ df: { label: __('Discount'), fieldtype: 'Data', - placeholder: __('Enter discount percentage.'), + placeholder: ( discount ? discount + '%' : __('Enter discount percentage.') ), input_class: 'input-xs', onchange: function() { - const frm = me.events.get_frm(); if (flt(this.value) != 0) { frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value)); me.hide_discount_control(this.value); From b164070a4f0e73ca82bdc2b0c4a673e8e6a602e3 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 15 Jul 2021 19:31:59 +0530 Subject: [PATCH 1222/1264] ci: make semgrep ignore existing errors (bp #26516) --- .../semgrep_rules/frappe_correctness.yml | 2 - .github/workflows/semgrep.yml | 38 ++++++------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index faab3344a6..d9603e89aa 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -98,8 +98,6 @@ rules: languages: [python] severity: WARNING paths: - exclude: - - test_*.py include: - "*/**/doctype/*" diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 389524e968..701c5c7cbe 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -1,34 +1,20 @@ name: Semgrep on: - pull_request: - branches: - - develop - - version-13-hotfix - - version-13-pre-release + pull_request: { } + push: + branches: ["develop"] + jobs: semgrep: name: Frappe Linter runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup python3 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Setup semgrep - run: | - python -m pip install -q semgrep - git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q - - - name: Semgrep errors - run: | - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files - semgrep --config="r/python.lang.correctness" --quiet --error $files - - - name: Semgrep warnings - run: | - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files + - uses: actions/checkout@v2 + - uses: returntocorp/semgrep-action@v1 + env: + SEMGREP_TIMEOUT: 120 + with: + config: >- + r/python.lang.correctness + .github/helper/semgrep_rules From 627a8a8cfd86ed5cd990d455d145d357f1ec8f8d Mon Sep 17 00:00:00 2001 From: Ankush Date: Fri, 16 Jul 2021 13:03:18 +0530 Subject: [PATCH 1223/1264] chore: disable semgrep on push events (bp #26523) --- .github/workflows/semgrep.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 701c5c7cbe..e27b406df0 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -2,8 +2,6 @@ name: Semgrep on: pull_request: { } - push: - branches: ["develop"] jobs: semgrep: From 13e9aa59564d5230aee949049bff32170646b486 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jul 2021 16:32:23 +0530 Subject: [PATCH 1224/1264] fix: added patch to fix missing FG item --- erpnext/patches.txt | 1 + .../add_missing_fg_item_for_stock_entry.py | 110 ++++++++++++++++++ .../repost_item_valuation.py | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 4 + 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 29376f00a1..f63c7edea2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -291,3 +291,4 @@ erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef +erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py new file mode 100644 index 0000000000..48999e6f99 --- /dev/null +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -0,0 +1,110 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.utils import cstr, flt, cint +from erpnext.stock.stock_ledger import make_sl_entries +from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + +def execute(): + if not frappe.db.has_column('Work Order', 'has_batch_no'): + return + + if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')): + return + + frappe.reload_doc('manufacturing', 'doctype', 'work_order') + filters = { + 'docstatus': 1, + 'produced_qty': ('>', 0), + 'creation': ('>=', '2021-06-29 00:00:00'), + 'has_batch_no': 1 + } + + fields = ['name', 'production_item'] + + work_orders = [d.name for d in frappe.get_all('Work Order', filters = filters, fields=fields)] + + if not work_orders: + return + + repost_stock_entries = [] + stock_entries = frappe.db.sql_list(''' + SELECT + se.name + FROM + `tabStock Entry` se + WHERE + se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in {work_orders} + and not exists( + select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1 + ) + Order BY + se.posting_date, se.posting_time + '''.format(work_orders=tuple(work_orders))) + + if stock_entries: + print('Length of stock entries', len(stock_entries)) + + for stock_entry in stock_entries: + doc = frappe.get_doc('Stock Entry', stock_entry) + doc.set_work_order_details() + doc.load_items_from_bom() + doc.calculate_rate_and_amount() + set_expense_account(doc) + doc.make_batches('t_warehouse') + + if doc.docstatus == 0: + doc.save() + else: + repost_stock_entry(doc) + repost_stock_entries.append(doc) + + for repost_doc in repost_stock_entries: + repost_future_sle_and_gle(repost_doc) + +def set_expense_account(doc): + for row in doc.items: + if row.is_finished_item and not row.expense_account: + row.expense_account = frappe.get_cached_value('Company', doc.company, 'stock_adjustment_account') + +def repost_stock_entry(doc): + doc.db_update() + for child_row in doc.items: + if child_row.is_finished_item: + child_row.db_update() + + sl_entries = [] + finished_item_row = doc.get_finished_item_row() + get_sle_for_target_warehouse(doc, sl_entries, finished_item_row) + + if sl_entries: + try: + make_sl_entries(sl_entries, True) + except Exception: + print(f'SLE entries not posted for the stock entry {doc.name}') + traceback = frappe.get_traceback() + frappe.log_error(traceback) + +def get_sle_for_target_warehouse(doc, sl_entries, finished_item_row): + for d in doc.get('items'): + if cstr(d.t_warehouse) and finished_item_row and d.name == finished_item_row.name: + sle = doc.get_sl_entries(d, { + "warehouse": cstr(d.t_warehouse), + "actual_qty": flt(d.transfer_qty), + "incoming_rate": flt(d.valuation_rate) + }) + + sle.recalculate_rate = 1 + sl_entries.append(sle) + +def repost_future_sle_and_gle(doc): + args = frappe._dict({ + "posting_date": doc.posting_date, + "posting_time": doc.posting_time, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "company": doc.company + }) + + create_repost_item_valuation_entry(args) \ No newline at end of file diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 55f2ebb224..5f31d9caf0 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -133,6 +133,6 @@ def repost_entries(): def get_repost_item_valuation_entries(): return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` - WHERE status != 'Completed' and creation <= %s and docstatus = 1 + WHERE status in ('Queued', 'In Progress') and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc """, now(), as_dict=1) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index c9838d75f1..872b1d0516 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -719,6 +719,10 @@ class StockEntry(StockController): frappe.throw(_("Multiple items cannot be marked as finished item")) if self.purpose == "Manufacture": + if not finished_items: + frappe.throw(_('Finished Good has not set in the stock entry {0}') + .format(self.name)) + allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")) From 3362c080b634401f4cd26c2775320ac3b2c8910c Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 16 Jul 2021 15:00:08 +0530 Subject: [PATCH 1225/1264] fix: validation check when no conversion_factor (#26527) --- erpnext/stock/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 8a6a3a3e4a..b57b2aa6b8 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -314,13 +314,16 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto for row_idx, row in enumerate(result): data = row.items() if is_dict_obj else enumerate(row) for key, value in data: - if key not in convertible_columns or not conversion_factors[row_idx-1]: + if key not in convertible_columns: continue + # If no conversion factor for the UOM, defaults to 1 + if not conversion_factors[row_idx]: + conversion_factors[row_idx] = 1 if convertible_columns.get(key) == 'rate': - new_value = flt(value) * conversion_factors[row_idx-1] + new_value = flt(value) * conversion_factors[row_idx] else: - new_value = flt(value) / conversion_factors[row_idx-1] + new_value = flt(value) / conversion_factors[row_idx] if not is_dict_obj: row.insert(key+1, new_value) @@ -386,4 +389,4 @@ def is_reposting_item_valuation_in_progress(): reposting_in_progress = frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) if reposting_in_progress: - frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) \ No newline at end of file + frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) From d8ed9dfcf476214a3171ef4f7aaeac884fb99c25 Mon Sep 17 00:00:00 2001 From: Ankush Date: Sat, 17 Jul 2021 12:49:56 +0530 Subject: [PATCH 1226/1264] chore: update CODEOWNERS (#26536) (#26537) --- CODEOWNERS | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7cf65a7a73..219b6bb782 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,16 +3,33 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -manufacturing/ @rohitwaghchaure @marination -accounts/ @deepeshgarg007 @nextchamp-saqib -loan_management/ @deepeshgarg007 @rohitwaghchaure -pos* @nextchamp-saqib @rohitwaghchaure -assets/ @nextchamp-saqib @deepeshgarg007 -stock/ @marination @rohitwaghchaure -buying/ @marination @deepeshgarg007 -hr/ @Anurag810 @rohitwaghchaure -projects/ @hrwX @nextchamp-saqib -support/ @hrwX @marination -healthcare/ @ruchamahabal @marination -erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib -requirements.txt @gavindsouza +erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 +erpnext/assets/ @nextchamp-saqib @deepeshgarg007 +erpnext/erpnext_integrations/ @nextchamp-saqib +erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007 +erpnext/regional @nextchamp-saqib @deepeshgarg007 +erpnext/selling @nextchamp-saqib @deepeshgarg007 +erpnext/support/ @nextchamp-saqib @deepeshgarg007 +pos* @nextchamp-saqib + +erpnext/buying/ @marination @rohitwaghchaure @ankush +erpnext/e_commerce/ @marination +erpnext/maintenance/ @marination @rohitwaghchaure +erpnext/manufacturing/ @marination @rohitwaghchaure @ankush +erpnext/portal/ @marination +erpnext/quality_management/ @marination @rohitwaghchaure +erpnext/shopping_cart/ @marination +erpnext/stock/ @marination @rohitwaghchaure @ankush + +erpnext/crm/ @ruchamahabal +erpnext/education/ @ruchamahabal +erpnext/healthcare/ @ruchamahabal +erpnext/hr/ @ruchamahabal +erpnext/non_profit/ @ruchamahabal +erpnext/payroll @ruchamahabal +erpnext/projects/ @ruchamahabal + +erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination + +.github/ @surajshetty3416 @ankush +requirements.txt @gavindsouza From d5ff6361594e771c399c7b8f8384253f49ba434b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 17 Jul 2021 14:42:38 -0400 Subject: [PATCH 1227/1264] fix: missing parameter 'country' --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 4fd8413d83..8456b49c8e 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -391,5 +391,5 @@ def set_default_accounts(company): }) company.save() - install_country_fixtures(company.name) + install_country_fixtures(company.name, company.country) company.create_default_tax_template() From cad11707822064aa6fa85cebe4d6d54d53b186ee Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Jul 2021 14:36:54 +0530 Subject: [PATCH 1228/1264] fix: Add missing cess amount in GSTR-3B report --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 3 +++ 1 file changed, 3 insertions(+) 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 641520437f..6de228fbc7 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -322,6 +322,9 @@ class GSTR3BReport(Document): inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100) + if self.invoice_cess.get(inv): + self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2) + self.set_inter_state_supply(inter_state_supply_details) def set_supplies_liable_to_reverse_charge(self): From ff9d631f1525fe4f27caac4afc65eddb165cc27b Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 19 Jul 2021 20:09:37 +0530 Subject: [PATCH 1229/1264] fix:Ignore mandatory fields while creating payment reconciliation Journal Entry --- .../doctype/payment_reconciliation/payment_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 6635128f9e..d788d91855 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company): } ] }) - + jv.flags.ignore_mandatory = True jv.submit() \ No newline at end of file From 92273cade025ec4fc782905a702fe6a2d454474e Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 19 Jul 2021 20:44:05 +0530 Subject: [PATCH 1230/1264] fix(ux): item description should fall back to name (#26339) (#26552) Don't set item description = item code from front end. This is already being set to item_name in before_insert and item_name is better fallback than item code for description. Also fixed wrong condition for erasing description while duplicating item. --- erpnext/stock/doctype/item/item.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index b55374b8d8..264baeaa47 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -100,10 +100,11 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__('Duplicate'), function() { var new_item = frappe.model.copy_doc(frm.doc); - if(new_item.item_name===new_item.item_code) { + // Duplicate item could have different name, causing "copy paste" error. + if (new_item.item_name===new_item.item_code) { new_item.item_name = null; } - if(new_item.description===new_item.description) { + if (new_item.item_code===new_item.description || new_item.item_code===new_item.description) { new_item.description = null; } frappe.set_route('Form', 'Item', new_item.name); @@ -186,8 +187,6 @@ frappe.ui.form.on("Item", { item_code: function(frm) { if(!frm.doc.item_name) frm.set_value("item_name", frm.doc.item_code); - if(!frm.doc.description) - frm.set_value("description", frm.doc.item_code); }, is_stock_item: function(frm) { From 85c8daae9c83b3045735c85d31b14e0e1949bafd Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 20 Jul 2021 09:57:18 +0530 Subject: [PATCH 1231/1264] fix: Pass doc and other parameters to properly prefill information - while creating customer from form dashboard --- erpnext/public/js/utils/customer_quick_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/customer_quick_entry.js b/erpnext/public/js/utils/customer_quick_entry.js index ebe6cd98f8..7bd21df67b 100644 --- a/erpnext/public/js/utils/customer_quick_entry.js +++ b/erpnext/public/js/utils/customer_quick_entry.js @@ -1,9 +1,9 @@ frappe.provide('frappe.ui.form'); frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ - init: function(doctype, after_insert) { + init: function(doctype, after_insert, init_callback, doc, force) { + this._super(doctype, after_insert, init_callback, doc, force); this.skip_redirect_on_error = true; - this._super(doctype, after_insert); }, render_dialog: function() { From c14aa45720507515f6e8ea73f7053ca5371df656 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jul 2021 18:19:15 +0530 Subject: [PATCH 1232/1264] fix: incorrect valuation rate calculation in gross profit report --- erpnext/accounts/report/gross_profit/gross_profit.py | 3 ++- erpnext/controllers/queries.py | 1 + erpnext/manufacturing/doctype/bom/bom.py | 2 +- erpnext/stock/doctype/batch/batch.py | 6 +++--- erpnext/stock/doctype/pick_list/pick_list.py | 1 + erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.py | 4 ++-- .../supplier_wise_sales_analytics.py | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 84c74543da..6d8623c189 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -241,6 +241,7 @@ class GrossProfitGenerator(object): sle.voucher_detail_no == row.item_row: previous_stock_value = len(my_sle) > i+1 and \ flt(my_sle[i+1].stock_value) or 0.0 + if previous_stock_value: return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) else: @@ -335,7 +336,7 @@ class GrossProfitGenerator(object): res = frappe.db.sql("""select item_code, voucher_type, voucher_no, voucher_detail_no, stock_value, warehouse, actual_qty as qty from `tabStock Ledger Entry` - where company=%(company)s + where company=%(company)s and is_cancelled = 0 order by item_code desc, warehouse desc, posting_date desc, posting_time desc, creation desc""", self.filters, as_dict=True) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 280319321f..21c052a391 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -407,6 +407,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): INNER JOIN `tabBatch` batch on sle.batch_no = batch.name where batch.disabled = 0 + and sle.is_cancelled = 0 and sle.item_code = %(item_code)s and sle.warehouse = %(warehouse)s and (sle.batch_no like %(txt)s diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 9da461f497..2fbbca4b19 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -748,7 +748,7 @@ def get_valuation_rate(args): if valuation_rate <= 0: last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` - where item_code = %s and valuation_rate > 0 + where item_code = %s and valuation_rate > 0 and is_cancelled = 0 order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code']) valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index b6eef6ca48..b37ae3f4f6 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -162,19 +162,19 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No out = float(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and batch_no=%s {0}""".format(cond), + where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond), (warehouse, batch_no))[0][0] or 0) if batch_no and not warehouse: out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty from `tabStock Ledger Entry` - where batch_no=%s + where is_cancelled = 0 and batch_no=%s group by warehouse''', batch_no, as_dict=1) if not batch_no and item_code and warehouse: out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty from `tabStock Ledger Entry` - where item_code = %s and warehouse=%s + where is_cancelled = 0 and item_code = %s and warehouse=%s group by batch_no''', (item_code, warehouse), as_dict=1) return out diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index e795742ea4..516ae43089 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -239,6 +239,7 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re and sle.`item_code`=%(item_code)s and sle.`company` = %(company)s and batch.disabled = 0 + and sle.is_cancelled=0 and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s {warehouse_condition} GROUP BY diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 872b1d0516..654755ec2f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1789,7 +1789,7 @@ def get_expired_batch_items(): from `tabBatch` b, `tabStock Ledger Entry` sle where b.expiry_date <= %s and b.expiry_date is not NULL - and b.batch_id = sle.batch_no + and b.batch_id = sle.batch_no and sle.is_cancelled = 0 group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1) @frappe.whitelist() diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 93482e8bea..b4f458388b 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -60,7 +60,7 @@ class StockLedgerEntry(Document): if self.batch_no and not self.get("allow_negative_stock"): batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and item_code=%s and batch_no=%s""", + where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""", (self.warehouse, self.item_code, self.batch_no))[0][0]) if batch_bal_after_transaction < 0: @@ -152,7 +152,7 @@ class StockLedgerEntry(Document): last_transaction_time = frappe.db.sql(""" select MAX(timestamp(posting_date, posting_time)) as posting_time from `tabStock Ledger Entry` - where docstatus = 1 and item_code = %s + where docstatus = 1 and is_cancelled = 0 and item_code = %s and warehouse = %s""", (self.item_code, self.warehouse))[0][0] cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00") diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py index 5873a7a300..4108a57554 100644 --- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py +++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py @@ -69,7 +69,7 @@ def get_consumed_details(filters): i.stock_uom, sle.actual_qty, sle.stock_value_difference, sle.voucher_no, sle.voucher_type from `tabStock Ledger Entry` sle, `tabItem` i - where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1): + where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1): consumed_details.setdefault(d.item_code, []).append(d) return consumed_details From 2d225e621fe54db10cb4f18f097e8f11d4c5e517 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 20 Jul 2021 20:41:04 +0530 Subject: [PATCH 1233/1264] fix: Price list rate not fetched for return sales invoice fixed (#26560) Co-authored-by: Subin Tom --- erpnext/stock/get_item_details.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 4657700dbb..cf52803fca 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,9 +74,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - if not doc or cint(doc.get('is_return')) == 0: - # get price list rate only if the invoice is not a credit or debit note - get_price_list_rate(args, item, out) + + get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) From 41705acbd91be747f75b7eec583c5c5264164ed4 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 20 Jul 2021 20:48:57 +0530 Subject: [PATCH 1234/1264] fix: delete child docs when parent doc is deleted (#26518) * fix: Make code more readable * fix: Delete child table info when parent doc is deleted * fix: Sider issues * fix: Remove trailing whitespace --- .../transaction_deletion_record.py | 186 +++++++++++------- 1 file changed, 112 insertions(+), 74 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index ece9fb5699..c3db27f81c 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -12,10 +12,14 @@ from frappe.desk.notifications import clear_notifications class TransactionDeletionRecord(Document): def validate(self): frappe.only_for('System Manager') + self.validate_doctypes_to_be_ignored() + + def validate_doctypes_to_be_ignored(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in self.doctypes_to_be_ignored: if doctype.doctype_name not in doctypes_to_be_ignored_list: - frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed")) + frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."), + title=_("Not Allowed")) def before_submit(self): if not self.doctypes_to_be_ignored: @@ -23,54 +27,9 @@ class TransactionDeletionRecord(Document): self.delete_bins() self.delete_lead_addresses() - - company_obj = frappe.get_doc('Company', self.company) - # reset company values - company_obj.total_monthly_sales = 0 - company_obj.sales_monthly_history = None - company_obj.save() - # Clear notification counts + self.reset_company_values() clear_notifications() - - singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') - tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') - doctypes_to_be_ignored_list = singles - for doctype in self.doctypes_to_be_ignored: - doctypes_to_be_ignored_list.append(doctype.doctype_name) - - docfields = frappe.get_all('DocField', - filters = { - 'fieldtype': 'Link', - 'options': 'Company', - 'parent': ['not in', doctypes_to_be_ignored_list]}, - fields=['parent', 'fieldname']) - - for docfield in docfields: - if docfield['parent'] != self.doctype: - no_of_docs = frappe.db.count(docfield['parent'], { - docfield['fieldname'] : self.company - }) - - if no_of_docs > 0: - self.delete_version_log(docfield['parent'], docfield['fieldname']) - self.delete_communications(docfield['parent'], docfield['fieldname']) - - # populate DocTypes table - if docfield['parent'] not in tables: - self.append('doctypes', { - 'doctype_name' : docfield['parent'], - 'no_of_docs' : no_of_docs - }) - - # delete the docs linked with the specified company - frappe.db.delete(docfield['parent'], { - docfield['fieldname'] : self.company - }) - - naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') - if naming_series: - if '#' in naming_series: - self.update_naming_series(naming_series, docfield['parent']) + self.delete_company_transactions() def populate_doctypes_to_be_ignored_table(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() @@ -79,6 +38,111 @@ class TransactionDeletionRecord(Document): 'doctype_name' : doctype }) + def delete_bins(self): + frappe.db.sql("""delete from tabBin where warehouse in + (select name from tabWarehouse where company=%s)""", self.company) + + def delete_lead_addresses(self): + """Delete addresses to which leads are linked""" + leads = frappe.get_all('Lead', filters={'company': self.company}) + leads = ["'%s'" % row.get("name") for row in leads] + addresses = [] + if leads: + addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name + in ({leads})""".format(leads=",".join(leads))) + + if addresses: + addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] + + frappe.db.sql("""delete from tabAddress where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) + + frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' + and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) + + frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) + + def reset_company_values(self): + company_obj = frappe.get_doc('Company', self.company) + company_obj.total_monthly_sales = 0 + company_obj.sales_monthly_history = None + company_obj.save() + + def delete_company_transactions(self): + doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() + docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) + + tables = self.get_all_child_doctypes() + for docfield in docfields: + if docfield['parent'] != self.doctype: + no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname']) + + if no_of_docs > 0: + self.delete_version_log(docfield['parent'], docfield['fieldname']) + self.delete_communications(docfield['parent'], docfield['fieldname']) + self.populate_doctypes_table(tables, docfield['parent'], no_of_docs) + + self.delete_child_tables(docfield['parent'], docfield['fieldname']) + self.delete_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname']) + + naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') + if naming_series: + if '#' in naming_series: + self.update_naming_series(naming_series, docfield['parent']) + + def get_doctypes_to_be_ignored_list(self): + singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') + doctypes_to_be_ignored_list = singles + for doctype in self.doctypes_to_be_ignored: + doctypes_to_be_ignored_list.append(doctype.doctype_name) + + return doctypes_to_be_ignored_list + + def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list): + docfields = frappe.get_all('DocField', + filters = { + 'fieldtype': 'Link', + 'options': 'Company', + 'parent': ['not in', doctypes_to_be_ignored_list]}, + fields=['parent', 'fieldname']) + + return docfields + + def get_all_child_doctypes(self): + return frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') + + def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname): + return frappe.db.count(doctype, {company_fieldname : self.company}) + + def populate_doctypes_table(self, tables, doctype, no_of_docs): + if doctype not in tables: + self.append('doctypes', { + 'doctype_name' : doctype, + 'no_of_docs' : no_of_docs + }) + + def delete_child_tables(self, doctype, company_fieldname): + parent_docs_to_be_deleted = frappe.get_all(doctype, { + company_fieldname : self.company + }, pluck = 'name') + + child_tables = frappe.get_all('DocField', filters = { + 'fieldtype': 'Table', + 'parent': doctype + }, pluck = 'options') + + for table in child_tables: + frappe.db.delete(table, { + 'parent': ['in', parent_docs_to_be_deleted] + }) + + def delete_docs_linked_with_specified_company(self, doctype, company_fieldname): + frappe.db.delete(doctype, { + company_fieldname : self.company + }) + def update_naming_series(self, naming_series, doctype_name): if '.' in naming_series: prefix, hashes = naming_series.rsplit('.', 1) @@ -107,32 +171,6 @@ class TransactionDeletionRecord(Document): frappe.delete_doc('Communication', communication_names, ignore_permissions=True) - def delete_bins(self): - frappe.db.sql("""delete from tabBin where warehouse in - (select name from tabWarehouse where company=%s)""", self.company) - - def delete_lead_addresses(self): - """Delete addresses to which leads are linked""" - leads = frappe.get_all('Lead', filters={'company': self.company}) - leads = ["'%s'" % row.get("name") for row in leads] - addresses = [] - if leads: - addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name - in ({leads})""".format(leads=",".join(leads))) - - if addresses: - addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - - frappe.db.sql("""delete from tabAddress where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) - - frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) - - frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) - @frappe.whitelist() def get_doctypes_to_be_ignored(): doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget', From 57514f7b1dbb94171e7d7426f6554eee334bcc39 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 21 Jul 2021 00:46:34 +0530 Subject: [PATCH 1235/1264] feat(Non Profit): API Endpoint to update halted Razorpay subscriptions (#26427) (#26564) * feat: Update Subscription Activated field to Subscription Status to accomodate Halted status * feat: API Endpoint to halt Razorpay subscription * fix: sider * fix: validation message * test: halted razorpay subscription --- erpnext/non_profit/doctype/member/member.json | 16 ++-- erpnext/non_profit/doctype/member/member.py | 4 +- .../doctype/membership/membership.py | 92 ++++++++++++++----- .../doctype/membership/test_membership.py | 56 +++++++++-- erpnext/patches.txt | 1 + ...date_subscription_status_in_memberships.py | 9 ++ 6 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 erpnext/patches/v13_0/update_subscription_status_in_memberships.py diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index f190cfae75..7c1baf1a8d 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -26,7 +26,7 @@ "razorpay_details_section", "subscription_id", "customer_id", - "subscription_activated", + "subscription_status", "column_break_21", "subscription_start", "subscription_end" @@ -151,12 +151,6 @@ "fieldname": "column_break_21", "fieldtype": "Column Break" }, - { - "default": "0", - "fieldname": "subscription_activated", - "fieldtype": "Check", - "label": "Subscription Activated" - }, { "fieldname": "subscription_start", "fieldtype": "Date", @@ -166,11 +160,17 @@ "fieldname": "subscription_end", "fieldtype": "Date", "label": "Subscription End" + }, + { + "fieldname": "subscription_status", + "fieldtype": "Select", + "label": "Subscription Status", + "options": "\nActive\nHalted" } ], "image_field": "image", "links": [], - "modified": "2020-11-09 12:12:10.174647", + "modified": "2021-07-11 14:27:26.368039", "modified_by": "Administrator", "module": "Non Profit", "name": "Member", diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 30be585e9a..67828d6efc 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -84,7 +84,9 @@ def create_member(user_details): "email_id": user_details.email, "pan_number": user_details.pan or None, "membership_type": user_details.plan_id, - "subscription_id": user_details.subscription_id or None + "customer_id": user_details.customer_id or None, + "subscription_id": user_details.subscription_id or None, + "subscription_status": user_details.subscription_status or "" }) member.insert(ignore_permissions=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index e8ae6187b7..b584116df3 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings): return invoice -def get_member_based_on_subscription(subscription_id, email): - members = frappe.get_all("Member", filters={ - "subscription_id": subscription_id, - "email_id": email - }, order_by="creation desc") +def get_member_based_on_subscription(subscription_id, email=None, customer_id=None): + filters = {"subscription_id": subscription_id} + if email: + filters.update({"email_id": email}) + if customer_id: + filters.update({"customer_id": customer_id}) + + members = frappe.get_all("Member", filters=filters, order_by="creation desc") try: return frappe.get_doc("Member", members[0]["name"]) @@ -209,8 +212,6 @@ def get_member_based_on_subscription(subscription_id, email): def verify_signature(data, endpoint="Membership"): - if frappe.flags.in_test or os.environ.get("CI"): - return True signature = frappe.request.headers.get("X-Razorpay-Signature") settings = frappe.get_doc("Non Profit Settings") @@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"): @frappe.whitelist(allow_guest=True) def trigger_razorpay_subscription(*args, **kwargs): data = frappe.request.get_data(as_text=True) - try: - verify_signature(data) - except Exception as e: - log = frappe.log_error(e, "Membership Webhook Verification Error") - notify_failure(log) - return { "status": "Failed", "reason": e} - - if isinstance(data, six.string_types): - data = json.loads(data) - data = frappe._dict(data) + data = process_request_data(data) subscription = data.payload.get("subscription", {}).get("entity", {}) subscription = frappe._dict(subscription) @@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs): # Update membership values member.subscription_start = datetime.fromtimestamp(subscription.start_at) member.subscription_end = datetime.fromtimestamp(subscription.end_at) - member.subscription_activated = 1 + member.subscription_status = "Active" member.flags.ignore_mandatory = True member.save() @@ -294,9 +286,67 @@ def trigger_razorpay_subscription(*args, **kwargs): message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) - return { "status": "Failed", "reason": e} + return {"status": "Failed", "reason": e} - return { "status": "Success" } + return {"status": "Success"} + + +@frappe.whitelist(allow_guest=True) +def update_halted_razorpay_subscription(*args, **kwargs): + """ + When all retries have been exhausted, Razorpay moves the subscription to the halted state. + The customer has to manually retry the charge or change the card linked to the subscription, + for the subscription to move back to the active state. + """ + if frappe.request: + data = frappe.request.get_data(as_text=True) + data = process_request_data(data) + elif frappe.flags.in_test: + data = kwargs.get("data") + data = frappe._dict(data) + else: + return + + if not data.event == "subscription.halted": + return + + subscription = data.payload.get("subscription", {}).get("entity", {}) + subscription = frappe._dict(subscription) + + try: + member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id) + if not member: + frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id)) + + member.subscription_status = "Halted" + member.flags.ignore_mandatory = True + member.save() + + if subscription.get("notes"): + member = get_additional_notes(member, subscription) + + except Exception as e: + message = "{0}\n\n{1}".format(e, frappe.get_traceback()) + log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name)) + notify_failure(log) + return {"status": "Failed", "reason": e} + + return {"status": "Success"} + + +def process_request_data(data): + try: + verify_signature(data) + except Exception as e: + log = frappe.log_error(e, "Membership Webhook Verification Error") + notify_failure(log) + return {"status": "Failed", "reason": e} + + if isinstance(data, six.string_types): + data = json.loads(data) + data = frappe._dict(data) + + return data def get_company_for_memberships(): @@ -362,4 +412,4 @@ def set_expired_status(): `tabMembership` SET `status` = 'Expired' WHERE `status` not in ('Cancelled') AND `to_date` < %s - """, (nowdate())) \ No newline at end of file + """, (nowdate())) diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py index 31da792e53..0f5a9bed82 100644 --- a/erpnext/non_profit/doctype/membership/test_membership.py +++ b/erpnext/non_profit/doctype/membership/test_membership.py @@ -6,6 +6,7 @@ import unittest import frappe import erpnext from erpnext.non_profit.doctype.member.member import create_member +from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription from frappe.utils import nowdate, add_months class TestMembership(unittest.TestCase): @@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase): plan = setup_membership() # make test member - self.member_doc = create_member(frappe._dict({ - 'fullname': "_Test_Member", - 'email': "_test_member_erpnext@example.com", - 'plan_id': plan.name - })) + self.member_doc = create_member( + frappe._dict({ + "fullname": "_Test_Member", + "email": "_test_member_erpnext@example.com", + "plan_id": plan.name, + "subscription_id": "sub_DEX6xcJ1HSW4CR", + "customer_id": "cust_C0WlbKhp3aLA7W", + "subscription_status": "Active" + }) + ) self.member_doc.make_customer_and_link() self.member = self.member_doc.name @@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase): "to_date": add_months(nowdate(), 3), }) + def test_halted_memberships(self): + make_membership(self.member, { + "from_date": add_months(nowdate(), 2), + "to_date": add_months(nowdate(), 3) + }) + + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active") + payload = get_subscription_payload() + update_halted_razorpay_subscription(data=payload) + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted") + + def tearDown(self): + frappe.db.rollback() + def set_config(key, value): frappe.db.set_value("Non Profit Settings", None, key, value) @@ -115,4 +135,28 @@ def setup_membership(): else: plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm") - return plan \ No newline at end of file + return plan + +def get_subscription_payload(): + return { + "entity": "event", + "account_id": "acc_BFQ7uQEaa7j2z7", + "event": "subscription.halted", + "contains": [ + "subscription" + ], + "payload": { + "subscription": { + "entity": { + "id": "sub_DEX6xcJ1HSW4CR", + "entity": "subscription", + "plan_id": "_rzpy_test_milythm", + "customer_id": "cust_C0WlbKhp3aLA7W", + "status": "halted", + "notes": { + "Important": "Notes for Internal Reference" + }, + } + } + } + } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f63c7edea2..2a83635117 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -292,3 +292,4 @@ erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry +erpnext.patches.v13_0.update_subscription_status_in_memberships diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py new file mode 100644 index 0000000000..28e650e9ce --- /dev/null +++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + if frappe.db.exists('DocType', 'Member'): + frappe.reload_doc('Non Profit', 'doctype', 'Member') + + if frappe.db.has_column('Member', 'subscription_activated'): + frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1') + frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated') \ No newline at end of file From 8a64a84d1afdfd18cefcc99a0860ed63fff34b84 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Jul 2021 13:25:53 +0530 Subject: [PATCH 1236/1264] fix: GST Reports timeout issue --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 5 ++--- erpnext/regional/report/gstr_1/gstr_1.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) 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 641520437f..6a61ae2b42 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -214,9 +214,8 @@ class GSTR3BReport(Document): for d in item_details: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: self.is_nil_exempt.append(d.item_code) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index cfcb8c3444..b81fa810fe 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -217,9 +217,8 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) item_tax_rate = {} From f3158ea448ea9c545db6fd2ebc97110b9d77f998 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 21 Jul 2021 19:47:41 +0530 Subject: [PATCH 1237/1264] fix: removed Remarks column from AR/AP report --- .../report/accounts_receivable/accounts_receivable.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a11b77a6f6..b54646fd27 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -99,7 +99,6 @@ class ReceivablePayableReport(object): voucher_no = gle.voucher_no, party = gle.party, posting_date = gle.posting_date, - remarks = gle.remarks, account_currency = gle.account_currency, invoiced = 0.0, paid = 0.0, @@ -579,7 +578,7 @@ class ReceivablePayableReport(object): self.gl_entries = frappe.db.sql(""" select name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, - against_voucher_type, against_voucher, account_currency, remarks, {0} + against_voucher_type, against_voucher, account_currency, {0} from `tabGL Entry` where @@ -792,8 +791,6 @@ class ReceivablePayableReport(object): self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link', options='Supplier Group') - self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200) - def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120): if not fieldname: fieldname = scrub(label) if fieldtype=='Currency': options='currency' From 6928fc17c695c05c25d52be70b9e5ed8110cb4dd Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 21 Jul 2021 19:54:06 +0530 Subject: [PATCH 1238/1264] chore: remove warning rules semgrep-action doesn't consider severity, hence ignoring these rules for now. --- .github/helper/semgrep_rules/security.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml index 5a5098bf50..8b21979208 100644 --- a/.github/helper/semgrep_rules/security.yml +++ b/.github/helper/semgrep_rules/security.yml @@ -8,18 +8,3 @@ rules: dynamic content. Avoid it or use safe_eval(). languages: [python] severity: ERROR - -- id: frappe-sqli-format-strings - patterns: - - pattern-inside: | - @frappe.whitelist() - def $FUNC(...): - ... - - pattern-either: - - pattern: frappe.db.sql("..." % ...) - - pattern: frappe.db.sql(f"...", ...) - - pattern: frappe.db.sql("...".format(...), ...) - message: | - Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines - languages: [python] - severity: WARNING From 5b32fa5ccd8c4bf5a017a9ff7ad7b4112daaabd0 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 22 Jul 2021 14:00:01 +0530 Subject: [PATCH 1239/1264] fix: SQL error on fetching RM in production plan (bp #26592) * fix: SQL error on fetching RM in production plan * refactor: avoid passing by reference and mutations --- .../production_plan/production_plan.py | 15 ++++-------- .../production_plan/test_production_plan.py | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 38a0ee77ad..6a024f275a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -747,9 +747,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) -def get_warehouse_list(warehouses, warehouse_list=None): - if not warehouse_list: - warehouse_list = [] +def get_warehouse_list(warehouses): + warehouse_list = [] if isinstance(warehouses, str): warehouses = json.loads(warehouses) @@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None): else: warehouse_list.append(row.get("warehouse")) + return warehouse_list + @frappe.whitelist() def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) - warehouse_list = [] if warehouses: - get_warehouse_list(warehouses, warehouse_list) - - if warehouse_list: - warehouses = list(set(warehouse_list)) + warehouses = list(set(get_warehouse_list(warehouses))) if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses: warehouses.remove(doc.get("for_warehouse")) - warehouse_list = None - doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index cce1bb61b6..93e6d7a97f 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -10,7 +10,7 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests +from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list class TestProductionPlan(unittest.TestCase): def setUp(self): @@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase): pln.cancel() frappe.delete_doc("Production Plan", pln.name) + def test_get_warehouse_list_group(self): + """Check if required warehouses are returned""" + warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"} + + missing_warehouse = expected_warehouses - warehouses + + self.assertTrue(len(missing_warehouse) == 0, + msg=f"Following warehouses were expected {', '.join(missing_warehouse)}") + + def test_get_warehouse_list_single(self): + warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Scrap Warehouse - _TC", } + + self.assertEqual(warehouses, expected_warehouses) + + def create_production_plan(**args): args = frappe._dict(args) From 56c67743abe7520d3e0df19b807636abbee54d2d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 22 Jul 2021 16:10:06 +0530 Subject: [PATCH 1240/1264] fix: incorrect bom name (#26600) --- erpnext/manufacturing/doctype/bom/bom.js | 7 ++++--- erpnext/manufacturing/doctype/bom/bom.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 15a7c316c9..bfbc6790b2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -83,7 +83,7 @@ frappe.ui.form.on("BOM", { if (!frm.doc.__islocal && frm.doc.docstatus<2) { frm.add_custom_button(__("Update Cost"), function() { - frm.events.update_cost(frm); + frm.events.update_cost(frm, true); }); frm.add_custom_button(__("Browse BOM"), function() { frappe.route_options = { @@ -318,14 +318,15 @@ frappe.ui.form.on("BOM", { }) }, - update_cost: function(frm) { + update_cost: function(frm, save_doc=false) { return frappe.call({ doc: frm.doc, method: "update_cost", freeze: true, args: { update_parent: true, - from_child_bom:false + save: save_doc, + from_child_bom: false }, callback: function(r) { refresh_field("items"); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 2fbbca4b19..af081c449c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -330,7 +330,7 @@ class BOM(WebsiteGenerator): frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) if not from_child_bom: - frappe.msgprint(_("Cost Updated")) + frappe.msgprint(_("Cost Updated"), alert=True) def update_parent_cost(self): if self.total_cost: From 45d506c489df5b37d599c1c72ae6b16cc8809002 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 23 Jul 2021 16:40:45 +0530 Subject: [PATCH 1241/1264] fix: serial no and batch validation --- erpnext/controllers/stock_controller.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2526e6df0e..17bd7354f9 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -53,12 +53,17 @@ class StockController(AccountsController): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos for d in self.get("items"): if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no: - serial_nos = get_serial_nos(d.serial_no) - for serial_no_data in frappe.get_all("Serial No", - filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]): - if serial_no_data.batch_no != d.batch_no: + serial_nos = frappe.get_all("Serial No", + fields=["batch_no", "name", "warehouse"], + filters={ + "name": ("in", get_serial_nos(d.serial_no)) + } + ) + + for row in serial_nos: + if row.warehouse and row.batch_no != d.batch_no: frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}") - .format(d.idx, serial_no_data.name, d.batch_no)) + .format(d.idx, row.name, d.batch_no)) if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2: expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date") From 328444b530afea225103660a0c5fea9124e0f014 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 23 Jul 2021 20:28:02 +0530 Subject: [PATCH 1242/1264] fix(India): Default value for export type --- erpnext/patches.txt | 1 + .../v13_0/update_export_type_for_gst.py | 24 +++++++++++++++++++ erpnext/regional/india/setup.py | 2 -- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v13_0/update_export_type_for_gst.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2a83635117..b891719b02 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -293,3 +293,4 @@ erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships +erpnext.patches.v13_0.update_export_type_for_gst diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py new file mode 100644 index 0000000000..478a2a6c80 --- /dev/null +++ b/erpnext/patches/v13_0/update_export_type_for_gst.py @@ -0,0 +1,24 @@ +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # Update custom fields + fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'}) + if fieldname: + frappe.db.set_value('Custom Field', fieldname, 'default', '') + + fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'}) + if fieldname: + frappe.db.set_value('Custom Field', fieldname, 'default', '') + + # Update Customer/Supplier Masters + frappe.db.sql(""" + UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export') + """) + + frappe.db.sql(""" + UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas') + """) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 92654608da..e9372f9b8f 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -641,7 +641,6 @@ def make_custom_fields(update=True): 'label': 'Export Type', 'fieldtype': 'Select', 'insert_after': 'gst_category', - 'default': 'Without Payment of Tax', 'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)', 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } @@ -660,7 +659,6 @@ def make_custom_fields(update=True): 'label': 'Export Type', 'fieldtype': 'Select', 'insert_after': 'gst_category', - 'default': 'Without Payment of Tax', 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } From 9ef157b23b566369fd04ad68533288c41445b527 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 23 Jul 2021 20:44:34 +0530 Subject: [PATCH 1243/1264] fix: wrong operation time in Work Order (#26613) * fix: wrong operation time in Work Order Top level item time operation was not considering the BOM.quantity Co-authored-by: Ankush Menat --- .../doctype/work_order/work_order.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 0a8e5329c1..69812c7452 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -487,21 +487,20 @@ class WorkOrder(Document): return operations = [] - if not self.use_multi_level_bom: - bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") - operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) - else: + + if self.use_multi_level_bom: bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation() - bom_traversal = list(reversed(bom_tree.level_order_traversal())) - bom_traversal.append(bom_tree) # add operation on top level item last + bom_traversal = reversed(bom_tree.level_order_traversal()) - for d in bom_traversal: - if d.is_bom: - operations.extend(_get_operations(d.name, qty=d.exploded_qty)) + for node in bom_traversal: + if node.is_bom: + operations.extend(_get_operations(node.name, qty=node.exploded_qty)) - for correct_index, operation in enumerate(operations, start=1): - operation.idx = correct_index + bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") + operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) + for correct_index, operation in enumerate(operations, start=1): + operation.idx = correct_index self.set('operations', operations) self.calculate_time() From 017ed3f5c11b8e3467c24544f0df4dc7a36b6271 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 24 Jul 2021 00:08:02 +0530 Subject: [PATCH 1244/1264] fix: employee status server-side validation (#26615) --- erpnext/hr/doctype/appraisal/appraisal.py | 3 +- erpnext/hr/doctype/attendance/attendance.py | 2 ++ .../attendance_request/attendance_request.py | 3 +- .../compensatory_leave_request.py | 3 +- erpnext/hr/doctype/employee/employee.py | 8 +++-- erpnext/hr/doctype/employee/test_employee.py | 30 +++++++++++++++++-- .../employee_advance/employee_advance.py | 6 ++-- .../employee_checkin/employee_checkin.py | 4 ++- .../employee_promotion/employee_promotion.py | 5 ++-- .../employee_referral/employee_referral.py | 2 ++ .../employee_transfer/employee_transfer.py | 4 --- .../hr/doctype/expense_claim/expense_claim.py | 3 +- .../leave_application/leave_application.py | 3 +- .../leave_encashment/leave_encashment.py | 3 +- .../shift_assignment/shift_assignment.py | 2 ++ .../hr/doctype/shift_request/shift_request.py | 3 +- .../doctype/travel_request/travel_request.py | 4 ++- erpnext/hr/utils.py | 11 +++++-- .../additional_salary/additional_salary.py | 2 ++ .../employee_benefit_application.py | 3 +- .../employee_benefit_claim.py | 3 +- .../employee_incentive/employee_incentive.py | 2 ++ .../employee_tax_exemption_declaration.py | 3 +- ...employee_tax_exemption_proof_submission.py | 3 +- .../retention_bonus/retention_bonus.py | 5 ++-- .../doctype/salary_slip/salary_slip.py | 2 ++ .../projects/doctype/timesheet/timesheet.py | 3 ++ 27 files changed, 91 insertions(+), 34 deletions(-) diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py index f7601870fa..c2ed457984 100644 --- a/erpnext/hr/doctype/appraisal/appraisal.py +++ b/erpnext/hr/doctype/appraisal/appraisal.py @@ -9,7 +9,7 @@ from frappe.utils import flt, getdate from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee class Appraisal(Document): def validate(self): @@ -19,6 +19,7 @@ class Appraisal(Document): if not self.goals: frappe.throw(_("Goals cannot be empty")) + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_existing_appraisal() diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 3412675d81..f79f0fe418 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -8,11 +8,13 @@ from frappe.utils import getdate, nowdate from frappe import _ from frappe.model.document import Document from frappe.utils import cstr, get_datetime, formatdate +from erpnext.hr.utils import validate_active_employee class Attendance(Document): def validate(self): from erpnext.controllers.status_updater import validate_status validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"]) + validate_active_employee(self.employee) self.validate_attendance_date() self.validate_duplicate_record() self.validate_employee_status() diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py index 090d53262c..7f88fed73a 100644 --- a/erpnext/hr/doctype/attendance_request/attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/attendance_request.py @@ -8,10 +8,11 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import date_diff, add_days, getdate from erpnext.hr.doctype.employee.employee import is_holiday -from erpnext.hr.utils import validate_dates +from erpnext.hr.utils import validate_dates, validate_active_employee class AttendanceRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.from_date, self.to_date) if self.half_day: if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date): diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index a6fe429be1..0d7fded921 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.utils import date_diff, add_days, getdate, cint, format_date from frappe.model.document import Document -from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \ +from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \ get_holidays_for_employee, create_additional_leave_ledger_entry class CompensatoryLeaveRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.work_from_date, self.work_end_date) if self.half_day: if not self.half_day_date: diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index fa017d9d4c..5ca47560b1 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -13,8 +13,10 @@ from frappe.model.document import Document from erpnext.utilities.transaction_base import delete_events from frappe.utils.nestedset import NestedSet -class EmployeeUserDisabledError(frappe.ValidationError): pass -class EmployeeLeftValidationError(frappe.ValidationError): pass +class EmployeeUserDisabledError(frappe.ValidationError): + pass +class InactiveEmployeeStatusError(frappe.ValidationError): + pass class Employee(NestedSet): nsm_parent_field = 'reports_to' @@ -196,7 +198,7 @@ class Employee(NestedSet): message += "

    • " + "
    • ".join(link_to_employees) message += "

    " message += _("Please make sure the employees above report to another Active employee.") - throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee")) + throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee")) if not self.relieving_date: throw(_("Please enter relieving date.")) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 7d652a7366..8fc7cf1934 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -7,7 +7,7 @@ import frappe import erpnext import unittest import frappe.utils -from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError +from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError test_records = frappe.get_test_records('Employee') @@ -45,10 +45,33 @@ class TestEmployee(unittest.TestCase): employee2_doc.save() employee1_doc.reload() employee1_doc.status = 'Left' - self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) + self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) + + def test_employee_status_inactive(self): + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip + from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + + employee = make_employee("test_employee_status@company.com") + employee_doc = frappe.get_doc("Employee", employee) + employee_doc.status = "Inactive" + employee_doc.save() + employee_doc.reload() + + make_holiday_list() + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") + + frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""") + salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly", + employee=employee_doc.name, company=employee_doc.company) + salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name) + + self.assertRaises(InactiveEmployeeStatusError, salary_slip.save) + + def tearDown(self): + frappe.db.rollback() def make_employee(user, company=None, **kwargs): - "" if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", @@ -80,4 +103,5 @@ def make_employee(user, company=None, **kwargs): employee.insert() return employee.name else: + frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active") return frappe.get_value("Employee", {"employee_name":user}, "name") diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index cb72f6b6d9..ece627c50d 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import flt, nowdate from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account +from erpnext.hr.utils import validate_active_employee class EmployeeAdvanceOverPayment(frappe.ValidationError): pass @@ -18,6 +19,7 @@ class EmployeeAdvance(Document): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.set_status() def on_cancel(self): @@ -183,9 +185,9 @@ def make_return_entry(employee, company, employee_advance_name, return_amount, bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment) if not bank_cash_account: frappe.throw(_("Please set a Default Cash Account in Company defaults")) - + advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency') - + je = frappe.new_doc('Journal Entry') je.posting_date = nowdate() je.voucher_type = get_voucher_type(mode_of_payment) diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index 15fbd4e015..60ea0f9895 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -9,9 +9,11 @@ from frappe.model.document import Document from frappe import _ from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift +from erpnext.hr.utils import validate_active_employee class EmployeeCheckin(Document): def validate(self): + validate_active_employee(self.employee) self.validate_duplicate_log() self.fetch_shift() @@ -122,7 +124,7 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): """Given a set of logs in chronological order calculates the total working hours based on the parameters. Zero is returned for all invalid cases. - + :param logs: The List of 'Employee Checkin'. :param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin' :param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out' diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index 83fb235f92..a3a61834c8 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -7,12 +7,11 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee +from erpnext.hr.utils import update_employee, validate_active_employee class EmployeePromotion(Document): def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot promote Employee with status Left or Inactive")) + validate_active_employee(self.employee) def before_submit(self): if getdate(self.promotion_date) > getdate(): diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py index 45d68729ce..0493306166 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -7,9 +7,11 @@ import frappe from frappe import _ from frappe.utils import get_link_to_form from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeReferral(Document): def validate(self): + validate_active_employee(self.referrer) self.set_full_name() self.set_referral_bonus_payment_status() diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index 6eec9fa12a..c2007747fb 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -10,10 +10,6 @@ from frappe.utils import getdate from erpnext.hr.utils import update_employee class EmployeeTransfer(Document): - def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot transfer Employee with status Left or Inactive")) - def before_submit(self): if getdate(self.transfer_date) > getdate(): frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"), diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 5010fc3f75..8f8dbb2224 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -6,7 +6,7 @@ import frappe, erpnext from frappe import _ from frappe.utils import get_fullname, flt, cstr, get_link_to_form from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee from erpnext.accounts.party import get_party_account from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account @@ -23,6 +23,7 @@ class ExpenseClaim(AccountsController): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.validate_advances() self.validate_sanctioned_amount() self.calculate_total_amount() diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index cee6f374fd..93fb19f4a1 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate -from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange @@ -22,6 +22,7 @@ class LeaveApplication(Document): return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type) def validate(self): + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_balance_leaves() diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index e041b7fb8f..912bd8ad92 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate, nowdate, flt -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves @@ -15,6 +15,7 @@ from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leav class LeaveEncashment(Document): def validate(self): set_employee_name(self) + validate_active_employee(self.employee) self.get_leave_details_for_encashment() self.validate_salary_structure() diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index ab65260c09..89ae4d535d 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -9,10 +9,12 @@ from frappe.model.document import Document from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday +from erpnext.hr.utils import validate_active_employee from datetime import timedelta, datetime class ShiftAssignment(Document): def validate(self): + validate_active_employee(self.employee) self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 177c45edc6..6461f07552 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate -from erpnext.hr.utils import share_doc_with_approver +from erpnext.hr.utils import share_doc_with_approver, validate_active_employee class OverlapError(frappe.ValidationError): pass class ShiftRequest(Document): def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_shift_request_overlap_dates() self.validate_approver() diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py index 01d3f34706..60834d3f4a 100644 --- a/erpnext/hr/doctype/travel_request/travel_request.py +++ b/erpnext/hr/doctype/travel_request/travel_request.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class TravelRequest(Document): - pass + def validate(self): + validate_active_employee(self.employee) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index ebb1734347..a6a8406803 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -3,13 +3,12 @@ import erpnext import frappe -from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError from frappe import _ from frappe.desk.form import assign_to from frappe.model.document import Document from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, - get_datetime, getdate, nowdate, today, unique) - + get_datetime, getdate, nowdate, today, unique, get_link_to_form) class DuplicateDeclarationError(frappe.ValidationError): pass @@ -20,6 +19,7 @@ class EmployeeBoardingController(Document): Assign to the concerned person and roles as per the onboarding/separation template ''' def validate(self): + validate_active_employee(self.employee) # remove the task if linked before submitting the form if self.amended_from: for activity in self.activities: @@ -522,3 +522,8 @@ def share_doc_with_approver(doc, user): approver = approvers.get(doc.doctype) if doc_before_save.get(approver) != doc.get(approver): frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver)) + +def validate_active_employee(employee): + if frappe.db.get_value("Employee", employee, "status") == "Inactive": + frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format( + get_link_to_form("Employee", employee)), InactiveEmployeeStatusError) \ No newline at end of file diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 7db4b8686a..b978cbe2b5 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -7,6 +7,7 @@ import frappe from frappe.model.document import Document from frappe import _, bold from frappe.utils import getdate, date_diff, comma_and, formatdate +from erpnext.hr.utils import validate_active_employee class AdditionalSalary(Document): def on_submit(self): @@ -19,6 +20,7 @@ class AdditionalSalary(Document): self.update_employee_referral(cancel=True) def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_salary_structure() self.validate_recurring_additional_salary_overlap() diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py index 27df30a459..5ebe514ac0 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py @@ -9,10 +9,11 @@ from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt from frappe.model.document import Document from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure -from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount +from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee class EmployeeBenefitApplication(Document): def validate(self): + validate_active_employee(self.employee) self.validate_duplicate_on_payroll_period() if not self.max_benefits: self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period) diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py index d9937a7bb9..c6713f3aa4 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -8,12 +8,13 @@ from frappe import _ from frappe.utils import flt from frappe.model.document import Document from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits -from erpnext.hr.utils import get_previous_claimed_amount +from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure class EmployeeBenefitClaim(Document): def validate(self): + validate_active_employee(self.employee) max_benefits = get_max_benefits(self.employee, self.claim_date) if not max_benefits or max_benefits <= 0: frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee)) diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py index ead3db126f..6b918ba76d 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py @@ -6,9 +6,11 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeIncentive(Document): def validate(self): + validate_active_employee(self.employee) self.validate_salary_structure() def validate_salary_structure(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index fb71a2877a..e11d60a464 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -8,11 +8,12 @@ from frappe.model.document import Document from frappe import _ from frappe.utils import flt from frappe.model.mapper import get_mapped_doc -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionDeclaration(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.declarations) validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee) self.set_total_declared_amount() diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py index 5bc33a65f2..8131ae0fa8 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py @@ -7,11 +7,12 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import flt -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionProofSubmission(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.tax_exemption_proofs) self.set_total_actual_amount() self.set_total_exemption_amount() diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py index 049ea265cc..055bea7410 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py @@ -7,11 +7,10 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import getdate - +from erpnext.hr.utils import validate_active_employee class RetentionBonus(Document): def validate(self): - if frappe.get_value('Employee', self.employee, 'status') != 'Active': - frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees')) + validate_active_employee(self.employee) if getdate(self.bonus_payment_date) < getdate(): frappe.throw(_('Bonus Payment Date cannot be a past date')) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 81e5dc9f87..3e82c0d428 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -19,6 +19,7 @@ from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_appli from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry from erpnext.accounts.utils import get_fiscal_year +from erpnext.hr.utils import validate_active_employee from six import iteritems class SalarySlip(TransactionBase): @@ -39,6 +40,7 @@ class SalarySlip(TransactionBase): def validate(self): self.status = self.get_status() + validate_active_employee(self.employee) self.validate_dates() self.check_existing() if not self.salary_slip_based_on_timesheet: diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index c8bd80fca0..ae38d4ca19 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -15,12 +15,15 @@ from erpnext.manufacturing.doctype.workstation.workstation import (check_if_with WorkstationHolidayError) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.setup.utils import get_exchange_rate +from erpnext.hr.utils import validate_active_employee class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass class Timesheet(Document): def validate(self): + if self.employee: + validate_active_employee(self.employee) self.set_employee_name() self.set_status() self.validate_dates() From 4209b3bda9dce91efa816d7dd43a655210ef02a5 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 26 Jul 2021 13:00:32 +0530 Subject: [PATCH 1245/1264] fix: included company in Link Document Type filters for contact (#26576) (#26634) (cherry picked from commit cbddedab7bf2fc7637b861214c3373a742da830b) Co-authored-by: Noah Jacob --- erpnext/hooks.py | 3 ++- erpnext/public/js/contact.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 erpnext/public/js/contact.js diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 52daec9180..1ba752a146 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -24,7 +24,8 @@ doctype_js = { "Address": "public/js/address.js", "Communication": "public/js/communication.js", "Event": "public/js/event.js", - "Newsletter": "public/js/newsletter.js" + "Newsletter": "public/js/newsletter.js", + "Contact": "public/js/contact.js" } override_doctype_class = { diff --git a/erpnext/public/js/contact.js b/erpnext/public/js/contact.js new file mode 100644 index 0000000000..41a0e8a9f9 --- /dev/null +++ b/erpnext/public/js/contact.js @@ -0,0 +1,16 @@ + + +frappe.ui.form.on("Contact", { + refresh(frm) { + frm.set_query('link_doctype', "links", function() { + return { + query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes", + filters: { + fieldtype: ["in", ["HTML", "Text Editor"]], + fieldname: ["in", ["contact_html", "company_description"]], + } + }; + }); + frm.refresh_field("links"); + } +}); From 06fb0b93b5905ab0ba7acd6939a817ecebe717e9 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 26 Jul 2021 16:46:47 +0530 Subject: [PATCH 1246/1264] fix: Supplier invoice importer fix v13 (#26633) * fix: Supplier Invoice Importer fix Co-authored-by: Subin Tom --- erpnext/controllers/accounts_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4c313c43a7..cdd865ac4a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1112,8 +1112,11 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) - d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount')) d.outstanding = d.payment_amount + elif not d.invoice_portion: + d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount')) + def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] From d066eab6cd15ded671cbb5c34d8f4a8847a7ba64 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Jul 2021 18:38:50 +0530 Subject: [PATCH 1247/1264] fix: Test case for GSTR-3b report --- .../regional/doctype/gstr_3b_report/gstr_3b_report.py | 10 ++++++++-- erpnext/regional/report/gstr_1/gstr_1.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) 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 641520437f..6fd135d560 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -281,9 +281,15 @@ class GSTR3BReport(Document): if self.get('invoice_items'): # Build itemised tax for export invoices, nil and exempted where tax table is blank for invoice, items in iteritems(self.invoice_items): - if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type') - == "Without Payment of Tax"): + if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \ + == "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) + else: + for item in items.keys(): + if item in self.is_nil_exempt + self.is_non_gst and \ + item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []): + self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, []) + self.items_based_on_tax_rate[invoice][0].append(item) def set_outward_taxable_supplies(self): inter_state_supply_details = {} diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index cfcb8c3444..f9de2d527b 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -287,7 +287,8 @@ class Gstr1Report(object): # Build itemised tax for export invoices where tax table is blank for invoice, items in iteritems(self.invoice_items): if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \ - and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax": + and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \ + and self.invoices.get(invoice, {}).get('gst_category') == "Overseas": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) def get_columns(self): From 8d52a2270999f8fa37139aa02ec9bf839f95a343 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 26 Jul 2021 19:00:57 +0530 Subject: [PATCH 1248/1264] fix: Additional discount calculations in Invoices (#26553) * fix: Additional discount calculations in Invoices * revert: Client side handling for Dynamic GST Rates * fix: Add update item tax template method back * fix: Revert refresh field * fix: add company change trigger --- .../public/js/controllers/taxes_and_totals.js | 69 +++---------------- erpnext/public/js/controllers/transaction.js | 44 +++++++++++- 2 files changed, 53 insertions(+), 60 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 52efbb5f6c..53d5278bbf 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -65,28 +65,23 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.frm.refresh_fields(); }, - calculate_discount_amount: function(){ + calculate_discount_amount: function() { if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) { - this.calculate_item_values(); - this.calculate_net_total(); this.set_discount_amount(); this.apply_discount_amount(); } }, _calculate_taxes_and_totals: function() { - frappe.run_serially([ - () => this.validate_conversion_rate(), - () => this.calculate_item_values(), - () => this.update_item_tax_map(), - () => this.initialize_taxes(), - () => this.determine_exclusive_rate(), - () => this.calculate_net_total(), - () => this.calculate_taxes(), - () => this.manipulate_grand_total_for_inclusive_tax(), - () => this.calculate_totals(), - () => this._cleanup() - ]); + this.validate_conversion_rate(); + this.calculate_item_values(); + this.initialize_taxes(); + this.determine_exclusive_rate(); + this.calculate_net_total(); + this.calculate_taxes(); + this.manipulate_grand_total_for_inclusive_tax(); + this.calculate_totals(); + this._cleanup(); }, validate_conversion_rate: function() { @@ -107,7 +102,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, calculate_item_values: function() { - var me = this; + let me = this; if (!this.discount_amount_applied) { $.each(this.frm.doc["items"] || [], function(i, item) { frappe.model.round_floats_in(item); @@ -268,46 +263,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); }, - update_item_tax_map: function() { - let me = this; - let item_codes = []; - let item_rates = {}; - let item_tax_templates = {}; - - $.each(this.frm.doc.items || [], function(i, item) { - if (item.item_code) { - // Use combination of name and item code in case same item is added multiple times - item_codes.push([item.item_code, item.name]); - item_rates[item.name] = item.net_rate; - item_tax_templates[item.name] = item.item_tax_template; - } - }); - - if (item_codes.length) { - return this.frm.call({ - method: "erpnext.stock.get_item_details.get_item_tax_info", - args: { - company: me.frm.doc.company, - tax_category: cstr(me.frm.doc.tax_category), - item_codes: item_codes, - item_rates: item_rates, - item_tax_templates: item_tax_templates - }, - callback: function(r) { - if (!r.exc) { - $.each(me.frm.doc.items || [], function(i, item) { - if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) { - item.item_tax_template = r.message[item.name].item_tax_template; - item.item_tax_rate = r.message[item.name].item_tax_rate; - me.add_taxes_from_item_tax_template(item.item_tax_rate); - } - }); - } - } - }); - } - }, - add_taxes_from_item_tax_template: function(item_tax_map) { let me = this; @@ -632,8 +587,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); }); } - - this.frm.refresh_fields(); }, set_discount_amount: function() { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b3af3d67ea..5475383759 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -826,9 +826,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.run_serially([ () => me.frm.script_manager.trigger("currency"), + () => me.update_item_tax_map(), () => me.apply_default_taxes(), - () => me.apply_pricing_rule(), - () => me.calculate_taxes_and_totals() + () => me.apply_pricing_rule() ]); } } @@ -1787,6 +1787,46 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ ]); }, + update_item_tax_map: function() { + let me = this; + let item_codes = []; + let item_rates = {}; + let item_tax_templates = {}; + + $.each(this.frm.doc.items || [], function(i, item) { + if (item.item_code) { + // Use combination of name and item code in case same item is added multiple times + item_codes.push([item.item_code, item.name]); + item_rates[item.name] = item.net_rate; + item_tax_templates[item.name] = item.item_tax_template; + } + }); + + if (item_codes.length) { + return this.frm.call({ + method: "erpnext.stock.get_item_details.get_item_tax_info", + args: { + company: me.frm.doc.company, + tax_category: cstr(me.frm.doc.tax_category), + item_codes: item_codes, + item_rates: item_rates, + item_tax_templates: item_tax_templates + }, + callback: function(r) { + if (!r.exc) { + $.each(me.frm.doc.items || [], function(i, item) { + if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) { + item.item_tax_template = r.message[item.name].item_tax_template; + item.item_tax_rate = r.message[item.name].item_tax_rate; + me.add_taxes_from_item_tax_template(item.item_tax_rate); + } + }); + } + } + }); + } + }, + item_tax_template: function(doc, cdt, cdn) { var me = this; if(me.frm.updating_party_details) return; From fb72df7dce3124d36b4e86553fc509928a3585b2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 19:46:20 +0530 Subject: [PATCH 1249/1264] fix: Exchange rate revaluation posting date and precision fixes --- .../exchange_rate_revaluation.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 56193216a2..e94875f2d7 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -99,10 +99,12 @@ class ExchangeRateRevaluation(Document): sum(debit) - sum(credit) as balance from `tabGL Entry` where account in (%s) + and posting_date <= %s + and is_cancelled = 0 group by account, party_type, party having sum(debit) != sum(credit) order by account - """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1) + """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1) return account_details @@ -143,9 +145,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate":d.get("new_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name, }) @@ -154,9 +156,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - reverse_dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate": d.get("current_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name }) @@ -185,9 +187,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N account_details = {} company_currency = erpnext.get_company_currency(company) - balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False) + balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False) if balance: - balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party) + balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party) current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0 new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date) new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate From 5749e52bf6f4a67d4ee899b07edc2c2ac8434bf6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 19:46:50 +0530 Subject: [PATCH 1250/1264] fix: Ignore GL Entry on cancel --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index e94875f2d7..c8d5737d75 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document): if not (self.company and self.posting_date): frappe.throw(_("Please select Company and Posting Date to getting entries")) + def on_cancel(self): + self.ignore_linked_doctypes = ('GL Entry') + @frappe.whitelist() def check_journal_entry_condition(self): total_debit = frappe.db.get_value("Journal Entry Account", { From dc2cd35b933ee038940578325efbec7c2f522906 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 21:26:22 +0530 Subject: [PATCH 1251/1264] fix: Convert null values to empty string on grouping --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index c8d5737d75..f2b0a8c08a 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -104,7 +104,7 @@ class ExchangeRateRevaluation(Document): where account in (%s) and posting_date <= %s and is_cancelled = 0 - group by account, party_type, party + group by account, NULLIF(party_type,''), NULLIF(party,'') having sum(debit) != sum(credit) order by account """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1) From cfd73ed554b21550592dd57abc561bd8eb72f3a9 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 26 Jul 2021 19:42:19 +0530 Subject: [PATCH 1252/1264] ci: auto backport squashed commits based on labels (#26622) (#26640) (cherry picked from commit 057a0a98428b138b20646b820e7ab27c99bc5f22) Co-authored-by: Ankush --- .github/workflows/backport.yml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 7c6b8432b8..cc98f4544f 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,16 +1,25 @@ name: Backport on: - pull_request: + pull_request_target: types: - closed - labeled jobs: - backport: - runs-on: ubuntu-18.04 - name: Backport + main: + runs-on: ubuntu-latest steps: - - name: Backport - uses: tibdex/backport@v1 + - name: Checkout Actions + uses: actions/checkout@v2 with: - github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + repository: "ankush/backport" + path: ./actions + ref: develop + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Run backport + uses: ./actions/backport + with: + token: ${{secrets.BACKPORT_BOT_TOKEN}} + labelsToAdd: "backport" + title: "{{originalTitle}}" From aaea5edbdb2d14ce3599e9e5d47048cf032e3f6c Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 27 Jul 2021 09:39:33 +0530 Subject: [PATCH 1253/1264] fix: Salary component account filter (#26605) * fix: salary component account filter * fix: cleanup --- .../doctype/salary_component/salary_component.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js index dbf75140ac..e9e6f81862 100644 --- a/erpnext/payroll/doctype/salary_component/salary_component.js +++ b/erpnext/payroll/doctype/salary_component/salary_component.js @@ -4,11 +4,18 @@ frappe.ui.form.on('Salary Component', { setup: function(frm) { frm.set_query("account", "accounts", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = frappe.get_doc(cdt, cdn); + + let root_type = "Liability"; + if (frm.doc.type == "Deduction") { + root_type = "Expense"; + } + return { filters: { "is_group": 0, - "company": d.company + "company": d.company, + "root_type": root_type } }; }); From 6c48a2ed7949e33401dc6efe8682540bc3ba9c1c Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 27 Jul 2021 09:41:55 +0530 Subject: [PATCH 1254/1264] fix: Removed set_status after cancel from Expense Claim and Employee Advance (#25901) * fix: removed set_status after cancel from hr doctypes * fix: semgrep on_submit issue * fix: sider * fix: spaces * fix: update flag for db_set --- .../employee_advance/employee_advance.py | 3 +-- .../hr/doctype/expense_claim/expense_claim.py | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index ece627c50d..cbb3cc813b 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -24,7 +24,6 @@ class EmployeeAdvance(Document): def on_cancel(self): self.ignore_linked_doctypes = ('GL Entry') - self.set_status() def set_status(self): if self.docstatus == 0: @@ -231,4 +230,4 @@ def get_voucher_type(mode_of_payment=None): if mode_of_payment_type == "Bank": voucher_type = "Bank Entry" - return voucher_type \ No newline at end of file + return voucher_type diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 8f8dbb2224..95e2806aed 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -36,8 +36,8 @@ class ExpenseClaim(AccountsController): if self.task and not self.project: self.project = frappe.db.get_value("Task", self.task, "project") - def set_status(self): - self.status = { + def set_status(self, update=False): + status = { "0": "Draft", "1": "Submitted", "2": "Cancelled" @@ -45,14 +45,18 @@ class ExpenseClaim(AccountsController): paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount) precision = self.precision("grand_total") - if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 - and flt(self.grand_total, precision) == flt(paid_amount, precision))) \ - and self.docstatus == 1 and self.approval_status == 'Approved': - self.status = "Paid" + if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 + and flt(self.grand_total, precision) == flt(paid_amount, precision))) and self.approval_status == 'Approved': + status = "Paid" elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved': - self.status = "Unpaid" + status = "Unpaid" elif self.docstatus == 1 and self.approval_status == 'Rejected': - self.status = 'Rejected' + status = 'Rejected' + + if update: + self.db_set("status", status) + else: + self.status = status def on_update(self): share_doc_with_approver(self, self.expense_approver) @@ -75,7 +79,7 @@ class ExpenseClaim(AccountsController): if self.is_paid: update_reimbursed_amount(self) - self.set_status() + self.set_status(update=True) self.update_claimed_amount_in_employee_advance() def on_cancel(self): @@ -87,7 +91,6 @@ class ExpenseClaim(AccountsController): if self.is_paid: update_reimbursed_amount(self) - self.set_status() self.update_claimed_amount_in_employee_advance() def update_claimed_amount_in_employee_advance(self): From 5448aa25e71dca107d4868c05297a2030a2d4089 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 27 Jul 2021 10:18:53 +0530 Subject: [PATCH 1255/1264] chore: code owners updated (#26659) --- CODEOWNERS | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 219b6bb782..a4a14de1b8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,13 +21,13 @@ erpnext/quality_management/ @marination @rohitwaghchaure erpnext/shopping_cart/ @marination erpnext/stock/ @marination @rohitwaghchaure @ankush -erpnext/crm/ @ruchamahabal -erpnext/education/ @ruchamahabal -erpnext/healthcare/ @ruchamahabal -erpnext/hr/ @ruchamahabal +erpnext/crm/ @ruchamahabal @pateljannat +erpnext/education/ @ruchamahabal @pateljannat +erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand +erpnext/hr/ @ruchamahabal @pateljannat erpnext/non_profit/ @ruchamahabal -erpnext/payroll @ruchamahabal -erpnext/projects/ @ruchamahabal +erpnext/payroll @ruchamahabal @pateljannat +erpnext/projects/ @ruchamahabal @pateljannat erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination From 6e6823c3aa5555d9fa3f4a3061031c2e6ec59da4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Jul 2021 10:59:25 +0530 Subject: [PATCH 1256/1264] feat: Enhancements in TDS --- .../tax_withholding_category.json | 350 ++++++------------ .../tax_withholding_category.py | 24 +- .../test_tax_withholding_category.py | 47 ++- 3 files changed, 184 insertions(+), 237 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index f9160e281d..331770fbe8 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -1,263 +1,151 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "Prompt", - "beta": 0, - "creation": "2018-04-13 18:42:06.431683", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2018-04-13 18:42:06.431683", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "category_details_section", + "category_name", + "round_off_tax_amount", + "column_break_2", + "consider_party_ledger_amount", + "tax_on_excess_amount", + "section_break_8", + "rates", + "section_break_7", + "accounts" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "category_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": "Category 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "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": "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": "Tax Withholding Rates", - "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 + "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": "rates", "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": "Rates", - "length": 0, - "no_copy": 0, "options": "Tax Withholding Rate", - "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 + "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": "section_break_7", - "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, + "fieldname": "section_break_7", + "fieldtype": "Section Break", "label": "Account 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 - }, + "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": "accounts", - "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": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Tax Withholding 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Tax Withholding Account", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "category_details_section", + "fieldtype": "Section Break", + "label": "Category Details", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach", + "fieldname": "consider_party_ledger_amount", + "fieldtype": "Check", + "label": "Consider Entire Party Ledger Amount", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Tax will be withheld only for amount exceeding the cumulative threshold", + "fieldname": "tax_on_excess_amount", + "fieldtype": "Check", + "label": "Only Deduct Tax On Excess Amount ", + "show_days": 1, + "show_seconds": 1 + }, + { + "description": "Checking this will round off the tax amount to the nearest integer", + "fieldname": "round_off_tax_amount", + "fieldtype": "Data", + "label": "Round Off Tax Amount", + "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": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-07-17 22:53:26.193179", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Withholding Category", - "name_case": "", - "owner": "Administrator", + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-07-26 21:47:34.396071", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Withholding Category", + "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, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "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": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "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": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 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", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index b9ee4a0963..45c8e1b49f 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, getdate +from frappe.utils import flt, getdate, cint from erpnext.accounts.utils import get_fiscal_year class TaxWithholdingCategory(Document): @@ -86,7 +86,10 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): "rate": tax_rate_detail.tax_withholding_rate, "threshold": tax_rate_detail.single_threshold, "cumulative_threshold": tax_rate_detail.cumulative_threshold, - "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category + "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category, + "consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount, + "tax_on_excess_amount": tax_withholding.tax_on_excess_amount, + "round_off_tax_amount": tax_withholding.round_off_tax_amount }) def get_tax_withholding_rates(tax_withholding, fiscal_year): @@ -235,10 +238,15 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): tds_amount = 0 + invoice_filters = { + 'name': ('in', vouchers), + 'docstatus': 1 + } - supp_credit_amt = frappe.db.get_value('Purchase Invoice', { - 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1 - }, 'sum(net_total)') or 0.0 + if not cint(tax_details.consider_party_ledger_amount): + invoice_filters.update({'apply_tds': 1}) + + supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0 supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', { 'parent': ('in', vouchers), 'docstatus': 1, @@ -255,6 +263,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu cumulative_threshold = tax_details.get('cumulative_threshold', 0) if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): + if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount): + supp_credit_amt -= cumulative_threshold + if ldc and is_valid_certificate( ldc.valid_from, ldc.valid_upto, inv.get('posting_date') or inv.get('transaction_date'), tax_deducted, @@ -263,6 +274,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) else: tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 + + if cint(tax_details.round_off_tax_amount): + tds_amount = round(tds_amount) return tds_amount diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index dd26be7c99..2ba22ca435 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -87,6 +87,31 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_tax_withholding_category_checks(self): + invoices = [] + frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category") + + # First Invoice with no tds check + pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True) + pi.apply_tds = 0 + pi.save() + pi.submit() + invoices.append(pi) + + # Second Invoice will apply TDS checked + pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000) + pi1.submit() + invoices.append(pi1) + + # Cumulative threshold is 30000 + # Threshold calculation should be on both the invoices + # TDS should be applied only on 1000 + self.assertEqual(pi1.taxes[0].tax_amount, 1000) + + for d in invoices: + d.cancel() + + def test_cumulative_threshold_tcs(self): frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS") invoices = [] @@ -195,7 +220,7 @@ def create_sales_invoice(**args): def create_records(): # create a new suppliers - for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']: + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']: if frappe.db.exists('Supplier', name): continue @@ -311,3 +336,23 @@ def create_tax_with_holding_category(): 'account': 'TDS - _TC' }] }).insert() + + if not frappe.db.exists("Tax Withholding Category", "New TDS Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "New TDS Category", + "category_name": "New TDS Category", + "round_off_tax_amount": 1, + "consider_party_ledger_amount": 1, + "tax_on_excess_amount": 1, + "rates": [{ + 'fiscal_year': fiscal_year, + 'tax_withholding_rate': 10, + 'single_threshold': 0, + 'cumulative_threshold': 30000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() From b122a1eaa094c6e320dddd6c5b5f6716932f8028 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 27 Jul 2021 16:59:06 +0530 Subject: [PATCH 1257/1264] fix: reload manufacturing setting before patch (#26641) (#26670) (cherry picked from commit c8d7a8c781f6c448fd872427d611ffab70c136db) Co-authored-by: Ankush --- erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py index 48999e6f99..d7ad1fc696 100644 --- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -10,6 +10,7 @@ def execute(): if not frappe.db.has_column('Work Order', 'has_batch_no'): return + frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings') if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')): return @@ -107,4 +108,4 @@ def repost_future_sle_and_gle(doc): "company": doc.company }) - create_repost_item_valuation_entry(args) \ No newline at end of file + create_repost_item_valuation_entry(args) From af58ac9e104b0a278835880bcdebc47a4eaa38c7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 27 Jul 2021 18:43:20 +0530 Subject: [PATCH 1258/1264] fix: not able to add employee in the job card --- erpnext/manufacturing/doctype/job_card/job_card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 420bb00803..69c7f5c614 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -192,11 +192,11 @@ class JobCard(Document): "completed_qty": args.get("completed_qty") or 0.0 }) elif args.get("start_time"): - new_args = { + new_args = frappe._dict({ "from_time": get_datetime(args.get("start_time")), "operation": args.get("sub_operation"), "completed_qty": 0.0 - } + }) if employees: for name in employees: From ac2e139d5bdfe58fb03ee73ef88cf70d855b8caf Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 27 Jul 2021 20:44:59 +0200 Subject: [PATCH 1259/1264] fix: force reload of Opportunity in patch (#26668) --- erpnext/patches/v13_0/rename_issue_doctype_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py index fa1dfed643..41c51c36dc 100644 --- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py +++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py @@ -37,7 +37,7 @@ def execute(): if frappe.db.exists('DocType', 'Opportunity'): opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc') - frappe.reload_doc('crm', 'doctype', 'opportunity') + frappe.reload_doctype('Opportunity', force=True) rename_field('Opportunity', 'mins_to_first_response', 'first_response_time') # change fieldtype to duration From 64af124f88f2133790cd4d7f160f1fac9afbc409 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Jul 2021 10:43:02 +0530 Subject: [PATCH 1260/1264] fix(minor): Consider grand total for threshold check --- .../tax_withholding_category/tax_withholding_category.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 45c8e1b49f..020de3c3f3 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -243,10 +243,13 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu 'docstatus': 1 } + field = 'sum(net_total)' + if not cint(tax_details.consider_party_ledger_amount): invoice_filters.update({'apply_tds': 1}) + field = 'sum(grand_total)' - supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0 + supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0 supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', { 'parent': ('in', vouchers), 'docstatus': 1, From 93502499412e6a268f5d570b33b131af74b328f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Jul 2021 12:57:59 +0530 Subject: [PATCH 1261/1264] fix: Chnage fieldtype from data to check --- .../tax_withholding_category.json | 4 ++-- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_tds_check_field.py | 8 ++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v13_0/update_tds_check_field.py diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index 331770fbe8..153906ffe9 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -94,7 +94,7 @@ { "description": "Checking this will round off the tax amount to the nearest integer", "fieldname": "round_off_tax_amount", - "fieldtype": "Data", + "fieldtype": "Check", "label": "Round Off Tax Amount", "show_days": 1, "show_seconds": 1 @@ -102,7 +102,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-07-26 21:47:34.396071", + "modified": "2021-07-27 21:47:34.396071", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Category", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b891719b02..32763754d2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -294,3 +294,4 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_export_type_for_gst +erpnext.patches.v13_0.update_tds_check_field #3 diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py new file mode 100644 index 0000000000..16bf76d530 --- /dev/null +++ b/erpnext/patches/v13_0/update_tds_check_field.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + if frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): + frappe.db.sql(""" + UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0 + WHERE round_off_tax_amount IS NULL + """) \ No newline at end of file From 90c5cb0a3106c173c0607b9193d31d7cd4749e9f Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 28 Jul 2021 13:42:33 +0530 Subject: [PATCH 1262/1264] fix: documentation link for E Invoicing (#26685) --- .../regional/doctype/e_invoice_settings/e_invoice_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index cc2d9f06d2..54e488610d 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -3,7 +3,7 @@ frappe.ui.form.on('E Invoice Settings', { refresh(frm) { - const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing'; + const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing'; frm.dashboard.set_headline( __("Read {0} for more information on E Invoicing features.", [`documentation`]) ); From fa8e6ac7cd604fffaeecb4a4e73f4b08a20ad2b2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Jul 2021 15:30:05 +0530 Subject: [PATCH 1263/1264] fix: Patch --- erpnext/patches/v13_0/update_tds_check_field.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py index 16bf76d530..3d149586a0 100644 --- a/erpnext/patches/v13_0/update_tds_check_field.py +++ b/erpnext/patches/v13_0/update_tds_check_field.py @@ -1,7 +1,8 @@ import frappe def execute(): - if frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): + if frappe.db.has_table("Tax Withholding Category") \ + and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): frappe.db.sql(""" UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0 WHERE round_off_tax_amount IS NULL From 1b6dd84c0a25e143ec3ac92b3f439d6d90ca6573 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 28 Jul 2021 18:11:11 +0530 Subject: [PATCH 1264/1264] fix(bom): remove manual permission checking (#26689) (#26690) get_list does the permission checking. (cherry picked from commit d95f16ac8fb084e33ab936545fc60acd6a4ff618) Co-authored-by: Ankush --- erpnext/manufacturing/doctype/bom/bom.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index af081c449c..ebd9ae2dc5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1069,13 +1069,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if barcodes: or_cond_filters["name"] = ("in", barcodes) - for cond in get_match_cond(doctype, as_condition=False): - for key, value in cond.items(): - if key == doctype: - key = "name" - - query_filters[key] = ("in", value) - if filters and filters.get("item_code"): has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants") if not has_variants: @@ -1084,7 +1077,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if filters and filters.get("is_stock_item"): query_filters["is_stock_item"] = 1 - return frappe.get_all("Item", + return frappe.get_list("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, limit_start=start, limit_page_length=page_len, as_list=1)