From 6a394c5be70a6d5b02f50571206b82c90599f9a9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 21 Dec 2022 12:11:49 +0530 Subject: [PATCH 01/43] fix: typerror on multi warehouse in Packed Items DN(with bundled item with varying warehouses)-> Sales Invoice. (cherry picked from commit e684eb32d0cf62f67f2b1de30ec7368d36708321) --- erpnext/accounts/report/gross_profit/gross_profit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index b71b31a5ec2..6bd3ee5aa6b 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -551,6 +551,7 @@ class GrossProfitGenerator(object): return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) else: return flt(row.qty) * self.get_average_buying_rate(row, item_code) + return 0.0 def get_buying_amount(self, row, item_code): # IMP NOTE From d58719a30a2355ecd7930961ed0e6b3a243f05a9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 21 Dec 2022 12:37:13 +0530 Subject: [PATCH 02/43] test: type error on bundled products with different warehouses (cherry picked from commit 5918bb03f7db388a27cb9319530b56c383304242) --- .../report/gross_profit/test_gross_profit.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index 1279dec25af..d9febb74fd4 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -6,6 +6,8 @@ from frappe.utils import add_days, flt, nowdate from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.gross_profit.gross_profit import execute +from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice +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.stock_entry_utils import make_stock_entry @@ -14,6 +16,7 @@ class TestGrossProfit(FrappeTestCase): def setUp(self): self.create_company() self.create_item() + self.create_bundle() self.create_customer() self.create_sales_invoice() self.clear_old_entries() @@ -42,6 +45,7 @@ class TestGrossProfit(FrappeTestCase): self.company = company.name self.cost_center = company.cost_center self.warehouse = "Stores - " + abbr + self.finished_warehouse = "Finished Goods - " + abbr self.income_account = "Sales - " + abbr self.expense_account = "Cost of Goods Sold - " + abbr self.debit_to = "Debtors - " + abbr @@ -53,6 +57,23 @@ class TestGrossProfit(FrappeTestCase): ) self.item = item if isinstance(item, str) else item.item_code + def create_bundle(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + + item2 = create_item( + item_code="_Test GP Item 2", is_stock_item=1, company=self.company, warehouse=self.warehouse + ) + self.item2 = item2 if isinstance(item2, str) else item2.item_code + + # This will be parent item + bundle = create_item( + item_code="_Test GP bundle", is_stock_item=0, company=self.company, warehouse=self.warehouse + ) + self.bundle = bundle if isinstance(bundle, str) else bundle.item_code + + # Create Product Bundle + self.product_bundle = make_product_bundle(parent=self.bundle, items=[self.item, self.item2]) + def create_customer(self): name = "_Test GP Customer" if frappe.db.exists("Customer", name): @@ -93,6 +114,28 @@ class TestGrossProfit(FrappeTestCase): ) return sinv + def create_delivery_note( + self, item=None, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False + ): + """ + Helper function to populate default values in Delivery Note + """ + dnote = create_delivery_note( + company=self.company, + customer=self.customer, + currency="INR", + item=item or self.item, + qty=qty, + rate=rate, + cost_center=self.cost_center, + warehouse=self.warehouse, + return_against=None, + expense_account=self.expense_account, + do_not_save=do_not_save, + do_not_submit=do_not_submit, + ) + return dnote + def clear_old_entries(self): doctype_list = [ "Sales Invoice", @@ -206,3 +249,55 @@ class TestGrossProfit(FrappeTestCase): } gp_entry = [x for x in data if x.parent_invoice == sinv.name] self.assertDictContainsSubset(expected_entry_with_dn, gp_entry[0]) + + def test_bundled_delivery_note_with_different_warehouses(self): + """ + Test Delivery Note with bundled item. Packed Item from the bundle having different warehouses + """ + se = make_stock_entry( + company=self.company, + item_code=self.item, + target=self.warehouse, + qty=1, + basic_rate=100, + do_not_submit=True, + ) + item = se.items[0] + se.append( + "items", + { + "item_code": self.item2, + "s_warehouse": "", + "t_warehouse": self.finished_warehouse, + "qty": 1, + "basic_rate": 100, + "conversion_factor": item.conversion_factor or 1.0, + "transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0), + "serial_no": item.serial_no, + "batch_no": item.batch_no, + "cost_center": item.cost_center, + "expense_account": item.expense_account, + }, + ) + se = se.save().submit() + + # Make a Delivery note with Product bundle + # Packed Items will have different warehouses + dnote = self.create_delivery_note(item=self.bundle, qty=1, rate=200, do_not_submit=True) + dnote.packed_items[1].warehouse = self.finished_warehouse + dnote = dnote.submit() + + # make Sales Invoice for above delivery note + sinv = make_sales_invoice(dnote.name) + sinv = sinv.save().submit() + + filters = frappe._dict( + company=self.company, + from_date=nowdate(), + to_date=nowdate(), + group_by="Invoice", + sales_invoice=sinv.name, + ) + + columns, data = execute(filters=filters) + self.assertGreater(len(data), 0) From 8e1c0cd234ac1d74fa452c84bb13b27da91ec475 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 15 Sep 2022 11:27:35 +0530 Subject: [PATCH 03/43] fix: No permission to read doctype (cherry picked from commit c0da948a4ef9f5fb1dd5764ee1908bd6f0475c7e) --- .../bank_clearance_summary/bank_clearance_summary.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 9d2deea523b..449ebdcd924 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -22,8 +22,7 @@ def get_columns(): { "label": _("Payment Document Type"), "fieldname": "payment_document_type", - "fieldtype": "Link", - "options": "Doctype", + "fieldtype": "Data", "width": 130, }, { @@ -33,15 +32,15 @@ def get_columns(): "options": "payment_document_type", "width": 140, }, - {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100}, + {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120}, {"label": _("Cheque/Reference No"), "fieldname": "cheque_no", "width": 120}, - {"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 100}, + {"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 120}, { "label": _("Against Account"), "fieldname": "against", "fieldtype": "Link", "options": "Account", - "width": 170, + "width": 200, }, {"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 120}, ] From 1068d0ec63f7e240b08d52426dac3216826fdbcc Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 24 Dec 2022 18:11:04 +0530 Subject: [PATCH 04/43] fix: `shipping_address` in PO (cherry picked from commit 7e1b6b3c2aa114331ad9085cfefee340b8ca2ad0) # Conflicts: # erpnext/buying/doctype/purchase_order/purchase_order.json --- .../purchase_order/purchase_order.json | 151 +++++++++++++++++- .../doctype/sales_order/sales_order.py | 6 +- 2 files changed, 153 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 2144ae00366..12ad27f0d53 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -113,6 +113,43 @@ "disable_rounded_total", "in_words", "advance_paid", +<<<<<<< HEAD +======= + "discount_section", + "apply_discount_on", + "base_discount_amount", + "column_break_45", + "additional_discount_percentage", + "discount_amount", + "sec_tax_breakup", + "other_charges_calculation", + "address_and_contact_tab", + "section_addresses", + "supplier_address", + "address_display", + "col_break_address", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "shipping_address_section", + "shipping_address", + "column_break_99", + "shipping_address_display", + "company_billing_address_section", + "billing_address", + "column_break_103", + "billing_address_display", + "drop_ship", + "customer", + "customer_name", + "column_break_19", + "customer_contact_person", + "customer_contact_display", + "customer_contact_mobile", + "customer_contact_email", + "terms_tab", +>>>>>>> 7e1b6b3c2a (fix: `shipping_address` in PO) "payment_schedule_section", "payment_terms_template", "payment_schedule", @@ -370,7 +407,7 @@ { "fieldname": "shipping_address", "fieldtype": "Link", - "label": "Company Shipping Address", + "label": "Shipping Address", "options": "Address", "print_hide": 1 }, @@ -1164,13 +1201,125 @@ "fieldtype": "Link", "label": "Project", "options": "Project" +<<<<<<< HEAD +======= + }, + { + "default": "0", + "fieldname": "is_old_subcontracting_flow", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Old Subcontracting Flow", + "read_only": 1 + }, + { + "depends_on": "is_internal_supplier", + "fieldname": "set_from_warehouse", + "fieldtype": "Link", + "label": "Set From Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "dashboard", + "fieldtype": "Tab Break", + "label": "Dashboard", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_40", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_53", + "fieldtype": "Column Break" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address & Contact" + }, + { + "fieldname": "company_billing_address_section", + "fieldtype": "Section Break", + "label": "Company Billing Address" + }, + { + "collapsible": 1, + "fieldname": "additional_info_section", + "fieldtype": "Section Break", + "label": "Additional Info", + "oldfieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "tax_withholding_net_total", + "fieldtype": "Currency", + "hidden": 1, + "label": "Tax Withholding Net Total", + "no_copy": 1, + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "base_tax_withholding_net_total", + "fieldtype": "Currency", + "hidden": 1, + "label": "Base Tax Withholding Net Total", + "no_copy": 1, + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_99", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_103", + "fieldtype": "Column Break" + }, + { + "fieldname": "incoterm", + "fieldtype": "Link", + "label": "Incoterm", + "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" + }, + { + "fieldname": "shipping_address_section", + "fieldtype": "Section Break", + "label": "Shipping Address" +>>>>>>> 7e1b6b3c2a (fix: `shipping_address` in PO) } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-11-17 12:34:36.033363", +======= + "modified": "2022-12-25 18:08:59.074182", +>>>>>>> 7e1b6b3c2a (fix: `shipping_address` in PO) "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 303e4fff4af..6b624c2421f 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1097,8 +1097,6 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): target.discount_amount = 0.0 target.inter_company_order_reference = "" target.shipping_rule = "" - target.customer = "" - target.customer_name = "" target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") @@ -1125,9 +1123,11 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): "contact_email", "contact_person", "taxes_and_charges", - "shipping_address", "terms", ], + "field_map": [ + ["shipping_address_name", "shipping_address"], + ], "validation": {"docstatus": ["=", 1]}, }, "Sales Order Item": { From 91dad909ec5676e087e3a0312a5482ac9940d4e6 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 25 Dec 2022 21:40:49 +0530 Subject: [PATCH 05/43] chore: conflicts --- .../purchase_order/purchase_order.json | 149 ------------------ 1 file changed, 149 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 12ad27f0d53..6358896e7dc 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -113,43 +113,6 @@ "disable_rounded_total", "in_words", "advance_paid", -<<<<<<< HEAD -======= - "discount_section", - "apply_discount_on", - "base_discount_amount", - "column_break_45", - "additional_discount_percentage", - "discount_amount", - "sec_tax_breakup", - "other_charges_calculation", - "address_and_contact_tab", - "section_addresses", - "supplier_address", - "address_display", - "col_break_address", - "contact_person", - "contact_display", - "contact_mobile", - "contact_email", - "shipping_address_section", - "shipping_address", - "column_break_99", - "shipping_address_display", - "company_billing_address_section", - "billing_address", - "column_break_103", - "billing_address_display", - "drop_ship", - "customer", - "customer_name", - "column_break_19", - "customer_contact_person", - "customer_contact_display", - "customer_contact_mobile", - "customer_contact_email", - "terms_tab", ->>>>>>> 7e1b6b3c2a (fix: `shipping_address` in PO) "payment_schedule_section", "payment_terms_template", "payment_schedule", @@ -1201,125 +1164,13 @@ "fieldtype": "Link", "label": "Project", "options": "Project" -<<<<<<< HEAD -======= - }, - { - "default": "0", - "fieldname": "is_old_subcontracting_flow", - "fieldtype": "Check", - "hidden": 1, - "label": "Is Old Subcontracting Flow", - "read_only": 1 - }, - { - "depends_on": "is_internal_supplier", - "fieldname": "set_from_warehouse", - "fieldtype": "Link", - "label": "Set From Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "terms_tab", - "fieldtype": "Tab Break", - "label": "Terms" - }, - { - "fieldname": "more_info_tab", - "fieldtype": "Tab Break", - "label": "More Info" - }, - { - "fieldname": "dashboard", - "fieldtype": "Tab Break", - "label": "Dashboard", - "show_dashboard": 1 - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_40", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_53", - "fieldtype": "Column Break" - }, - { - "fieldname": "address_and_contact_tab", - "fieldtype": "Tab Break", - "label": "Address & Contact" - }, - { - "fieldname": "company_billing_address_section", - "fieldtype": "Section Break", - "label": "Company Billing Address" - }, - { - "collapsible": 1, - "fieldname": "additional_info_section", - "fieldtype": "Section Break", - "label": "Additional Info", - "oldfieldtype": "Section Break" - }, - { - "default": "0", - "fieldname": "tax_withholding_net_total", - "fieldtype": "Currency", - "hidden": 1, - "label": "Tax Withholding Net Total", - "no_copy": 1, - "options": "currency", - "read_only": 1 - }, - { - "fieldname": "base_tax_withholding_net_total", - "fieldtype": "Currency", - "hidden": 1, - "label": "Base Tax Withholding Net Total", - "no_copy": 1, - "options": "currency", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_99", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_103", - "fieldtype": "Column Break" - }, - { - "fieldname": "incoterm", - "fieldtype": "Link", - "label": "Incoterm", - "options": "Incoterm" - }, - { - "depends_on": "incoterm", - "fieldname": "named_place", - "fieldtype": "Data", - "label": "Named Place" - }, - { - "fieldname": "shipping_address_section", - "fieldtype": "Section Break", - "label": "Shipping Address" ->>>>>>> 7e1b6b3c2a (fix: `shipping_address` in PO) } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-11-17 12:34:36.033363", -======= "modified": "2022-12-25 18:08:59.074182", ->>>>>>> 7e1b6b3c2a (fix: `shipping_address` in PO) "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", From 820e82265bc00d2c08d8b2837915b5ebbfbd566e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 25 Dec 2022 21:46:58 +0530 Subject: [PATCH 06/43] chore: linter --- erpnext/stock/doctype/stock_entry/stock_entry.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d53f4cdbac9..b27abae513e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -10,7 +10,6 @@ import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -from six import iteritems, itervalues, string_types from frappe.utils import ( add_days, cint, @@ -23,6 +22,7 @@ from frappe.utils import ( nowdate, today, ) +from six import iteritems, itervalues, string_types import erpnext from erpnext.accounts.general_ledger import process_gl_map @@ -2625,4 +2625,3 @@ def get_incorrect_stock_entries() -> Dict: stock_entries.setdefault(row.name, row) return stock_entries - From 3aa335d36ab30ac2fd097d4cb4c19a30cd2f31a0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 26 Dec 2022 10:12:35 +0530 Subject: [PATCH 07/43] refactor: Customer and Supplier Ledger summary will have hidden fields for better handling of user permission (#33432) --- .../customer_ledger_summary.py | 72 +++++++++++++++++++ .../supplier_ledger_summary.js | 18 ----- .../customer_group/customer_group.json | 14 +++- .../supplier_group/supplier_group.json | 16 ++++- .../setup/doctype/territory/territory.json | 14 +++- 5 files changed, 110 insertions(+), 24 deletions(-) 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 3beaa2bfe74..f5b46bde586 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -27,6 +27,7 @@ class PartyLedgerSummaryReport(object): ) self.get_gl_entries() + self.get_additional_columns() self.get_return_invoices() self.get_party_adjustment_amounts() @@ -34,6 +35,42 @@ class PartyLedgerSummaryReport(object): data = self.get_data() return columns, data + def get_additional_columns(self): + """ + Additional Columns for 'User Permission' based access control + """ + from frappe import qb + + if self.filters.party_type == "Customer": + self.territories = frappe._dict({}) + self.customer_group = frappe._dict({}) + + customer = qb.DocType("Customer") + result = ( + frappe.qb.from_(customer) + .select( + customer.name, customer.territory, customer.customer_group, customer.default_sales_partner + ) + .where((customer.disabled == 0)) + .run(as_dict=True) + ) + + for x in result: + self.territories[x.name] = x.territory + self.customer_group[x.name] = x.customer_group + else: + self.supplier_group = frappe._dict({}) + supplier = qb.DocType("Supplier") + result = ( + frappe.qb.from_(supplier) + .select(supplier.name, supplier.supplier_group) + .where((supplier.disabled == 0)) + .run(as_dict=True) + ) + + for x in result: + self.supplier_group[x.name] = x.supplier_group + def get_columns(self): columns = [ { @@ -117,6 +154,35 @@ class PartyLedgerSummaryReport(object): }, ] + # Hidden columns for handling 'User Permissions' + if self.filters.party_type == "Customer": + columns += [ + { + "label": _("Territory"), + "fieldname": "territory", + "fieldtype": "Link", + "options": "Territory", + "hidden": 1, + }, + { + "label": _("Customer Group"), + "fieldname": "customer_group", + "fieldtype": "Link", + "options": "Customer Group", + "hidden": 1, + }, + ] + else: + columns += [ + { + "label": _("Supplier Group"), + "fieldname": "supplier_group", + "fieldtype": "Link", + "options": "Supplier Group", + "hidden": 1, + } + ] + return columns def get_data(self): @@ -144,6 +210,12 @@ class PartyLedgerSummaryReport(object): ), ) + if self.filters.party_type == "Customer": + self.party_data[gle.party].update({"territory": self.territories.get(gle.party)}) + self.party_data[gle.party].update({"customer_group": self.customer_group.get(gle.party)}) + else: + self.party_data[gle.party].update({"supplier_group": self.supplier_group.get(gle.party)}) + amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr) self.party_data[gle.party].closing_balance += amount diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js index f81297760ed..5dc4c3d1c15 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -63,24 +63,6 @@ frappe.query_reports["Supplier Ledger Summary"] = { "fieldtype": "Link", "options": "Payment Terms Template" }, - { - "fieldname":"territory", - "label": __("Territory"), - "fieldtype": "Link", - "options": "Territory" - }, - { - "fieldname":"sales_partner", - "label": __("Sales Partner"), - "fieldtype": "Link", - "options": "Sales Partner" - }, - { - "fieldname":"sales_person", - "label": __("Sales Person"), - "fieldtype": "Link", - "options": "Sales Person" - }, { "fieldname":"tax_id", "label": __("Tax Id"), diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json index 0e2ed9efcf8..d6a431ea616 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.json +++ b/erpnext/setup/doctype/customer_group/customer_group.json @@ -139,10 +139,11 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2021-02-08 17:01:52.162202", + "modified": "2022-12-24 11:15:17.142746", "modified_by": "Administrator", "module": "Setup", "name": "Customer Group", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_customer_group", "owner": "Administrator", "permissions": [ @@ -198,10 +199,19 @@ "role": "Customer", "select": 1, "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 } ], "search_fields": "parent_customer_group", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.json b/erpnext/setup/doctype/supplier_group/supplier_group.json index 9119bb947cb..b3ed608cd03 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.json +++ b/erpnext/setup/doctype/supplier_group/supplier_group.json @@ -6,6 +6,7 @@ "creation": "2013-01-10 16:34:24", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "supplier_group_name", "parent_supplier_group", @@ -106,10 +107,11 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 18:10:49.228407", + "modified": "2022-12-24 11:16:12.486719", "modified_by": "Administrator", "module": "Setup", "name": "Supplier Group", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_supplier_group", "owner": "Administrator", "permissions": [ @@ -156,8 +158,18 @@ "permlevel": 1, "read": 1, "role": "Purchase User" + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 } ], "show_name_in_global_search": 1, - "sort_order": "ASC" + "sort_field": "modified", + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/erpnext/setup/doctype/territory/territory.json b/erpnext/setup/doctype/territory/territory.json index a25bda054b9..c3a49933746 100644 --- a/erpnext/setup/doctype/territory/territory.json +++ b/erpnext/setup/doctype/territory/territory.json @@ -123,11 +123,12 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2021-02-08 17:10:03.767426", + "modified": "2022-12-24 11:16:39.964956", "modified_by": "Administrator", "module": "Setup", "name": "Territory", "name_case": "Title Case", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_territory", "owner": "Administrator", "permissions": [ @@ -175,10 +176,19 @@ "role": "Customer", "select": 1, "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 } ], "search_fields": "parent_territory,territory_manager", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file From c3b9059c1b8a5a829c0391dd1a97db33cf9adc3d Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 21 Dec 2022 17:33:37 +0530 Subject: [PATCH 08/43] feat: provision to setup opening balances for earnings and deductions while creating SSA --- .../payroll_settings/payroll_settings.json | 10 +- .../doctype/salary_slip/salary_slip.py | 25 ++++- .../doctype/salary_slip/test_salary_slip.py | 98 +++++++++++++++++++ .../salary_structure_assignment.js | 39 +++++++- .../salary_structure_assignment.json | 26 ++++- .../salary_structure_assignment.py | 76 ++++++++++++++ 6 files changed, 269 insertions(+), 5 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json index 54377e94b30..f4db6f099a6 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json @@ -11,6 +11,7 @@ "max_working_hours_against_timesheet", "include_holidays_in_total_working_days", "disable_rounded_total", + "define_opening_balance_for_earning_and_deductions", "column_break_11", "daily_wages_fraction_for_half_day", "email_salary_slip_to_employee", @@ -91,13 +92,20 @@ "fieldname": "show_leave_balances_in_salary_slip", "fieldtype": "Check", "label": "Show Leave Balances in Salary Slip" + }, + { + "default": "0", + "description": "If checked, then the system will enable the provision to set the opening balance for earnings and deductions till date while creating a Salary Structure Assignment (if any)", + "fieldname": "define_opening_balance_for_earning_and_deductions", + "fieldtype": "Check", + "label": "Define Opening Balance for Earning and Deductions" } ], "icon": "fa fa-cog", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-03 17:49:59.579723", + "modified": "2022-12-21 17:30:08.704247", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Settings", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index b2243838202..232b5f39a7d 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1063,7 +1063,26 @@ class SalarySlip(TransactionBase): ) exempted_amount = flt(exempted_amount[0][0]) if exempted_amount else 0 - return taxable_earnings - exempted_amount + opening_taxable_earning = self.get_opening_for( + "taxable_earnings_till_date", start_date, end_date + ) + + return (taxable_earnings + opening_taxable_earning) - exempted_amount + + def get_opening_for(self, field_to_select, start_date, end_date): + return ( + frappe.db.get_value( + "Salary Structure Assignment", + { + "employee": self.employee, + "salary_structure": self.salary_structure, + "from_date": ["between", [start_date, end_date]], + "docstatus": 1, + }, + field_to_select, + ) + or 0 + ) def get_tax_paid_in_period(self, start_date, end_date, tax_component): # find total_tax_paid, tax paid for benefit, additional_salary @@ -1092,7 +1111,9 @@ class SalarySlip(TransactionBase): )[0][0] ) - return total_tax_paid + tax_deducted_till_date = self.get_opening_for("tax_deducted_till_date", start_date, end_date) + + return total_tax_paid + tax_deducted_till_date def get_taxable_earnings( self, allow_tax_exemption=False, based_on_payment_days=0, payroll_period=None diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 6e3b57239d4..32d0c7ed08f 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -1030,6 +1030,104 @@ class TestSalarySlip(FrappeTestCase): activity_type.wage_rate = 25 activity_type.save() + def test_salary_slip_generation_against_opening_entries_in_ssa(self): + import math + + from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + + payroll_period = frappe.db.get_value( + "Payroll Period", + { + "company": "_Test Company", + "start_date": ["<=", "2023-03-31"], + "end_date": [">=", "2022-04-01"], + }, + "name", + ) + + if not payroll_period: + payroll_period = create_payroll_period( + name="_Test Payroll Period for Tax", + company="_Test Company", + start_date="2022-04-01", + end_date="2023-03-31", + ) + else: + payroll_period = frappe.get_cached_doc("Payroll Period", payroll_period) + + emp = make_employee( + "test_employee_ss_with_opening_balance@salary.com", + company="_Test Company", + **{"date_of_joining": "2021-12-01"}, + ) + employee_doc = frappe.get_doc("Employee", emp) + + create_tax_slab(payroll_period, allow_tax_exemption=True) + + salary_structure_name = "Test Salary Structure for Opening Balance" + if not frappe.db.exists("Salary Structure", salary_structure_name): + salary_structure_doc = make_salary_structure( + salary_structure_name, + "Monthly", + company="_Test Company", + employee=emp, + from_date="2022-04-01", + payroll_period=payroll_period, + test_tax=True, + ) + + # validate no salary slip exists for the employee + self.assertTrue( + frappe.db.count( + "Salary Slip", + { + "employee": emp, + "salary_structure": salary_structure_doc.name, + "docstatus": 1, + "start_date": [">=", "2022-04-01"], + }, + ) + == 0 + ) + + remaining_sub_periods = get_period_factor( + emp, + get_first_day("2022-10-01"), + get_last_day("2022-10-01"), + "Monthly", + payroll_period, + depends_on_payment_days=0, + )[1] + + prev_period = math.ceil(remaining_sub_periods) + + annual_tax = 93036 # 89220 #data[0].get('applicable_tax') + monthly_tax_amount = 7732.40 # 7435 #annual_tax/12 + annual_earnings = 933600 # data[0].get('ctc') + monthly_earnings = 77800 # annual_earnings/12 + + # Get Salary Structure Assignment + ssa = frappe.get_value( + "Salary Structure Assignment", + {"employee": emp, "salary_structure": salary_structure_doc.name}, + "name", + ) + ssa_doc = frappe.get_doc("Salary Structure Assignment", ssa) + + # Set opening balance for earning and tax deduction in Salary Structure Assignment + ssa_doc.taxable_earnings_till_date = monthly_earnings * prev_period + ssa_doc.tax_deducted_till_date = monthly_tax_amount * prev_period + ssa_doc.save() + + # Create Salary Slip + salary_slip = make_salary_slip( + salary_structure_doc.name, employee=employee_doc.name, posting_date=getdate("2022-10-01") + ) + for deduction in salary_slip.deductions: + if deduction.salary_component == "TDS": + self.assertEqual(deduction.amount, rounded(monthly_tax_amount)) + def get_no_of_days(): no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year, getdate(nowdate()).month) diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js index 6cd897e95d1..7cb573d6307 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js @@ -42,6 +42,13 @@ frappe.ui.form.on('Salary Structure Assignment', { }); }, + refresh: function(frm) { + if(frm.doc.__onload){ + frm.unhide_earnings_and_taxation_section = frm.doc.__onload.earning_and_deduction_entries_does_not_exists; + frm.trigger("set_earnings_and_taxation_section_visibility"); + } + }, + employee: function(frm) { if(frm.doc.employee){ frappe.call({ @@ -59,6 +66,8 @@ frappe.ui.form.on('Salary Structure Assignment', { } } }); + + frm.trigger("valiadte_joining_date_and_salary_slips"); } else{ frm.set_value("company", null); @@ -71,5 +80,33 @@ frappe.ui.form.on('Salary Structure Assignment', { frm.set_value("payroll_payable_account", r.default_payroll_payable_account); }); } - } + }, + + valiadte_joining_date_and_salary_slips: function(frm) { + frappe.call({ + method: "earning_and_deduction_entries_does_not_exists", + doc: frm.doc, + callback: function(data) { + let earning_and_deduction_entries_does_not_exists = data.message; + frm.unhide_earnings_and_taxation_section = earning_and_deduction_entries_does_not_exists; + frm.trigger("set_earnings_and_taxation_section_visibility"); + } + }); + }, + + set_earnings_and_taxation_section_visibility: function(frm) { + if(frm.unhide_earnings_and_taxation_section){ + frm.set_df_property('earnings_and_taxation_section', 'hidden', 0); + } + else{ + frm.set_df_property('earnings_and_taxation_section', 'hidden', 1); + } + }, + + from_date: function(frm) { + if (frm.doc.from_date) { + frm.trigger("valiadte_joining_date_and_salary_slips" ); + } + }, + }); 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 c8b98e5aafc..15bd2b3cc6f 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json @@ -22,6 +22,10 @@ "base", "column_break_9", "variable", + "earnings_and_taxation_section", + "tax_deducted_till_date", + "column_break_18", + "taxable_earnings_till_date", "amended_from" ], "fields": [ @@ -141,11 +145,31 @@ "fieldtype": "Link", "label": "Payroll Payable Account", "options": "Account" + }, + { + "allow_on_submit": 1, + "fieldname": "earnings_and_taxation_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "tax_deducted_till_date", + "fieldtype": "Currency", + "label": "Tax Deducted Till Date" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "taxable_earnings_till_date", + "fieldtype": "Currency", + "label": "Taxable Earnings Till Date" } ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:44:46.267974", + "modified": "2022-12-21 17:06:38.602361", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Structure Assignment", diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py index e34e48e6c05..69fc5eb4917 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py @@ -13,10 +13,36 @@ class DuplicateAssignment(frappe.ValidationError): class SalaryStructureAssignment(Document): + def onload(self): + if self.employee: + self.set_onload( + "earning_and_deduction_entries_exists", self.earning_and_deduction_entries_does_not_exists() + ) + def validate(self): self.validate_dates() self.validate_income_tax_slab() self.set_payroll_payable_account() + self.valiadte_missing_taxable_earnings_and_deductions_till_date() + + def valiadte_missing_taxable_earnings_and_deductions_till_date(self): + if self.earning_and_deduction_entries_does_not_exists(): + if not self.taxable_earnings_till_date and not self.tax_deducted_till_date: + frappe.msgprint( + _( + """ + Not found any salary slip record(s) for the employee {0}.

+ Please specify {1} and {2} (if any), + for the correct tax calculation in future salary slips. + """ + ).format( + self.employee, + "" + _("Taxable Earnings Till Date") + "", + "" + _("Tax Deducted Till Date") + "", + ), + indicator="orange", + title=_("Warning"), + ) def validate_dates(self): joining_date, relieving_date = frappe.db.get_value( @@ -76,6 +102,56 @@ class SalaryStructureAssignment(Document): ) self.payroll_payable_account = payroll_payable_account + @frappe.whitelist() + def earning_and_deduction_entries_does_not_exists(self): + if self.enabled_settings_to_specify_earnings_and_deductions_till_date(): + if not self.joined_in_the_same_month() and not self.have_salary_slips(): + return True + else: + if self.docstatus in [1, 2] and ( + self.taxable_earnings_till_date or self.tax_deducted_till_date + ): + return True + return False + else: + return False + + def enabled_settings_to_specify_earnings_and_deductions_till_date(self): + """returns True if settings are enabled to specify earnings and deductions till date else False""" + + if frappe.db.get_single_value( + "Payroll Settings", "define_opening_balance_for_earning_and_deductions" + ): + return True + return False + + def have_salary_slips(self): + """returns True if salary structure assignment has salary slips else False""" + + salary_slip = frappe.db.get_value( + "Salary Slip", filters={"employee": self.employee, "docstatus": 1} + ) + + if salary_slip: + return True + + return False + + def joined_in_the_same_month(self): + """returns True if employee joined in same month as salary structure assignment from date else False""" + + date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining") + from_date = getdate(self.from_date) + + if not self.from_date or not date_of_joining: + return False + + elif date_of_joining.month == from_date.month: + return True + + else: + return False + def get_assigned_salary_structure(employee, on_date): if not employee or not on_date: From bc04e05b4658a0c70b46e96720aa341c3b985ff5 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 26 Dec 2022 12:08:44 +0530 Subject: [PATCH 09/43] fix: use get_all instead of get_value as get_value api dont supports between condition --- .../doctype/salary_slip/salary_slip.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 232b5f39a7d..0563541eaa5 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1063,27 +1063,26 @@ class SalarySlip(TransactionBase): ) exempted_amount = flt(exempted_amount[0][0]) if exempted_amount else 0 - opening_taxable_earning = self.get_opening_for( + opening_taxable_earning = self.get_opening_balance_for( "taxable_earnings_till_date", start_date, end_date ) return (taxable_earnings + opening_taxable_earning) - exempted_amount - def get_opening_for(self, field_to_select, start_date, end_date): - return ( - frappe.db.get_value( - "Salary Structure Assignment", - { - "employee": self.employee, - "salary_structure": self.salary_structure, - "from_date": ["between", [start_date, end_date]], - "docstatus": 1, - }, - field_to_select, - ) - or 0 + def get_opening_balance_for(self, field_to_select, start_date, end_date): + opening_balance = frappe.db.get_all( + "Salary Structure Assignment", + { + "employee": self.employee, + "salary_structure": self.salary_structure, + "from_date": ["between", (start_date, end_date)], + "docstatus": 1, + }, + field_to_select, ) + return opening_balance[0].get(field_to_select) if opening_balance else 0.0 + def get_tax_paid_in_period(self, start_date, end_date, tax_component): # find total_tax_paid, tax paid for benefit, additional_salary total_tax_paid = flt( @@ -1111,7 +1110,9 @@ class SalarySlip(TransactionBase): )[0][0] ) - tax_deducted_till_date = self.get_opening_for("tax_deducted_till_date", start_date, end_date) + tax_deducted_till_date = self.get_opening_balance_for( + "tax_deducted_till_date", start_date, end_date + ) return total_tax_paid + tax_deducted_till_date From 7b813d6045a7e28b1fbc008c3e3063ab74aa617d Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 26 Dec 2022 12:22:34 +0530 Subject: [PATCH 10/43] fix: patch --- erpnext/patches/v11_0/create_salary_structure_assignments.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v11_0/create_salary_structure_assignments.py b/erpnext/patches/v11_0/create_salary_structure_assignments.py index b81e867b9dd..51b2a2cc0b1 100644 --- a/erpnext/patches/v11_0/create_salary_structure_assignments.py +++ b/erpnext/patches/v11_0/create_salary_structure_assignments.py @@ -13,8 +13,10 @@ from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assign def execute(): + frappe.reload_doc("Payroll", "doctype", "Payroll Settings") frappe.reload_doc("Payroll", "doctype", "Salary Structure") frappe.reload_doc("Payroll", "doctype", "Salary Structure Assignment") + frappe.db.sql( """ delete from `tabSalary Structure Assignment` From 64454e0d4e68cc9208ebe1daff2cad3a5d6055bf Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 26 Dec 2022 12:48:37 +0530 Subject: [PATCH 11/43] fix: provision to set tax_deducted_till_date after document is subnmmited --- .../salary_structure_assignment.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 15bd2b3cc6f..4db023c6d51 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json @@ -23,9 +23,9 @@ "column_break_9", "variable", "earnings_and_taxation_section", - "tax_deducted_till_date", - "column_break_18", "taxable_earnings_till_date", + "column_break_18", + "tax_deducted_till_date", "amended_from" ], "fields": [ @@ -147,11 +147,11 @@ "options": "Account" }, { - "allow_on_submit": 1, "fieldname": "earnings_and_taxation_section", "fieldtype": "Section Break" }, { + "allow_on_submit": 1, "fieldname": "tax_deducted_till_date", "fieldtype": "Currency", "label": "Tax Deducted Till Date" @@ -169,7 +169,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2022-12-21 17:06:38.602361", + "modified": "2022-12-26 12:47:42.521891", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Structure Assignment", From 19feebbcb6a30a57de79a74fd2189b7d4dce828e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 26 Dec 2022 11:39:58 +0530 Subject: [PATCH 12/43] fix: `shipping_address` for non-drop shipping item (cherry picked from commit 67a7ccf3cead654e66a3d1b5ccb253d90b19c43b) --- .../doctype/sales_order/sales_order.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 6b624c2421f..8b28e02797d 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1090,6 +1090,15 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): ] items_to_map = list(set(items_to_map)) + def is_drop_ship_order(target): + drop_ship = True + for item in target.items: + if not item.delivered_by_supplier: + drop_ship = False + break + + return drop_ship + def set_missing_values(source, target): target.supplier = "" target.apply_discount_on = "" @@ -1097,6 +1106,14 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): target.discount_amount = 0.0 target.inter_company_order_reference = "" target.shipping_rule = "" + + if is_drop_ship_order(target): + target.customer = source.customer + target.customer_name = source.customer_name + target.shipping_address = source.shipping_address_name + else: + target.customer = target.customer_name = target.shipping_address = None + target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") @@ -1123,11 +1140,9 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): "contact_email", "contact_person", "taxes_and_charges", + "shipping_address", "terms", ], - "field_map": [ - ["shipping_address_name", "shipping_address"], - ], "validation": {"docstatus": ["=", 1]}, }, "Sales Order Item": { From 79643a57169e9546fe71e70aa9f218c5a76f1a5e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 27 Dec 2022 17:49:38 +0530 Subject: [PATCH 13/43] chore: linter (#33455) fix: linter --- .../salary_structure_assignment.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py index 69fc5eb4917..21196c3e52d 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py @@ -30,11 +30,7 @@ class SalaryStructureAssignment(Document): if not self.taxable_earnings_till_date and not self.tax_deducted_till_date: frappe.msgprint( _( - """ - Not found any salary slip record(s) for the employee {0}.

- Please specify {1} and {2} (if any), - for the correct tax calculation in future salary slips. - """ + """Not found any salary slip record(s) for the employee {0}.

Please specify {1} and {2} (if any), for the correct tax calculation in future salary slips.""" ).format( self.employee, "" + _("Taxable Earnings Till Date") + "", From 334219e36a58865ca2faed5b7ad9a267243b9366 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Dec 2022 18:05:24 +0530 Subject: [PATCH 14/43] fix: Tax withheld vouchers naming rule (#33467) --- .../doctype/tax_withheld_vouchers/tax_withheld_vouchers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index ce8c0c37086..a573e7fc763 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -36,11 +36,11 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-09-13 23:40:41.479208", + "modified": "2022-12-28 23:40:41.479208", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", - "naming_rule": "Autoincrement", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", From 6e363a62db3bdd0340aef4a22f131a7e4816d1e0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 29 Dec 2022 09:34:51 +0530 Subject: [PATCH 15/43] fix: use base_net_amount in case of missing stock qty (#33457) (cherry picked from commit e3a0ce5d6328d2ef68fb00419775ce113889c133) --- .../item_wise_purchase_register.py | 5 +---- 1 file changed, 1 insertion(+), 4 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 c04b9c71252..d34c21348c8 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 @@ -53,9 +53,6 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum item_details = get_item_details() for d in item_list: - if not d.stock_qty: - continue - item_record = item_details.get(d.item_code) purchase_receipt = None @@ -94,7 +91,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum "expense_account": expense_account, "stock_qty": d.stock_qty, "stock_uom": d.stock_uom, - "rate": d.base_net_amount / d.stock_qty, + "rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount, "amount": d.base_net_amount, } ) From 5ec11bad4fecc0bd3a16b53952608ce34e76ed00 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 29 Dec 2022 10:31:06 +0530 Subject: [PATCH 16/43] fix: reconciled credit notes being fetched again in Payment Reconciliation tool (#33471) fix: reconciled cr note showing up as Payments --- .../payment_reconciliation/payment_reconciliation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 56c9ff07e26..f42583cb34c 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -172,22 +172,22 @@ class PaymentReconciliation(Document): query = ( qb.from_(gl) .select( - gl.voucher_type.as_("reference_type"), - gl.voucher_no.as_("reference_name"), + gl.against_voucher_type.as_("reference_type"), + gl.against_voucher.as_("reference_name"), (Sum(dr_or_cr) - Sum(reconciled_dr_or_cr)).as_("amount"), gl.posting_date, gl.account_currency.as_("currency"), ) .where( - (gl.voucher_type == voucher_type) - & (gl.voucher_no.isin(sub_query)) + (gl.against_voucher.isin(sub_query)) + & (gl.against_voucher_type == voucher_type) & (gl.is_cancelled == 0) & (gl.account == self.receivable_payable_account) & (gl.party_type == self.party_type) & (gl.party == self.party) ) .where(Criterion.all(conditions)) - .groupby(gl.voucher_no) + .groupby(gl.against_voucher) .having(qb.Field("amount") > 0) ) dr_cr_notes = query.run(as_dict=True) From acf8b464f30154ff804b9004e124de6bf362d9b7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 29 Dec 2022 10:41:36 +0530 Subject: [PATCH 17/43] fix: Conversion factor error for invoices without item code (petty expenses) (#32714) * fix: Set default uom conversion factor to 1 for invoices * chore: set default conversion_factor as 1 * chore: remove print statements (cherry picked from commit 617518389ac0c6459197e27f94efbcb14d409dbf) # Conflicts: # erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json --- .../doctype/sales_invoice_item/sales_invoice_item.json | 4 ++++ erpnext/controllers/accounts_controller.py | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) 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 9b62c6a8f37..1ef416cb115 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -843,7 +843,11 @@ "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-10-10 20:57:38.340026", +======= + "modified": "2022-12-28 16:17:33.484531", +>>>>>>> 617518389a (fix: Conversion factor error for invoices without item code (petty expenses) (#32714)) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b2dea226f4e..74f6b6542e5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -573,7 +573,12 @@ class AccountsController(TransactionBase): if bool(uom) != bool(stock_uom): # xor item.stock_uom = item.uom = uom or stock_uom - item.conversion_factor = get_uom_conv_factor(item.get("uom"), item.get("stock_uom")) + # UOM cannot be zero so substitute as 1 + item.conversion_factor = ( + get_uom_conv_factor(item.get("uom"), item.get("stock_uom")) + or item.get("conversion_factor") + or 1 + ) if self.doctype == "Purchase Invoice": self.set_expense_account(for_validate) From c8506355513ac55fc582603b8ffd7a7e4fa2144b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 16 Dec 2022 21:58:22 +0530 Subject: [PATCH 18/43] fix: ERR journals reported in AR/AP Exchange Rate Revaluation on Receivable/Payable will included in AR/AP report (cherry picked from commit b09eade3e4b41833e507410410f63b93d8d17de7) --- .../accounts_receivable.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 1ca95099fe8..6293357cca0 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -5,7 +5,7 @@ from collections import OrderedDict import frappe -from frappe import _, scrub +from frappe import _, qb, scrub from frappe.utils import cint, cstr, flt, getdate, nowdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -95,6 +95,9 @@ class ReceivablePayableReport(object): # Get return entries self.get_return_entries() + # Get Exchange Rate Revaluations + self.get_exchange_rate_revaluations() + self.data = [] for gle in self.gl_entries: self.update_voucher_balance(gle) @@ -258,7 +261,8 @@ class ReceivablePayableReport(object): row.invoice_grand_total = row.invoiced if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( - abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision + (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) + or (row.voucher_no in self.err_journals) ): # non-zero oustanding, we must consider this row @@ -1033,3 +1037,17 @@ class ReceivablePayableReport(object): "data": {"labels": self.ageing_column_labels, "datasets": rows}, "type": "percentage", } + + def get_exchange_rate_revaluations(self): + je = qb.DocType("Journal Entry") + results = ( + qb.from_(je) + .select(je.name) + .where( + (je.company == self.filters.company) + & (je.posting_date.lte(self.filters.report_date)) + & (je.voucher_type == "Exchange Rate Revaluation") + ) + .run() + ) + self.err_journals = [x[0] for x in results] if results else [] From ec15965a6c0576597cd2cbdd6678ca32aa8ce256 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 18 Dec 2022 12:39:54 +0530 Subject: [PATCH 19/43] test: err for party should be in AR/AP report (cherry picked from commit 2ed86760d7696b0129a0d01cc4287cfe001f3a66) --- .../test_accounts_receivable.py | 139 +++++++++++++++--- 1 file changed, 121 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index f38890e980c..09305a20ea1 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1,18 +1,51 @@ import unittest import frappe -from frappe.utils import add_days, getdate, today +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_days, flt, getdate, today +from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute class TestAccountsReceivable(unittest.TestCase): - def test_accounts_receivable(self): + def setUp(self): frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'") frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabJournal Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabExchange Rate Revaluation` where company='_Test Company 2'") + self.create_usd_account() + + def tearDown(self): + frappe.db.rollback() + + def create_usd_account(self): + name = "Debtors USD" + exists = frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"} + ) + if exists: + self.debtors_usd = exists[0].name + else: + debtors = frappe.get_doc( + "Account", + frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors"} + )[0].name, + ) + + debtors_usd = frappe.new_doc("Account") + debtors_usd.company = debtors.company + debtors_usd.account_name = "Debtors USD" + debtors_usd.account_currency = "USD" + debtors_usd.parent_account = debtors.parent_account + debtors_usd.account_type = debtors.account_type + self.debtors_usd = debtors_usd.save().name + + def test_accounts_receivable(self): filters = { "company": "_Test Company 2", "based_on_payment_terms": 1, @@ -24,7 +57,7 @@ class TestAccountsReceivable(unittest.TestCase): } # check invoice grand total and invoiced column's value for 3 payment terms - name = make_sales_invoice() + name = make_sales_invoice().name report = execute(filters) expected_data = [[100, 30], [100, 50], [100, 20]] @@ -65,8 +98,74 @@ class TestAccountsReceivable(unittest.TestCase): ], ) + @change_settings( + "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + ) + def test_exchange_revaluation_for_party(self): + """ + Exchange Revaluation for party on Receivable/Payable shoule be included + """ -def make_sales_invoice(): + company = "_Test Company 2" + customer = "_Test Customer 2" + + # Using Exchange Gain/Loss account for unrealized as well. + company_doc = frappe.get_doc("Company", company) + company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account + company_doc.save() + + si = make_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si.currency = "USD" + si.conversion_rate = 0.90 + si.debit_to = self.debtors_usd + si = si.save().submit() + + # Exchange Revaluation + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = company + err.posting_date = today() + accounts = err.get_accounts_data() + err.extend("accounts", accounts) + err.accounts[0].new_exchange_rate = 0.95 + row = err.accounts[0] + row.new_balance_in_base_currency = flt( + row.new_exchange_rate * flt(row.balance_in_account_currency) + ) + row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency) + err.set_total_gain_loss() + err = err.save().submit() + + # Submit JV for ERR + jv = frappe.get_doc(err.make_jv_entry()) + jv = jv.save() + for x in jv.accounts: + x.cost_center = get_default_cost_center(jv.company) + jv.submit() + + filters = { + "company": company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + report = execute(filters) + + expected_data_for_err = [0, -5, 0, 5] + row = [x for x in report[1] if x.voucher_type == jv.doctype and x.voucher_no == jv.name][0] + self.assertEqual( + expected_data_for_err, + [ + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + ], + ) + + +def make_sales_invoice(no_payment_schedule=False, do_not_submit=False): frappe.set_user("Administrator") si = create_sales_invoice( @@ -81,22 +180,26 @@ def make_sales_invoice(): do_not_save=1, ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), - ) + if not no_payment_schedule: + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), + ) - si.submit() + si = si.save() - return si.name + if not do_not_submit: + si = si.submit() + + return si def make_payment(docname): From cf133b2f1ce0a66164144071bd9cb300648ddf8f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 29 Dec 2022 14:45:10 +0530 Subject: [PATCH 20/43] fix: debit note not pulled on reconciliation tool --- .../payment_reconciliation/payment_reconciliation.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index f42583cb34c..1e75471c848 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -149,14 +149,16 @@ class PaymentReconciliation(Document): reconciled_dr_or_cr = ( gl["debit_in_account_currency"] - if dr_or_cr == gl["credit_in_account_currency"] + if dr_or_cr.name == "credit_in_account_currency" else gl["credit_in_account_currency"] ) + having_clause = qb.Field("amount") > 0 + if self.minimum_payment_amount: - conditions.append(dr_or_cr.gte(self.minimum_payment_amount)) + having_clause = qb.Field("amount") >= self.minimum_payment_amount if self.maximum_payment_amount: - conditions.append(dr_or_cr.lte(self.maximum_payment_amount)) + having_clause = having_clause & qb.Field("amount") <= self.maximum_payment_amount sub_query = ( qb.from_(doc) @@ -188,7 +190,7 @@ class PaymentReconciliation(Document): ) .where(Criterion.all(conditions)) .groupby(gl.against_voucher) - .having(qb.Field("amount") > 0) + .having(having_clause) ) dr_cr_notes = query.run(as_dict=True) return dr_cr_notes From 529fb411ab8a601f4329c0246d82f4e34bc10e3b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 30 Dec 2022 11:08:31 +0530 Subject: [PATCH 21/43] Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation" (cherry picked from commit 728dc1acf4db979e1dde7d950279d8335028e1a7) # Conflicts: # erpnext/hooks.py # erpnext/stock/doctype/stock_entry/stock_entry.py # erpnext/stock/doctype/stock_entry/test_stock_entry.py --- erpnext/hooks.py | 4 + .../stock/doctype/stock_entry/stock_entry.py | 99 ++++++++++++++++++- .../doctype/stock_entry/test_stock_entry.py | 44 +-------- 3 files changed, 106 insertions(+), 41 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 850daf9efed..6e784c7322a 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -525,8 +525,12 @@ scheduler_events = { "erpnext.hr.utils.allocate_earned_leaves", "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", +<<<<<<< HEAD "erpnext.crm.doctype.lead.lead.daily_open_lead", "erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries", +======= + "erpnext.crm.utils.open_leads_opportunities_based_on_todays_event", +>>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") ], "weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"], "monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"], diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b27abae513e..bc3934957b9 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -4,12 +4,12 @@ import json from collections import defaultdict -from typing import Dict import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum +<<<<<<< HEAD from frappe.utils import ( add_days, cint, @@ -23,6 +23,9 @@ from frappe.utils import ( today, ) from six import iteritems, itervalues, string_types +======= +from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate +>>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") import erpnext from erpnext.accounts.general_ledger import process_gl_map @@ -2568,6 +2571,7 @@ def get_supplied_items(purchase_order): return supplied_item_details +<<<<<<< HEAD def audit_incorrect_valuation_entries(): # Audit of stock transfer entries having incorrect valuation from erpnext.controllers.stock_controller import create_repost_item_valuation_entry @@ -2625,3 +2629,96 @@ def get_incorrect_stock_entries() -> Dict: stock_entries.setdefault(row.name, row) return stock_entries +======= +@frappe.whitelist() +def get_items_from_subcontract_order(source_name, target_doc=None): + from erpnext.controllers.subcontracting_controller import make_rm_stock_entry + + if isinstance(target_doc, str): + target_doc = frappe.get_doc(json.loads(target_doc)) + + order_doctype = "Purchase Order" if target_doc.purchase_order else "Subcontracting Order" + target_doc = make_rm_stock_entry( + subcontract_order=source_name, order_doctype=order_doctype, target_doc=target_doc + ) + + return target_doc + + +def get_available_materials(work_order) -> dict: + data = get_stock_entry_data(work_order) + + available_materials = {} + for row in data: + key = (row.item_code, row.warehouse) + if row.purpose != "Material Transfer for Manufacture": + key = (row.item_code, row.s_warehouse) + + if key not in available_materials: + available_materials.setdefault( + key, + frappe._dict( + {"item_details": row, "batch_details": defaultdict(float), "qty": 0, "serial_nos": []} + ), + ) + + item_data = available_materials[key] + + if row.purpose == "Material Transfer for Manufacture": + item_data.qty += row.qty + if row.batch_no: + item_data.batch_details[row.batch_no] += row.qty + + if row.serial_no: + item_data.serial_nos.extend(get_serial_nos(row.serial_no)) + item_data.serial_nos.sort() + else: + # Consume raw material qty in case of 'Manufacture' or 'Material Consumption for Manufacture' + + item_data.qty -= row.qty + if row.batch_no: + item_data.batch_details[row.batch_no] -= row.qty + + if row.serial_no: + for serial_no in get_serial_nos(row.serial_no): + item_data.serial_nos.remove(serial_no) + + return available_materials + + +def get_stock_entry_data(work_order): + stock_entry = frappe.qb.DocType("Stock Entry") + stock_entry_detail = frappe.qb.DocType("Stock Entry Detail") + + return ( + frappe.qb.from_(stock_entry) + .from_(stock_entry_detail) + .select( + stock_entry_detail.item_name, + stock_entry_detail.original_item, + stock_entry_detail.item_code, + stock_entry_detail.qty, + (stock_entry_detail.t_warehouse).as_("warehouse"), + (stock_entry_detail.s_warehouse).as_("s_warehouse"), + stock_entry_detail.description, + stock_entry_detail.stock_uom, + stock_entry_detail.expense_account, + stock_entry_detail.cost_center, + stock_entry_detail.batch_no, + stock_entry_detail.serial_no, + stock_entry.purpose, + ) + .where( + (stock_entry.name == stock_entry_detail.parent) + & (stock_entry.work_order == work_order) + & (stock_entry.docstatus == 1) + & (stock_entry_detail.s_warehouse.isnotnull()) + & ( + stock_entry.purpose.isin( + ["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"] + ) + ) + ) + .orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx) + ).run(as_dict=1) +>>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index c38b3d997a3..820491b6c85 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,8 +5,12 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings +<<<<<<< HEAD from frappe.utils import add_days, flt, now, nowdate, nowtime, today from six import iteritems +======= +from frappe.utils import add_days, flt, nowdate, nowtime, today +>>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -18,8 +22,6 @@ from erpnext.stock.doctype.item.test_item import ( from erpnext.stock.doctype.serial_no.serial_no import * # noqa from erpnext.stock.doctype.stock_entry.stock_entry import ( FinishedGoodError, - audit_incorrect_valuation_entries, - get_incorrect_stock_entries, move_sample_to_retention_warehouse, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -1573,44 +1575,6 @@ class TestStockEntry(FrappeTestCase): self.assertRaises(BatchExpiredError, se.save) - def test_audit_incorrect_stock_entries(self): - item_code = "Test Incorrect Valuation Rate Item - 001" - create_item(item_code=item_code, is_stock_item=1) - - make_stock_entry( - item_code=item_code, - purpose="Material Receipt", - posting_date=add_days(nowdate(), -10), - qty=2, - rate=500, - to_warehouse="_Test Warehouse - _TC", - ) - - transfer_entry = make_stock_entry( - item_code=item_code, - purpose="Material Transfer", - qty=2, - rate=500, - from_warehouse="_Test Warehouse - _TC", - to_warehouse="_Test Warehouse 1 - _TC", - ) - - sle_name = frappe.db.get_value( - "Stock Ledger Entry", {"voucher_no": transfer_entry.name, "actual_qty": (">", 0)}, "name" - ) - - frappe.db.set_value( - "Stock Ledger Entry", sle_name, {"modified": add_days(now(), -1), "stock_value_difference": 10} - ) - - stock_entries = get_incorrect_stock_entries() - self.assertTrue(transfer_entry.name in stock_entries) - - audit_incorrect_valuation_entries() - - stock_entries = get_incorrect_stock_entries() - self.assertFalse(transfer_entry.name in stock_entries) - def make_serialized_item(**args): args = frappe._dict(args) From c18a451362ab909bef35fc6b3d4a49cb927bf64e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 30 Dec 2022 14:28:18 +0530 Subject: [PATCH 22/43] fix: conflicts --- erpnext/hooks.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 6e784c7322a..10d4143a16b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -525,12 +525,7 @@ scheduler_events = { "erpnext.hr.utils.allocate_earned_leaves", "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", -<<<<<<< HEAD - "erpnext.crm.doctype.lead.lead.daily_open_lead", - "erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries", -======= - "erpnext.crm.utils.open_leads_opportunities_based_on_todays_event", ->>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") + "erpnext.crm.doctype.lead.lead.daily_open_lead" ], "weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"], "monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"], From 1c7c591ee253ed403b747843c69c201aab3a13f9 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 30 Dec 2022 14:30:35 +0530 Subject: [PATCH 23/43] fix: conflicts --- .../stock/doctype/stock_entry/stock_entry.py | 156 ------------------ 1 file changed, 156 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index bc3934957b9..7f668492498 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -9,7 +9,6 @@ import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -<<<<<<< HEAD from frappe.utils import ( add_days, cint, @@ -23,9 +22,6 @@ from frappe.utils import ( today, ) from six import iteritems, itervalues, string_types -======= -from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate ->>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") import erpnext from erpnext.accounts.general_ledger import process_gl_map @@ -2570,155 +2566,3 @@ def get_supplied_items(purchase_order): return supplied_item_details - -<<<<<<< HEAD -def audit_incorrect_valuation_entries(): - # Audit of stock transfer entries having incorrect valuation - from erpnext.controllers.stock_controller import create_repost_item_valuation_entry - - stock_entries = get_incorrect_stock_entries() - - for stock_entry, values in stock_entries.items(): - reposting_data = frappe._dict( - { - "posting_date": values.posting_date, - "posting_time": values.posting_time, - "voucher_type": "Stock Entry", - "voucher_no": stock_entry, - "company": values.company, - } - ) - - create_repost_item_valuation_entry(reposting_data) - - -def get_incorrect_stock_entries() -> Dict: - stock_entry = frappe.qb.DocType("Stock Entry") - stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") - transfer_purposes = [ - "Material Transfer", - "Material Transfer for Manufacture", - "Send to Subcontractor", - ] - - query = ( - frappe.qb.from_(stock_entry) - .inner_join(stock_ledger_entry) - .on(stock_entry.name == stock_ledger_entry.voucher_no) - .select( - stock_entry.name, - stock_entry.company, - stock_entry.posting_date, - stock_entry.posting_time, - Sum(stock_ledger_entry.stock_value_difference).as_("stock_value"), - ) - .where( - (stock_entry.docstatus == 1) - & (stock_entry.purpose.isin(transfer_purposes)) - & (stock_ledger_entry.modified > add_days(today(), -2)) - ) - .groupby(stock_ledger_entry.voucher_detail_no) - .having(Sum(stock_ledger_entry.stock_value_difference) != 0) - ) - - data = query.run(as_dict=True) - stock_entries = {} - - for row in data: - if abs(row.stock_value) > 0.1 and row.name not in stock_entries: - stock_entries.setdefault(row.name, row) - - return stock_entries -======= -@frappe.whitelist() -def get_items_from_subcontract_order(source_name, target_doc=None): - from erpnext.controllers.subcontracting_controller import make_rm_stock_entry - - if isinstance(target_doc, str): - target_doc = frappe.get_doc(json.loads(target_doc)) - - order_doctype = "Purchase Order" if target_doc.purchase_order else "Subcontracting Order" - target_doc = make_rm_stock_entry( - subcontract_order=source_name, order_doctype=order_doctype, target_doc=target_doc - ) - - return target_doc - - -def get_available_materials(work_order) -> dict: - data = get_stock_entry_data(work_order) - - available_materials = {} - for row in data: - key = (row.item_code, row.warehouse) - if row.purpose != "Material Transfer for Manufacture": - key = (row.item_code, row.s_warehouse) - - if key not in available_materials: - available_materials.setdefault( - key, - frappe._dict( - {"item_details": row, "batch_details": defaultdict(float), "qty": 0, "serial_nos": []} - ), - ) - - item_data = available_materials[key] - - if row.purpose == "Material Transfer for Manufacture": - item_data.qty += row.qty - if row.batch_no: - item_data.batch_details[row.batch_no] += row.qty - - if row.serial_no: - item_data.serial_nos.extend(get_serial_nos(row.serial_no)) - item_data.serial_nos.sort() - else: - # Consume raw material qty in case of 'Manufacture' or 'Material Consumption for Manufacture' - - item_data.qty -= row.qty - if row.batch_no: - item_data.batch_details[row.batch_no] -= row.qty - - if row.serial_no: - for serial_no in get_serial_nos(row.serial_no): - item_data.serial_nos.remove(serial_no) - - return available_materials - - -def get_stock_entry_data(work_order): - stock_entry = frappe.qb.DocType("Stock Entry") - stock_entry_detail = frappe.qb.DocType("Stock Entry Detail") - - return ( - frappe.qb.from_(stock_entry) - .from_(stock_entry_detail) - .select( - stock_entry_detail.item_name, - stock_entry_detail.original_item, - stock_entry_detail.item_code, - stock_entry_detail.qty, - (stock_entry_detail.t_warehouse).as_("warehouse"), - (stock_entry_detail.s_warehouse).as_("s_warehouse"), - stock_entry_detail.description, - stock_entry_detail.stock_uom, - stock_entry_detail.expense_account, - stock_entry_detail.cost_center, - stock_entry_detail.batch_no, - stock_entry_detail.serial_no, - stock_entry.purpose, - ) - .where( - (stock_entry.name == stock_entry_detail.parent) - & (stock_entry.work_order == work_order) - & (stock_entry.docstatus == 1) - & (stock_entry_detail.s_warehouse.isnotnull()) - & ( - stock_entry.purpose.isin( - ["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"] - ) - ) - ) - .orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx) - ).run(as_dict=1) ->>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") From 8521e12753aeb7a423a5bb0ba89ab365255ff7fb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 30 Dec 2022 14:35:12 +0530 Subject: [PATCH 24/43] fix: conflicts --- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 820491b6c85..4fe3cc1fdab 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,12 +5,8 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -<<<<<<< HEAD from frappe.utils import add_days, flt, now, nowdate, nowtime, today from six import iteritems -======= -from frappe.utils import add_days, flt, nowdate, nowtime, today ->>>>>>> 728dc1acf4 (Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation") from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( From f0475e9cc503e8622363b9cecdbeed8d9398b0b6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 30 Dec 2022 15:32:38 +0530 Subject: [PATCH 25/43] fix: linter --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 10d4143a16b..fb56860dae5 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -525,7 +525,7 @@ scheduler_events = { "erpnext.hr.utils.allocate_earned_leaves", "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" + "erpnext.crm.doctype.lead.lead.daily_open_lead", ], "weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"], "monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"], From 038fa4dbe231526117cb27b38a9dc9fa756cdb8a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Dec 2022 20:56:50 +0530 Subject: [PATCH 26/43] chore: resolve conflicts --- .../doctype/sales_invoice_item/sales_invoice_item.json | 5 ----- 1 file changed, 5 deletions(-) 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 1ef416cb115..091c00f0f11 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -843,12 +843,7 @@ "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-10-10 20:57:38.340026", -======= "modified": "2022-12-28 16:17:33.484531", ->>>>>>> 617518389a (fix: Conversion factor error for invoices without item code (petty expenses) (#32714)) - "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", "naming_rule": "Random", From 3f9b8923a918aa0959a265bc077e54a40ec7e0c6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Dec 2022 20:57:38 +0530 Subject: [PATCH 27/43] chore: resolve conflicts --- .../accounts/doctype/sales_invoice_item/sales_invoice_item.json | 1 + 1 file changed, 1 insertion(+) 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 091c00f0f11..4fbb4ac1b50 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -844,6 +844,7 @@ "istable": 1, "links": [], "modified": "2022-12-28 16:17:33.484531", + "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", "naming_rule": "Random", From 4ba2f1ec96a5fc370039bead72f036919e37c6c0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 2 Jan 2023 08:52:04 +0530 Subject: [PATCH 28/43] fix: Multi-currency issues in Bank Reconciliation Tool (backport #33488) (#33493) fix: Multi-currency issues in Bank Recociliation Tool (cherry picked from commit ad53ecf2b48a808dc98f2834dbbfb9c5ebf73c72) Co-authored-by: Deepesh Garg --- .../bank_reconciliation_tool.py | 2 +- .../doctype/bank_transaction/bank_transaction.py | 2 +- .../js/bank_reconciliation_tool/dialog_manager.js | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 0efe086d94e..710c0146153 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -299,7 +299,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers): dict( account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"] ), - ["credit", "debit"], + ["credit_in_account_currency as credit", "debit_in_account_currency as debit"], as_dict=1, ) gl_amount, transaction_amount = ( diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index e43f18b5c74..69b611669f3 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -138,7 +138,7 @@ def get_paid_amount(payment_entry, currency, bank_account): ) elif doc.payment_type == "Pay": paid_amount_field = ( - "paid_amount" if doc.paid_to_account_currency == currency else "base_paid_amount" + "paid_amount" if doc.paid_from_account_currency == currency else "base_paid_amount" ) return frappe.db.get_value( diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index bb799af36ea..9f5e3c882aa 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -371,12 +371,14 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { fieldname: "deposit", fieldtype: "Currency", label: "Deposit", + options: "currency", read_only: 1, }, { fieldname: "withdrawal", fieldtype: "Currency", label: "Withdrawal", + options: "currency", read_only: 1, }, { @@ -394,6 +396,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { fieldname: "allocated_amount", fieldtype: "Currency", label: "Allocated Amount", + options: "Currency", read_only: 1, }, @@ -401,8 +404,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { fieldname: "unallocated_amount", fieldtype: "Currency", label: "Unallocated Amount", + options: "Currency", read_only: 1, }, + { + fieldname: "currency", + fieldtype: "Link", + label: "Currency", + options: "Currency", + read_only: 1, + hidden: 1, + } ]; me.dialog = new frappe.ui.Dialog({ From 66ba098462517de67dc1b0d8aab7aac7a7781e1b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 31 Dec 2022 14:14:25 -0500 Subject: [PATCH 29/43] fix: add missing 'ordered_qty' to get_bin_details (cherry picked from commit 8d62cdfd5f683c20c8ac0bd06e2b40b86579fd56) --- 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 47911b46146..0976d91a484 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1160,10 +1160,10 @@ def get_bin_details(item_code, warehouse, company=None): bin_details = frappe.db.get_value( "Bin", {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty", "reserved_qty"], + ["projected_qty", "actual_qty", "reserved_qty", "ordered_qty"], as_dict=True, cache=True, - ) or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} + ) or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0, "ordered_qty": 0} if company: bin_details["company_total_stock"] = get_company_total_stock(item_code, company) return bin_details From 196ba6759e31844869a62aa864a332ee17619a3f Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 31 Dec 2022 15:41:07 -0500 Subject: [PATCH 30/43] test: get_item_details contains bin details (cherry picked from commit 239a5f8bf4c6dda9a955dc6472d9387d80afe619) --- erpnext/stock/doctype/item/test_item.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 911c8d0a665..462220c90e3 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -81,6 +81,7 @@ class TestItem(FrappeTestCase): def test_get_item_details(self): # delete modified item price record and make as per test_records frappe.db.sql("""delete from `tabItem Price`""") + frappe.db.sql("""delete from `tabBin`""") to_check = { "item_code": "_Test Item", @@ -101,9 +102,26 @@ class TestItem(FrappeTestCase): "batch_no": None, "uom": "_Test UOM", "conversion_factor": 1.0, + "reserved_qty": 1, + "actual_qty": 5, + "ordered_qty": 10, + "projected_qty": 14, } make_test_objects("Item Price") + make_test_objects( + "Bin", + [ + { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "reserved_qty": 1, + "actual_qty": 5, + "ordered_qty": 10, + "projected_qty": 14, + } + ], + ) company = "_Test Company" currency = frappe.get_cached_value("Company", company, "default_currency") @@ -127,7 +145,7 @@ class TestItem(FrappeTestCase): ) for key, value in to_check.items(): - self.assertEqual(value, details.get(key)) + self.assertEqual(value, details.get(key), key) def test_item_tax_template(self): expected_item_tax_template = [ From c9bf062f633ddc723d43753cae4064c765683037 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 29 Dec 2022 12:34:18 +0530 Subject: [PATCH 31/43] fix: consider child nodes while getting bin details (cherry picked from commit c716dcc01e1b29779dac28b4d531b3fa3ee27143) --- erpnext/controllers/selling_controller.py | 2 +- erpnext/public/js/controllers/buying.js | 3 +- erpnext/stock/get_item_details.py | 38 ++++++++++++++++------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 791e39fed88..57f8a3e1513 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -26,7 +26,7 @@ class SellingController(StockController): super(SellingController, self).onload() if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"): for item in self.get("items"): - item.update(get_bin_details(item.item_code, item.warehouse)) + item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True)) def validate(self): super(SellingController, self).validate() diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index b86659dd2c2..0b25afc52cb 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -217,7 +217,8 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ args: { item_code: item.item_code, warehouse: item.warehouse, - company: doc.company + company: doc.company, + include_child_warehouses: true } }); } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 47911b46146..ce019952963 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -102,9 +102,11 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru elif out.get("warehouse"): if doc and doc.get("doctype") == "Purchase Order": # calculate company_total_stock only for po - bin_details = get_bin_details(args.item_code, out.warehouse, args.company) + bin_details = get_bin_details( + args.item_code, out.warehouse, args.company, include_child_warehouses=True + ) else: - bin_details = get_bin_details(args.item_code, out.warehouse) + bin_details = get_bin_details(args.item_code, out.warehouse, include_child_warehouses=True) out.update(bin_details) @@ -1045,7 +1047,9 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa res[fieldname] = pos_profile.get(fieldname) if res.get("warehouse"): - res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty") + res.actual_qty = get_bin_details( + args.item_code, res.warehouse, include_child_warehouses=True + ).get("actual_qty") return res @@ -1156,14 +1160,26 @@ def get_projected_qty(item_code, warehouse): @frappe.whitelist() -def get_bin_details(item_code, warehouse, company=None): - bin_details = frappe.db.get_value( - "Bin", - {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty", "reserved_qty"], - as_dict=True, - cache=True, - ) or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} +def get_bin_details(item_code, warehouse, company=None, include_child_warehouses=False): + bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} + + if warehouse: + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + + warehouses = get_child_warehouses(warehouse) if include_child_warehouses else [warehouse] + bin_details = frappe.db.get_value( + "Bin", + filters={"item_code": item_code, "warehouse": ["in", warehouses]}, + fieldname=[ + "sum(projected_qty) as projected_qty", + "sum(actual_qty) as actual_qty", + "sum(reserved_qty) as reserved_qty", + ], + as_dict=True, + cache=True, + ) + bin_details = {k: 0 if not v else v for k, v in bin_details.items()} + if company: bin_details["company_total_stock"] = get_company_total_stock(item_code, company) return bin_details From 4fb2d2c0003e8574e1ebbb90e9be80a21b03ee89 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 29 Dec 2022 16:38:08 +0530 Subject: [PATCH 32/43] chore: use `frappe.qb` instead of `frappe.db.get_value` (cherry picked from commit c3911a592a51a390c0b426707f7ec777686cb621) --- erpnext/stock/get_item_details.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index ce019952963..41df74d8ae8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1164,21 +1164,22 @@ def get_bin_details(item_code, warehouse, company=None, include_child_warehouses bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} if warehouse: + from frappe.query_builder.functions import Coalesce, Sum + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses warehouses = get_child_warehouses(warehouse) if include_child_warehouses else [warehouse] - bin_details = frappe.db.get_value( - "Bin", - filters={"item_code": item_code, "warehouse": ["in", warehouses]}, - fieldname=[ - "sum(projected_qty) as projected_qty", - "sum(actual_qty) as actual_qty", - "sum(reserved_qty) as reserved_qty", - ], - as_dict=True, - cache=True, - ) - bin_details = {k: 0 if not v else v for k, v in bin_details.items()} + + bin = frappe.qb.DocType("Bin") + bin_details = ( + frappe.qb.from_(bin) + .select( + Coalesce(Sum(bin.projected_qty), 0).as_("projected_qty"), + Coalesce(Sum(bin.actual_qty), 0).as_("actual_qty"), + Coalesce(Sum(bin.reserved_qty), 0).as_("reserved_qty"), + ) + .where((bin.item_code == item_code) & (bin.warehouse.isin(warehouses))) + ).run(as_dict=True)[0] if company: bin_details["company_total_stock"] = get_company_total_stock(item_code, company) From 299e02ae4665bb3254644e9281f5c6cba179a354 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 2 Jan 2023 18:23:50 +0530 Subject: [PATCH 33/43] chore: linter --- erpnext/stock/doctype/stock_entry/stock_entry.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7f668492498..3da24974919 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -9,18 +9,7 @@ import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -from frappe.utils import ( - add_days, - cint, - comma_or, - cstr, - flt, - format_time, - formatdate, - getdate, - nowdate, - today, -) +from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate from six import iteritems, itervalues, string_types import erpnext @@ -2565,4 +2554,3 @@ def get_supplied_items(purchase_order): ) return supplied_item_details - From d2f86ead747680906db6648afb1cebc610879d53 Mon Sep 17 00:00:00 2001 From: Samuel Danieli <23150094+scdanieli@users.noreply.github.com> Date: Mon, 28 Nov 2022 10:18:00 +0000 Subject: [PATCH 34/43] feat: explicit time period for mark attendance --- erpnext/hr/doctype/attendance/attendance.py | 67 +--- .../hr/doctype/attendance/attendance_list.js | 303 ++++++++++-------- 2 files changed, 182 insertions(+), 188 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 0a81fe5eec6..dd7eb0f5021 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -5,7 +5,8 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, cstr, formatdate, get_datetime, getdate, nowdate +from frappe.query_builder import Criterion +from frappe.utils import cint, cstr, formatdate, get_datetime, get_link_to_form, getdate, nowdate, add_days from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee @@ -221,75 +222,39 @@ def mark_bulk_attendance(data): attendance.submit() -def get_month_map(): - return frappe._dict( - { - "January": 1, - "February": 2, - "March": 3, - "April": 4, - "May": 5, - "June": 6, - "July": 7, - "August": 8, - "September": 9, - "October": 10, - "November": 11, - "December": 12, - } - ) - - @frappe.whitelist() -def get_unmarked_days(employee, month, exclude_holidays=0): - import calendar - - month_map = get_month_map() - today = get_datetime() - +def get_unmarked_days(employee, from_date, to_date, exclude_holidays=0): joining_date, relieving_date = frappe.get_cached_value( "Employee", employee, ["date_of_joining", "relieving_date"] ) - start_day = 1 - end_day = calendar.monthrange(today.year, month_map[month])[1] + 1 - if joining_date and joining_date.year == today.year and joining_date.month == month_map[month]: - start_day = joining_date.day - - if ( - relieving_date and relieving_date.year == today.year and relieving_date.month == month_map[month] - ): - end_day = relieving_date.day + 1 - - dates_of_month = [ - "{}-{}-{}".format(today.year, month_map[month], r) for r in range(start_day, end_day) - ] - month_start, month_end = dates_of_month[0], dates_of_month[-1] + from_date = max(getdate(from_date), joining_date or getdate(from_date)) + to_date = min(getdate(to_date), relieving_date or getdate(to_date)) records = frappe.get_all( "Attendance", fields=["attendance_date", "employee"], filters=[ - ["attendance_date", ">=", month_start], - ["attendance_date", "<=", month_end], + ["attendance_date", ">=", from_date], + ["attendance_date", "<=", to_date], ["employee", "=", employee], ["docstatus", "!=", 2], ], ) - marked_days = [get_datetime(record.attendance_date) for record in records] + marked_days = [getdate(record.attendance_date) for record in records] + if cint(exclude_holidays): - holiday_dates = get_holiday_dates_for_employee(employee, month_start, month_end) - holidays = [get_datetime(record) for record in holiday_dates] + holiday_dates = get_holiday_dates_for_employee(employee, from_date, to_date) + holidays = [getdate(record) for record in holiday_dates] marked_days.extend(holidays) unmarked_days = [] - for date in dates_of_month: - date_time = get_datetime(date) - if today.day <= date_time.day and today.month <= date_time.month: - break - if date_time not in marked_days: - unmarked_days.append(date) + while from_date <= to_date: + if from_date not in marked_days: + unmarked_days.append(from_date) + + from_date = add_days(from_date, 1) return unmarked_days diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 7d69a83e35b..bf9bc690b1b 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -1,5 +1,6 @@ -frappe.listview_settings['Attendance'] = { +frappe.listview_settings["Attendance"] = { add_fields: ["status", "attendance_date"], + get_indicator: function (doc) { if (["Present", "Work From Home"].includes(doc.status)) { return [__(doc.status), "green", "status,=," + doc.status]; @@ -10,157 +11,185 @@ frappe.listview_settings['Attendance'] = { } }, - onload: function(list_view) { + onload: function (list_view) { let me = this; - const months = moment.months(); - const curMonth = moment().format("MMMM"); - months.splice(months.indexOf(curMonth) + 1); - list_view.page.add_inner_button(__("Mark Attendance"), function() { + + list_view.page.add_inner_button(__("Mark Attendance"), function () { + let first_day_of_month = moment().startOf('month'); + + if (moment().toDate().getDate() === 1) { + first_day_of_month = first_day_of_month.subtract(1, "month"); + } + let dialog = new frappe.ui.Dialog({ title: __("Mark Attendance"), - fields: [{ - fieldname: 'employee', - label: __('For Employee'), - fieldtype: 'Link', - options: 'Employee', - get_query: () => { - return {query: "erpnext.controllers.queries.employee_query"}; + fields: [ + { + fieldname: "employee", + label: __("For Employee"), + fieldtype: "Link", + options: "Employee", + get_query: () => { + return { + query: "erpnext.controllers.queries.employee_query", + }; + }, + reqd: 1, + onchange: () => me.reset_dialog(dialog), }, - reqd: 1, - onchange: function() { - dialog.set_df_property("unmarked_days", "hidden", 1); - dialog.set_df_property("status", "hidden", 1); - dialog.set_df_property("exclude_holidays", "hidden", 1); - dialog.set_df_property("month", "value", ''); - dialog.set_df_property("unmarked_days", "options", []); - dialog.no_unmarked_days_left = false; - } - }, - { - label: __("For Month"), - fieldtype: "Select", - fieldname: "month", - options: months, - reqd: 1, - onchange: function() { - if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { - dialog.set_df_property("status", "hidden", 0); - dialog.set_df_property("exclude_holidays", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", []); - dialog.no_unmarked_days_left = false; - me.get_multi_select_options( - dialog.fields_dict.employee.value, - dialog.fields_dict.month.value, - dialog.fields_dict.exclude_holidays.get_value() - ).then(options => { - if (options.length > 0) { - dialog.set_df_property("unmarked_days", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", options); - } else { - dialog.no_unmarked_days_left = true; - } - }); - } - } - }, - { - label: __("Status"), - fieldtype: "Select", - fieldname: "status", - options: ["Present", "Absent", "Half Day", "Work From Home"], - hidden: 1, - reqd: 1, - - }, - { - label: __("Exclude Holidays"), - fieldtype: "Check", - fieldname: "exclude_holidays", - hidden: 1, - onchange: function() { - if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { - dialog.set_df_property("status", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", []); - dialog.no_unmarked_days_left = false; - me.get_multi_select_options( - dialog.fields_dict.employee.value, - dialog.fields_dict.month.value, - dialog.fields_dict.exclude_holidays.get_value() - ).then(options => { - if (options.length > 0) { - dialog.set_df_property("unmarked_days", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", options); - } else { - dialog.no_unmarked_days_left = true; - } - }); - } - } - }, - { - label: __("Unmarked Attendance for days"), - fieldname: "unmarked_days", - fieldtype: "MultiCheck", - options: [], - columns: 2, - hidden: 1 - }], + { + fieldtype: "Section Break", + fieldname: "time_period_section", + hidden: 1, + }, + { + label: __("Start"), + fieldtype: "Date", + fieldname: "from_date", + reqd: 1, + default: first_day_of_month.toDate(), + onchange: () => me.get_unmarked_days(dialog), + }, + { + fieldtype: "Column Break", + fieldname: "time_period_column", + }, + { + label: __("End"), + fieldtype: "Date", + fieldname: "to_date", + reqd: 1, + default: moment().subtract(1, 'days').toDate(), + onchange: () => me.get_unmarked_days(dialog), + }, + { + fieldtype: "Section Break", + fieldname: "days_section", + hidden: 1, + }, + { + label: __("Status"), + fieldtype: "Select", + fieldname: "status", + options: ["Present", "Absent", "Half Day", "Work From Home"], + reqd: 1, + }, + { + label: __("Exclude Holidays"), + fieldtype: "Check", + fieldname: "exclude_holidays", + onchange: () => me.get_unmarked_days(dialog), + }, + { + label: __("Unmarked Attendance for days"), + fieldname: "unmarked_days", + fieldtype: "MultiCheck", + options: [], + columns: 2, + }, + ], primary_action(data) { if (cur_dialog.no_unmarked_days_left) { - frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}", - [dialog.fields_dict.month.value, dialog.fields_dict.employee.value])); + frappe.msgprint( + __( + "Attendance from {0} to {1} has already been marked for the Employee {2}", + [data.from_date, data.to_date, data.employee] + ) + ); } else { - frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status, data.month]), () => { - frappe.call({ - method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance", - args: { - data: data - }, - callback: function (r) { - if (r.message === 1) { - frappe.show_alert({ - message: __("Attendance Marked"), - indicator: 'blue' - }); - cur_dialog.hide(); - } - } - }); - }); + frappe.confirm( + __("Mark attendance as {0} for {1} on selected dates?", [ + data.status, + data.employee, + ]), + () => { + frappe.call({ + method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance", + args: { + data: data, + }, + callback: function (r) { + if (r.message === 1) { + frappe.show_alert({ + message: __("Attendance Marked"), + indicator: "blue", + }); + cur_dialog.hide(); + } + }, + }); + } + ); } dialog.hide(); list_view.refresh(); }, - primary_action_label: __('Mark Attendance') - + primary_action_label: __("Mark Attendance"), }); dialog.show(); }); }, - get_multi_select_options: function(employee, month, exclude_holidays) { - return new Promise(resolve => { - frappe.call({ - method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days', - async: false, - args: { - employee: employee, - month: month, - exclude_holidays: exclude_holidays - } - }).then(r => { - var options = []; - for (var d in r.message) { - var momentObj = moment(r.message[d], 'YYYY-MM-DD'); - var date = momentObj.format('DD-MM-YYYY'); - options.push({ - "label": date, - "value": r.message[d], - "checked": 1 - }); - } - resolve(options); - }); + reset_dialog: function (dialog) { + let fields = dialog.fields_dict; + + dialog.set_df_property( + "time_period_section", + "hidden", + fields.employee.value ? 0 : 1 + ); + + dialog.set_df_property("days_section", "hidden", 1); + dialog.set_df_property("unmarked_days", "options", []); + dialog.no_unmarked_days_left = false; + fields.exclude_holidays.value = false; + + fields.to_date.datepicker.update({ + maxDate: moment().subtract(1, 'days').toDate() }); - } + + this.get_unmarked_days(dialog) + }, + + get_unmarked_days: function (dialog) { + let fields = dialog.fields_dict; + if (fields.employee.value && fields.from_date.value && fields.to_date.value) { + dialog.set_df_property("days_section", "hidden", 0); + dialog.set_df_property("status", "hidden", 0); + dialog.set_df_property("exclude_holidays", "hidden", 0); + dialog.no_unmarked_days_left = false; + + frappe + .call({ + method: "erpnext.hr.doctype.attendance.attendance.get_unmarked_days", + async: false, + args: { + employee: fields.employee.value, + from_date: fields.from_date.value, + to_date: fields.to_date.value, + exclude_holidays: fields.exclude_holidays.value, + }, + }) + .then((r) => { + var options = []; + + for (var d in r.message) { + var momentObj = moment(r.message[d], "YYYY-MM-DD"); + var date = momentObj.format("DD-MM-YYYY"); + options.push({ + label: date, + value: r.message[d], + checked: 1, + }); + } + + dialog.set_df_property( + "unmarked_days", + "options", + options.length > 0 ? options : [] + ); + dialog.no_unmarked_days_left = options.length === 0; + }); + } + }, }; From a2bd8d22cb6dddc0c93127cbf0b3c55ed90a026d Mon Sep 17 00:00:00 2001 From: Samuel Danieli <23150094+scdanieli@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:16:53 +0000 Subject: [PATCH 35/43] test: get_unmarked_days --- .../hr/doctype/attendance/test_attendance.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py index b78f5c06206..82a162e777a 100644 --- a/erpnext/hr/doctype/attendance/test_attendance.py +++ b/erpnext/hr/doctype/attendance/test_attendance.py @@ -6,6 +6,7 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import ( add_days, add_months, + get_first_day, get_last_day, get_year_ending, get_year_start, @@ -13,12 +14,11 @@ from frappe.utils import ( nowdate, ) +from erpnext.setup.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.attendance.attendance import ( - get_month_map, get_unmarked_days, mark_attendance, ) -from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.tests.test_utils import get_first_sunday test_records = frappe.get_test_records("Attendance") @@ -55,9 +55,12 @@ class TestAttendance(FrappeTestCase): frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list) mark_attendance(employee, attendance_date, "Present") - month_name = get_month_name(attendance_date) - unmarked_days = get_unmarked_days(employee, month_name) + unmarked_days = get_unmarked_days( + employee, + get_first_day(attendance_date), + get_last_day(attendance_date) + ) unmarked_days = [getdate(date) for date in unmarked_days] # attendance already marked for the day @@ -81,9 +84,13 @@ class TestAttendance(FrappeTestCase): frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list) mark_attendance(employee, attendance_date, "Present") - month_name = get_month_name(attendance_date) - unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True) + unmarked_days = unmarked_days = get_unmarked_days( + employee, + get_first_day(attendance_date), + get_last_day(attendance_date), + exclude_holidays=True + ) unmarked_days = [getdate(date) for date in unmarked_days] # attendance already marked for the day @@ -110,9 +117,12 @@ class TestAttendance(FrappeTestCase): attendance_date = add_days(date, 2) mark_attendance(employee, attendance_date, "Present") - month_name = get_month_name(attendance_date) - unmarked_days = get_unmarked_days(employee, month_name) + unmarked_days = get_unmarked_days( + employee, + get_first_day(attendance_date), + get_last_day(attendance_date) + ) unmarked_days = [getdate(date) for date in unmarked_days] # attendance already marked for the day @@ -124,10 +134,3 @@ class TestAttendance(FrappeTestCase): def tearDown(self): frappe.db.rollback() - - -def get_month_name(date): - month_number = date.month - for month, number in get_month_map().items(): - if number == month_number: - return month From 03af48b50bfeac6577ad061077a48025a1f80f00 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 3 Jan 2023 14:36:36 +0530 Subject: [PATCH 36/43] chore(style): fix formatting --- erpnext/hr/doctype/attendance/attendance.py | 14 +++++++++---- .../hr/doctype/attendance/test_attendance.py | 20 +++++-------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index dd7eb0f5021..2e734f2aa30 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -5,8 +5,16 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.query_builder import Criterion -from frappe.utils import cint, cstr, formatdate, get_datetime, get_link_to_form, getdate, nowdate, add_days +from frappe.utils import ( + add_days, + cint, + cstr, + formatdate, + get_datetime, + get_link_to_form, + getdate, + nowdate, +) from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee @@ -107,8 +115,6 @@ class Attendance(Document): frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee)) def unlink_attendance_from_checkins(self): - from frappe.utils import get_link_to_form - EmployeeCheckin = frappe.qb.DocType("Employee Checkin") linked_logs = ( frappe.qb.from_(EmployeeCheckin) diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py index 82a162e777a..bcb1cf88609 100644 --- a/erpnext/hr/doctype/attendance/test_attendance.py +++ b/erpnext/hr/doctype/attendance/test_attendance.py @@ -14,11 +14,8 @@ from frappe.utils import ( nowdate, ) -from erpnext.setup.doctype.employee.test_employee import make_employee -from erpnext.hr.doctype.attendance.attendance import ( - get_unmarked_days, - mark_attendance, -) +from erpnext.hr.doctype.attendance.attendance import get_unmarked_days, mark_attendance +from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.tests.test_utils import get_first_sunday test_records = frappe.get_test_records("Attendance") @@ -57,9 +54,7 @@ class TestAttendance(FrappeTestCase): mark_attendance(employee, attendance_date, "Present") unmarked_days = get_unmarked_days( - employee, - get_first_day(attendance_date), - get_last_day(attendance_date) + employee, get_first_day(attendance_date), get_last_day(attendance_date) ) unmarked_days = [getdate(date) for date in unmarked_days] @@ -86,10 +81,7 @@ class TestAttendance(FrappeTestCase): mark_attendance(employee, attendance_date, "Present") unmarked_days = unmarked_days = get_unmarked_days( - employee, - get_first_day(attendance_date), - get_last_day(attendance_date), - exclude_holidays=True + employee, get_first_day(attendance_date), get_last_day(attendance_date), exclude_holidays=True ) unmarked_days = [getdate(date) for date in unmarked_days] @@ -119,9 +111,7 @@ class TestAttendance(FrappeTestCase): mark_attendance(employee, attendance_date, "Present") unmarked_days = get_unmarked_days( - employee, - get_first_day(attendance_date), - get_last_day(attendance_date) + employee, get_first_day(attendance_date), get_last_day(attendance_date) ) unmarked_days = [getdate(date) for date in unmarked_days] From 8df11516be28d011159438e977d5cce448988a4a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 3 Jan 2023 15:44:58 +0530 Subject: [PATCH 37/43] fix(test): holiday list dates in attendance test setup --- erpnext/hr/doctype/attendance/test_attendance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py index bcb1cf88609..88f4c387bde 100644 --- a/erpnext/hr/doctype/attendance/test_attendance.py +++ b/erpnext/hr/doctype/attendance/test_attendance.py @@ -25,7 +25,7 @@ class TestAttendance(FrappeTestCase): def setUp(self): from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list - from_date = get_year_start(getdate()) + from_date = get_year_start(add_months(getdate(), -1)) to_date = get_year_ending(getdate()) self.holiday_list = make_holiday_list(from_date=from_date, to_date=to_date) From e5a187e08c48775ca95d6b21bb5ee05db281b60b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 3 Jan 2023 16:18:42 +0530 Subject: [PATCH 38/43] fix(test): monthly attendance sheet --- .../test_monthly_attendance_sheet.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py index 91da08eee50..84c66a7bf3f 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py @@ -1,7 +1,7 @@ import frappe from dateutil.relativedelta import relativedelta from frappe.tests.utils import FrappeTestCase -from frappe.utils import now_datetime +from frappe.utils import add_months, getdate from erpnext.hr.doctype.attendance.attendance import mark_attendance from erpnext.hr.doctype.employee.test_employee import make_employee @@ -14,9 +14,7 @@ class TestMonthlyAttendanceSheet(FrappeTestCase): frappe.db.delete("Attendance", {"employee": self.employee}) def test_monthly_attendance_sheet_report(self): - now = now_datetime() - previous_month = now.month - 1 - previous_month_first = now.replace(day=1).replace(month=previous_month).date() + previous_month_first = add_months(getdate(), -1).replace(day=1) company = frappe.db.get_value("Employee", self.employee, "company") @@ -27,8 +25,8 @@ class TestMonthlyAttendanceSheet(FrappeTestCase): filters = frappe._dict( { - "month": previous_month, - "year": now.year, + "month": previous_month_first.month, + "year": previous_month_first.year, "company": company, } ) From 6516e8042b97ef92f2632f8fa722080dc8df4668 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:51:57 +0530 Subject: [PATCH 39/43] fix(ecommerce): remove query parameters from referrer (backport #33269) (#33513) fix(ecommerce): remove query parameters from referer inclusion of query parameters results in logic failure example: - logic check if referrer is `all-products` - `http://shop.example/all-products` -> `all-products`, valid outcome - `http://shop.example/all-products?start=1` -> `all-products?start=1`, invalid outcome Signed-off-by: Sabu Siyad (cherry picked from commit b6bd408f19fd7b65c8a41fe05c467789eb91b399) Co-authored-by: Sabu Siyad Co-authored-by: Deepesh Garg --- erpnext/setup/doctype/item_group/item_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index a4e23267cd4..b0ff1ccfcd7 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -153,7 +153,7 @@ def get_parent_item_groups(item_group_name, from_item=False): if from_item and frappe.request.environ.get("HTTP_REFERER"): # base page after 'Home' will vary on Item page - last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1] + last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] if last_page and last_page in ("shop-by-category", "all-products"): base_nav_page_title = " ".join(last_page.split("-")).title() base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} From ea99ac9c296aa92f018c72a73761fdfcc3173fe1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 18:41:40 +0530 Subject: [PATCH 40/43] fix: Deferred revenue date comparison (backport #33515) (#33517) fix: Deferred revenue date comparison (#33515) (cherry picked from commit a3ab8f973a887c83ec19513b6eb4219de22bf0e0) Co-authored-by: Deepesh Garg --- 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 a8776fa3448..e9734bb570b 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -378,7 +378,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): return # check if books nor frozen till endate: - if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto): + if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto): end_date = get_last_day(add_days(accounts_frozen_upto, 1)) if via_journal_entry: From 865f233add436141cf4d12354f4dd79335ca48aa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 22:13:44 +0530 Subject: [PATCH 41/43] fix: Missing opening entry in general ledger (backport #33519) (#33527) fix: Missing opening entry in general ledger (#33519) (cherry picked from commit c78399c618ddc81cddba76caa7b8ab5563cc6a4b) Co-authored-by: Deepesh Garg --- 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 98c81086ab2..33d46bc901d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -282,7 +282,7 @@ def get_conditions(filters): ): conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')") - conditions.append("(posting_date <=%(to_date)s)") + conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')") if filters.get("project"): conditions.append("project in %(project)s") From b6ed0698b4d23e35d0d3d5e32d448c501a6a3370 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 3 Jan 2023 22:56:58 +0530 Subject: [PATCH 42/43] chore: resolve conflicts --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 71f2b73c670..ef2c28580c3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -10,7 +10,6 @@ from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum from frappe.utils import ( - add_days, cint, comma_or, cstr, @@ -19,7 +18,6 @@ from frappe.utils import ( formatdate, getdate, nowdate, - today, ) from six import iteritems, itervalues, string_types From be48b4a02887389489746aa660b3f62fd6644e7e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 4 Jan 2023 08:24:20 +0530 Subject: [PATCH 43/43] chore: resolve conflicts --- erpnext/stock/doctype/stock_entry/stock_entry.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ef2c28580c3..3da24974919 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -9,16 +9,7 @@ import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -from frappe.utils import ( - cint, - comma_or, - cstr, - flt, - format_time, - formatdate, - getdate, - nowdate, -) +from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate from six import iteritems, itervalues, string_types import erpnext