From 9bf5f76ac835914bf5124227e564770f93680113 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 6 Apr 2022 17:33:46 +0530 Subject: [PATCH 01/10] fix: Deferred Revenue/Expense Account validation --- erpnext/accounts/deferred_revenue.py | 3 +-- .../doctype/journal_entry/journal_entry.json | 13 +++++++++-- .../process_deferred_accounting.py | 4 ++-- .../sales_invoice/test_sales_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 22 +++++++++++++++++++ 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 0611f880c5..a8776fa344 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -386,7 +386,6 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): doc, credit_account, debit_account, - against, amount, base_amount, end_date, @@ -570,7 +569,6 @@ def book_revenue_via_journal_entry( doc, credit_account, debit_account, - against, amount, base_amount, posting_date, @@ -591,6 +589,7 @@ def book_revenue_via_journal_entry( journal_entry.voucher_type = ( "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense" ) + journal_entry.process_deferred_accounting = deferred_process debit_entry = { "account": credit_account, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 335fd350de..4493c72254 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -3,7 +3,7 @@ "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", - "creation": "2013-03-25 10:53:52", + "creation": "2022-01-25 10:29:58.717206", "doctype": "DocType", "document_type": "Document", "engine": "InnoDB", @@ -13,6 +13,7 @@ "voucher_type", "naming_series", "finance_book", + "process_deferred_accounting", "reversal_of", "tax_withholding_category", "column_break1", @@ -524,13 +525,20 @@ "label": "Reversal Of", "options": "Journal Entry", "read_only": 1 + }, + { + "fieldname": "process_deferred_accounting", + "fieldtype": "Link", + "label": "Process Deferred Accounting", + "options": "Process Deferred Accounting", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, "links": [], - "modified": "2022-01-04 13:39:36.485954", + "modified": "2022-04-06 17:18:46.865259", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", @@ -578,6 +586,7 @@ "search_fields": "voucher_type,posting_date, due_date, cheque_no", "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py index 08a7f4110f..8ec726b36c 100644 --- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py @@ -11,7 +11,7 @@ from erpnext.accounts.deferred_revenue import ( convert_deferred_expense_to_expense, convert_deferred_revenue_to_income, ) -from erpnext.accounts.general_ledger import make_reverse_gl_entries +from erpnext.accounts.general_ledger import make_gl_entries class ProcessDeferredAccounting(Document): @@ -34,4 +34,4 @@ class ProcessDeferredAccounting(Document): filters={"against_voucher_type": self.doctype, "against_voucher": self.name}, ) - make_reverse_gl_entries(gl_entries=gl_entries) + make_gl_entries(gl_entries=gl_entries, cancel=1) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6c38a7e597..2309210dd8 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3104,7 +3104,7 @@ class TestSalesInvoice(unittest.TestCase): acc_settings = frappe.get_single("Accounts Settings") acc_settings.book_deferred_entries_via_journal_entry = 0 - acc_settings.submit_journal_entriessubmit_journal_entries = 0 + acc_settings.submit_journal_entries = 0 acc_settings.save() frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8a9318e184..7cbd2bd6c7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -180,6 +180,7 @@ class AccountsController(TransactionBase): else: self.validate_deferred_start_and_end_date() + self.validate_deferred_income_expense_account() self.set_inter_company_account() if self.doctype == "Purchase Invoice": @@ -208,6 +209,27 @@ class AccountsController(TransactionBase): (self.doctype, self.name), ) + def validate_deferred_income_expense_account(self): + field_map = { + "Sales Invoice": "deferred_revenue_account", + "Purchase Invoice": "deferred_expense_account", + } + + for item in self.get("items"): + if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): + if not item.get(field_map.get(self.doctype)): + default_deferred_account = frappe.db.get_value( + "Company", self.company, "default_" + field_map.get(self.doctype) + ) + if not default_deferred_account: + frappe.throw( + _( + "Row #{0}: Please update deferred revenue/expense account in item row or default account in company master" + ).format(item.idx) + ) + else: + item.set(field_map.get(self.doctype), default_deferred_account) + def validate_deferred_start_and_end_date(self): for d in self.items: if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"): From b532ade3839d502a0aab5a67db77654a2077066a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 8 Apr 2022 17:14:10 +0530 Subject: [PATCH 02/10] fix: Download JSON for GSTR-1 report --- erpnext/regional/report/gstr_1/gstr_1.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 9999a6d167..943bd2c3d2 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -78,8 +78,9 @@ frappe.query_reports["GSTR-1"] = { } }); - report.page.add_inner_button(__("Download as JSON"), function () { + let filters = report.get_values(); + frappe.call({ method: 'erpnext.regional.report.gstr_1.gstr_1.get_json', args: { From dfff4beaf47da9a044f0a293a7fc50f8cda3c51e Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 11 Apr 2022 14:10:57 +0530 Subject: [PATCH 03/10] fix: Handle multiple item transfer in separate SEs against WO - Check for pending qty in child items to show/hide "Start" button - If no qty needed to transfer (FG qty is fulfilled), but RM qty pending: map pending in SE with For Quantity = 0 --- erpnext/manufacturing/doctype/work_order/work_order.js | 6 ++++-- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 6433a99283..2751778fe4 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -540,8 +540,10 @@ erpnext.work_order = { || frm.doc.transfer_material_against == 'Job Card') ? 0 : 1; if (show_start_btn) { - if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) - && frm.doc.status != 'Stopped') { + let pending_to_transfer = frm.doc.required_items.some( + item => flt(item.transferred_qty) < flt(item.required_qty) + ) + if (pending_to_transfer && frm.doc.status != 'Stopped') { frm.has_start_btn = true; frm.add_custom_button(__('Create Pick List'), function() { erpnext.work_order.create_pick_list(frm); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1e624714d0..c4aa8a4711 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1803,7 +1803,9 @@ class StockEntry(StockController): or (desire_to_transfer > 0 and backflush_based_on == "Material Transferred for Manufacture") or allow_overproduction ): - item_dict[item]["qty"] = desire_to_transfer + # "No need for transfer but qty still pending to transfer" case can occur + # when transferring multiple RM in different Stock Entries + item_dict[item]["qty"] = desire_to_transfer if (desire_to_transfer > 0) else pending_to_issue elif pending_to_issue > 0: item_dict[item]["qty"] = pending_to_issue else: From 553178bfe72e7031a7fe775fc5d2136c9a5aefcf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Apr 2022 15:29:20 +0530 Subject: [PATCH 04/10] test: Add test --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 2309210dd8..7781fe3391 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2240,6 +2240,14 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") + def test_deferred_revenue_missing_account(self): + si = create_sales_invoice(posting_date="2019-01-10", do_not_submit=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + + self.assertRaises(frappe.ValidationError, si.save) + def test_fixed_deferred_revenue(self): deferred_account = create_account( account_name="Deferred Revenue", From be2e5ce966bb206777c35398ae6fb99f1dcd7e79 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 11 Apr 2022 15:32:36 +0530 Subject: [PATCH 05/10] style: Missing Semicolon --- erpnext/manufacturing/doctype/work_order/work_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 2751778fe4..20f15039ef 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -542,7 +542,7 @@ erpnext.work_order = { if (show_start_btn) { let pending_to_transfer = frm.doc.required_items.some( item => flt(item.transferred_qty) < flt(item.required_qty) - ) + ); if (pending_to_transfer && frm.doc.status != 'Stopped') { frm.has_start_btn = true; frm.add_custom_button(__('Create Pick List'), function() { From 5aa60bb65142a9dbd2fbbec5c26150cd559275e6 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 11 Apr 2022 16:44:55 +0530 Subject: [PATCH 06/10] test: Multiple RM transfer in separate Stock Entries - Added test and acceptance of 0 as For Quantity in test helper --- .../doctype/work_order/test_work_order.py | 50 +++++++++++++++++++ .../doctype/work_order/work_order.py | 6 ++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 8934f9c4e0..2aba48231b 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1144,6 +1144,56 @@ class TestWorkOrder(FrappeTestCase): for index, row in enumerate(ste_manu.get("items"), start=1): self.assertEqual(index, row.idx) + @change_settings( + "Manufacturing Settings", + {"backflush_raw_materials_based_on": "Material Transferred for Manufacture"}, + ) + def test_work_order_multiple_material_transfer(self): + """ + Test transferring multiple RMs in separate Stock Entries. + """ + work_order = make_wo_order_test_record(planned_start_date=now(), qty=1) + test_stock_entry.make_stock_entry( # stock up RM + item_code="_Test Item", + target="_Test Warehouse - _TC", + qty=1, + basic_rate=5000.0, + ) + test_stock_entry.make_stock_entry( # stock up RM + item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", + qty=2, + basic_rate=1000.0, + ) + + transfer_entry = frappe.get_doc( + make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1) + ) + del transfer_entry.get("items")[0] # transfer only one RM + transfer_entry.submit() + + # WO's "Material Transferred for Mfg" shows all is transferred, one RM is pending + work_order.reload() + self.assertEqual(work_order.material_transferred_for_manufacturing, 1) + self.assertEqual(work_order.required_items[0].transferred_qty, 0) + self.assertEqual(work_order.required_items[1].transferred_qty, 2) + + final_transfer_entry = frappe.get_doc( # transfer last RM with For Quantity = 0 + make_stock_entry(work_order.name, "Material Transfer for Manufacture", 0) + ) + final_transfer_entry.save() + + self.assertEqual(final_transfer_entry.fg_completed_qty, 0.0) + self.assertEqual(final_transfer_entry.items[0].qty, 1) + + final_transfer_entry.submit() + work_order.reload() + + # WO's "Material Transferred for Mfg" shows all is transferred, no RM is pending + self.assertEqual(work_order.material_transferred_for_manufacturing, 1) + self.assertEqual(work_order.required_items[0].transferred_qty, 1) + self.assertEqual(work_order.required_items[1].transferred_qty, 2) + def update_job_card(job_card, jc_qty=None): employee = frappe.db.get_value("Employee", {"status": "Active"}, "name") diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 2ee848c356..2802310250 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1186,7 +1186,11 @@ def make_stock_entry(work_order_id, purpose, qty=None): stock_entry.from_bom = 1 stock_entry.bom_no = work_order.bom_no stock_entry.use_multi_level_bom = work_order.use_multi_level_bom - stock_entry.fg_completed_qty = qty or (flt(work_order.qty) - flt(work_order.produced_qty)) + # accept 0 qty as well + stock_entry.fg_completed_qty = ( + qty if qty is not None else (flt(work_order.qty) - flt(work_order.produced_qty)) + ) + if work_order.bom_no: stock_entry.inspection_required = frappe.db.get_value( "BOM", work_order.bom_no, "inspection_required" From 60fb71bd2a6bfb0a223b233759c8aa8cbda7a1bb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Apr 2022 11:14:02 +0530 Subject: [PATCH 07/10] fix: ignore item-less maintenance visit for sr no (#30684) --- .../maintenance/doctype/maintenance_visit/maintenance_visit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 72686e7403..e2f6cb3a6c 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -12,6 +12,9 @@ frappe.ui.form.on('Maintenance Visit', { // filters for serial no based on item code if (frm.doc.maintenance_type === "Scheduled") { let item_code = frm.doc.purposes[0].item_code; + if (!item_code) { + return; + } frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule", args: { From 993c6c0de9c1e3cfc3c4673e3bc16542204022be Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 12 Apr 2022 12:24:47 +0530 Subject: [PATCH 08/10] feat: Ignore permlevel for specific fields --- erpnext/controllers/buying_controller.py | 3 +++ erpnext/controllers/selling_controller.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index eda36868b9..233b476a14 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -22,6 +22,9 @@ class QtyMismatchError(ValidationError): class BuyingController(StockController, Subcontracting): + def __setup__(self): + self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"] + def get_feed(self): if self.get("supplier_name"): return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 7877827ac7..19fedb3c38 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -16,6 +16,9 @@ from erpnext.stock.utils import get_incoming_rate class SellingController(StockController): + def __setup__(self): + self.flags.ignore_permlevel_for_fields = ["selling_price_list", "price_list_currency"] + def get_feed(self): return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total) From 9a1c560c8248fd0253a3c1ccbceb6f3eee92bcce Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Apr 2022 15:27:08 +0530 Subject: [PATCH 09/10] fix: Do not show disabled dimensions in reports --- .../accounting_dimension/accounting_dimension.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 897151a97b..445378300b 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -205,10 +205,16 @@ def get_doctypes_with_dimensions(): return frappe.get_hooks("accounting_dimension_doctypes") -def get_accounting_dimensions(as_list=True): +def get_accounting_dimensions(as_list=True, filters=None): + + if not filters: + filters = {"disabled": 0} + if frappe.flags.accounting_dimensions is None: frappe.flags.accounting_dimensions = frappe.get_all( - "Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"] + "Accounting Dimension", + fields=["label", "fieldname", "disabled", "document_type"], + filters=filters, ) if as_list: From 8261b2bb4f5c89d9c73e2d5514ca38d353fa5145 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 12 Apr 2022 15:30:49 +0530 Subject: [PATCH 10/10] refactor: trigger generate schedule when any change made in items table (#29874) * refactor: trigger generate schedule when any change made in items table * chore: added serial validation on server side * test: serials updated in schedules after save * fix: schedule not generating after updating some fields * feat: generate_schedule is triggered on_save when items table is changed * test: updated tests to check other field changes on save * chore: removed serial validation function for schedules table and added no_of_visits validation function * test: updated for manually deleted schedele rows * refactor: updated validate_items_table_change to return bool * test: updated test_schedule_with_serials to cover validate_items_table_change * fix: linting --- .../maintenance_schedule.js | 20 ------------- .../maintenance_schedule.py | 22 +++++++++++++- .../test_maintenance_schedule.py | 30 +++++++++++++++++++ 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 035290d8f1..5252798ba5 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -140,26 +140,6 @@ erpnext.maintenance.MaintenanceSchedule = class MaintenanceSchedule extends frap } } - start_date(doc, cdt, cdn) { - this.set_no_of_visits(doc, cdt, cdn); - } - - end_date(doc, cdt, cdn) { - this.set_no_of_visits(doc, cdt, cdn); - } - - periodicity(doc, cdt, cdn) { - this.set_no_of_visits(doc, cdt, cdn); - } - - set_no_of_visits(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'); - - } - } }; extend_cscript(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({frm: cur_frm})); diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 9a23c07106..04c080cc72 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -213,6 +213,26 @@ class MaintenanceSchedule(TransactionBase): if chk: throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order)) + def validate_items_table_change(self): + doc_before_save = self.get_doc_before_save() + if not doc_before_save: + return + for prev_item, item in zip(doc_before_save.items, self.items): + fields = [ + "item_code", + "start_date", + "end_date", + "periodicity", + "sales_person", + "no_of_visits", + "serial_no", + ] + for field in fields: + b_doc = prev_item.as_dict() + doc = item.as_dict() + if cstr(b_doc[field]) != cstr(doc[field]): + return True + def validate_no_of_visits(self): return len(self.schedules) != sum(d.no_of_visits for d in self.items) @@ -221,7 +241,7 @@ class MaintenanceSchedule(TransactionBase): self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() - if not self.schedules or self.validate_no_of_visits(): + if not self.schedules or self.validate_items_table_change() or self.validate_no_of_visits(): self.generate_schedule() def on_update(self): diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index a98cd10e32..2268e0f7af 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -123,6 +123,36 @@ class TestMaintenanceSchedule(unittest.TestCase): frappe.db.rollback() + def test_schedule_with_serials(self): + # Checks whether serials are automatically updated when changing in items table. + # Also checks if other fields trigger generate schdeule if changed in items table. + item_code = "_Test Serial Item" + make_serial_item_with_serial(item_code) + ms = make_maintenance_schedule(item_code=item_code, serial_no="TEST001, TEST002") + ms.save() + + # Before Save + self.assertEqual(ms.schedules[0].serial_no, "TEST001, TEST002") + self.assertEqual(ms.schedules[0].sales_person, "Sales Team") + self.assertEqual(len(ms.schedules), 4) + self.assertFalse(ms.validate_items_table_change()) + # After Save + ms.items[0].serial_no = "TEST001" + ms.items[0].sales_person = "_Test Sales Person" + ms.items[0].no_of_visits = 2 + self.assertTrue(ms.validate_items_table_change()) + ms.save() + self.assertEqual(ms.schedules[0].serial_no, "TEST001") + self.assertEqual(ms.schedules[0].sales_person, "_Test Sales Person") + self.assertEqual(len(ms.schedules), 2) + # When user manually deleted a row from schedules table. + ms.schedules.pop() + self.assertEqual(len(ms.schedules), 1) + ms.save() + self.assertEqual(len(ms.schedules), 2) + + frappe.db.rollback() + def make_serial_item_with_serial(item_code): serial_item_doc = create_item(item_code, is_stock_item=1)