From c809e61103f84a42f23a1bb4a3d1fb56ebf5cff3 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 10 Oct 2023 20:40:50 +0200 Subject: [PATCH 001/142] feat(payment): add advance payment status to advance payment doctypes to better track advance payments --- .../doctype/payment_request/payment_request.py | 11 +++++++++++ .../doctype/purchase_order/purchase_order.json | 18 ++++++++++++++++-- .../doctype/purchase_order/purchase_order.py | 3 +++ erpnext/controllers/accounts_controller.py | 6 ++++++ .../doctype/sales_order/sales_order.json | 18 ++++++++++++++++-- .../selling/doctype/sales_order/sales_order.py | 2 ++ 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index df4f1b2c3f..e216d1476c 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -115,6 +115,17 @@ class PaymentRequest(Document): elif self.payment_channel == "Phone": self.request_phone_payment() + if ( + self.reference_doctype in ["Sales Order"] and ref_doc.advance_payment_status == "Not Requested" + ): + ref_doc.db_set("advance_payment_status", "Requested") + + if ( + self.reference_doctype in ["Purchase Order"] + and ref_doc.advance_payment_status == "Not Initiated" + ): + ref_doc.db_set("advance_payment_status", "Initiated") + def request_phone_payment(self): controller = _get_payment_gateway_controller(self.payment_gateway) request_amount = self.get_request_amount() diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index f74df6630e..f1ecdf5c00 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -134,6 +134,7 @@ "more_info_tab", "tracking_section", "status", + "advance_payment_status", "column_break_75", "per_billed", "per_received", @@ -1269,13 +1270,26 @@ "fieldtype": "Tab Break", "label": "Connections", "show_dashboard": 1 + }, + { + "default": "Not Initiated", + "fieldname": "advance_payment_status", + "fieldtype": "Select", + "hidden": 1, + "in_standard_filter": 1, + "label": "Advance Payment Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "Not Initiated\nInitiated\nPartially Paid\nPaid", + "print_hide": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-10-01 20:58:07.851037", + "modified": "2023-10-10 13:37:40.158761", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", @@ -1330,4 +1344,4 @@ "timeline_field": "supplier", "title_field": "supplier_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 465fe96b58..ca60348abb 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -349,6 +349,9 @@ class PurchaseOrder(BuyingController): self.validate_budget() self.update_reserved_qty_for_subcontract() + if not self.advance_payment_status: + self.advance_payment_status = "Not Initiated" + frappe.get_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total ) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6812940ee2..42dd8471f4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1689,6 +1689,12 @@ class AccountsController(TransactionBase): ) frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid) + frappe.db.set_value( + self.doctype, + self.name, + "advance_payment_status", + "Partially Paid" if advance_paid < order_total else "Paid", + ) @property def company_abbr(self): diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index a74084d21f..a0468080a8 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -131,6 +131,7 @@ "per_billed", "per_picked", "billing_status", + "advance_payment_status", "sales_team_section_break", "sales_partner", "column_break7", @@ -1639,13 +1640,26 @@ "no_copy": 1, "print_hide": 1, "report_hide": 1 + }, + { + "default": "Not Requested", + "fieldname": "advance_payment_status", + "fieldtype": "Select", + "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, + "in_standard_filter": 1, + "label": "Advance Payment Status", + "no_copy": 1, + "options": "Not Requested\nRequested\nPartially Paid\nPaid", + "print_hide": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-07-24 08:59:11.599875", + "modified": "2023-10-10 13:36:07.526793", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", @@ -1723,4 +1737,4 @@ "title_field": "customer_name", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index aae0fee467..002ffe010f 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -88,6 +88,8 @@ class SalesOrder(SellingController): self.billing_status = "Not Billed" if not self.delivery_status: self.delivery_status = "Not Delivered" + if not self.advance_payment_status: + self.advance_payment_status = "Not Requested" self.reset_default_field_value("set_warehouse", "items", "warehouse") From e97af14ff4e9e7ee51748223e2f762b128d50835 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 10 Oct 2023 23:49:44 +0200 Subject: [PATCH 002/142] fixup! feat(payment): add advance payment status to advance payment doctypes to better track advance payments --- .../purchase_order/purchase_order.json | 1 - erpnext/patches.txt | 1 + .../v15_0/create_advance_payment_status.py | 54 +++++++++++++++++++ .../doctype/sales_order/sales_order.json | 1 - 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v15_0/create_advance_payment_status.py diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index f1ecdf5c00..97f2310c1a 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1272,7 +1272,6 @@ "show_dashboard": 1 }, { - "default": "Not Initiated", "fieldname": "advance_payment_status", "fieldtype": "Select", "hidden": 1, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e9c056e3a9..4c7d8e5221 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -344,5 +344,6 @@ erpnext.patches.v15_0.delete_woocommerce_settings_doctype erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults erpnext.patches.v14_0.update_invoicing_period_in_subscription execute:frappe.delete_doc("Page", "welcome-to-erpnext") +erpnext.patches.v15_0.create_advance_payment_status # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v15_0/create_advance_payment_status.py b/erpnext/patches/v15_0/create_advance_payment_status.py new file mode 100644 index 0000000000..ff5ba8f2d5 --- /dev/null +++ b/erpnext/patches/v15_0/create_advance_payment_status.py @@ -0,0 +1,54 @@ +import frappe + + +def execute(): + """ + Description: + Calculate the new Advance Payment Statuse column in SO & PO + """ + + if frappe.reload_doc("selling", "doctype", "Sales Order"): + so = frappe.qb.DocType("Sales Order") + frappe.qb.update(so).set(so.advance_payment_status, "Not Requested").where( + so.docstatus == 1 + ).where(so.advance_paid == 0.0).run() + + frappe.qb.update(so).set(so.advance_payment_status, "Partially Paid").where( + so.docstatus == 1 + ).where(so.advance_payment_status.isnull()).where( + so.advance_paid < (so.rounded_total or so.grand_total) + ).run() + + frappe.qb.update(so).set(so.advance_payment_status, "Paid").where(so.docstatus == 1).where( + so.advance_payment_status.isnull() + ).where(so.advance_paid == (so.rounded_total or so.grand_total)).run() + + pr = frappe.qb.DocType("Payment Request") + frappe.qb.update(so).join(pr).on(so.name == pr.reference_name).set( + so.advance_payment_status, "Requested" + ).where(so.docstatus == 1).where(pr.docstatus == 1).where( + so.advance_payment_status == "Not Requested" + ).run() + + if frappe.reload_doc("buying", "doctype", "Purchase Order"): + po = frappe.qb.DocType("Purchase Order") + frappe.qb.update(po).set(po.advance_payment_status, "Not Initiated").where( + po.docstatus == 1 + ).where(po.advance_paid == 0.0).run() + + frappe.qb.update(po).set(po.advance_payment_status, "Partially Paid").where( + po.docstatus == 1 + ).where(po.advance_payment_status.isnull()).where( + po.advance_paid < (po.rounded_total or po.grand_total) + ).run() + + frappe.qb.update(po).set(po.advance_payment_status, "Paid").where(po.docstatus == 1).where( + po.advance_payment_status.isnull() + ).where(po.advance_paid == (po.rounded_total or po.grand_total)).run() + + pr = frappe.qb.DocType("Payment Request") + frappe.qb.update(po).join(pr).on(po.name == pr.reference_name).set( + po.advance_payment_status, "Initiated" + ).where(po.docstatus == 1).where(pr.docstatus == 1).where( + po.advance_payment_status == "Not Initiated" + ).run() diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index a0468080a8..7fb49a9b67 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1642,7 +1642,6 @@ "report_hide": 1 }, { - "default": "Not Requested", "fieldname": "advance_payment_status", "fieldtype": "Select", "hidden": 1, From 8b21ca2db917626d8392949a31d4e77766fa94c7 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sat, 14 Oct 2023 16:34:18 +0200 Subject: [PATCH 003/142] fixup! feat(payment): add advance payment status to advance payment doctypes to better track advance payments --- .../payment_request/payment_request.py | 37 +++++++++++++++++++ .../purchase_order/purchase_order_list.js | 4 +- .../purchase_order_analysis.js | 2 +- erpnext/controllers/status_updater.py | 16 ++++++-- .../doctype/sales_order/sales_order.json | 2 +- .../doctype/sales_order/sales_order_list.js | 4 +- .../payment_terms_status_for_sales_order.py | 2 +- .../sales_order_analysis.js | 2 +- 8 files changed, 59 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index e216d1476c..b690fed5b0 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -119,12 +119,16 @@ class PaymentRequest(Document): self.reference_doctype in ["Sales Order"] and ref_doc.advance_payment_status == "Not Requested" ): ref_doc.db_set("advance_payment_status", "Requested") + ref_doc.set_status(update=True) + ref_doc.notify_update() if ( self.reference_doctype in ["Purchase Order"] and ref_doc.advance_payment_status == "Not Initiated" ): ref_doc.db_set("advance_payment_status", "Initiated") + ref_doc.set_status(update=True) + ref_doc.notify_update() def request_phone_payment(self): controller = _get_payment_gateway_controller(self.payment_gateway) @@ -164,6 +168,39 @@ class PaymentRequest(Document): self.check_if_payment_entry_exists() self.set_as_cancelled() + if self.reference_doctype in ["Sales Order", "Purchase Order"]: + + ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) + if self.reference_doctype in ["Sales Order"] and ref_doc.advance_payment_status == "Requested": + peer_pr = frappe.db.count( + "Payment Request", + { + "reference_doctype": self.reference_doctype, + "reference_name": self.reference_name, + "docstatus": 1, + }, + ) + if not peer_pr: + ref_doc.db_set("advance_payment_status", "Not Requested") + ref_doc.set_status(update=True) + ref_doc.notify_update() + + if ( + self.reference_doctype in ["Purchase Order"] and ref_doc.advance_payment_status == "Initiated" + ): + peer_pr = frappe.db.count( + "Payment Request", + { + "reference_doctype": self.reference_doctype, + "reference_name": self.reference_name, + "docstatus": 1, + }, + ) + if not peer_pr: + ref_doc.db_set("advance_payment_status", "Not Initiated") + ref_doc.set_status(update=True) + ref_doc.notify_update() + def make_invoice(self): ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart": diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_list.js b/erpnext/buying/doctype/purchase_order/purchase_order_list.js index 6594746cfc..d39d7f9213 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_list.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order_list.js @@ -1,6 +1,6 @@ frappe.listview_settings['Purchase Order'] = { add_fields: ["base_grand_total", "company", "currency", "supplier", - "supplier_name", "per_received", "per_billed", "status"], + "supplier_name", "per_received", "per_billed", "status", "advance_payment_status"], get_indicator: function (doc) { if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; @@ -8,6 +8,8 @@ frappe.listview_settings['Purchase Order'] = { return [__("On Hold"), "orange", "status,=,On Hold"]; } else if (doc.status === "Delivered") { return [__("Delivered"), "green", "status,=,Closed"]; + } else if (doc.advance_payment_status == "Initiated") { + return [__("To Pay"), "gray", "advance_payment_status,=,Initiated"]; } else if (flt(doc.per_received, 2) < 100 && doc.status !== "Closed") { if (flt(doc.per_billed, 2) < 100) { return [__("To Receive and Bill"), "orange", diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 91506c0ab3..3bf4f2bb29 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -54,7 +54,7 @@ frappe.query_reports["Purchase Order Analysis"] = { "fieldtype": "MultiSelectList", "width": "80", get_data: function(txt) { - let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"] + let status = ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"] let options = [] for (let option of status){ options.push({ diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 73a248fb53..2b79b895eb 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -53,6 +53,10 @@ status_map = { "To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note", ], + [ + "To Pay", + "eval:self.advance_payment_status == 'Requested' and self.docstatus == 1", + ], [ "Completed", "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1", @@ -63,15 +67,19 @@ status_map = { ], "Purchase Order": [ ["Draft", None], - [ - "To Receive and Bill", - "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1", - ], ["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"], [ "To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1", ], + [ + "To Receive and Bill", + "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1", + ], + [ + "To Pay", + "eval:self.advance_payment_status == 'Initiated' and self.docstatus == 1", + ], [ "Completed", "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 7fb49a9b67..084537eb4f 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1270,7 +1270,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nOn Hold\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed", + "options": "\nDraft\nOn Hold\nTo Pay\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed", "print_hide": 1, "read_only": 1, "reqd": 1, diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js index 518f018726..37686a85c3 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_list.js +++ b/erpnext/selling/doctype/sales_order/sales_order_list.js @@ -1,6 +1,6 @@ frappe.listview_settings['Sales Order'] = { add_fields: ["base_grand_total", "customer_name", "currency", "delivery_date", - "per_delivered", "per_billed", "status", "order_type", "name", "skip_delivery_note"], + "per_delivered", "per_billed", "status", "advance_payment_status", "order_type", "name", "skip_delivery_note"], get_indicator: function (doc) { if (doc.status === "Closed") { // Closed @@ -10,6 +10,8 @@ frappe.listview_settings['Sales Order'] = { return [__("On Hold"), "orange", "status,=,On Hold"]; } else if (doc.status === "Completed") { return [__("Completed"), "green", "status,=,Completed"]; + } else if (doc.advance_payment_status === "Requested") { + return [__("To Pay"), "gray", "advance_payment_status,=,Requested"]; } else if (!doc.skip_delivery_note && flt(doc.per_delivered, 2) < 100) { if (frappe.datetime.get_diff(doc.delivery_date) < 0) { // not delivered & overdue diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py index 3682c5fd62..00acc803d5 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py @@ -209,7 +209,7 @@ def get_so_with_invoices(filters): ) .where( (so.docstatus == 1) - & (so.status.isin(["To Deliver and Bill", "To Bill"])) + & (so.status.isin(["To Deliver and Bill", "To Bill", "To Pay"])) & (so.payment_terms_template != "NULL") & (so.company == conditions.company) & (so.transaction_date[conditions.start_date : conditions.end_date]) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index ac3d3dbf71..fc685e0fe9 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -56,7 +56,7 @@ frappe.query_reports["Sales Order Analysis"] = { "fieldtype": "MultiSelectList", "width": "80", get_data: function(txt) { - let status = ["To Bill", "To Deliver", "To Deliver and Bill", "Completed"] + let status = ["To Pay", "To Bill", "To Deliver", "To Deliver and Bill", "Completed"] let options = [] for (let option of status){ options.push({ From 43fed29514c0d839312f33e1d7d490ae73d9830f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Dec 2023 19:32:48 +0530 Subject: [PATCH 004/142] fix: use child table values instead of global min max --- erpnext/stock/doctype/item/item.js | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 6e810e5987..b964c843a7 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -600,26 +600,12 @@ $.extend(erpnext.item, { } }); } else { - frappe.call({ - method: "frappe.client.get", - args: { - doctype: "Item Attribute", - name: d.attribute - } - }).then((r) => { - if(r.message) { - const from = r.message.from_range; - const to = r.message.to_range; - const increment = r.message.increment; - - let values = []; - for(var i = from; i <= to; i = flt(i + increment, 6)) { - values.push(i); - } - attr_val_fields[d.attribute] = values; - resolve(); - } - }); + let values = []; + for(var i = d.from_range; i <= d.to_range; i = flt(i + d.increment, 6)) { + values.push(i); + } + attr_val_fields[d.attribute] = values; + resolve(); } }); From 37767738b0d7ba97399b7ce8a1c73b55ecf93f62 Mon Sep 17 00:00:00 2001 From: "Indrajith.vs" <91895505+Gubbu77@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:12:52 +0530 Subject: [PATCH 005/142] fix(Report): Increased the column width in the Account Balance Report --- erpnext/accounts/report/account_balance/account_balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/account_balance/account_balance.py b/erpnext/accounts/report/account_balance/account_balance.py index 824a965cdc..b3e80a7fa3 100644 --- a/erpnext/accounts/report/account_balance/account_balance.py +++ b/erpnext/accounts/report/account_balance/account_balance.py @@ -22,7 +22,7 @@ def get_columns(filters): "fieldtype": "Link", "fieldname": "account", "options": "Account", - "width": 100, + "width": 200, }, { "label": _("Currency"), @@ -30,7 +30,7 @@ def get_columns(filters): "fieldname": "currency", "options": "Currency", "hidden": 1, - "width": 50, + "width": 100, }, { "label": _("Balance"), From f52d7c7665d4097b19a8bb6c5f99acf38cd8adcf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 2 Jan 2024 13:37:39 +0530 Subject: [PATCH 006/142] fix: Pricing rule application/removal on qty change --- .../doctype/pricing_rule/pricing_rule.py | 5 +++ erpnext/public/js/controllers/transaction.js | 41 ++++++++++++++++--- erpnext/stock/get_item_details.py | 4 +- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 82bd662ea7..ca704908e8 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -579,12 +579,17 @@ def apply_price_discount_rule(pricing_rule, item_details, args): item_details[field] += pricing_rule.get(field, 0) if pricing_rule else args.get(field, 0) +@frappe.whitelist() def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None): from erpnext.accounts.doctype.pricing_rule.utils import ( get_applied_pricing_rules, get_pricing_rule_items, ) + if isinstance(item_details, str): + item_details = json.loads(item_details) + item_details = frappe._dict(item_details) + for d in get_applied_pricing_rules(pricing_rules): if not d or not frappe.db.exists("Pricing Rule", d): continue diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 9427c38064..f2ce831a0d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1199,8 +1199,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let item = frappe.get_doc(cdt, cdn); // item.pricing_rules = '' frappe.run_serially([ - () => this.remove_pricing_rule(item), + () => this.remove_pricing_rule_for_item(item), () => this.conversion_factor(doc, cdt, cdn, true), + () => this.apply_price_list(item, true), //reapply price list before applying pricing rule () => this.calculate_stock_uom_rate(doc, cdt, cdn), () => this.apply_pricing_rule(item, true) ]); @@ -1433,8 +1434,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe ignore_pricing_rule() { if(this.frm.doc.ignore_pricing_rule) { - var me = this; - var item_list = []; + let me = this; + let item_list = []; $.each(this.frm.doc["items"] || [], function(i, d) { if (d.item_code) { @@ -1473,6 +1474,34 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } + remove_pricing_rule_for_item(item) { + let me = this; + return this.frm.call({ + method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.remove_pricing_rule_for_item", + args: { + pricing_rules: item.pricing_rules, + item_details: { + "doctype": item.doctype, + "name": item.name, + "item_code": item.item_code, + "pricing_rules": item.pricing_rules, + "parenttype": item.parenttype, + "parent": item.parent, + "price_list_rate": item.price_list_rate + }, + item_code: item.item_code, + rate: item.price_list_rate, + }, + callback: function(r) { + if (!r.exc && r.message) { + me.remove_pricing_rule(r.message); + me.calculate_taxes_and_totals(); + if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on"); + } + } + }); + } + apply_pricing_rule(item, calculate_taxes_and_totals) { var me = this; var args = this._get_args(item); @@ -1685,8 +1714,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.set_value("plc_conversion_rate", ""); } - var me = this; - var args = this._get_args(item); + let me = this; + let args = this._get_args(item); if (!((args.items && args.items.length) || args.price_list)) { return; } @@ -1728,7 +1757,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "discount_amount", "margin_rate_or_amount", "rate_with_margin"]; if(item.remove_free_item) { - var items = []; + let items = []; me.frm.doc.items.forEach(d => { if(d.item_code != item.remove_free_item || !d.is_free_item) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e746595921..ebcdd11bf1 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -497,8 +497,8 @@ def update_barcode_value(out): def get_barcode_data(items_list): - # get itemwise batch no data - # exmaple: {'LED-GRE': [Batch001, Batch002]} + # get item-wise batch no data + # example: {'LED-GRE': [Batch001, Batch002]} # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse itemwise_barcode = {} From 0d3a77dce94fcd1c413bc13bd32f3b848a630384 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 31 Dec 2023 14:13:59 +0100 Subject: [PATCH 007/142] fix: calculation of P/L in balance sheet --- .../accounts/report/balance_sheet/balance_sheet.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 5d6ca23a6b..d45dc07b85 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -124,11 +124,11 @@ def get_provisional_profit_loss( key = period if consolidated else period.key effective_liability = 0.0 if liability: - effective_liability += flt(liability[-2].get(key)) + effective_liability += flt(liability[0].get(key)) if equity: - effective_liability += flt(equity[-2].get(key)) + effective_liability += flt(equity[0].get(key)) - provisional_profit_loss[key] = flt(asset[-2].get(key)) - effective_liability + provisional_profit_loss[key] = flt(asset[0].get(key)) - effective_liability total_row[key] = effective_liability + provisional_profit_loss[key] if provisional_profit_loss[key]: @@ -193,11 +193,11 @@ def get_report_summary( for period in period_list: key = period if consolidated else period.key if asset: - net_asset += asset[-2].get(key) + net_asset += asset[0].get(key) if liability: - net_liability += liability[-2].get(key) + net_liability += liability[0].get(key) if equity: - net_equity += equity[-2].get(key) + net_equity += equity[0].get(key) if provisional_profit_loss: net_provisional_profit_loss += provisional_profit_loss.get(key) From 73ecf51a2762044d6fb8e16a9a57289c92aa4771 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 2 Jan 2024 20:39:32 +0100 Subject: [PATCH 008/142] test: balance sheet --- .../balance_sheet/test_balance_sheet.py | 148 ++++++++++++++---- 1 file changed, 117 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/test_balance_sheet.py b/erpnext/accounts/report/balance_sheet/test_balance_sheet.py index 3cb6efebee..7c67ecb9f1 100644 --- a/erpnext/accounts/report/balance_sheet/test_balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/test_balance_sheet.py @@ -3,49 +3,135 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import today +from frappe.utils.data import today from erpnext.accounts.report.balance_sheet.balance_sheet import execute +COMPANY = "_Test Company 6" +COMPANY_SHORT_NAME = "_TC6" + +test_dependencies = ["Company"] + class TestBalanceSheet(FrappeTestCase): def test_balance_sheet(self): - from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice - from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( - create_sales_invoice, - make_sales_invoice, - ) - from erpnext.accounts.utils import get_fiscal_year + frappe.db.sql(f"delete from `tabJournal Entry` where company='{COMPANY}'") + frappe.db.sql(f"delete from `tabGL Entry` where company='{COMPANY}'") - frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'") - frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 6'") - frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'") + create_account("VAT Liabilities", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY) + create_account("Advance VAT Paid", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY) + create_account("My Bank", f"Bank Accounts - {COMPANY_SHORT_NAME}", COMPANY) - pi = make_purchase_invoice( - company="_Test Company 6", - warehouse="Finished Goods - _TC6", - expense_account="Cost of Goods Sold - _TC6", - cost_center="Main - _TC6", - qty=10, - rate=100, + # 1000 equity paid to bank account + make_journal_entry( + [ + dict( + account_name="My Bank", + debit_in_account_currency=1000, + credit_in_account_currency=0, + ), + dict( + account_name="Capital Stock", + debit_in_account_currency=0, + credit_in_account_currency=1000, + ), + ] ) - si = create_sales_invoice( - company="_Test Company 6", - debit_to="Debtors - _TC6", - income_account="Sales - _TC6", - cost_center="Main - _TC6", - qty=5, - rate=110, + + # 110 income paid to bank account (100 revenue + 10 VAT) + make_journal_entry( + [ + dict( + account_name="My Bank", + debit_in_account_currency=110, + credit_in_account_currency=0, + ), + dict( + account_name="Sales", + debit_in_account_currency=0, + credit_in_account_currency=100, + ), + dict( + account_name="VAT Liabilities", + debit_in_account_currency=0, + credit_in_account_currency=10, + ), + ] ) + + # offset VAT Liabilities with intra-year advance payment + make_journal_entry( + [ + dict( + account_name="My Bank", + debit_in_account_currency=0, + credit_in_account_currency=10, + ), + dict( + account_name="Advance VAT Paid", + debit_in_account_currency=10, + credit_in_account_currency=0, + ), + ] + ) + filters = frappe._dict( - company="_Test Company 6", + company=COMPANY, period_start_date=today(), period_end_date=today(), periodicity="Yearly", ) - result = execute(filters)[1] - for account_dict in result: - if account_dict.get("account") == "Current Liabilities - _TC6": - self.assertEqual(account_dict.total, 1000) - if account_dict.get("account") == "Current Assets - _TC6": - self.assertEqual(account_dict.total, 550) + results = execute(filters) + name_and_total = { + account_dict["account_name"]: account_dict["total"] + for account_dict in results[1] + if "total" in account_dict and "account_name" in account_dict + } + + self.assertNotIn("Sales", name_and_total) + + self.assertIn("My Bank", name_and_total) + self.assertEqual(name_and_total["My Bank"], 1100) + + self.assertIn("VAT Liabilities", name_and_total) + self.assertEqual(name_and_total["VAT Liabilities"], 10) + + self.assertIn("Advance VAT Paid", name_and_total) + self.assertEqual(name_and_total["Advance VAT Paid"], -10) + + self.assertIn("Duties and Taxes", name_and_total) + self.assertEqual(name_and_total["Duties and Taxes"], 0) + + self.assertIn("Application of Funds (Assets)", name_and_total) + self.assertEqual(name_and_total["Application of Funds (Assets)"], 1100) + + self.assertIn("Equity", name_and_total) + self.assertEqual(name_and_total["Equity"], 1000) + + self.assertIn("'Provisional Profit / Loss (Credit)'", name_and_total) + self.assertEqual(name_and_total["'Provisional Profit / Loss (Credit)'"], 100) + + +def make_journal_entry(rows): + jv = frappe.new_doc("Journal Entry") + jv.posting_date = today() + jv.company = COMPANY + jv.user_remark = "test" + + for row in rows: + row["account"] = row.pop("account_name") + " - " + COMPANY_SHORT_NAME + jv.append("accounts", row) + + jv.insert() + jv.submit() + + +def create_account(account_name: str, parent_account: str, company: str): + if frappe.db.exists("Account", {"account_name": account_name, "company": company}): + return + + acc = frappe.new_doc("Account") + acc.account_name = account_name + acc.company = COMPANY + acc.parent_account = parent_account + acc.insert() From 5d33bbaff069e6cd576c3a61f1b6dc614fef43b6 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:26:54 +0100 Subject: [PATCH 009/142] refactor(Journal Entry): remove unused/redundant list indicators --- .../journal_entry/journal_entry_list.js | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry_list.js b/erpnext/accounts/doctype/journal_entry/journal_entry_list.js index 48d6115e3d..acd17cb4c7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry_list.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry_list.js @@ -1,12 +1,18 @@ -frappe.listview_settings['Journal Entry'] = { - add_fields: ["voucher_type", "posting_date", "total_debit", "company", "user_remark"], - get_indicator: function(doc) { - if(doc.docstatus==0) { - return [__("Draft", "red", "docstatus,=,0")] - } else if(doc.docstatus==2) { - return [__("Cancelled", "grey", "docstatus,=,2")] - } else { - return [__(doc.voucher_type), "blue", "voucher_type,=," + doc.voucher_type] +frappe.listview_settings["Journal Entry"] = { + add_fields: [ + "voucher_type", + "posting_date", + "total_debit", + "company", + "user_remark", + ], + get_indicator: function (doc) { + if (doc.docstatus === 1) { + return [ + __(doc.voucher_type), + "blue", + `voucher_type,=,${doc.voucher_type}`, + ]; } - } + }, }; From f01f6d50b51dc41d701f137662eb338f84c06c6c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 8 Jan 2024 15:49:49 +0530 Subject: [PATCH 010/142] fix!(portal): remove Homepage --- .../v12_0/set_default_homepage_type.py | 5 - erpnext/portal/doctype/homepage/__init__.py | 0 erpnext/portal/doctype/homepage/homepage.js | 13 - erpnext/portal/doctype/homepage/homepage.json | 122 ------- erpnext/portal/doctype/homepage/homepage.py | 32 -- .../portal/doctype/homepage/test_homepage.py | 19 - .../doctype/homepage_section/__init__.py | 0 .../homepage_section/homepage_section.js | 6 - .../homepage_section/homepage_section.json | 336 ------------------ .../homepage_section/homepage_section.py | 31 -- .../homepage_section/test_homepage_section.py | 38 -- .../doctype/homepage_section_card/__init__.py | 0 .../homepage_section_card.json | 203 ----------- .../homepage_section_card.py | 27 -- erpnext/templates/includes/macros.html | 32 -- erpnext/templates/pages/home.css | 8 - erpnext/templates/pages/home.html | 76 ---- erpnext/templates/pages/home.py | 49 --- 18 files changed, 997 deletions(-) delete mode 100644 erpnext/patches/v12_0/set_default_homepage_type.py delete mode 100644 erpnext/portal/doctype/homepage/__init__.py delete mode 100644 erpnext/portal/doctype/homepage/homepage.js delete mode 100644 erpnext/portal/doctype/homepage/homepage.json delete mode 100644 erpnext/portal/doctype/homepage/homepage.py delete mode 100644 erpnext/portal/doctype/homepage/test_homepage.py delete mode 100644 erpnext/portal/doctype/homepage_section/__init__.py delete mode 100644 erpnext/portal/doctype/homepage_section/homepage_section.js delete mode 100644 erpnext/portal/doctype/homepage_section/homepage_section.json delete mode 100644 erpnext/portal/doctype/homepage_section/homepage_section.py delete mode 100644 erpnext/portal/doctype/homepage_section/test_homepage_section.py delete mode 100644 erpnext/portal/doctype/homepage_section_card/__init__.py delete mode 100644 erpnext/portal/doctype/homepage_section_card/homepage_section_card.json delete mode 100644 erpnext/portal/doctype/homepage_section_card/homepage_section_card.py delete mode 100644 erpnext/templates/pages/home.css delete mode 100644 erpnext/templates/pages/home.html delete mode 100644 erpnext/templates/pages/home.py diff --git a/erpnext/patches/v12_0/set_default_homepage_type.py b/erpnext/patches/v12_0/set_default_homepage_type.py deleted file mode 100644 index d91fe33a3f..0000000000 --- a/erpnext/patches/v12_0/set_default_homepage_type.py +++ /dev/null @@ -1,5 +0,0 @@ -import frappe - - -def execute(): - frappe.db.set_single_value("Homepage", "hero_section_based_on", "Default") diff --git a/erpnext/portal/doctype/homepage/__init__.py b/erpnext/portal/doctype/homepage/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js deleted file mode 100644 index 6739979b98..0000000000 --- a/erpnext/portal/doctype/homepage/homepage.js +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Homepage', { - refresh: function(frm) { - frm.add_custom_button(__('Set Meta Tags'), () => { - frappe.utils.set_meta_tag('home'); - }); - frm.add_custom_button(__('Customize Homepage Sections'), () => { - frappe.set_route('List', 'Homepage Section', 'List'); - }); - }, -}); diff --git a/erpnext/portal/doctype/homepage/homepage.json b/erpnext/portal/doctype/homepage/homepage.json deleted file mode 100644 index 2b891f7268..0000000000 --- a/erpnext/portal/doctype/homepage/homepage.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "actions": [], - "beta": 1, - "creation": "2016-04-22 05:27:52.109319", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "company", - "hero_section_based_on", - "column_break_2", - "title", - "section_break_4", - "tag_line", - "description", - "hero_image", - "slideshow", - "hero_section" - ], - "fields": [ - { - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Company", - "options": "Company", - "reqd": 1 - }, - { - "fieldname": "hero_section_based_on", - "fieldtype": "Select", - "label": "Hero Section Based On", - "options": "Default\nSlideshow\nHomepage Section" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fieldname": "title", - "fieldtype": "Data", - "label": "Title" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "label": "Hero Section" - }, - { - "depends_on": "eval:doc.hero_section_based_on === 'Default'", - "description": "Company Tagline for website homepage", - "fieldname": "tag_line", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Tag Line", - "reqd": 1 - }, - { - "depends_on": "eval:doc.hero_section_based_on === 'Default'", - "description": "Company Description for website homepage", - "fieldname": "description", - "fieldtype": "Text", - "in_list_view": 1, - "label": "Description", - "reqd": 1 - }, - { - "depends_on": "eval:doc.hero_section_based_on === 'Default'", - "fieldname": "hero_image", - "fieldtype": "Attach Image", - "label": "Hero Image" - }, - { - "depends_on": "eval:doc.hero_section_based_on === 'Slideshow'", - "fieldname": "slideshow", - "fieldtype": "Link", - "label": "Homepage Slideshow", - "options": "Website Slideshow" - }, - { - "depends_on": "eval:doc.hero_section_based_on === 'Homepage Section'", - "fieldname": "hero_section", - "fieldtype": "Link", - "label": "Homepage Section", - "options": "Homepage Section" - } - ], - "issingle": 1, - "links": [], - "modified": "2022-12-19 21:10:29.127277", - "modified_by": "Administrator", - "module": "Portal", - "name": "Homepage", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Administrator", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "title_field": "company", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/portal/doctype/homepage/homepage.py b/erpnext/portal/doctype/homepage/homepage.py deleted file mode 100644 index 3539b280ca..0000000000 --- a/erpnext/portal/doctype/homepage/homepage.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document -from frappe.website.utils import delete_page_cache - - -class Homepage(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - company: DF.Link - description: DF.Text - hero_image: DF.AttachImage | None - hero_section: DF.Link | None - hero_section_based_on: DF.Literal["Default", "Slideshow", "Homepage Section"] - slideshow: DF.Link | None - tag_line: DF.Data - title: DF.Data | None - # end: auto-generated types - - def validate(self): - if not self.description: - self.description = frappe._("This is an example website auto-generated from ERPNext") - delete_page_cache("home") diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py deleted file mode 100644 index e1532239c3..0000000000 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe -from frappe.utils import set_request -from frappe.website.serve import get_response - - -class TestHomepage(unittest.TestCase): - def test_homepage_load(self): - set_request(method="GET", path="home") - response = get_response() - - self.assertEqual(response.status_code, 200) - - html = frappe.safe_decode(response.get_data()) - self.assertTrue('