From 5f376009d04d641b0076e392144902a5bd78581c Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 9 May 2024 05:31:55 +0000 Subject: [PATCH 001/203] chore(release): Bumped to Version 15.23.0 # [15.23.0](https://github.com/frappe/erpnext/compare/v15.22.2...v15.23.0) (2024-05-09) ### Bug Fixes * Add PO reference ([c417b0c](https://github.com/frappe/erpnext/commit/c417b0c15a269b147c3efef560c838a37f8a2601)) * correct ordered qty on SO when removing PO item - v15 ([#41342](https://github.com/frappe/erpnext/issues/41342)) ([5c1043f](https://github.com/frappe/erpnext/commit/5c1043fe88d31a6c0621d00ad6ac914605ec88c4)) * Cost center not getting saved in PSOA ([fbbc0af](https://github.com/frappe/erpnext/commit/fbbc0af7bcb6f12d5c3249ff670ebe09fb81626e)) * Do not deduct TCS for opening invoices ([1999b7a](https://github.com/frappe/erpnext/commit/1999b7a6c5cdff5432644d8f63069e7b62887fb7)) * filter validation for batch-wise balance history report (backport [#41356](https://github.com/frappe/erpnext/issues/41356)) ([#41361](https://github.com/frappe/erpnext/issues/41361)) ([913cea0](https://github.com/frappe/erpnext/commit/913cea001815a66dd994673056e9968691d969a5)) * future subscripition updates ([b27a55e](https://github.com/frappe/erpnext/commit/b27a55e802899c4fc6d3508c19614f0e3d3d5366)) * GL Entries against orders as an advance ([e49a401](https://github.com/frappe/erpnext/commit/e49a401c23e64c46d34cdc935339ca504ed07f06)) * incorrect qty picked in the pick list (backport [#41378](https://github.com/frappe/erpnext/issues/41378)) ([#41381](https://github.com/frappe/erpnext/issues/41381)) ([a293ec0](https://github.com/frappe/erpnext/commit/a293ec0db31b5c05c58ce29910df132f74327ecb)) * incorrect query for Purchase Invoice rate in GP ([20daae4](https://github.com/frappe/erpnext/commit/20daae4de975be6db46679eb675c0e956fae77fb)) * **Item:** allow UOM conversion for non-stock items (backport [#41267](https://github.com/frappe/erpnext/issues/41267)) ([#41346](https://github.com/frappe/erpnext/issues/41346)) ([cd3ca1e](https://github.com/frappe/erpnext/commit/cd3ca1ee253c2124a2f4fe234248348f4dadbb17)) * Merge debit and credit in transaction currency while merging gle with similar head ([9a58823](https://github.com/frappe/erpnext/commit/9a58823867dff30491c2bf4b1fa4d921955b4b1c)) * missing Item Name on Save for Quotation created from Item (backport [#41233](https://github.com/frappe/erpnext/issues/41233)) ([#41304](https://github.com/frappe/erpnext/issues/41304)) ([6491577](https://github.com/frappe/erpnext/commit/649157750101028127b801e6138839d357c49bec)) * Patch to fix the incorrect debit and credit in transaction currency ([f7e165b](https://github.com/frappe/erpnext/commit/f7e165b5ffd029973bd4cb54d1f852d22dda96df)) * Patch to remove cancelled asset capitalization from asset ([a755540](https://github.com/frappe/erpnext/commit/a755540708ef024d28c8c5967554d5d19292653b)) * pick list with multiple batch issue (backport [#41335](https://github.com/frappe/erpnext/issues/41335)) ([#41338](https://github.com/frappe/erpnext/issues/41338)) ([1b1dfa8](https://github.com/frappe/erpnext/commit/1b1dfa8893c23691fbfea1754af78cb39266e9a8)) * pricing rule rounding ([b93828c](https://github.com/frappe/erpnext/commit/b93828cd337be9c6b71903f1a9cfc54fec956b9f)) * Purchase Invoice gain loss gl entry for periodic inventory ([0513481](https://github.com/frappe/erpnext/commit/0513481ec8bcc86ba0f11818f0e1591b12b38553)) * resolved conflict ([5039f45](https://github.com/frappe/erpnext/commit/5039f45be461f0467f6c50a0b770fc35d24896ae)) * resolved conflict ([81afdcf](https://github.com/frappe/erpnext/commit/81afdcf745a69c335710a0630173810bd517fc19)) * search for item price in stock UOM (backport [#41075](https://github.com/frappe/erpnext/issues/41075)) ([#41313](https://github.com/frappe/erpnext/issues/41313)) ([112f96b](https://github.com/frappe/erpnext/commit/112f96bae0d7734e2db56aa510bf6bf3bd8248fd)) * update project URLs (backport [#41331](https://github.com/frappe/erpnext/issues/41331)) ([#41332](https://github.com/frappe/erpnext/issues/41332)) ([bb619a6](https://github.com/frappe/erpnext/commit/bb619a64fe0e5583f3394f67a742ca1ea703ad4d)) ### Features * **Item Price:** make UOM mandatory (backport [#40588](https://github.com/frappe/erpnext/issues/40588)) ([#41312](https://github.com/frappe/erpnext/issues/41312)) ([cab0e30](https://github.com/frappe/erpnext/commit/cab0e30cebc5c7a2c9f6a264a7bd554d5b89e58f)) ### Performance Improvements * index on item code for the Pick List Item doctype (backport [#41357](https://github.com/frappe/erpnext/issues/41357)) ([#41364](https://github.com/frappe/erpnext/issues/41364)) ([bba738f](https://github.com/frappe/erpnext/commit/bba738f5f4f63432068ce7f19ce553b2e7563208)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 1ad327da5ee..77eeaf1b7ca 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.22.2" +__version__ = "15.23.0" def get_default_company(user=None): From aa2b64476e1267b6e0859c8185ef7e356d720e05 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 16:53:35 +0530 Subject: [PATCH 002/203] fix: valuation issue for batch (backport #41425) (backport #41430) (#41434) fix: valuation issue for batch (backport #41425) (#41430) fix: valuation issue for batch (#41425) (cherry picked from commit 065163146c6137e235c1da1409537d12563a140f) Co-authored-by: rohitwaghchaure (cherry picked from commit f55a131dea68986c3f3a29336fc5748184d6e054) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../serial_and_batch_bundle.py | 14 +++++++++++++- .../test_serial_and_batch_bundle.py | 2 ++ erpnext/stock/serial_batch_bundle.py | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 91951834bf9..67f946eb7c8 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1149,7 +1149,18 @@ def make_batch_nos(item_code, batch_nos): continue batch_nos_details.append( - (batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description) + ( + batch_no, + batch_no, + now(), + now(), + user, + user, + item.item_code, + item.item_name, + item.description, + 1, + ) ) fields = [ @@ -1162,6 +1173,7 @@ def make_batch_nos(item_code, batch_nos): "item", "item_name", "description", + "use_batchwise_valuation", ] frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details)) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index 5f75b40411c..c313917bd4c 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -498,6 +498,8 @@ class TestSerialandBatchBundle(FrappeTestCase): make_batch_nos(item_code, batch_nos) self.assertTrue(frappe.db.exists("Batch", batch_id)) + use_batchwise_valuation = frappe.db.get_value("Batch", batch_id, "use_batchwise_valuation") + self.assertEqual(use_batchwise_valuation, 1) batch_id = "TEST-BATTCCH-VAL-00001" batch_nos = [{"batch_no": batch_id, "qty": 1}] diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 72945e9abc3..573d7280ca1 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -553,7 +553,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation): self.set_stock_value_difference() def get_batch_no_ledgers(self) -> list[dict]: - if not self.batchwise_valuation_batches: + if not self.batches: return [] parent = frappe.qb.DocType("Serial and Batch Bundle") @@ -575,7 +575,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation): Sum(child.qty).as_("qty"), ) .where( - (child.batch_no.isin(self.batchwise_valuation_batches)) + (child.batch_no.isin(self.batches)) & (parent.warehouse == self.sle.warehouse) & (parent.item_code == self.sle.item_code) & (parent.docstatus == 1) From 1b2c2515bc60524705e116f107dd75245b1575cb Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 13 May 2024 11:24:52 +0000 Subject: [PATCH 003/203] chore(release): Bumped to Version 15.23.1 ## [15.23.1](https://github.com/frappe/erpnext/compare/v15.23.0...v15.23.1) (2024-05-13) ### Bug Fixes * valuation issue for batch (backport [#41425](https://github.com/frappe/erpnext/issues/41425)) (backport [#41430](https://github.com/frappe/erpnext/issues/41430)) ([#41434](https://github.com/frappe/erpnext/issues/41434)) ([aa2b644](https://github.com/frappe/erpnext/commit/aa2b64476e1267b6e0859c8185ef7e356d720e05)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 77eeaf1b7ca..c5331681dff 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.23.0" +__version__ = "15.23.1" def get_default_company(user=None): From a0011c5b529662d28a1e0fc623c96644fbb68c96 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 8 May 2024 16:23:46 +0200 Subject: [PATCH 004/203] fix(Sales Order): only show permitted actions (cherry picked from commit c29d95537185d909612103b65573242a91ef0d70) --- .../doctype/sales_order/sales_order.js | 128 +++++++++++------- 1 file changed, 82 insertions(+), 46 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index d95bf9811a2..8c37e2f088e 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -58,7 +58,8 @@ frappe.ui.form.on("Sales Order", { if ( frm.doc.status !== "Closed" && flt(frm.doc.per_delivered, 2) < 100 && - flt(frm.doc.per_billed, 2) < 100 + flt(frm.doc.per_billed, 2) < 100 && + frm.has_perm("write") ) { frm.add_custom_button(__("Update Items"), () => { erpnext.utils.update_child_items({ @@ -74,7 +75,8 @@ frappe.ui.form.on("Sales Order", { if ( frm.doc.__onload && frm.doc.__onload.has_unreserved_stock && - flt(frm.doc.per_picked) === 0 + flt(frm.doc.per_picked) === 0 && + frappe.model.can_create("Stock Reservation Entry") ) { frm.add_custom_button( __("Reserve"), @@ -85,7 +87,11 @@ frappe.ui.form.on("Sales Order", { } // Stock Reservation > Unreserve button will be only visible if the SO has un-delivered reserved stock. - if (frm.doc.__onload && frm.doc.__onload.has_reserved_stock) { + if ( + frm.doc.__onload && + frm.doc.__onload.has_reserved_stock && + frappe.model.can_cancel("Stock Reservation Entry") + ) { frm.add_custom_button( __("Unreserve"), () => frm.events.cancel_stock_reservation_entries(frm), @@ -94,7 +100,7 @@ frappe.ui.form.on("Sales Order", { } frm.doc.items.forEach((item) => { - if (flt(item.stock_reserved_qty) > 0) { + if (flt(item.stock_reserved_qty) > 0 && frappe.model.can_read("Stock Reservation Entry")) { frm.add_custom_button( __("Reserved Stock"), () => frm.events.show_reserved_stock(frm), @@ -142,6 +148,10 @@ frappe.ui.form.on("Sales Order", { }, get_items_from_internal_purchase_order(frm) { + if (!frappe.model.can_read("Purchase Order")) { + return; + } + frm.add_custom_button( __("Purchase Order"), () => { @@ -630,15 +640,17 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } - if (!doc.__onload || !doc.__onload.has_reserved_stock) { - // Don't show the `Reserve` button if the Sales Order has Picked Items. - if (flt(doc.per_picked, 2) < 100 && flt(doc.per_delivered, 2) < 100) { - this.frm.add_custom_button( - __("Pick List"), - () => this.create_pick_list(), - __("Create") - ); - } + if ( + (!doc.__onload || !doc.__onload.has_reserved_stock) && + flt(doc.per_picked, 2) < 100 && + flt(doc.per_delivered, 2) < 100 && + frappe.model.can_create("Pick List") + ) { + this.frm.add_custom_button( + __("Pick List"), + () => this.create_pick_list(), + __("Create") + ); } const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1; @@ -653,20 +665,25 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex (order_is_a_sale || order_is_a_custom_sale) && allow_delivery ) { - this.frm.add_custom_button( - __("Delivery Note"), - () => this.make_delivery_note_based_on_delivery_date(true), - __("Create") - ); - this.frm.add_custom_button( - __("Work Order"), - () => this.make_work_order(), - __("Create") - ); + if (frappe.model.can_create("Delivery Note")) { + this.frm.add_custom_button( + __("Delivery Note"), + () => this.make_delivery_note_based_on_delivery_date(true), + __("Create") + ); + } + + if (frappe.model.can_create("Work Order")) { + this.frm.add_custom_button( + __("Work Order"), + () => this.make_work_order(), + __("Create") + ); + } } // sales invoice - if (flt(doc.per_billed, 2) < 100) { + if (flt(doc.per_billed, 2) < 100 && frappe.model.can_create("Sales Invoice")) { this.frm.add_custom_button( __("Sales Invoice"), () => me.make_sales_invoice(), @@ -676,8 +693,10 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex // material request if ( - !doc.order_type || - ((order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered, 2) < 100) + (!doc.order_type || + ((order_is_a_sale || order_is_a_custom_sale) && + flt(doc.per_delivered, 2) < 100)) && + frappe.model.can_create("Material Request") ) { this.frm.add_custom_button( __("Material Request"), @@ -692,7 +711,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } // Make Purchase Order - if (!this.frm.doc.is_internal_customer) { + if (!this.frm.doc.is_internal_customer && frappe.model.can_create("Purchase Order")) { this.frm.add_custom_button( __("Purchase Order"), () => this.make_purchase_order(), @@ -702,24 +721,32 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex // maintenance if (flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) { - this.frm.add_custom_button( - __("Maintenance Visit"), - () => this.make_maintenance_visit(), - __("Create") - ); - this.frm.add_custom_button( - __("Maintenance Schedule"), - () => this.make_maintenance_schedule(), - __("Create") - ); + if (frappe.model.can_create("Maintenance Visit")) { + this.frm.add_custom_button( + __("Maintenance Visit"), + () => this.make_maintenance_visit(), + __("Create") + ); + } + if (frappe.model.can_create("Maintenance Schedule")) { + this.frm.add_custom_button( + __("Maintenance Schedule"), + () => this.make_maintenance_schedule(), + __("Create") + ); + } } // project - if (flt(doc.per_delivered, 2) < 100) { + if (flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) { this.frm.add_custom_button(__("Project"), () => this.make_project(), __("Create")); } - if (doc.docstatus === 1 && !doc.inter_company_order_reference) { + if ( + doc.docstatus === 1 && + !doc.inter_company_order_reference && + frappe.model.can_create("Purchase Order") + ) { let me = this; let internal = me.frm.doc.is_internal_customer; if (internal) { @@ -743,18 +770,27 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex flt(doc.per_billed, precision("per_billed", doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance ) { - this.frm.add_custom_button( - __("Payment Request"), - () => this.make_payment_request(), - __("Create") - ); - this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create")); + if (frappe.model.can_create("Payment Request")) { + this.frm.add_custom_button( + __("Payment Request"), + () => this.make_payment_request(), + __("Create") + ); + } + + if (frappe.model.can_create("Payment Entry")) { + this.frm.add_custom_button( + __("Payment"), + () => this.make_payment_entry(), + __("Create") + ); + } } this.frm.page.set_inner_btn_group_as_primary(__("Create")); } } - if (this.frm.doc.docstatus === 0) { + if (this.frm.doc.docstatus === 0 && frappe.model.can_read("Quotation")) { this.frm.add_custom_button( __("Quotation"), function () { From cef6d0d74d11067e29e58d080fa09a328a5f016a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 8 May 2024 17:55:16 +0200 Subject: [PATCH 005/203] fix(Delivery Note): only show permitted actions (cherry picked from commit 418bdc1dcc0c8c8eaaa6555b3689436515270c7c) --- erpnext/public/js/controllers/transaction.js | 2 +- .../doctype/delivery_note/delivery_note.js | 132 +++++++++++------- 2 files changed, 81 insertions(+), 53 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b421aeebfb3..a0685d80985 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -325,7 +325,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } const me = this; - if (!this.frm.is_new() && this.frm.doc.docstatus === 0) { + if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) { this.frm.add_custom_button(__("Quality Inspection(s)"), () => { me.make_quality_inspection(); }, __("Create")); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 23d0adc5708..06881c99c12 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -79,7 +79,12 @@ frappe.ui.form.on("Delivery Note", { }, refresh: function (frm) { - if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100) { + if ( + frm.doc.docstatus === 1 && + frm.doc.is_return === 1 && + frm.doc.per_billed !== 100 && + frappe.model.can_create("Sales Invoice") + ) { frm.add_custom_button( __("Credit Note"), function () { @@ -93,7 +98,11 @@ frappe.ui.form.on("Delivery Note", { frm.page.set_inner_btn_group_as_primary(__("Create")); } - if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) { + if ( + frm.doc.docstatus == 1 && + !frm.doc.inter_company_reference && + frappe.model.can_create("Purchase Receipt") + ) { let internal = frm.doc.is_internal_customer; if (internal) { let button_label = @@ -140,42 +149,46 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( refresh(doc, dt, dn) { var me = this; super.refresh(); - if (!doc.is_return && (doc.status != "Closed" || this.frm.is_new())) { - if (this.frm.doc.docstatus === 0) { - this.frm.add_custom_button( - __("Sales Order"), - function () { - if (!me.frm.doc.customer) { - frappe.throw({ - title: __("Mandatory"), - message: __("Please Select a Customer"), - }); - } - erpnext.utils.map_current_doc({ - method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - args: { - for_reserved_stock: 1, - }, - source_doctype: "Sales Order", - target: me.frm, - setters: { - customer: me.frm.doc.customer, - }, - get_query_filters: { - docstatus: 1, - status: ["not in", ["Closed", "On Hold"]], - per_delivered: ["<", 99.99], - company: me.frm.doc.company, - project: me.frm.doc.project || undefined, - }, + if ( + !doc.is_return && + (doc.status != "Closed" || this.frm.is_new()) && + this.frm.has_perm("write") && + frappe.model.can_read("Sales Order") && + this.frm.doc.docstatus === 0 + ) { + this.frm.add_custom_button( + __("Sales Order"), + function () { + if (!me.frm.doc.customer) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Customer"), }); - }, - __("Get Items From") - ); - } + } + erpnext.utils.map_current_doc({ + method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", + args: { + for_reserved_stock: 1, + }, + source_doctype: "Sales Order", + target: me.frm, + setters: { + customer: me.frm.doc.customer, + }, + get_query_filters: { + docstatus: 1, + status: ["not in", ["Closed", "On Hold"]], + per_delivered: ["<", 99.99], + company: me.frm.doc.company, + project: me.frm.doc.project || undefined, + }, + }); + }, + __("Get Items From") + ); } - if (!doc.is_return && doc.status != "Closed") { + if (!doc.is_return && doc.status != "Closed" && frappe.model.can_create("Shipment")) { if (doc.docstatus == 1) { this.frm.add_custom_button( __("Shipment"), @@ -186,7 +199,11 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } - if (flt(doc.per_installed, 2) < 100 && doc.docstatus == 1) + if ( + flt(doc.per_installed, 2) < 100 && + doc.docstatus == 1 && + frappe.model.can_create("Installation Note") + ) { this.frm.add_custom_button( __("Installation Note"), function () { @@ -194,8 +211,9 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( }, __("Create") ); + } - if (doc.docstatus == 1) { + if (doc.docstatus == 1 && this.frm.has_perm("create")) { this.frm.add_custom_button( __("Sales Return"), function () { @@ -205,7 +223,7 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } - if (doc.docstatus == 1) { + if (doc.docstatus == 1 && frappe.model.can_create("Delivery Trip")) { this.frm.add_custom_button( __("Delivery Trip"), function () { @@ -215,19 +233,23 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } - if (doc.docstatus == 0 && !doc.__islocal) { - if (doc.__onload && doc.__onload.has_unpacked_items) { - this.frm.add_custom_button( - __("Packing Slip"), - function () { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", - frm: me.frm, - }); - }, - __("Create") - ); - } + if ( + doc.docstatus == 0 && + !doc.__islocal && + doc.__onload && + doc.__onload.has_unpacked_items && + frappe.model.can_create("Packing Slip") + ) { + this.frm.add_custom_button( + __("Packing Slip"), + function () { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", + frm: me.frm, + }); + }, + __("Create") + ); } if (!doc.__islocal && doc.docstatus == 1) { @@ -254,7 +276,13 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( } } - if (doc.docstatus == 1 && !doc.is_return && doc.status != "Closed" && flt(doc.per_billed) < 100) { + if ( + doc.docstatus == 1 && + !doc.is_return && + doc.status != "Closed" && + flt(doc.per_billed) < 100 && + frappe.model.can_create("Sales Invoice") + ) { // show Make Invoice button only if Delivery Note is not created from Sales Invoice var from_sales_invoice = false; from_sales_invoice = me.frm.doc.items.some(function (item) { From 7b8b58f8ca25d7950498114d47e39695e6f01010 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 May 2024 05:12:37 +0000 Subject: [PATCH 006/203] chore(release): Bumped to Version 15.23.2 ## [15.23.2](https://github.com/frappe/erpnext/compare/v15.23.1...v15.23.2) (2024-05-15) ### Bug Fixes * 'Bill for Rejected Quantity in Purchase Invoice' feature not working (backport [#41437](https://github.com/frappe/erpnext/issues/41437)) ([#41445](https://github.com/frappe/erpnext/issues/41445)) ([b8db903](https://github.com/frappe/erpnext/commit/b8db9033204da35b0e214d86d81f31f42a0c3586)) * address filter and quotation to for prospect ([37741b5](https://github.com/frappe/erpnext/commit/37741b5b45a83525cdcef51ef7566fcb5098a1ce)) * address filter and quotation to for prospect ([394b1f4](https://github.com/frappe/erpnext/commit/394b1f499d1aa6ee78045ef0f87b460005f4b0c2)) * address filter and quotation to for prospect ([cdbb515](https://github.com/frappe/erpnext/commit/cdbb51537905d16de7929725e6cc8e50bc0845a5)) * Asset cancelation issue ([e18be9b](https://github.com/frappe/erpnext/commit/e18be9b21e7905ab4c8eb3d3be94ea3010b821c5)) * consistent use of "Address & Contact" (backport [#41386](https://github.com/frappe/erpnext/issues/41386)) ([#41388](https://github.com/frappe/erpnext/issues/41388)) ([b6f82a6](https://github.com/frappe/erpnext/commit/b6f82a656f5a5399a71da0cbc0beade7a5ebad5f)) * daily prorata based depreciation bug in wdv method ([c335c2c](https://github.com/frappe/erpnext/commit/c335c2c85a26ee9c4c0b306c4cf07ae18c936cb0)) * data getting override in delivery trip (backport [#41431](https://github.com/frappe/erpnext/issues/41431)) ([#41433](https://github.com/frappe/erpnext/issues/41433)) ([15d2881](https://github.com/frappe/erpnext/commit/15d2881bc891a23c591d4db1defb7f1f2b211249)) * Duplicate party name column in AR/AP report ([55edbec](https://github.com/frappe/erpnext/commit/55edbec6fa508ce9e8e8efc9e672fdcfe71affae)) * incorrect total days calculation ([01b25b5](https://github.com/frappe/erpnext/commit/01b25b5821753a2111c7e82a2a4f26801447a423)) * **minor:** removed extra parameter ([ce2c6c3](https://github.com/frappe/erpnext/commit/ce2c6c31651536d735bcda0b3729012d905fd058)) * pro rata based depreciation with opening accumulated depreciation ([72f3fb2](https://github.com/frappe/erpnext/commit/72f3fb2a2ca84ff3ddb2fc25f20394cd6dfebeae)) * PSOA ageing ([6590d78](https://github.com/frappe/erpnext/commit/6590d782989de2f2c7f4689045147397a6b01bf6)) * removed same named function ([955ce9b](https://github.com/frappe/erpnext/commit/955ce9b670caf55d5b1c31e10c54f62bfb8d1dd6)) * removed unrelated code modification ([0aca1e8](https://github.com/frappe/erpnext/commit/0aca1e8e05dead191ee323df7f0615e6e12457f4)) * resolved conflict ([520e1e9](https://github.com/frappe/erpnext/commit/520e1e9c8f5771b5209e4d2fc5b8a7bd774ff06a)) * resolved conflict ([3a72f4b](https://github.com/frappe/erpnext/commit/3a72f4bd309602358457406652433d11e0995f2c)) * resolved conflict ([8cca74d](https://github.com/frappe/erpnext/commit/8cca74d5ef720788512108e0a2610db42935c6a9)) * Unknown column 'tabBatch.batch_no' in 'where clause' (backport [#41418](https://github.com/frappe/erpnext/issues/41418)) ([#41444](https://github.com/frappe/erpnext/issues/41444)) ([d988c7b](https://github.com/frappe/erpnext/commit/d988c7bd0698e2e300681384bcff847dd7d072bc)) * update description of Supplier Invoice Number ([a7533ff](https://github.com/frappe/erpnext/commit/a7533ff7f6a51dd41c299d05eefdb9e3e592140d)) * valuation issue for batch (backport [#41425](https://github.com/frappe/erpnext/issues/41425)) ([#41430](https://github.com/frappe/erpnext/issues/41430)) ([f55a131](https://github.com/frappe/erpnext/commit/f55a131dea68986c3f3a29336fc5748184d6e054)) * **wip:** daily depreciation bug ([1016ec2](https://github.com/frappe/erpnext/commit/1016ec2a148859c2016ee4e7af36e96e5f89bae8)) * **wip:** depreciation calculation after asset value adjustment ([3eff9c9](https://github.com/frappe/erpnext/commit/3eff9c97794a8caedb3862fc2fbeb499c2620684)) * zero valuation rate for batched item (backport [#41446](https://github.com/frappe/erpnext/issues/41446)) ([#41447](https://github.com/frappe/erpnext/issues/41447)) ([5c3a096](https://github.com/frappe/erpnext/commit/5c3a0965bc1de4886a05329154f32902938d6926)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index c5331681dff..a1f9b11f512 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.23.1" +__version__ = "15.23.2" def get_default_company(user=None): From 66684e7e2386bd54727ceed877e5d5d881e02896 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 18:55:40 +0530 Subject: [PATCH 007/203] fix: not able to submit landed cost voucher (backport #41481) (backport #41486) (#41487) fix: not able to submit landed cost voucher (backport #41481) (#41486) fix: not able to submit landed cost voucher (#41481) (cherry picked from commit 81a9521f04df8786b07d780cbbc838bbd08bfb6a) Co-authored-by: rohitwaghchaure (cherry picked from commit a070ad786d636fca77f99da798d9b272d70d2131) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- erpnext/controllers/stock_controller.py | 1 + .../test_landed_cost_voucher.py | 122 ++++++++++++++++++ .../serial_and_batch_bundle.py | 3 + erpnext/stock/serial_batch_bundle.py | 3 + 4 files changed, 129 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 49252bd5b54..63a8c842c9a 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -205,6 +205,7 @@ class StockController(AccountsController): "company": self.company, "is_rejected": 1 if row.get("rejected_warehouse") else 0, "use_serial_batch_fields": row.use_serial_batch_fields, + "via_landed_cost_voucher": via_landed_cost_voucher, "do_not_submit": True if not via_landed_cost_voucher else False, } diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 13b7f97b7c4..39f9ecb915d 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -946,6 +946,128 @@ class TestLandedCostVoucher(FrappeTestCase): frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), ) + def test_do_not_validate_against_landed_cost_voucher_for_serial_for_legacy_pr(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import get_auto_batch_nos + + frappe.flags.ignore_serial_batch_bundle_validation = True + frappe.flags.use_serial_and_batch_fields = True + sn_item = "Test Don't Validate Against LCV For Serial NO for Legacy PR" + sn_item_doc = make_item( + sn_item, + { + "has_serial_no": 1, + "serial_no_series": "SN-ALCVTDVLCVSNO-.####", + "is_stock_item": 1, + }, + ) + + serial_nos = [ + "SN-ALCVTDVLCVSNO-0001", + "SN-ALCVTDVLCVSNO-0002", + "SN-ALCVTDVLCVSNO-0003", + "SN-ALCVTDVLCVSNO-0004", + "SN-ALCVTDVLCVSNO-0005", + ] + + for sn in serial_nos: + if not frappe.db.exists("Serial No", sn): + sn_doc = frappe.get_doc( + { + "doctype": "Serial No", + "item_code": sn_item, + "serial_no": sn, + } + ) + sn_doc.insert() + + warehouse = "_Test Warehouse - _TC" + company = frappe.db.get_value("Warehouse", warehouse, "company") + + pr = make_purchase_receipt( + company=company, + warehouse=warehouse, + item_code=sn_item, + qty=5, + rate=100, + uom=sn_item_doc.stock_uom, + stock_uom=sn_item_doc.stock_uom, + ) + + pr.reload() + + for sn in serial_nos: + sn_doc = frappe.get_doc("Serial No", sn) + sn_doc.db_set( + { + "warehouse": warehouse, + "status": "Active", + } + ) + + for row in pr.items: + if row.item_code == sn_item: + row.db_set("serial_no", ", ".join(serial_nos)) + + stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": pr.name}) + for sle in stock_ledger_entries: + doc = frappe.get_doc("Stock Ledger Entry", sle.name) + if doc.item_code == sn_item: + doc.db_set("serial_no", ", ".join(serial_nos)) + + dn = create_delivery_note( + company=company, + warehouse=warehouse, + item_code=sn_item, + qty=5, + rate=100, + uom=sn_item_doc.stock_uom, + stock_uom=sn_item_doc.stock_uom, + ) + + stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": dn.name}) + for sle in stock_ledger_entries: + doc = frappe.get_doc("Stock Ledger Entry", sle.name) + if doc.item_code == sn_item: + doc.db_set("serial_no", ", ".join(serial_nos)) + + frappe.flags.ignore_serial_batch_bundle_validation = False + frappe.flags.use_serial_and_batch_fields = False + + lcv = make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=20, + distribute_charges_based_on="Qty", + do_not_save=True, + ) + + lcv.get_items_from_purchase_receipts() + lcv.save() + lcv.submit() + + pr.reload() + + for row in pr.items: + self.assertEqual(row.valuation_rate, 104) + self.assertTrue(row.serial_and_batch_bundle) + self.assertEqual( + row.valuation_rate, + frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), + ) + + lcv.cancel() + pr.reload() + + for row in pr.items: + self.assertEqual(row.valuation_rate, 100) + self.assertTrue(row.serial_and_batch_bundle) + self.assertEqual( + row.valuation_rate, + frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), + ) + def make_landed_cost_voucher(**args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index a1a8a85e745..4c9fc881986 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -428,6 +428,9 @@ class SerialandBatchBundle(Document): self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} should be submit first.") def check_future_entries_exists(self): + if self.flags and self.flags.via_landed_cost_voucher: + return + if not self.has_serial_no: return diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 573d7280ca1..21a96c41cb4 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -840,6 +840,9 @@ class SerialBatchCreation: self.set_auto_serial_batch_entries_for_inward() self.add_serial_nos_for_batch_item() + if hasattr(self, "via_landed_cost_voucher") and self.via_landed_cost_voucher: + doc.flags.via_landed_cost_voucher = self.via_landed_cost_voucher + self.set_serial_batch_entries(doc) if not doc.get("entries"): return frappe._dict({}) From c1f7d5a2d1b4c8ffec4509dacb40d19ce1c87162 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 May 2024 13:27:07 +0000 Subject: [PATCH 008/203] chore(release): Bumped to Version 15.23.3 ## [15.23.3](https://github.com/frappe/erpnext/compare/v15.23.2...v15.23.3) (2024-05-15) ### Bug Fixes * not able to submit landed cost voucher (backport [#41481](https://github.com/frappe/erpnext/issues/41481)) (backport [#41486](https://github.com/frappe/erpnext/issues/41486)) ([#41487](https://github.com/frappe/erpnext/issues/41487)) ([66684e7](https://github.com/frappe/erpnext/commit/66684e7e2386bd54727ceed877e5d5d881e02896)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a1f9b11f512..ca66978e7bb 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.23.2" +__version__ = "15.23.3" def get_default_company(user=None): From 68faf575db6690f92164e8ca6223366830e94e3a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 13 May 2024 15:39:22 +0530 Subject: [PATCH 009/203] feat: Config to enable immutable ledger (cherry picked from commit d56f52b0ba3665d829e971df1f7ea42770e9f89e) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json # erpnext/accounts/general_ledger.py (cherry picked from commit 32c935bc30b0dc93b6416e7717012ce25be3e174) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json --- .../accounts_settings/accounts_settings.js | 19 +++++++++++++++ .../accounts_settings/accounts_settings.json | 16 +++++++++++++ .../accounts_settings/accounts_settings.py | 1 + erpnext/accounts/general_ledger.py | 23 +++++++++++++++++-- 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js index 5b9a52e8f8b..4f59085db0a 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js @@ -3,4 +3,23 @@ frappe.ui.form.on("Accounts Settings", { refresh: function (frm) {}, + enable_immutable_ledger: function (frm) { + if (!frm.doc.enable_immutable_ledger) { + return; + } + + let msg = __("Enabling this will change the way how cancelled transactions are handled."); + msg += " "; + msg += __("Please enable only if the understand the effects of enabling this."); + msg += "
"; + msg += "Do you still want to enable immutable ledger?"; + + frappe.confirm( + msg, + () => {}, + () => { + frm.set_value("enable_immutable_ledger", 0); + } + ); + }, }); diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 305ee72edcc..e7f5b5a9d98 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -12,6 +12,7 @@ "unlink_advance_payment_on_cancelation_of_order", "column_break_13", "delete_linked_ledger_entries", + "enable_immutable_ledger", "invoicing_features_section", "check_supplier_invoice_uniqueness", "automatically_fetch_payment_terms", @@ -454,6 +455,13 @@ "fieldname": "remarks_section", "fieldtype": "Section Break", "label": "Remarks Column Length" + }, + { + "default": "0", + "description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well", + "fieldname": "enable_immutable_ledger", + "fieldtype": "Check", + "label": "Enable Immutable Ledger" } ], "icon": "icon-cog", @@ -461,7 +469,15 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2024-03-15 12:11:36.085158", +======= +<<<<<<< HEAD + "modified": "2024-01-30 14:04:26.553554", +======= + "modified": "2024-05-11 23:19:44.673975", +>>>>>>> d56f52b0ba (feat: Config to enable immutable ledger) +>>>>>>> 32c935bc30 (feat: Config to enable immutable ledger) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 07d1a65265b..34f0f24047b 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -39,6 +39,7 @@ class AccountsSettings(Document): determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] enable_common_party_accounting: DF.Check enable_fuzzy_matching: DF.Check + enable_immutable_ledger: DF.Check enable_party_matching: DF.Check frozen_accounts_modifier: DF.Link | None general_ledger_remarks_length: DF.Int diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 0ff9e973e59..8b99226ba27 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -7,7 +7,12 @@ import copy import frappe from frappe import _ from frappe.model.meta import get_field_precision +<<<<<<< HEAD from frappe.utils import cint, cstr, flt, formatdate, getdate, now +======= +from frappe.utils import cint, flt, formatdate, getdate, now +from frappe.utils.dashboard import cache_source +>>>>>>> d56f52b0ba (feat: Config to enable immutable ledger) import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -577,6 +582,8 @@ def make_reverse_gl_entries( and make reverse gl entries by swapping debit and credit """ + immutable_ledger_enabled = is_immutable_ledger_enabled() + if not gl_entries: gl_entry = frappe.qb.DocType("GL Entry") gl_entries = ( @@ -608,7 +615,6 @@ def make_reverse_gl_entries( for x in gl_entries: query = ( frappe.qb.update(gle) - .set(gle.is_cancelled, True) .set(gle.modified, now()) .set(gle.modified_by, frappe.session.user) .where( @@ -623,9 +629,14 @@ def make_reverse_gl_entries( & (gle.voucher_detail_no == x.voucher_detail_no) ) ) + + if not immutable_ledger_enabled: + query = query.set(gle.is_cancelled, True) + query.run() else: - set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) + if not immutable_ledger_enabled: + set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) for entry in gl_entries: new_gle = copy.deepcopy(entry) @@ -644,6 +655,10 @@ def make_reverse_gl_entries( new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"] new_gle["is_cancelled"] = 1 + if immutable_ledger_enabled: + new_gle["is_cancelled"] = 0 + new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate() + if new_gle["debit"] or new_gle["credit"]: make_entry(new_gle, adv_adj, "Yes") @@ -736,3 +751,7 @@ def validate_allowed_dimensions(gl_entry, dimension_filter_map): ), InvalidAccountDimensionError, ) + + +def is_immutable_ledger_enabled(): + return frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger") From f09a6d76a7050a85080755293313dfc2fc943723 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 May 2024 20:27:50 +0530 Subject: [PATCH 010/203] chore: Resolve conflicts (cherry picked from commit 983c55eca1ef34b5e2e84bc8f95976a6d9cadf8e) --- erpnext/accounts/general_ledger.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 8b99226ba27..3f01dee888d 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -7,12 +7,7 @@ import copy import frappe from frappe import _ from frappe.model.meta import get_field_precision -<<<<<<< HEAD from frappe.utils import cint, cstr, flt, formatdate, getdate, now -======= -from frappe.utils import cint, flt, formatdate, getdate, now -from frappe.utils.dashboard import cache_source ->>>>>>> d56f52b0ba (feat: Config to enable immutable ledger) import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( From ce60b77fa59d2cc3c284d861f330e1d6b45ce39e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 May 2024 20:59:47 +0530 Subject: [PATCH 011/203] chore: Resolve conflicts (cherry picked from commit 9315607ee730da03cc591526a98bd1c18f3fedb8) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json --- .../accounts/doctype/accounts_settings/accounts_settings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index e7f5b5a9d98..663a9e990d1 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -469,6 +469,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD <<<<<<< HEAD "modified": "2024-03-15 12:11:36.085158", ======= @@ -478,6 +479,9 @@ "modified": "2024-05-11 23:19:44.673975", >>>>>>> d56f52b0ba (feat: Config to enable immutable ledger) >>>>>>> 32c935bc30 (feat: Config to enable immutable ledger) +======= + "modified": "2024-05-11 23:19:44.673975", +>>>>>>> 9315607ee7 (chore: Resolve conflicts) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 7cbe03174f8f53e254a7be275f1a805a011e3f58 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 17 May 2024 10:37:32 +0530 Subject: [PATCH 012/203] chore: resolve conflicts --- .../doctype/accounts_settings/accounts_settings.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 663a9e990d1..7bf3826e781 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -469,19 +469,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2024-03-15 12:11:36.085158", -======= -<<<<<<< HEAD - "modified": "2024-01-30 14:04:26.553554", -======= "modified": "2024-05-11 23:19:44.673975", ->>>>>>> d56f52b0ba (feat: Config to enable immutable ledger) ->>>>>>> 32c935bc30 (feat: Config to enable immutable ledger) -======= - "modified": "2024-05-11 23:19:44.673975", ->>>>>>> 9315607ee7 (chore: Resolve conflicts) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 9b7bda958b7498a91501061a2f5b35ef6b958962 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Fri, 17 May 2024 05:27:58 +0000 Subject: [PATCH 013/203] chore(release): Bumped to Version 15.24.0 # [15.24.0](https://github.com/frappe/erpnext/compare/v15.23.3...v15.24.0) (2024-05-17) ### Features * Config to enable immutable ledger ([68faf57](https://github.com/frappe/erpnext/commit/68faf575db6690f92164e8ca6223366830e94e3a)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ca66978e7bb..73d34d5e0fe 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.23.3" +__version__ = "15.24.0" def get_default_company(user=None): From 4ddd6e685b8739b62a16574f84ad69a9ef723233 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 15:51:42 +0530 Subject: [PATCH 014/203] fix: valuation rate for legacy serial nos (backport #41543) (backport #41545) (#41546) fix: valuation rate for legacy serial nos (backport #41543) (#41545) fix: valuation rate for legacy serial nos (#41543) fix: valuation rate for serial nos (cherry picked from commit 214b38f7c88cd31b0caa42f3122355fb19d1d044) Co-authored-by: rohitwaghchaure (cherry picked from commit f121b33e291915f3a445de75380cefacb82041de) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- erpnext/stock/deprecated_serial_batch.py | 19 +-- .../delivery_note/test_delivery_note.py | 126 ++++++++++++++++++ erpnext/stock/serial_batch_bundle.py | 4 +- 3 files changed, 139 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 81daa924b75..5b826455d4c 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -17,15 +17,11 @@ class DeprecatedSerialNoValuation: if not serial_nos: return - actual_qty = flt(self.sle.actual_qty) - stock_value_change = 0 - if actual_qty < 0: - if not self.sle.is_cancelled: - outgoing_value = self.get_incoming_value_for_serial_nos(serial_nos) - stock_value_change = -1 * outgoing_value + if not self.sle.is_cancelled: + stock_value_change = self.get_incoming_value_for_serial_nos(serial_nos) - self.stock_value_change += stock_value_change + self.stock_value_change += flt(stock_value_change) def get_filterd_serial_nos(self): serial_nos = [] @@ -141,7 +137,14 @@ class DeprecatedBatchNoValuation: if not self.non_batchwise_balance_qty: continue - self.batch_avg_rate[batch_no] = self.non_batchwise_balance_value / self.non_batchwise_balance_qty + if self.non_batchwise_balance_value == 0: + self.batch_avg_rate[batch_no] = 0.0 + self.stock_value_differece[batch_no] = 0.0 + else: + self.batch_avg_rate[batch_no] = ( + self.non_batchwise_balance_value / self.non_batchwise_balance_qty + ) + self.stock_value_differece[batch_no] = self.non_batchwise_balance_value stock_value_change = self.batch_avg_rate[batch_no] * ledger.qty self.stock_value_change += stock_value_change diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 791a9730b50..192d828d843 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1697,6 +1697,132 @@ class TestDeliveryNote(FrappeTestCase): if row.serial_no: self.assertEqual(row.serial_no, serial_no) + def test_delivery_note_legacy_serial_no_valuation(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + frappe.flags.ignore_serial_batch_bundle_validation = True + sn_item = "Old Serial NO Item Valuation Test - 2" + make_item( + sn_item, + { + "has_serial_no": 1, + "serial_no_series": "SN-SOVOSN-.####", + "is_stock_item": 1, + }, + ) + + serial_nos = [ + "SN-SOVOSN-1234", + "SN-SOVOSN-2234", + ] + + for sn in serial_nos: + if not frappe.db.exists("Serial No", sn): + sn_doc = frappe.get_doc( + { + "doctype": "Serial No", + "item_code": sn_item, + "serial_no": sn, + } + ) + sn_doc.insert() + + warehouse = "_Test Warehouse - _TC" + company = frappe.db.get_value("Warehouse", warehouse, "company") + se_doc = make_stock_entry( + item_code=sn_item, + company=company, + target="_Test Warehouse - _TC", + qty=2, + basic_rate=150, + do_not_submit=1, + use_serial_batch_fields=0, + ) + se_doc.submit() + + se_doc.items[0].db_set("serial_no", "\n".join(serial_nos)) + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": se_doc.name, "voucher_type": "Stock Entry"}, + )[0] + + sle_doc = frappe.get_doc("Stock Ledger Entry", sle_data.name) + self.assertFalse(sle_doc.serial_no) + sle_doc.db_set("serial_no", "\n".join(serial_nos)) + sle_doc.reload() + self.assertTrue(sle_doc.serial_no) + self.assertFalse(sle_doc.is_cancelled) + + for sn in serial_nos: + sn_doc = frappe.get_doc("Serial No", sn) + sn_doc.db_set( + { + "status": "Active", + "warehouse": warehouse, + } + ) + + self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos)) + frappe.flags.ignore_serial_batch_bundle_validation = False + + se_doc = make_stock_entry( + item_code=sn_item, + company=company, + target="_Test Warehouse - _TC", + qty=2, + basic_rate=200, + ) + + serial_nos.extend(get_serial_nos_from_bundle(se_doc.items[0].serial_and_batch_bundle)) + + dn = create_delivery_note( + item_code=sn_item, + qty=3, + rate=500, + warehouse=warehouse, + company=company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos[0:3]), + ) + + dn.reload() + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, + fields=["stock_value_difference", "actual_qty"], + )[0] + + self.assertEqual(sle_data.actual_qty, 3 * -1) + self.assertEqual(sle_data.stock_value_difference, 500.0 * -1) + + dn = create_delivery_note( + item_code=sn_item, + qty=1, + rate=500, + warehouse=warehouse, + company=company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + use_serial_batch_fields=1, + serial_no=serial_nos[-1], + ) + + dn.reload() + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, + fields=["stock_value_difference", "actual_qty"], + )[0] + + self.assertEqual(sle_data.actual_qty, 1 * -1) + self.assertEqual(sle_data.stock_value_difference, 200.0 * -1) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 21a96c41cb4..50a7707d4a9 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -553,7 +553,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation): self.set_stock_value_difference() def get_batch_no_ledgers(self) -> list[dict]: - if not self.batches: + if not self.batchwise_valuation_batches: return [] parent = frappe.qb.DocType("Serial and Batch Bundle") @@ -575,7 +575,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation): Sum(child.qty).as_("qty"), ) .where( - (child.batch_no.isin(self.batches)) + (child.batch_no.isin(self.batchwise_valuation_batches)) & (parent.warehouse == self.sle.warehouse) & (parent.item_code == self.sle.item_code) & (parent.docstatus == 1) From b1d345ba0b57d527f892c3e669396339935b7a91 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 20 May 2024 10:22:57 +0000 Subject: [PATCH 015/203] chore(release): Bumped to Version 15.24.1 ## [15.24.1](https://github.com/frappe/erpnext/compare/v15.24.0...v15.24.1) (2024-05-20) ### Bug Fixes * valuation rate for legacy serial nos (backport [#41543](https://github.com/frappe/erpnext/issues/41543)) (backport [#41545](https://github.com/frappe/erpnext/issues/41545)) ([#41546](https://github.com/frappe/erpnext/issues/41546)) ([4ddd6e6](https://github.com/frappe/erpnext/commit/4ddd6e685b8739b62a16574f84ad69a9ef723233)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 73d34d5e0fe..4c3bc621038 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.24.0" +__version__ = "15.24.1" def get_default_company(user=None): From a3fdfba46f3452505e921f9211b8f178913fbbc7 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 22 May 2024 08:49:10 +0000 Subject: [PATCH 016/203] chore(release): Bumped to Version 15.25.0 # [15.25.0](https://github.com/frappe/erpnext/compare/v15.24.1...v15.25.0) (2024-05-22) ### Bug Fixes * added validation message for low gross purchase amount (backport [#41502](https://github.com/frappe/erpnext/issues/41502)) ([#41553](https://github.com/frappe/erpnext/issues/41553)) ([89ba7a2](https://github.com/frappe/erpnext/commit/89ba7a22fba54dbd51d674f7727a2ba441d02c5c)) * Auto reconcile only after selecting bank account (backport [#41489](https://github.com/frappe/erpnext/issues/41489)) ([#41554](https://github.com/frappe/erpnext/issues/41554)) ([33eb251](https://github.com/frappe/erpnext/commit/33eb251727fce9a04422233d693bcfa07ee9a122)) * bold total in exponential smoothing forecasting (backport [#41393](https://github.com/frappe/erpnext/issues/41393)) ([#41404](https://github.com/frappe/erpnext/issues/41404)) ([8101b51](https://github.com/frappe/erpnext/commit/8101b51899514c202d79b30f7ce46949ebd63778)) * BOM creator validation for parent row no (backport [#41413](https://github.com/frappe/erpnext/issues/41413)) ([#41558](https://github.com/frappe/erpnext/issues/41558)) ([953de99](https://github.com/frappe/erpnext/commit/953de995bcd2bcb502fd461d0fb3977a63350272)) * convert invoice_portion value from str to float (backport [#41485](https://github.com/frappe/erpnext/issues/41485)) ([#41555](https://github.com/frappe/erpnext/issues/41555)) ([0a36139](https://github.com/frappe/erpnext/commit/0a36139ef4fc55543b00ec7fdaac494c4fc59402)) * correct report name in inner button (backport [#41568](https://github.com/frappe/erpnext/issues/41568)) ([#41572](https://github.com/frappe/erpnext/issues/41572)) ([4ccc426](https://github.com/frappe/erpnext/commit/4ccc426fec6ef619dc058197b5881b3c0f2d8b4b)) * create custom field for accounting dimensions only if the field not exists already ([#41557](https://github.com/frappe/erpnext/issues/41557)) ([68fa55c](https://github.com/frappe/erpnext/commit/68fa55ca024f1b8d691ab2d861544a8026827b0e)) * Deadlock on submitting Sales Invoice (backport [#41417](https://github.com/frappe/erpnext/issues/41417)) ([#41562](https://github.com/frappe/erpnext/issues/41562)) ([49db19d](https://github.com/frappe/erpnext/commit/49db19d57af9484b3449a44678829d25ce876a97)) * Demo data clearing ([46ec61f](https://github.com/frappe/erpnext/commit/46ec61f0c923ed2800cd2642e4d935080a030ee8)) * Filters in trend reports ([52d5d7a](https://github.com/frappe/erpnext/commit/52d5d7a4d3fdc475c1db4eb445f40d40e7bed9ec)) * job card time logs overlap issue (backport [#41567](https://github.com/frappe/erpnext/issues/41567)) ([#41571](https://github.com/frappe/erpnext/issues/41571)) ([6554f19](https://github.com/frappe/erpnext/commit/6554f192fbe90033a71fa323462633c5130e1b46)) * minor Dr and Cr between Purchase Receipt and Purchase Invoice ([3872cdc](https://github.com/frappe/erpnext/commit/3872cdc54a2f1567ef2104da963a4c573fbe7e36)) * not able to delete line items in the subcontracting receipt (backport [#41569](https://github.com/frappe/erpnext/issues/41569)) ([#41570](https://github.com/frappe/erpnext/issues/41570)) ([26c3235](https://github.com/frappe/erpnext/commit/26c3235bf9e6972d04a082fcfcc172ef6b552460)) * not able to submit landed cost voucher (backport [#41481](https://github.com/frappe/erpnext/issues/41481)) ([#41486](https://github.com/frappe/erpnext/issues/41486)) ([a070ad7](https://github.com/frappe/erpnext/commit/a070ad786d636fca77f99da798d9b272d70d2131)) * possible sql error on General Ledger ([e0a0cb7](https://github.com/frappe/erpnext/commit/e0a0cb7b258cdbd83be99264bbc3416190e80a1d)) * print format bold for field "total" ([e36880d](https://github.com/frappe/erpnext/commit/e36880da2d181ae441cd17fdb86e8b6fe2a2be45)) * priority not working for multiple pricing rules (backport [#41516](https://github.com/frappe/erpnext/issues/41516)) ([#41525](https://github.com/frappe/erpnext/issues/41525)) ([5fd68f9](https://github.com/frappe/erpnext/commit/5fd68f9fcb0f2e5ee3579652841cc4d5335a933d)) * Show acheived amount and variance for parent item groups (backport [#41495](https://github.com/frappe/erpnext/issues/41495)) ([#41551](https://github.com/frappe/erpnext/issues/41551)) ([5e0bff3](https://github.com/frappe/erpnext/commit/5e0bff3a60561fe15b0a9f2dc054411a12f3c315)) * stock levels for batch (backport [#41494](https://github.com/frappe/erpnext/issues/41494)) ([#41501](https://github.com/frappe/erpnext/issues/41501)) ([938888c](https://github.com/frappe/erpnext/commit/938888cddd8dd343b8658f7e2cc71b367f59fd28)) * timeout error while submitting purchase invoice (backport [#41520](https://github.com/frappe/erpnext/issues/41520)) ([#41522](https://github.com/frappe/erpnext/issues/41522)) ([fcd9f89](https://github.com/frappe/erpnext/commit/fcd9f89deec07e2188959dcc3118700b68cd6b37)) * typerror on hide_fields ([8ae9da5](https://github.com/frappe/erpnext/commit/8ae9da5484543ddb2357bbe500ff5fba4d2a443d)) * Unable to add items in POS Invoice ([a90186f](https://github.com/frappe/erpnext/commit/a90186f9aec48d5b8d2adf388f91d6e8b6b85a69)) * validate reorder group warehouse (backport [#41478](https://github.com/frappe/erpnext/issues/41478)) ([#41480](https://github.com/frappe/erpnext/issues/41480)) ([a1ce514](https://github.com/frappe/erpnext/commit/a1ce514cf3e2abaed3ccc76b4885cb6a3ca1b28e)) * valuation rate for legacy serial nos (backport [#41543](https://github.com/frappe/erpnext/issues/41543)) ([#41545](https://github.com/frappe/erpnext/issues/41545)) ([f121b33](https://github.com/frappe/erpnext/commit/f121b33e291915f3a445de75380cefacb82041de)) ### Features * Config to enable immutable ledger ([32c935b](https://github.com/frappe/erpnext/commit/32c935bc30b0dc93b6416e7717012ce25be3e174)) * enter serial range in Serial/Batch Selector (backport [#41530](https://github.com/frappe/erpnext/issues/41530)) ([#41534](https://github.com/frappe/erpnext/issues/41534)) ([7225347](https://github.com/frappe/erpnext/commit/722534738774576bf39c8f1d6ebe8d964f1f5e1d)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4c3bc621038..e7adbd4e9e4 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.24.1" +__version__ = "15.25.0" def get_default_company(user=None): From 56d83d44be2717ca6330e24d81005f050a83a1de Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 29 May 2024 07:54:16 +0000 Subject: [PATCH 017/203] chore(release): Bumped to Version 15.26.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # [15.26.0](https://github.com/frappe/erpnext/compare/v15.25.0...v15.26.0) (2024-05-29) ### Bug Fixes * add in some indices to speed up Purchase Order deletion ([3ed3b21](https://github.com/frappe/erpnext/commit/3ed3b214c881c0e5dd10eda8ae76364cf706630b)) * cost center filter according to the company in project ([193b844](https://github.com/frappe/erpnext/commit/193b844bf1ad0b514953b5ea59cf81cfaf46ffa3)) * cost center filter according to the company in project ([e6aaab3](https://github.com/frappe/erpnext/commit/e6aaab38b06f326b2133e7404d4d6deef1c777a5)) * cost center filter according to the company in project ([563e15e](https://github.com/frappe/erpnext/commit/563e15e1c8ece1071f71e8c9fb495ec0b410d580)) * multiple issues related to serial and batch bundle (backport [#41662](https://github.com/frappe/erpnext/issues/41662)) ([#41668](https://github.com/frappe/erpnext/issues/41668)) ([dc0bb22](https://github.com/frappe/erpnext/commit/dc0bb220ed2d7a7e0bb2db8d284480d051b9e221)) * not allow template item in product bundle item ([8fb3294](https://github.com/frappe/erpnext/commit/8fb329467458eb7317c7e5c31721ad351158e26b)) * not allow template item in product bundle item ([b64f5a4](https://github.com/frappe/erpnext/commit/b64f5a4e542f94c7b066532c665daf2437a5d76b)) * not allow template item in product bundle item - v15/v14 ([9864377](https://github.com/frappe/erpnext/commit/9864377b47800b86f4a0a63c1e0e6f3f35e170a8)) * opening stock not showing in the stock ledger report for the bat… (backport [#41584](https://github.com/frappe/erpnext/issues/41584)) ([#41590](https://github.com/frappe/erpnext/issues/41590)) ([162ec7d](https://github.com/frappe/erpnext/commit/162ec7d6e88fdab528e6c850ee5c3cccae7f1474)) * SCR batch qty issue (backport [#41595](https://github.com/frappe/erpnext/issues/41595)) ([#41599](https://github.com/frappe/erpnext/issues/41599)) ([17ea958](https://github.com/frappe/erpnext/commit/17ea95817051714a1cfc49d854047ca4c39fba55)) * set expense account as Assets RBNB only if it is booked in linked PR (backport [#41368](https://github.com/frappe/erpnext/issues/41368)) ([#41673](https://github.com/frappe/erpnext/issues/41673)) ([0a0970e](https://github.com/frappe/erpnext/commit/0a0970e0e7b4662e093189b692748b16cd1e7588)) * Unable to 'Get Suppliers' using tags in Request for Quotation (backport [#41626](https://github.com/frappe/erpnext/issues/41626)) ([#41627](https://github.com/frappe/erpnext/issues/41627)) ([ca855e8](https://github.com/frappe/erpnext/commit/ca855e8ab81214e6d05e53db023d53554fb5e62c)) ### Features * Add Party details to Serial No Ledger Report ([#41656](https://github.com/frappe/erpnext/issues/41656)) ([7462de6](https://github.com/frappe/erpnext/commit/7462de66ce099391bbb11f0c566c92136d9539c1)) ### Performance Improvements * sales order UI render (backport [#41591](https://github.com/frappe/erpnext/issues/41591)) ([#41592](https://github.com/frappe/erpnext/issues/41592)) ([09d112a](https://github.com/frappe/erpnext/commit/09d112a0616db7d998eb319dc9114a96fdba9a7f)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index e7adbd4e9e4..279827a6f6e 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.25.0" +__version__ = "15.26.0" def get_default_company(user=None): From 64835b99eca0619835f876cf1e1c7ca15bfde4f9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 19:26:02 +0530 Subject: [PATCH 018/203] fix: batch selection issue (backport #41692) (backport #41693) (#41694) fix: batch selection issue (backport #41692) (#41693) fix: batch selection issue (#41692) (cherry picked from commit 968120d0eba08b0bf8fd807be2bcb93d64817c8f) Co-authored-by: rohitwaghchaure (cherry picked from commit 24775db97e3c58ae213d7bd0cdd1f902599ef351) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- erpnext/controllers/queries.py | 2 ++ erpnext/public/js/controllers/transaction.js | 3 +++ erpnext/stock/doctype/stock_entry/stock_entry.js | 4 +++- .../doctype/stock_settings/stock_settings.js | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 854682bec66..c57b2f3ddd6 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -422,6 +422,7 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, p & (stock_ledger_entry.batch_no.isnotnull()) ) .groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse) + .having(Sum(stock_ledger_entry.actual_qty) > 0) .offset(start) .limit(page_len) ) @@ -472,6 +473,7 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0 & (stock_ledger_entry.serial_and_batch_bundle.isnotnull()) ) .groupby(bundle.batch_no, bundle.warehouse) + .having(Sum(bundle.qty) > 0) .offset(start) .limit(page_len) ) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b421aeebfb3..937b19d9abb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2295,6 +2295,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (doc.is_return) { filters["is_return"] = 1; + if (["Sales Invoice", "Delivery Note"].includes(doc.doctype)) { + filters["is_inward"] = 1; + } } if (item.warehouse) filters["warehouse"] = item.warehouse; diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 4e00de0d7ce..c54876713c3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -111,7 +111,9 @@ frappe.ui.form.on("Stock Entry", { // or a pre-existing batch if (frm.doc.purpose != "Material Receipt") { filters["warehouse"] = item.s_warehouse || item.t_warehouse; - } else { + } + + if (!item.s_warehouse && item.t_warehouse) { filters["is_inward"] = 1; } diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js index 1972b193732..0443f3f1ece 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.js +++ b/erpnext/stock/doctype/stock_settings/stock_settings.js @@ -14,6 +14,22 @@ frappe.ui.form.on("Stock Settings", { frm.set_query("default_warehouse", filters); frm.set_query("sample_retention_warehouse", filters); }, + + use_serial_batch_fields(frm) { + if (frm.doc.use_serial_batch_fields && !frm.doc.disable_serial_no_and_batch_selector) { + frm.set_value("disable_serial_no_and_batch_selector", 1); + } + }, + + disable_serial_no_and_batch_selector(frm) { + if (!frm.doc.disable_serial_no_and_batch_selector && frm.doc.use_serial_batch_fields) { + frm.set_value("disable_serial_no_and_batch_selector", 1); + frappe.msgprint( + __("Serial No and Batch Selector cannot be use when Use Serial / Batch Fields is enabled.") + ); + } + }, + allow_negative_stock: function (frm) { if (!frm.doc.allow_negative_stock) { return; From 55121a22a5346666cfe9c0fc69f50417d8187059 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 30 May 2024 13:57:20 +0000 Subject: [PATCH 019/203] chore(release): Bumped to Version 15.26.1 ## [15.26.1](https://github.com/frappe/erpnext/compare/v15.26.0...v15.26.1) (2024-05-30) ### Bug Fixes * batch selection issue (backport [#41692](https://github.com/frappe/erpnext/issues/41692)) (backport [#41693](https://github.com/frappe/erpnext/issues/41693)) ([#41694](https://github.com/frappe/erpnext/issues/41694)) ([64835b9](https://github.com/frappe/erpnext/commit/64835b99eca0619835f876cf1e1c7ca15bfde4f9)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 279827a6f6e..9d6a51bb102 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.26.0" +__version__ = "15.26.1" def get_default_company(user=None): From d5158c6062f7545c1bc5ce92ea422f9d78c58212 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Jun 2024 17:31:27 +0530 Subject: [PATCH 020/203] chore: Sys Manager shluld have submit permission for repost doctypes (cherry picked from commit 4558f64c0fa7c2297a378211468186a7df8f2a8a) --- .../repost_accounting_ledger/repost_accounting_ledger.json | 6 +++++- .../repost_payment_ledger/repost_payment_ledger.json | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json index f9344ce4f3a..0d74b2150f7 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json @@ -53,13 +53,15 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-05-23 17:00:42.984798", + "modified": "2024-06-03 17:30:37.012593", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger", "owner": "Administrator", "permissions": [ { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -68,7 +70,9 @@ "read": 1, "report": 1, "role": "System Manager", + "select": 1, "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json index 4ecff8cac3b..2b4efead7f3 100644 --- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json +++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json @@ -98,13 +98,15 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-05-23 17:00:31.540640", + "modified": "2024-06-03 17:31:04.472279", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Payment Ledger", "owner": "Administrator", "permissions": [ { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -113,7 +115,9 @@ "read": 1, "report": 1, "role": "System Manager", + "select": 1, "share": 1, + "submit": 1, "write": 1 }, { From c5d72cd9345a68868bfa9eefbf7362868d98cb04 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:36:58 +0530 Subject: [PATCH 021/203] fix: Select both account number and company in the account form (backport #41731) (#41737) fix: Select both account number and company in the account form (#41731) * fix: Both the account number and the company should be selected in the account form * fix: select both account number and company in the account form (cherry picked from commit 5a75c847fefaa28041a0e7bd60efd3fe2299e76d) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- erpnext/accounts/doctype/account/account.js | 3 +-- erpnext/accounts/doctype/account/account.json | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index 411b90f84ee..ff44723afee 100644 --- a/erpnext/accounts/doctype/account/account.js +++ b/erpnext/accounts/doctype/account/account.js @@ -22,8 +22,7 @@ frappe.ui.form.on("Account", { // hide fields if group frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0); - // disable fields - frm.toggle_enable(["is_group", "company"], false); + frm.toggle_enable(["is_group", "company", "account_number"], frm.is_new()); if (cint(frm.doc.is_group) == 0) { frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account); diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 78f73efff11..0c9232015d9 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -55,8 +55,7 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Account Number", - "read_only": 1 + "label": "Account Number" }, { "default": "0", @@ -72,7 +71,6 @@ "oldfieldname": "company", "oldfieldtype": "Link", "options": "Company", - "read_only": 1, "remember_last_selected_value": 1, "reqd": 1 }, From 806ff5bc19597eb0a408b313c6f52a4064c52d11 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:37:39 +0530 Subject: [PATCH 022/203] fix: payment term when creating PO from SO (backport #41376) (#41744) fix: payment term when creating PO from SO (#41376) * fix: payment term when creating PO from SO * fix: payment term when creating PO from SO * fix: payment term when creating PO from SO * fix: payment term when creating PO from SO (cherry picked from commit 441596f795b7968fb0166dc794a11cfb73408358) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- erpnext/selling/doctype/sales_order/sales_order.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index bc8991486db..d6c08d957fe 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1224,11 +1224,19 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t target.discount_amount = 0.0 target.inter_company_order_reference = "" target.shipping_rule = "" + target.tc_name = "" + target.terms = "" + target.payment_terms_template = "" + target.payment_schedule = [] default_price_list = frappe.get_value("Supplier", supplier, "default_price_list") if default_price_list: target.buying_price_list = default_price_list + default_payment_terms = frappe.get_value("Supplier", supplier, "payment_terms") + if default_payment_terms: + target.payment_terms_template = default_payment_terms + if any(item.delivered_by_supplier == 1 for item in source.items): if source.shipping_address_name: target.shipping_address = source.shipping_address_name @@ -1280,7 +1288,6 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t "contact_person", "taxes_and_charges", "shipping_address", - "terms", ], "validation": {"docstatus": ["=", 1]}, }, @@ -1348,6 +1355,10 @@ 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.tc_name = "" + target.terms = "" + target.payment_terms_template = "" + target.payment_schedule = [] if is_drop_ship_order(target): target.customer = source.customer @@ -1383,7 +1394,6 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): "contact_person", "taxes_and_charges", "shipping_address", - "terms", ], "validation": {"docstatus": ["=", 1]}, }, From 460ee5d542d619d5f6c96cf44a65d84bd538cc90 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:38:25 +0530 Subject: [PATCH 023/203] fix(Company): Allow Equity type account in unrealised exchange gain/loss account (backport #41740) (#41747) fix(Company): Allow Equity type account in unrealised exchange gain/loss account (#41740) (cherry picked from commit d30c797d2432dafb881bfb9ebc2fbd9f37ff7366) Co-authored-by: Nabin Hait --- erpnext/setup/doctype/company/company.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 670d898b8df..2fc7a24c287 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -251,7 +251,10 @@ erpnext.company.setup_queries = function (frm) { ["discount_allowed_account", { root_type: "Expense" }], ["discount_received_account", { root_type: "Income" }], ["exchange_gain_loss_account", { root_type: ["in", ["Expense", "Income"]] }], - ["unrealized_exchange_gain_loss_account", { root_type: ["in", ["Expense", "Income"]] }], + [ + "unrealized_exchange_gain_loss_account", + { root_type: ["in", ["Expense", "Income", "Equity", "Liability"]] }, + ], [ "accumulated_depreciation_account", { root_type: "Asset", account_type: "Accumulated Depreciation" }, From 278682f4df3176b12e8b45d93b5b54dbd2552da8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:22:32 +0530 Subject: [PATCH 024/203] fix(stock settings): Allowed any number in stock frozen upto days field (backport #41736) (#41749) fix(stock settings): Allowed any number in stock frozen upto days field (#41736) (cherry picked from commit 740c495014a3e1861976c032a17a4b83cbe1bdda) Co-authored-by: Nabin Hait --- erpnext/stock/doctype/stock_settings/stock_settings.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index b5df094eee7..24e8dccd17c 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -84,14 +84,6 @@ class StockSettings(Document): make_mandatory=0, ) - stock_frozen_limit = 356 - submitted_stock_frozen = self.stock_frozen_upto_days or 0 - if submitted_stock_frozen > stock_frozen_limit: - self.stock_frozen_upto_days = stock_frozen_limit - frappe.msgprint( - _("`Freeze Stocks Older Than` should be smaller than %d days.") % stock_frozen_limit - ) - # show/hide barcode field for name in ["barcode", "barcodes", "scan_barcode"]: frappe.make_property_setter( From 5448d5d1cb2daa454931204da5110990d2e2b516 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 4 Jun 2024 11:41:30 +0530 Subject: [PATCH 025/203] fix: Use full name instead of abbreviation (#41759) --- erpnext/manufacturing/doctype/bom/bom.json | 6 +++--- .../doctype/bom_creator_item/bom_creator_item.json | 6 +++--- .../doctype/production_plan_item/production_plan_item.json | 4 ++-- .../report/process_loss_report/process_loss_report.py | 7 ++++++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 4ce8342af6e..58ce7c50583 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -591,13 +591,13 @@ "default": "0", "fieldname": "fg_based_operating_cost", "fieldtype": "Check", - "label": "FG based Operating Cost" + "label": "Finished Goods based Operating Cost" }, { "depends_on": "fg_based_operating_cost", "fieldname": "fg_based_section_section", "fieldtype": "Section Break", - "label": "FG Based Operating Cost Section" + "label": "Finished Goods Based Operating Cost" }, { "depends_on": "fg_based_operating_cost", @@ -637,7 +637,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2024-04-02 16:22:47.518411", + "modified": "2024-06-03 16:24:47.518411", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json index 56acd8a1a67..1726f898751 100644 --- a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json +++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json @@ -70,7 +70,7 @@ "fieldname": "fg_item", "fieldtype": "Link", "in_list_view": 1, - "label": "FG Item", + "label": "Finished Goods Item", "options": "Item", "reqd": 1 }, @@ -203,7 +203,7 @@ { "fieldname": "fg_reference_id", "fieldtype": "Data", - "label": "FG Reference", + "label": "Finished Goods Reference", "no_copy": 1, "read_only": 1 }, @@ -230,7 +230,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-16 13:34:06.321061", + "modified": "2024-06-03 18:45:24.339532", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator Item", diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 78a389760a7..089d7396b60 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -85,7 +85,7 @@ "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, - "label": "FG Warehouse", + "label": "Finished Goods Warehouse", "options": "Warehouse" }, { @@ -220,7 +220,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-27 13:24:43.571844", + "modified": "2024-06-03 13:10:20.252166", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py index 51efc6e655f..73560dd939b 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -102,7 +102,12 @@ def get_columns() -> Columns: "fieldtype": "Float", "width": "150", }, - {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"}, + { + "label": _("Finished Goods Value"), + "fieldname": "total_fg_value", + "fieldtype": "Float", + "width": "150", + }, { "label": _("Raw Material Value"), "fieldname": "total_rm_value", From dc922abfbe30c3087cc2599c11eddf4248c351d5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:55:35 +0530 Subject: [PATCH 026/203] fix: Ignore disabling default currency field while creating new company (backport #41699) (#41761) fix: Ignore disabling default currency field while creating new company (#41699) (cherry picked from commit 80f6228d45b67147815af15b27058b0173b402af) Co-authored-by: Nabin Hait --- erpnext/setup/doctype/company/company.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 2fc7a24c287..4712a10cc0a 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -12,10 +12,11 @@ frappe.ui.form.on("Company", { } }); } - - frm.call("check_if_transactions_exist").then((r) => { - frm.toggle_enable("default_currency", !r.message); - }); + if (!frm.doc.__islocal) { + frm.call("check_if_transactions_exist").then((r) => { + frm.toggle_enable("default_currency", !r.message); + }); + } }, setup: function (frm) { frm.__rename_queue = "long"; From a93cd02d546250de6e64903d56e741c5efb69bd9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:03:55 +0530 Subject: [PATCH 027/203] fix: incorrect accumulated depr amount in asset depr ledger (backport #41654) (#41763) fix: incorrect accumulated depr amount in asset depr ledger (#41654) * fix: incorrect accumulated depr amount in asset depr ledger * refactor: depreciation amount retrieval with query builder * style: apply formatting changes from pre-commit (cherry picked from commit a8fc32dc3256963ef72fbf1681e53b9a357dce23) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../asset_depreciation_ledger.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py index d285f28d8e3..e1545bdcd87 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder import DocType from frappe.utils import cstr, flt @@ -75,11 +76,24 @@ def get_data(filters): asset_data = assets_details.get(d.against_voucher) if asset_data: if not asset_data.get("accumulated_depreciation_amount"): - asset_data.accumulated_depreciation_amount = d.debit + asset_data.get( - "opening_accumulated_depreciation" - ) + AssetDepreciationSchedule = DocType("Asset Depreciation Schedule") + DepreciationSchedule = DocType("Depreciation Schedule") + query = ( + frappe.qb.from_(DepreciationSchedule) + .join(AssetDepreciationSchedule) + .on(DepreciationSchedule.parent == AssetDepreciationSchedule.name) + .select(DepreciationSchedule.accumulated_depreciation_amount) + .where( + (AssetDepreciationSchedule.asset == d.against_voucher) + & (DepreciationSchedule.parenttype == "Asset Depreciation Schedule") + & (DepreciationSchedule.schedule_date == d.posting_date) + ) + ).run(as_dict=True) + asset_data.accumulated_depreciation_amount = query[0]["accumulated_depreciation_amount"] + else: asset_data.accumulated_depreciation_amount += d.debit + asset_data.opening_accumulated_depreciation = asset_data.accumulated_depreciation_amount - d.debit row = frappe._dict(asset_data) row.update( From 77f58caa934924b5dd94441d84aef5b2144e93a5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:18:28 +0530 Subject: [PATCH 028/203] fix: Transaction currency value in Journal Entry (backport #41717) (#41720) fix: Transaction currency value in Journal Entry (cherry picked from commit 169d77da54edf9f12f0c4873a032f1f131b37621) Co-authored-by: Deepesh Garg --- .../accounts/doctype/journal_entry/journal_entry.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 402f19ee968..3ec47ff022a 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1031,6 +1031,17 @@ class JournalEntry(AccountsController): def build_gl_map(self): gl_map = [] + + company_currency = erpnext.get_company_currency(self.company) + if self.multi_currency: + for row in self.get("accounts"): + if row.account_currency != company_currency: + self.currency = row.account_currency + self.conversion_rate = row.exchange_rate + break + else: + self.currency = company_currency + for d in self.get("accounts"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): r = [d.user_remark, self.remark] From 27f3981269710ee2d1a6ab0662132b08e01bf2bf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:19:36 +0530 Subject: [PATCH 029/203] fix: Auditor permissions (backport #41659) (#41661) * fix: allow Auditor to select a company (cherry picked from commit 06401cc84fd0ffe8ab7e1cf7dec68544af72a3aa) * fix: allow Auditor to read a Fiscal Year (cherry picked from commit eaa4efbc457678155efe710b1fc6bd12516dcb62) --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- erpnext/accounts/doctype/fiscal_year/fiscal_year.json | 6 +++++- erpnext/setup/doctype/company/company.json | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json index fb997847448..66db37fe13b 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json @@ -82,7 +82,7 @@ "icon": "fa fa-calendar", "idx": 1, "links": [], - "modified": "2024-01-30 12:35:38.645968", + "modified": "2024-05-27 17:29:55.560840", "modified_by": "Administrator", "module": "Accounts", "name": "Fiscal Year", @@ -127,6 +127,10 @@ { "read": 1, "role": "Stock Manager" + }, + { + "read": 1, + "role": "Auditor" } ], "show_name_in_global_search": 1, diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 674805980f5..b2d6af03a10 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -795,7 +795,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2024-05-16 12:39:54.694232", + "modified": "2024-05-27 17:32:49.057386", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -851,6 +851,10 @@ "role": "Accounts Manager", "share": 1, "write": 1 + }, + { + "role": "Auditor", + "select": 1 } ], "show_name_in_global_search": 1, From 97b1b7f8423d9c485c68a1b1cb60da17d8e84dfc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:21:16 +0530 Subject: [PATCH 030/203] fix: filtered out data having none values in date field (backport #41457) (#41765) * fix: filtered out data having none values in date field (cherry picked from commit 3bd455ac856c5a073e7ec44bb7e855c64e7789cb) * style: code optimization (cherry picked from commit a36b7fb95a4b1e42f0a0da3e73d9e380f171d5b5) --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../report/fixed_asset_register/fixed_asset_register.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 4ff04c80434..e86ca15d00a 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -159,8 +159,9 @@ def prepare_chart_data(data, filters): if filters.filter_based_on not in ("Date Range", "Fiscal Year"): filters_filter_based_on = "Date Range" date_field = "purchase_date" - filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field) - filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field) + filtered_data = [d for d in data if not d.get(date_field)] + filters_from_date = min(filtered_data, key=lambda a: a.get(date_field)).get(date_field) + filters_to_date = max(filtered_data, key=lambda a: a.get(date_field)).get(date_field) else: filters_filter_based_on = filters.filter_based_on date_field = frappe.scrub(filters.date_based_on) From 95c0dc9b388d659abebe80c045af926a139a7f9a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:54:20 +0530 Subject: [PATCH 031/203] fix: incorrect batch selection while sales return (backport #41768) (#41772) fix: incorrect batch selection while sales return (#41768) (cherry picked from commit cb6d8afa26203dd8aeb278e1fe9dcc2d19776ecc) Co-authored-by: rohitwaghchaure --- .../js/utils/serial_no_batch_selector.js | 3 +- .../serial_and_batch_bundle.py | 59 ++++++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 3c3d90c6d4f..4928f2dc1a5 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -613,7 +613,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } render_data() { - if (this.bundle) { + if (this.bundle || this.frm.doc.is_return) { frappe .call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers", @@ -621,6 +621,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { item_code: this.item.item_code, name: this.bundle, voucher_no: !this.frm.is_new() ? this.item.parent : "", + child_row: this.frm.doc.is_return ? this.item : "", }, }) .then((r) => { diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index ad757351a9b..87bf2df5f61 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1209,31 +1209,44 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals @frappe.whitelist() -def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None): +def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None, child_row=None): filters = get_filters_for_bundle( - item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name + item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name, child_row=child_row ) + fields = [ + "`tabSerial and Batch Bundle`.`item_code`", + "`tabSerial and Batch Entry`.`qty`", + "`tabSerial and Batch Entry`.`warehouse`", + "`tabSerial and Batch Entry`.`batch_no`", + "`tabSerial and Batch Entry`.`serial_no`", + ] + + if not child_row: + fields.append("`tabSerial and Batch Bundle`.`name`") + return frappe.get_all( "Serial and Batch Bundle", - fields=[ - "`tabSerial and Batch Bundle`.`name`", - "`tabSerial and Batch Bundle`.`item_code`", - "`tabSerial and Batch Entry`.`qty`", - "`tabSerial and Batch Entry`.`warehouse`", - "`tabSerial and Batch Entry`.`batch_no`", - "`tabSerial and Batch Entry`.`serial_no`", - ], + fields=fields, filters=filters, order_by="`tabSerial and Batch Entry`.`idx`", ) -def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None): +def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None, child_row=None): filters = [ ["Serial and Batch Bundle", "is_cancelled", "=", 0], ] + if child_row and isinstance(child_row, str): + child_row = parse_json(child_row) + + if not name and child_row and child_row.get("qty") < 0: + bundle = get_reference_serial_and_batch_bundle(child_row) + if bundle: + voucher_no = None + filters.append(["Serial and Batch Bundle", "name", "=", bundle]) + if item_code: filters.append(["Serial and Batch Bundle", "item_code", "=", item_code]) @@ -1257,6 +1270,19 @@ def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name return filters +def get_reference_serial_and_batch_bundle(child_row): + field = { + "Sales Invoice Item": "sales_invoice_item", + "Delivery Note Item": "dn_detail", + "Purchase Receipt Item": "purchase_receipt_item", + "Purchase Invoice Item": "purchase_invoice_item", + "POS Invoice Item": "pos_invoice_item", + }.get(child_row.doctype) + + if field: + return frappe.get_cached_value(child_row.doctype, child_row.get(field), "serial_and_batch_bundle") + + @frappe.whitelist() def add_serial_batch_ledgers(entries, child_row, doc, warehouse, do_not_save=False) -> object: if isinstance(child_row, str): @@ -1334,9 +1360,6 @@ def get_type_of_transaction(parent_doc, child_row): if parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]: type_of_transaction = "Inward" - if parent_doc.get("is_return"): - type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward" - if parent_doc.get("doctype") == "Subcontracting Receipt": type_of_transaction = "Outward" if child_row.get("doctype") == "Subcontracting Receipt Item": @@ -1344,6 +1367,14 @@ def get_type_of_transaction(parent_doc, child_row): elif parent_doc.get("doctype") == "Stock Reconciliation": type_of_transaction = "Inward" + if parent_doc.get("is_return"): + type_of_transaction = "Inward" + if ( + parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"] + or child_row.get("doctype") == "Subcontracting Receipt Item" + ): + type_of_transaction = "Outward" + return type_of_transaction From 12addb7565459406102e09f9cfb804288777edd2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:54:34 +0530 Subject: [PATCH 032/203] fix: TypeError: 'float' object is not iterable (backport #41758) (#41764) * fix: TypeError: 'float' object is not iterable (#41758) (cherry picked from commit cf508ce1edd05a2fe04eec0ea7cb6c6fca82fb0a) # Conflicts: # erpnext/manufacturing/doctype/bom_creator/bom_creator.py # erpnext/public/js/bom_configurator/bom_configurator.bundle.js * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- .../stock/report/stock_ledger/stock_ledger.py | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 2920ebf69e8..e56c499d767 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -7,7 +7,7 @@ from collections import defaultdict import frappe from frappe import _ -from frappe.query_builder.functions import CombineDatetime +from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import cint, flt from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions @@ -69,12 +69,12 @@ def execute(filters=None): stock_value += sle.stock_value_difference if sle.batch_no: if not batch_balance_dict.get(sle.batch_no): - batch_balance_dict[sle.batch_no] = 0 + batch_balance_dict[sle.batch_no] = [0, 0] - batch_balance_dict[sle.batch_no] += sle.actual_qty + batch_balance_dict[sle.batch_no][0] += sle.actual_qty if filters.get("segregate_serial_batch_bundle"): - actual_qty = batch_balance_dict[sle.batch_no] + actual_qty = batch_balance_dict[sle.batch_no][0] if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty: actual_qty = sle.qty_after_transaction @@ -530,7 +530,9 @@ def get_opening_balance_from_batch(filters, columns, sl_entries): query_filters = { "batch_no": filters.batch_no, "docstatus": 1, + "is_cancelled": 0, "posting_date": ("<", filters.from_date), + "company": filters.company, } for fields in ["item_code", "warehouse"]: @@ -547,25 +549,30 @@ def get_opening_balance_from_batch(filters, columns, sl_entries): if opening_data.get(field) is None: opening_data[field] = 0.0 - query_filters = [ - ["Serial and Batch Entry", "batch_no", "=", filters.batch_no], - ["Serial and Batch Bundle", "docstatus", "=", 1], - ["Serial and Batch Bundle", "posting_date", "<", filters.from_date], - ] - - for fields in ["item_code", "warehouse"]: - if filters.get(fields): - query_filters.append(["Serial and Batch Bundle", fields, "=", filters.get(fields)]) - - bundle_data = frappe.get_all( - "Serial and Batch Bundle", - fields=[ - "sum(`tabSerial and Batch Entry`.`qty`) as qty", - "sum(`tabSerial and Batch Entry`.`stock_value_difference`) as stock_value", - ], - filters=query_filters, + table = frappe.qb.DocType("Stock Ledger Entry") + sabb_table = frappe.qb.DocType("Serial and Batch Entry") + query = ( + frappe.qb.from_(table) + .inner_join(sabb_table) + .on(table.serial_and_batch_bundle == sabb_table.parent) + .select( + Sum(sabb_table.qty).as_("qty"), + Sum(sabb_table.stock_value_difference).as_("stock_value"), + ) + .where( + (sabb_table.batch_no == filters.batch_no) + & (sabb_table.docstatus == 1) + & (table.posting_date < filters.from_date) + & (table.is_cancelled == 0) + ) ) + for field in ["item_code", "warehouse", "company"]: + if filters.get(field): + query = query.where(table[field] == filters.get(field)) + + bundle_data = query.run(as_dict=True) + if bundle_data: opening_data.qty_after_transaction += flt(bundle_data[0].qty) opening_data.stock_value += flt(bundle_data[0].stock_value) From d30aee5dd618c8ab5b898b49f11e71d0d3176af7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:50:18 +0530 Subject: [PATCH 033/203] fix: link opportunity in prospect after creating opportunity from prospect (backport #41769) (#41777) fix: link opportunity in prospect after creating opportunity from prospect (#41769) (cherry picked from commit 12e48f0b632816101cc2babc183fd4b4d170832a) Co-authored-by: Nabin Hait --- erpnext/crm/doctype/opportunity/opportunity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index aeaebafabe9..db9d31b53bb 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -118,6 +118,8 @@ class Opportunity(TransactionBase, CRMNote): self.title = self.customer_name self.calculate_totals() + + def on_update(self): self.update_prospect() def map_fields(self): From cd0852f05d71a0d10b171bd68258e7d35021702d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:50:28 +0530 Subject: [PATCH 034/203] fix: convert percentage from str to float (backport #41766) (#41776) fix: convert percentage from str to float (#41766) (cherry picked from commit 71f5470dfdee39322d007e24c2f9b5c60bb6db0b) Co-authored-by: Nabin Hait --- .../doctype/cost_center_allocation/cost_center_allocation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py index 784ad27dd47..34b2ec68743 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -4,7 +4,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import add_days, format_date, getdate +from frappe.utils import add_days, flt, format_date, getdate class MainCostCenterCantBeChild(frappe.ValidationError): @@ -60,7 +60,7 @@ class CostCenterAllocation(Document): self.validate_child_cost_centers() def validate_total_allocation_percentage(self): - total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])]) + total_percentage = sum([flt(d.percentage) for d in self.get("allocation_percentages", [])]) if total_percentage != 100: frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation) From a67fa6863ee051465ed81371d330e7ec10ba6737 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:53:28 +0530 Subject: [PATCH 035/203] fix(minor): corrected wrong filter condition (backport #41755) (#41762) * fix(minor): corrected wrong filter condition (#41755) (cherry picked from commit 60eb03a6c6ebbe008c114cb0f20a3f34dc6203c4) # Conflicts: # erpnext/assets/report/fixed_asset_register/fixed_asset_register.py * fix: resolved conflict --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Co-authored-by: Nabin Hait --- .../assets/report/fixed_asset_register/fixed_asset_register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index e86ca15d00a..5d4ef4e3845 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -159,7 +159,7 @@ def prepare_chart_data(data, filters): if filters.filter_based_on not in ("Date Range", "Fiscal Year"): filters_filter_based_on = "Date Range" date_field = "purchase_date" - filtered_data = [d for d in data if not d.get(date_field)] + filtered_data = [d for d in data if d.get(date_field)] filters_from_date = min(filtered_data, key=lambda a: a.get(date_field)).get(date_field) filters_to_date = max(filtered_data, key=lambda a: a.get(date_field)).get(date_field) else: From 3777cae8600280a436b8741cb94947dcc6334813 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:25:20 +0530 Subject: [PATCH 036/203] fix: local var referenced before assignment (backport #41780) (#41782) fix: local var referenced before assignment (#41780) (cherry picked from commit 4ff3e6caaa05d1ea2de948387b2505e89ce48f31) Co-authored-by: Nabin Hait --- erpnext/buying/doctype/purchase_order/purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 5351ff6d791..d1f19841ac5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -902,12 +902,12 @@ def get_mapped_subcontracting_order(source_name, target_doc=None): ) target_doc.populate_items_table() + source_doc = frappe.get_doc("Purchase Order", source_name) if target_doc.set_warehouse: for item in target_doc.items: item.warehouse = target_doc.set_warehouse else: - source_doc = frappe.get_doc("Purchase Order", source_name) if source_doc.set_warehouse: for item in target_doc.items: item.warehouse = source_doc.set_warehouse From b94a6f2d5cec1432ad29c5728ff90364646ed065 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:25:28 +0530 Subject: [PATCH 037/203] fix: bank account no fieldname mismatch (backport #41778) (#41783) fix: bank account no fieldname mismatch (#41778) (cherry picked from commit d163dbec7d7b2ac0d538ba07d175e195cac0f790) Co-authored-by: Nabin Hait --- erpnext/accounts/doctype/bank_transaction/auto_match_party.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py index 75f02e659f4..a1271b9c32a 100644 --- a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py +++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py @@ -68,6 +68,9 @@ class AutoMatchbyAccountIBAN: party, or_filters=or_filters, pluck="name", limit_page_length=1 ) + if "bank_ac_no" in or_filters: + or_filters["bank_account_no"] = or_filters.pop("bank_ac_no") + if party_result: result = ( party, From 03bf48032e13af7452ff123c428493b0c1ae95d7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:25:40 +0530 Subject: [PATCH 038/203] fix: Copy depreciation schedule on asset split only if it exists (backport #41775) (#41784) fix: Copy depreciation schedule on asset split only if it exists (#41775) (cherry picked from commit 689e1cfc23fd98bc20cac850bb75dde637574dfd) Co-authored-by: Nabin Hait --- erpnext/assets/doctype/asset/asset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 4edfcd11c89..063a5447ab5 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -1140,6 +1140,8 @@ def create_new_asset_after_split(asset, split_qty): for row in new_asset.get("finance_books"): current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) + if not current_asset_depr_schedule_doc: + continue new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row) From dcdaa02173ffcba329e37f7c61ea22104d3a30d9 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 5 Jun 2024 01:20:36 +0000 Subject: [PATCH 039/203] chore(release): Bumped to Version 15.27.0 # [15.27.0](https://github.com/frappe/erpnext/compare/v15.26.1...v15.27.0) (2024-06-05) ### Bug Fixes * Auditor permissions (backport [#41659](https://github.com/frappe/erpnext/issues/41659)) ([#41661](https://github.com/frappe/erpnext/issues/41661)) ([27f3981](https://github.com/frappe/erpnext/commit/27f3981269710ee2d1a6ab0662132b08e01bf2bf)) * bank account no fieldname mismatch (backport [#41778](https://github.com/frappe/erpnext/issues/41778)) ([#41783](https://github.com/frappe/erpnext/issues/41783)) ([b94a6f2](https://github.com/frappe/erpnext/commit/b94a6f2d5cec1432ad29c5728ff90364646ed065)) * batch selection issue (backport [#41692](https://github.com/frappe/erpnext/issues/41692)) ([#41693](https://github.com/frappe/erpnext/issues/41693)) ([24775db](https://github.com/frappe/erpnext/commit/24775db97e3c58ae213d7bd0cdd1f902599ef351)) * **Company:** Allow Equity type account in unrealised exchange gain/loss account (backport [#41740](https://github.com/frappe/erpnext/issues/41740)) ([#41747](https://github.com/frappe/erpnext/issues/41747)) ([460ee5d](https://github.com/frappe/erpnext/commit/460ee5d542d619d5f6c96cf44a65d84bd538cc90)) * condition filter for transaction pricing rule ([e493b11](https://github.com/frappe/erpnext/commit/e493b11ae3cc40a4e03666dba166d45f25f631a7)) * convert percentage from str to float (backport [#41766](https://github.com/frappe/erpnext/issues/41766)) ([#41776](https://github.com/frappe/erpnext/issues/41776)) ([cd0852f](https://github.com/frappe/erpnext/commit/cd0852f05d71a0d10b171bd68258e7d35021702d)) * Copy depreciation schedule on asset split only if it exists (backport [#41775](https://github.com/frappe/erpnext/issues/41775)) ([#41784](https://github.com/frappe/erpnext/issues/41784)) ([03bf480](https://github.com/frappe/erpnext/commit/03bf48032e13af7452ff123c428493b0c1ae95d7)) * filtered out data having none values in date field (backport [#41457](https://github.com/frappe/erpnext/issues/41457)) ([#41765](https://github.com/frappe/erpnext/issues/41765)) ([97b1b7f](https://github.com/frappe/erpnext/commit/97b1b7f8423d9c485c68a1b1cb60da17d8e84dfc)) * Filters in account balance report ([0b9d89b](https://github.com/frappe/erpnext/commit/0b9d89b966cd7fdd78ac3f57ee63ec18cc1c74f2)) * French chart of account the 4191 code must be of type Income Account ([797b718](https://github.com/frappe/erpnext/commit/797b718e5a3e487a1d2c5cb810e5855ea0645c0c)) * get assets received but not billed account only if any asset item is received (backport [#41734](https://github.com/frappe/erpnext/issues/41734)) ([#41735](https://github.com/frappe/erpnext/issues/41735)) ([638c944](https://github.com/frappe/erpnext/commit/638c94451e987dee6a8097f70fa95d32150ed5fe)) * Ignore disabling default currency field while creating new company (backport [#41699](https://github.com/frappe/erpnext/issues/41699)) ([#41761](https://github.com/frappe/erpnext/issues/41761)) ([dc922ab](https://github.com/frappe/erpnext/commit/dc922abfbe30c3087cc2599c11eddf4248c351d5)) * incorrect accumulated depr amount in asset depr ledger (backport [#41654](https://github.com/frappe/erpnext/issues/41654)) ([#41763](https://github.com/frappe/erpnext/issues/41763)) ([a93cd02](https://github.com/frappe/erpnext/commit/a93cd02d546250de6e64903d56e741c5efb69bd9)) * incorrect batch selection while sales return (backport [#41768](https://github.com/frappe/erpnext/issues/41768)) ([#41772](https://github.com/frappe/erpnext/issues/41772)) ([95c0dc9](https://github.com/frappe/erpnext/commit/95c0dc9b388d659abebe80c045af926a139a7f9a)) * key error for stock ledger report (backport [#41700](https://github.com/frappe/erpnext/issues/41700)) ([#41703](https://github.com/frappe/erpnext/issues/41703)) ([b22d714](https://github.com/frappe/erpnext/commit/b22d71410327fb504fc758778d9332ff16cb9dcf)) * link opportunity in prospect after creating opportunity from prospect (backport [#41769](https://github.com/frappe/erpnext/issues/41769)) ([#41777](https://github.com/frappe/erpnext/issues/41777)) ([d30aee5](https://github.com/frappe/erpnext/commit/d30aee5dd618c8ab5b898b49f11e71d0d3176af7)) * local var referenced before assignment (backport [#41780](https://github.com/frappe/erpnext/issues/41780)) ([#41782](https://github.com/frappe/erpnext/issues/41782)) ([3777cae](https://github.com/frappe/erpnext/commit/3777cae8600280a436b8741cb94947dcc6334813)) * **minor:** corrected wrong filter condition (backport [#41755](https://github.com/frappe/erpnext/issues/41755)) ([#41762](https://github.com/frappe/erpnext/issues/41762)) ([a67fa68](https://github.com/frappe/erpnext/commit/a67fa6863ee051465ed81371d330e7ec10ba6737)) * payment term when creating PO from SO (backport [#41376](https://github.com/frappe/erpnext/issues/41376)) ([#41744](https://github.com/frappe/erpnext/issues/41744)) ([806ff5b](https://github.com/frappe/erpnext/commit/806ff5bc19597eb0a408b313c6f52a4064c52d11)) * Select both account number and company in the account form (backport [#41731](https://github.com/frappe/erpnext/issues/41731)) ([#41737](https://github.com/frappe/erpnext/issues/41737)) ([c5d72cd](https://github.com/frappe/erpnext/commit/c5d72cd9345a68868bfa9eefbf7362868d98cb04)) * show material to supplier button (backport [#41686](https://github.com/frappe/erpnext/issues/41686)) ([#41688](https://github.com/frappe/erpnext/issues/41688)) ([1a24914](https://github.com/frappe/erpnext/commit/1a24914aedc7f5ed2caa9416f846679b4d204180)) * **stock settings:** Allowed any number in stock frozen upto days field (backport [#41736](https://github.com/frappe/erpnext/issues/41736)) ([#41749](https://github.com/frappe/erpnext/issues/41749)) ([278682f](https://github.com/frappe/erpnext/commit/278682f4df3176b12e8b45d93b5b54dbd2552da8)) * test for pricing rule transaction with cond ([0823de8](https://github.com/frappe/erpnext/commit/0823de86ad22069901c525173feca6ccbda21b56)) * Transaction currency value in Journal Entry (backport [#41717](https://github.com/frappe/erpnext/issues/41717)) ([#41720](https://github.com/frappe/erpnext/issues/41720)) ([77f58ca](https://github.com/frappe/erpnext/commit/77f58caa934924b5dd94441d84aef5b2144e93a5)) * TypeError: 'float' object is not iterable (backport [#41758](https://github.com/frappe/erpnext/issues/41758)) ([#41764](https://github.com/frappe/erpnext/issues/41764)) ([12addb7](https://github.com/frappe/erpnext/commit/12addb7565459406102e09f9cfb804288777edd2)) * Use full name instead of abbreviation ([#41759](https://github.com/frappe/erpnext/issues/41759)) ([5448d5d](https://github.com/frappe/erpnext/commit/5448d5d1cb2daa454931204da5110990d2e2b516)) * work order created message pops up if no items are selected (backport [#41677](https://github.com/frappe/erpnext/issues/41677)) ([#41682](https://github.com/frappe/erpnext/issues/41682)) ([4e9faf6](https://github.com/frappe/erpnext/commit/4e9faf6ad6beca3ac38b95af28c192bf5e612f34)) ### Features * optional to reconcile all serial nos / batches in stock reconciliation (backport [#41696](https://github.com/frappe/erpnext/issues/41696)) ([#41713](https://github.com/frappe/erpnext/issues/41713)) ([38249f4](https://github.com/frappe/erpnext/commit/38249f41708a5c955192c817d380eec38419d144)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 9d6a51bb102..fca29051bb5 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.26.1" +__version__ = "15.27.0" def get_default_company(user=None): From 822bc581f3965f85f0080cfc0b2426520538fccb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 3 Jun 2024 10:09:20 +0530 Subject: [PATCH 040/203] fix: Do no apply pricing rule on qty change for mapped docs (cherry picked from commit 2b1242170c87f558945df18d3414903a3311570b) --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 937b19d9abb..139ab4bff0f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1246,8 +1246,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } qty(doc, cdt, cdn) { - if (!this.frm.doc.__onload?.load_after_mapping) { - let item = frappe.get_doc(cdt, cdn); + let item = frappe.get_doc(cdt, cdn); + if (!this.is_a_mapped_document(item)) { // item.pricing_rules = '' frappe.run_serially([ () => this.remove_pricing_rule_for_item(item), From 0e562e9933fba768731f04a89a15b51554caed8a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 6 Jun 2024 13:57:14 +0530 Subject: [PATCH 041/203] chore: repost settings permission overhaul (cherry picked from commit 458d8f5ed8affecf0d25bd732dc5af6f786d894c) --- .../repost_accounting_ledger_settings.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json index 8aa0a840c7e..dd314ca6601 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -17,7 +17,7 @@ "in_create": 1, "issingle": 1, "links": [], - "modified": "2023-11-07 14:24:13.321522", + "modified": "2024-06-06 13:56:37.908879", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger Settings", @@ -30,13 +30,17 @@ "print": 1, "read": 1, "role": "Administrator", + "select": 1, "share": 1, "write": 1 }, { + "create": 1, + "delete": 1, "read": 1, "role": "System Manager", - "select": 1 + "select": 1, + "write": 1 } ], "sort_field": "modified", From 94720851c6b819f3466dbae45a566f2d1830e2e3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 6 Jun 2024 17:22:27 +0530 Subject: [PATCH 042/203] fix: Add posting date to key for consolidated view (cherry picked from commit 2c164b47a13bc458b10df95f0aa3f2425bef1bfb) --- erpnext/accounts/report/general_ledger/general_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 2946cfa0e82..44fad110baa 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -482,6 +482,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): elif group_by_voucher_consolidated: keylist = [ + gle.get("posting_date"), gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account"), From edd4e674d32a9728b4bda3248b6b618613605643 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 6 Jun 2024 17:22:27 +0530 Subject: [PATCH 043/203] fix: Add posting date to key for consolidated view (cherry picked from commit 2c164b47a13bc458b10df95f0aa3f2425bef1bfb) (cherry picked from commit 94720851c6b819f3466dbae45a566f2d1830e2e3) --- erpnext/accounts/report/general_ledger/general_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 2946cfa0e82..44fad110baa 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -482,6 +482,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): elif group_by_voucher_consolidated: keylist = [ + gle.get("posting_date"), gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account"), From df4307fef8a9bdfb893100d98dee65d949ef8643 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 6 Jun 2024 12:20:49 +0000 Subject: [PATCH 044/203] chore(release): Bumped to Version 15.27.1 ## [15.27.1](https://github.com/frappe/erpnext/compare/v15.27.0...v15.27.1) (2024-06-06) ### Bug Fixes * Add posting date to key for consolidated view ([edd4e67](https://github.com/frappe/erpnext/commit/edd4e674d32a9728b4bda3248b6b618613605643)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index fca29051bb5..22be77c9b4a 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.0" +__version__ = "15.27.1" def get_default_company(user=None): From 4d3e88313013af0e3741a0b108c49f6680fc95a5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:46:02 +0530 Subject: [PATCH 045/203] fix: typo in method - holiday list (backport #41811) (#41812) fix: typo in method - holiday list (#41811) (cherry picked from commit 699cfd85c64916163f6ef156d47297c5725028ac) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- erpnext/setup/doctype/holiday_list/holiday_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py index 9d854c7c412..6932f7be825 100644 --- a/erpnext/setup/doctype/holiday_list/holiday_list.py +++ b/erpnext/setup/doctype/holiday_list/holiday_list.py @@ -42,7 +42,7 @@ class HolidayList(Document): def validate(self): self.validate_days() self.total_holidays = len(self.holidays) - self.validate_dupliacte_date() + self.validate_duplicate_date() @frappe.whitelist() def get_weekly_off_dates(self): @@ -148,7 +148,7 @@ class HolidayList(Document): def clear_table(self): self.set("holidays", []) - def validate_dupliacte_date(self): + def validate_duplicate_date(self): unique_dates = [] for row in self.holidays: if row.holiday_date in unique_dates: From 4d37739dd3ede937e93e8328fa6a147462efc241 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:45:50 +0530 Subject: [PATCH 046/203] fix(Vehicle): allow doc renaming and add test (backport #41729) (#41785) * fix(Vehicle): allow doc renaming and add test (#41729) fix(Vehicle): allow renaming doc and add test (cherry picked from commit c76f466528e2e6651df5d72e14f9418b8d5bec65) # Conflicts: # erpnext/setup/doctype/vehicle/vehicle.json * fix: resolve conflicts for backporting --------- Co-authored-by: Viny Selopal <52369157+vinyselopal@users.noreply.github.com> Co-authored-by: Viny Selopal --- erpnext/setup/doctype/vehicle/test_vehicle.py | 26 +++++++++++++++++++ erpnext/setup/doctype/vehicle/vehicle.json | 6 +++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/vehicle/test_vehicle.py b/erpnext/setup/doctype/vehicle/test_vehicle.py index 97fe651122c..3cd52386284 100644 --- a/erpnext/setup/doctype/vehicle/test_vehicle.py +++ b/erpnext/setup/doctype/vehicle/test_vehicle.py @@ -26,3 +26,29 @@ class TestVehicle(unittest.TestCase): } ) vehicle.insert() + + def test_renaming_vehicle(self): + license_plate = random_string(10).upper() + + vehicle = frappe.get_doc( + { + "doctype": "Vehicle", + "license_plate": license_plate, + "make": "Skoda", + "model": "Slavia", + "last_odometer": 5000, + "acquisition_date": frappe.utils.nowdate(), + "location": "Mumbai", + "chassis_no": "1234EFGH", + "uom": "Litre", + "vehicle_value": frappe.utils.flt(500000), + } + ) + vehicle.insert() + + new_license_plate = random_string(10).upper() + frappe.rename_doc("Vehicle", license_plate, new_license_plate) + + self.assertEqual( + new_license_plate, frappe.db.get_value("Vehicle", new_license_plate, "license_plate") + ) diff --git a/erpnext/setup/doctype/vehicle/vehicle.json b/erpnext/setup/doctype/vehicle/vehicle.json index b19d45924fb..c76719e4b92 100644 --- a/erpnext/setup/doctype/vehicle/vehicle.json +++ b/erpnext/setup/doctype/vehicle/vehicle.json @@ -2,7 +2,8 @@ "allow_copy": 0, "allow_guest_to_view": 0, "allow_import": 0, - "allow_rename": 0, + "actions": [], + "allow_rename": 1, "autoname": "field:license_plate", "beta": 0, "creation": "2016-09-03 03:33:27.680331", @@ -834,7 +835,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2022-06-27 14:48:30.813359", + "links": [], + "modified": "2024-05-31 06:38:15.399283", "modified_by": "Administrator", "module": "Setup", "name": "Vehicle", From f1ad0f6f269c7eeb9668798f0e6815a620c38a95 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 7 Jun 2024 16:50:21 +0530 Subject: [PATCH 047/203] fix: enable no_copy for timesheet in sales invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 1b36bf50f86..90b6e768092 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -787,6 +787,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Time Sheets", + "no_copy": 1, "options": "Sales Invoice Timesheet", "print_hide": 1 }, @@ -2187,7 +2188,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-05-08 18:02:28.549041", + "modified": "2024-06-07 16:49:32.458402", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From 8d31243a42a37b4dd0947d4d506815e8d999a009 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:25:58 +0530 Subject: [PATCH 048/203] fix: valuation rate for backdated legacy serial/batches (backport #41788) (#41821) fix: valuation rate for backdated legacy serial/batches (#41788) (cherry picked from commit 3956354e0834a438c24de0f2dee7c5c8c4e613cc) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index abbd48853cc..ef088927024 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -36,6 +36,7 @@ from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, get_incoming_rate, get_or_make_bin, + get_serial_nos_data, get_stock_balance, get_valuation_method, ) @@ -811,9 +812,10 @@ class update_entries_after: self.update_outgoing_rate_on_transaction(sle) def get_serialized_values(self, sle): + from erpnext.stock.serial_batch_bundle import SerialNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) - serial_nos = cstr(sle.serial_no).split("\n") if incoming_rate < 0: # wrong incoming rate @@ -826,7 +828,15 @@ class update_entries_after: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry if not sle.is_cancelled: - outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) + new_sle = copy.deepcopy(sle) + new_sle.qty = new_sle.actual_qty + new_sle.serial_nos = get_serial_nos_data(new_sle.get("serial_no")) + + sn_obj = SerialNoValuation( + sle=new_sle, warehouse=new_sle.get("warehouse"), item_code=new_sle.get("item_code") + ) + + outgoing_value = sn_obj.get_incoming_rate() stock_value_change = -1 * outgoing_value else: stock_value_change = actual_qty * sle.outgoing_rate @@ -1272,6 +1282,8 @@ class update_entries_after: self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction def update_batched_values(self, sle): + from erpnext.stock.serial_batch_bundle import BatchNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) @@ -1282,15 +1294,18 @@ class update_entries_after: if actual_qty > 0: stock_value_difference = incoming_rate * actual_qty else: - outgoing_rate = get_batch_incoming_rate( - item_code=sle.item_code, - warehouse=sle.warehouse, - batch_no=sle.batch_no, - posting_date=sle.posting_date, - posting_time=sle.posting_time, - creation=sle.creation, + new_sle = copy.deepcopy(sle) + + new_sle.qty = new_sle.actual_qty + new_sle.batch_nos = frappe._dict({new_sle.batch_no: new_sle}) + batch_obj = BatchNoValuation( + sle=new_sle, + warehouse=new_sle.get("warehouse"), + item_code=new_sle.get("item_code"), ) + outgoing_rate = batch_obj.get_incoming_rate() + if outgoing_rate is None: # This can *only* happen if qty available for the batch is zero. # in such case fall back various other rates. From a7971cf8443dac71347369fb38ab2edf0ed8dc27 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:05:50 +0530 Subject: [PATCH 049/203] fix: stock_value_change for legacy serial nos (backport #41825) (#41826) fix: stock_value_change for legacy serial nos (#41825) (cherry picked from commit f6a4d391c01f751f7cea5050690460b8032cbbbd) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ef088927024..e804ae18016 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -837,7 +837,7 @@ class update_entries_after: ) outgoing_value = sn_obj.get_incoming_rate() - stock_value_change = -1 * outgoing_value + stock_value_change = actual_qty * outgoing_value else: stock_value_change = actual_qty * sle.outgoing_rate @@ -1312,6 +1312,7 @@ class update_entries_after: # future entries will correct the overall accounting as each # batch individually uses moving average rates. outgoing_rate = self.get_fallback_rate(sle) + stock_value_difference = outgoing_rate * actual_qty self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference) From f36db7ebaf0c1e3c0de716f2733961be4f5b632e Mon Sep 17 00:00:00 2001 From: Poorvi Date: Fri, 10 May 2024 11:47:57 +0530 Subject: [PATCH 050/203] fix: fixing Item-wise sales register (cherry picked from commit c90185f5331e2271afb5ddc22340d3704f5bbcd3) --- .../accounts/report/purchase_register/purchase_register.py | 6 ++++-- erpnext/accounts/report/sales_register/sales_register.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 504c74babcb..7631506f506 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -376,8 +376,10 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") + pii = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(pi) + .left_join(pii).on(pi.name == pii.parent) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -386,7 +388,7 @@ def get_invoices(filters, additional_query_columns): pi.supplier, pi.supplier_name, pi.tax_id, - pi.bill_no, + pi.bill_no, pi.bill_date, pi.remarks, pi.base_net_total, @@ -395,7 +397,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where(pi.docstatus == 1) + .where((pi.docstatus == 1) & pii.item_code.isnotnull()) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f27569531b1..832845ffe2e 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -414,9 +414,11 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") + sii = frappe.qb.DocType("Sales Invoice Item") query = ( frappe.qb.from_(si) - .select( + .left_join(sii).on(si.name == sii.parent) + .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, si.posting_date, @@ -437,7 +439,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where(si.docstatus == 1) + .where((si.docstatus == 1) & sii.item_code.isnotnull()) .orderby(si.posting_date, si.name, order=Order.desc) ) From 96405e8e4957c672b7a4aa5f7e9ead081a6e143d Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Mon, 13 May 2024 09:35:10 +0530 Subject: [PATCH 051/203] fix: Item-wise Sales and Purchase register with no item codes #41373 (cherry picked from commit 1b45ecfcae5ed9587c5d03e14afaf6d39d3fa84f) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py --- .../item_wise_purchase_register.py | 36 +++++++++++++++- .../item_wise_sales_register.py | 43 +++++++++++++++++++ .../purchase_register/purchase_register.py | 4 +- .../report/sales_register/sales_register.py | 6 +-- 4 files changed, 81 insertions(+), 8 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 ebdea0602d1..02fb873c572 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 @@ -311,8 +311,8 @@ def get_conditions(filters): return conditions - def get_items(filters, additional_query_columns): +<<<<<<< HEAD conditions = get_conditions(filters) if additional_query_columns: additional_query_columns = "," + ",".join(additional_query_columns) @@ -339,6 +339,40 @@ def get_items(filters, additional_query_columns): filters, as_dict=1, ) +======= + pi = frappe.qb.DocType('Purchase Invoice') + pii = frappe.qb.DocType('Purchase Invoice Item') + Item = frappe.qb.DocType('Item') + query = (frappe.qb.from_(pi) + .join(pii).on(pi.name == pii.parent) + .left_join(Item).on(pii.item_code == Item.name) + .select( + pii.name.as_('pii_name'), pii.parent, + pi.posting_date, pi.credit_to, pi.company, + pi.supplier, pi.remarks, pi.base_net_total, + pi.unrealized_profit_loss_account, + pii.item_code, pii.description, pii.item_group, + pii.item_name.as_('pi_item_name'), pii.item_group.as_('pi_item_group'), + Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), + pii.project, pii.purchase_order, + pii.purchase_receipt, pii.po_detail, + pii.expense_account, pii.stock_qty, + pii.stock_uom, pii.base_net_amount, + pi.supplier_name, pi.mode_of_payment + ) + .where(pi.docstatus == 1)) + + if additional_query_columns: + query = query.select(*additional_query_columns) + + if filters.get("supplier"): + query = query.where(pi.supplier == filters['supplier']) + if filters.get("company"): + query = query.where(pi.company == filters['company']) + + return query.run(as_dict=True) + +>>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) def get_aii_accounts(): diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index c7819731930..fd538b7bfa6 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -388,6 +388,7 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) +<<<<<<< HEAD def get_items(filters, additional_query_columns, additional_conditions=None): conditions = get_conditions(filters, additional_conditions) if additional_query_columns: @@ -420,6 +421,48 @@ def get_items(filters, additional_query_columns, additional_conditions=None): filters, as_dict=1, ) # nosec +======= +def get_items(filters, additional_query_columns,additional_conditions=None): + si = frappe.qb.DocType('Sales Invoice') + sii = frappe.qb.DocType('Sales Invoice Item') + Item = frappe.qb.DocType('Item') + + query = ( + frappe.qb.from_(si) + .join(sii).on(si.name == sii.parent) + .left_join(Item).on(sii.item_code == Item.name) + .select( + sii.name, sii.parent, + si.posting_date, si.debit_to, + si.unrealized_profit_loss_account, + si.is_internal_customer, + si.customer, si.remarks, + si.territory, si.company, si.base_net_total, + sii.project, + sii.item_code, sii.description, + sii.item_name, sii.item_group, + sii.item_name.as_('si_item_name'), sii.item_group.as_('si_item_group'), + Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), + sii.sales_order, sii.delivery_note, + sii.income_account, sii.cost_center, + sii.enable_deferred_revenue, sii.deferred_revenue_account, + sii.stock_qty, sii.stock_uom, + sii.base_net_rate, sii.base_net_amount, + si.customer_name, si.customer_group, sii.so_detail, + si.update_stock, sii.uom, sii.qty + ) + .where(si.docstatus == 1) + ) + if filters.get("customer"): + query = query.where(si.customer == filters['customer']) + + if filters.get("customer_group"): + query = query.where(si.customer_group == filters['customer_group']) + + return query.run(as_dict=True) + + +>>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) def get_delivery_notes_against_sales_order(item_list): diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 7631506f506..25caa9e03fb 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -376,10 +376,8 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") - pii = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(pi) - .left_join(pii).on(pi.name == pii.parent) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -397,7 +395,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where((pi.docstatus == 1) & pii.item_code.isnotnull()) + .where((pi.docstatus == 1)) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 832845ffe2e..dc557433f7d 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -414,11 +414,9 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") - sii = frappe.qb.DocType("Sales Invoice Item") query = ( frappe.qb.from_(si) - .left_join(sii).on(si.name == sii.parent) - .select( + .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, si.posting_date, @@ -439,7 +437,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where((si.docstatus == 1) & sii.item_code.isnotnull()) + .where((si.docstatus == 1)) .orderby(si.posting_date, si.name, order=Order.desc) ) From a4b28ba354873a8bb5253a5902970fc780aa9441 Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Wed, 22 May 2024 12:26:02 +0530 Subject: [PATCH 052/203] fix: fixing Item-wise sales register and purchase register #41373 (cherry picked from commit 76073ae228d9f3ebe67eae562e2e48bd54ace252) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py --- .../item_wise_purchase_register.py | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 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 02fb873c572..bc2f24b8076 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 @@ -311,7 +311,9 @@ def get_conditions(filters): return conditions + def get_items(filters, additional_query_columns): +<<<<<<< HEAD <<<<<<< HEAD conditions = get_conditions(filters) if additional_query_columns: @@ -361,18 +363,62 @@ def get_items(filters, additional_query_columns): pi.supplier_name, pi.mode_of_payment ) .where(pi.docstatus == 1)) +======= + pi = frappe.qb.DocType("Purchase Invoice") + pii = frappe.qb.DocType("Purchase Invoice Item") + Item = frappe.qb.DocType("Item") + query = ( + frappe.qb.from_(pi) + .join(pii) + .on(pi.name == pii.parent) + # added left join + .left_join(Item) + .on(pii.item_code == Item.name) + .select( + pii.name.as_("pii_name"), + pii.parent, + pi.posting_date, + pi.credit_to, + pi.company, + pi.supplier, + pi.remarks, + pi.base_net_total, + pi.unrealized_profit_loss_account, + pii.item_code, + pii.description, + pii.item_group, + pii.item_name.as_("pi_item_name"), + pii.item_group.as_("pi_item_group"), + Item.item_name.as_("i_item_name"), + Item.item_group.as_("i_item_group"), + pii.project, + pii.purchase_order, + pii.purchase_receipt, + pii.po_detail, + pii.expense_account, + pii.stock_qty, + pii.stock_uom, + pii.base_net_amount, + pi.supplier_name, + pi.mode_of_payment, + ) + .where(pi.docstatus == 1) + ) +>>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) - if additional_query_columns: - query = query.select(*additional_query_columns) + if additional_query_columns: + query = query.select(*additional_query_columns) - if filters.get("supplier"): - query = query.where(pi.supplier == filters['supplier']) - if filters.get("company"): - query = query.where(pi.company == filters['company']) - - return query.run(as_dict=True) + if filters.get("supplier"): + query = query.where(pi.supplier == filters["supplier"]) + if filters.get("company"): + query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) +======= + return query.run(as_dict=True) +>>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) def get_aii_accounts(): From 5e0fa1b75ec1437516aa190550ccf8fbfab67a6e Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Wed, 22 May 2024 12:39:22 +0530 Subject: [PATCH 053/203] fix: fixing Item-wise sales register #41373 (cherry picked from commit eafa88b8e9da824d89e5673504c0de3422b54b6b) # Conflicts: # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py --- .../item_wise_purchase_register.py | 1 - .../item_wise_sales_register.py | 66 +++++++++++++++++-- .../purchase_register/purchase_register.py | 6 +- .../report/sales_register/sales_register.py | 4 +- 4 files changed, 67 insertions(+), 10 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 bc2f24b8076..086026efb53 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 @@ -371,7 +371,6 @@ def get_items(filters, additional_query_columns): frappe.qb.from_(pi) .join(pii) .on(pi.name == pii.parent) - # added left join .left_join(Item) .on(pii.item_code == Item.name) .select( diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index fd538b7bfa6..102d9d6a0af 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -388,6 +388,7 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) +<<<<<<< HEAD <<<<<<< HEAD def get_items(filters, additional_query_columns, additional_conditions=None): conditions = get_conditions(filters, additional_conditions) @@ -455,14 +456,71 @@ def get_items(filters, additional_query_columns,additional_conditions=None): ) if filters.get("customer"): query = query.where(si.customer == filters['customer']) +======= +def get_items(filters, additional_query_columns, additional_conditions=None): + si = frappe.qb.DocType("Sales Invoice") + sii = frappe.qb.DocType("Sales Invoice Item") + Item = frappe.qb.DocType("Item") +>>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) - if filters.get("customer_group"): - query = query.where(si.customer_group == filters['customer_group']) - - return query.run(as_dict=True) + query = ( + frappe.qb.from_(si) + .join(sii) + .on(si.name == sii.parent) + # added left join + .left_join(Item) + .on(sii.item_code == Item.name) + .select( + sii.name, + sii.parent, + si.posting_date, + si.debit_to, + si.unrealized_profit_loss_account, + si.is_internal_customer, + si.customer, + si.remarks, + si.territory, + si.company, + si.base_net_total, + sii.project, + sii.item_code, + sii.description, + sii.item_name, + sii.item_group, + sii.item_name.as_("si_item_name"), + sii.item_group.as_("si_item_group"), + Item.item_name.as_("i_item_name"), + Item.item_group.as_("i_item_group"), + sii.sales_order, + sii.delivery_note, + sii.income_account, + sii.cost_center, + sii.enable_deferred_revenue, + sii.deferred_revenue_account, + sii.stock_qty, + sii.stock_uom, + sii.base_net_rate, + sii.base_net_amount, + si.customer_name, + si.customer_group, + sii.so_detail, + si.update_stock, + sii.uom, + sii.qty, + ) + .where(si.docstatus == 1) + ) + if filters.get("customer"): + query = query.where(si.customer == filters["customer"]) + if filters.get("customer_group"): + query = query.where(si.customer_group == filters["customer_group"]) +<<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) +======= + return query.run(as_dict=True) +>>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) def get_delivery_notes_against_sales_order(item_list): diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 25caa9e03fb..1e24d96115d 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -377,7 +377,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") query = ( - frappe.qb.from_(pi) + frappe.qb.from_(pi) # qb .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -386,7 +386,7 @@ def get_invoices(filters, additional_query_columns): pi.supplier, pi.supplier_name, pi.tax_id, - pi.bill_no, + pi.bill_no, pi.bill_date, pi.remarks, pi.base_net_total, @@ -395,7 +395,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where((pi.docstatus == 1)) + .where(pi.docstatus == 1) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index dc557433f7d..f8b94d353f9 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -415,7 +415,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") query = ( - frappe.qb.from_(si) + frappe.qb.from_(si) # qb .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, @@ -437,7 +437,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where((si.docstatus == 1)) + .where(si.docstatus == 1) .orderby(si.posting_date, si.name, order=Order.desc) ) From 209af58f8318c58c3e8bad840aedd21ba8fa0c3c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 Jun 2024 20:17:24 +0530 Subject: [PATCH 054/203] chore: update condition queries in qb (cherry picked from commit d2af36e1eb2ba395b3bf35f933f40b6906852fa0) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py --- .../item_wise_purchase_register.py | 42 +++--- .../item_wise_sales_register.js | 6 + .../item_wise_sales_register.py | 126 ++++++++++-------- .../purchase_register/purchase_register.py | 2 +- .../report/sales_register/sales_register.py | 2 +- 5 files changed, 105 insertions(+), 73 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 086026efb53..dc4999a3b4e 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 @@ -5,14 +5,15 @@ import frappe from frappe import _ from frappe.utils import flt +from pypika import Order import erpnext from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import ( add_sub_total_row, add_total_row, + apply_group_by_conditions, get_grand_total, get_group_by_and_display_fields, - get_group_by_conditions, get_tax_accounts, ) from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -287,29 +288,27 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters): - conditions = "" +def apply_conditions(query, pi, pii, filters): + for opts in ("company", "supplier", "item_code", "mode_of_payment"): + if filters.get(opts): + query = query.where(pi[opts] == filters[opts]) - for opts in ( - ("company", " and `tabPurchase Invoice`.company=%(company)s"), - ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"), - ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"), - ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"), - ("item_group", " and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(pi.posting_date >= filters.get("from_date")) + + if filters.get("to_date"): + query = query.where(pi.posting_date <= filters.get("to_date")) + + if filters.get("item_group"): + query = query.where(pii.item_group == filters.get("item_group")) if not filters.get("group_by"): - conditions += ( - "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc" - ) + query = query.orderby(pi.posting_date, order=Order.desc) + query = query.orderby(pii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Purchase Invoice") + query = apply_group_by_conditions(filters, "Purchase Invoice") - return conditions + return query def get_items(filters, additional_query_columns): @@ -413,9 +412,14 @@ def get_items(filters, additional_query_columns): if filters.get("company"): query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= +======= + query = apply_conditions(query, pi, pii, filters) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) >>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index 7ed2ebd89ab..1f155de63a0 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -41,6 +41,12 @@ frappe.query_reports["Item-wise Sales Register"] = { label: __("Warehouse"), fieldtype: "Link", options: "Warehouse", + get_query: function () { + const company = frappe.query_report.get_filter_value("company"); + return { + filters: { company: company }, + }; + }, }, { fieldname: "brand", diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 102d9d6a0af..192cbc0e0cc 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -7,6 +7,7 @@ from frappe import _ from frappe.model.meta import get_field_precision from frappe.utils import cstr, flt from frappe.utils.xlsxutils import handle_html +from pypika import Order from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -333,59 +334,69 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters, additional_conditions=None): - conditions = "" +def apply_conditions(query, si, sii, filters, additional_conditions=None): + for opts in ("company", "customer", "item_code"): + if filters.get(opts): + query = query.where(si[opts] == filters[opts]) - for opts in ( - ("company", " and `tabSales Invoice`.company=%(company)s"), - ("customer", " and `tabSales Invoice`.customer = %(customer)s"), - ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(si.posting_date >= filters.get("from_date")) - if additional_conditions: - conditions += additional_conditions + if filters.get("to_date"): + query = query.where(si.posting_date <= filters.get("to_date")) if filters.get("mode_of_payment"): - conditions += """ and exists(select name from `tabSales Invoice Payment` - where parent=`tabSales Invoice`.name - and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" + sales_invoice = frappe.db.get_all( + "Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent" + ) + query = query.where(si.name.isin(sales_invoice)) if filters.get("warehouse"): if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"): lft, rgt = frappe.db.get_all( "Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True )[0] - conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) " + warehouses = frappe.db.get_all("Warehouse", {"lft": (">", lft), "rgt": ("<", rgt)}, pluck="name") + query = query.where(sii.warehouse.isin(warehouses)) else: - conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s""" + query = query.where(sii.warehouse == filters.get("warehouse")) if filters.get("brand"): - conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s""" + query = query.where(sii.brand == filters.get("brand")) if filters.get("item_group"): - conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s""" + query = query.where(sii.item_group == filters.get("item_group")) +<<<<<<< HEAD +======= + if filters.get("income_account"): + query = query.where( + (sii.income_account == filters.get("income_account")) + | (sii.deferred_revenue_account == filters.get("income_account")) + | (si.unrealized_profit_loss_account == filters.get("income_account")) + ) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) if not filters.get("group_by"): - conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc" + query = query.orderby(si.posting_date, order=Order.desc) + query = query.orderby(sii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Sales Invoice") + query = apply_group_by_conditions(query, si, sii, filters) - return conditions + return query -def get_group_by_conditions(filters, doctype): +def apply_group_by_conditions(query, si, ii, filters): if filters.get("group_by") == "Invoice": - return f"ORDER BY `tab{doctype} Item`.parent desc" + query = query.orderby(ii.parent, order=Order.desc) elif filters.get("group_by") == "Item": - return f"ORDER BY `tab{doctype} Item`.`item_code`" + query = query.orderby(ii.item_code) elif filters.get("group_by") == "Item Group": - return "ORDER BY `tab{} Item`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(ii.item_group) elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"): - return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(si[frappe.scrub(filters.get("group_by"))]) + + return query <<<<<<< HEAD @@ -460,16 +471,19 @@ def get_items(filters, additional_query_columns,additional_conditions=None): def get_items(filters, additional_query_columns, additional_conditions=None): si = frappe.qb.DocType("Sales Invoice") sii = frappe.qb.DocType("Sales Invoice Item") +<<<<<<< HEAD Item = frappe.qb.DocType("Item") >>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) +======= + item = frappe.qb.DocType("Item") +>>>>>>> d2af36e1eb (chore: update condition queries in qb) query = ( frappe.qb.from_(si) .join(sii) .on(si.name == sii.parent) - # added left join - .left_join(Item) - .on(sii.item_code == Item.name) + .left_join(item) + .on(sii.item_code == item.name) .select( sii.name, sii.parent, @@ -489,8 +503,8 @@ def get_items(filters, additional_query_columns, additional_conditions=None): sii.item_group, sii.item_name.as_("si_item_name"), sii.item_group.as_("si_item_group"), - Item.item_name.as_("i_item_name"), - Item.item_group.as_("i_item_group"), + item.item_name.as_("i_item_name"), + item.item_group.as_("i_item_group"), sii.sales_order, sii.delivery_note, sii.income_account, @@ -510,15 +524,24 @@ def get_items(filters, additional_query_columns, additional_conditions=None): ) .where(si.docstatus == 1) ) + + if additional_query_columns: + query = query.select(*additional_query_columns) + if filters.get("customer"): query = query.where(si.customer == filters["customer"]) if filters.get("customer_group"): query = query.where(si.customer_group == filters["customer_group"]) +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= +======= + query = apply_conditions(query, si, sii, filters, additional_conditions) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) >>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) @@ -528,16 +551,14 @@ def get_delivery_notes_against_sales_order(item_list): so_item_rows = list(set([d.so_detail for d in item_list])) if so_item_rows: - delivery_notes = frappe.db.sql( - """ - select parent, so_detail - from `tabDelivery Note Item` - where docstatus=1 and so_detail in (%s) - group by so_detail, parent - """ - % (", ".join(["%s"] * len(so_item_rows))), - tuple(so_item_rows), - as_dict=1, + dn_item = frappe.qb.DocType("Delivery Note Item") + delivery_notes = ( + frappe.qb.from_(dn_item) + .select(dn_item.parent, dn_item.so_detail) + .where(dn_item.docstatus == 1) + .where(dn_item.so_detail.isin(so_item_rows)) + .groupby(dn_item.so_detail, dn_item.parent) + .run(as_dict=True) ) for dn in delivery_notes: @@ -547,15 +568,16 @@ def get_delivery_notes_against_sales_order(item_list): def get_grand_total(filters, doctype): - return frappe.db.sql( - f""" SELECT - SUM(`tab{doctype}`.base_grand_total) - FROM `tab{doctype}` - WHERE `tab{doctype}`.docstatus = 1 - and posting_date between %s and %s - """, - (filters.get("from_date"), filters.get("to_date")), - )[0][0] # nosec + return flt( + frappe.db.get_value( + doctype, + { + "docstatus": 1, + "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), + }, + "sum(base_grand_total)", + ) + ) def get_tax_accounts( diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 1e24d96115d..504c74babcb 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -377,7 +377,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") query = ( - frappe.qb.from_(pi) # qb + frappe.qb.from_(pi) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f8b94d353f9..f27569531b1 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -415,7 +415,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") query = ( - frappe.qb.from_(si) # qb + frappe.qb.from_(si) .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, From 1fca6ea5e70da34cdce637bf2a4b1beb94bc3507 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Jun 2024 18:48:16 +0530 Subject: [PATCH 055/203] fix: Add additional condition application (cherry picked from commit 8ec364df6fe07817fd6c69c6e9db31af08d1a455) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py --- .../item_wise_purchase_register.py | 20 +++++++++++++++---- .../item_wise_sales_register.py | 12 +++++++++-- 2 files changed, 26 insertions(+), 6 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 dc4999a3b4e..bafd83db37a 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 @@ -30,7 +30,7 @@ def _execute(filters=None, additional_table_columns=None): company_currency = erpnext.get_company_currency(filters.company) - item_list = get_items(filters, get_query_columns(additional_table_columns)) + item_list = get_items(filters, additional_table_columns) aii_account_map = get_aii_accounts() if item_list: itemised_tax, tax_columns = get_tax_accounts( @@ -311,6 +311,7 @@ def apply_conditions(query, pi, pii, filters): return query +<<<<<<< HEAD def get_items(filters, additional_query_columns): <<<<<<< HEAD <<<<<<< HEAD @@ -363,6 +364,9 @@ def get_items(filters, additional_query_columns): ) .where(pi.docstatus == 1)) ======= +======= +def get_items(filters, additional_table_columns): +>>>>>>> 8ec364df6f (fix: Add additional condition application) pi = frappe.qb.DocType("Purchase Invoice") pii = frappe.qb.DocType("Purchase Invoice Item") Item = frappe.qb.DocType("Item") @@ -404,19 +408,27 @@ def get_items(filters, additional_query_columns): ) >>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) - if additional_query_columns: - query = query.select(*additional_query_columns) - if filters.get("supplier"): query = query.where(pi.supplier == filters["supplier"]) if filters.get("company"): query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= ======= +======= + if additional_table_columns: + for column in additional_table_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(pi[column.get("fieldname")]) + +>>>>>>> 8ec364df6f (fix: Add additional condition application) query = apply_conditions(query, pi, pii, filters) >>>>>>> d2af36e1eb (chore: update condition queries in qb) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 192cbc0e0cc..5a989a929ab 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -27,7 +27,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions) + item_list = get_items(filters, additional_table_columns, additional_conditions) if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) @@ -383,6 +383,9 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): else: query = apply_group_by_conditions(query, si, sii, filters) + for key, value in (additional_conditions or {}).items(): + query = query.where(si[key] == value) + return query @@ -526,7 +529,12 @@ def get_items(filters, additional_query_columns, additional_conditions=None): ) if additional_query_columns: - query = query.select(*additional_query_columns) + for column in additional_query_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(si[column.get("fieldname")]) if filters.get("customer"): query = query.where(si.customer == filters["customer"]) From c29db5afcda6d283c32676c437f525e7aaba5ef6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 9 Jun 2024 20:02:39 +0530 Subject: [PATCH 056/203] chore: resolve conflicts --- .../item_wise_purchase_register.py | 66 ------------- .../item_wise_sales_register.py | 97 ++----------------- 2 files changed, 10 insertions(+), 153 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 bafd83db37a..80c246cad55 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 @@ -311,62 +311,7 @@ def apply_conditions(query, pi, pii, filters): return query -<<<<<<< HEAD -def get_items(filters, additional_query_columns): -<<<<<<< HEAD -<<<<<<< HEAD - conditions = get_conditions(filters) - if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - f""" - select - `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, - `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, - `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, - `tabPurchase Invoice`.unrealized_profit_loss_account, - `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, - `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, - `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, - `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, - `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, - `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, - `tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`, - `tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {additional_query_columns} - from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem` - where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and - `tabItem`.name = `tabPurchase Invoice Item`.`item_code` and - `tabPurchase Invoice`.docstatus = 1 {conditions} - """, - filters, - as_dict=1, - ) -======= - pi = frappe.qb.DocType('Purchase Invoice') - pii = frappe.qb.DocType('Purchase Invoice Item') - Item = frappe.qb.DocType('Item') - query = (frappe.qb.from_(pi) - .join(pii).on(pi.name == pii.parent) - .left_join(Item).on(pii.item_code == Item.name) - .select( - pii.name.as_('pii_name'), pii.parent, - pi.posting_date, pi.credit_to, pi.company, - pi.supplier, pi.remarks, pi.base_net_total, - pi.unrealized_profit_loss_account, - pii.item_code, pii.description, pii.item_group, - pii.item_name.as_('pi_item_name'), pii.item_group.as_('pi_item_group'), - Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), - pii.project, pii.purchase_order, - pii.purchase_receipt, pii.po_detail, - pii.expense_account, pii.stock_qty, - pii.stock_uom, pii.base_net_amount, - pi.supplier_name, pi.mode_of_payment - ) - .where(pi.docstatus == 1)) -======= -======= def get_items(filters, additional_table_columns): ->>>>>>> 8ec364df6f (fix: Add additional condition application) pi = frappe.qb.DocType("Purchase Invoice") pii = frappe.qb.DocType("Purchase Invoice Item") Item = frappe.qb.DocType("Item") @@ -406,20 +351,12 @@ def get_items(filters, additional_table_columns): ) .where(pi.docstatus == 1) ) ->>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) if filters.get("supplier"): query = query.where(pi.supplier == filters["supplier"]) if filters.get("company"): query = query.where(pi.company == filters["company"]) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) -======= -======= -======= if additional_table_columns: for column in additional_table_columns: if column.get("_doctype"): @@ -428,12 +365,9 @@ def get_items(filters, additional_table_columns): else: query = query.select(pi[column.get("fieldname")]) ->>>>>>> 8ec364df6f (fix: Add additional condition application) query = apply_conditions(query, pi, pii, filters) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) ->>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) def get_aii_accounts(): diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 5a989a929ab..cd50b118715 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -84,9 +84,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= "company": d.company, "sales_order": d.sales_order, "delivery_note": d.delivery_note, - "income_account": d.unrealized_profit_loss_account - if d.is_internal_customer == 1 - else d.income_account, + "income_account": get_income_account(d), "cost_center": d.cost_center, "stock_qty": d.stock_qty, "stock_uom": d.stock_uom, @@ -151,6 +149,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= return columns, data, None, None, None, skip_total_row +def get_income_account(row): + if row.enable_deferred_revenue: + return row.deferred_revenue_account + elif row.is_internal_customer == 1: + return row.unrealized_profit_loss_account + else: + return row.income_account + + def get_columns(additional_table_columns, filters): columns = [] @@ -367,8 +374,6 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): if filters.get("item_group"): query = query.where(sii.item_group == filters.get("item_group")) -<<<<<<< HEAD -======= if filters.get("income_account"): query = query.where( (sii.income_account == filters.get("income_account")) @@ -376,7 +381,6 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): | (si.unrealized_profit_loss_account == filters.get("income_account")) ) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) if not filters.get("group_by"): query = query.orderby(si.posting_date, order=Order.desc) query = query.orderby(sii.item_group, order=Order.desc) @@ -402,84 +406,10 @@ def apply_group_by_conditions(query, si, ii, filters): return query -<<<<<<< HEAD -<<<<<<< HEAD -def get_items(filters, additional_query_columns, additional_conditions=None): - conditions = get_conditions(filters, additional_conditions) - if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - """ - select - `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent, - `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, - `tabSales Invoice`.unrealized_profit_loss_account, - `tabSales Invoice`.is_internal_customer, - `tabSales Invoice`.customer, `tabSales Invoice`.remarks, - `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, - `tabSales Invoice Item`.project, - `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, - `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, - `tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group, - `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, - `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, - `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, - `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, - `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, - `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail, - `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {} - from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem` - where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and - `tabItem`.name = `tabSales Invoice Item`.`item_code` and - `tabSales Invoice`.docstatus = 1 {} - """.format(additional_query_columns, conditions), - filters, - as_dict=1, - ) # nosec -======= -def get_items(filters, additional_query_columns,additional_conditions=None): - si = frappe.qb.DocType('Sales Invoice') - sii = frappe.qb.DocType('Sales Invoice Item') - Item = frappe.qb.DocType('Item') - - query = ( - frappe.qb.from_(si) - .join(sii).on(si.name == sii.parent) - .left_join(Item).on(sii.item_code == Item.name) - .select( - sii.name, sii.parent, - si.posting_date, si.debit_to, - si.unrealized_profit_loss_account, - si.is_internal_customer, - si.customer, si.remarks, - si.territory, si.company, si.base_net_total, - sii.project, - sii.item_code, sii.description, - sii.item_name, sii.item_group, - sii.item_name.as_('si_item_name'), sii.item_group.as_('si_item_group'), - Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), - sii.sales_order, sii.delivery_note, - sii.income_account, sii.cost_center, - sii.enable_deferred_revenue, sii.deferred_revenue_account, - sii.stock_qty, sii.stock_uom, - sii.base_net_rate, sii.base_net_amount, - si.customer_name, si.customer_group, sii.so_detail, - si.update_stock, sii.uom, sii.qty - ) - .where(si.docstatus == 1) - ) - if filters.get("customer"): - query = query.where(si.customer == filters['customer']) -======= def get_items(filters, additional_query_columns, additional_conditions=None): si = frappe.qb.DocType("Sales Invoice") sii = frappe.qb.DocType("Sales Invoice Item") -<<<<<<< HEAD - Item = frappe.qb.DocType("Item") ->>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) -======= item = frappe.qb.DocType("Item") ->>>>>>> d2af36e1eb (chore: update condition queries in qb) query = ( frappe.qb.from_(si) @@ -542,16 +472,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None): if filters.get("customer_group"): query = query.where(si.customer_group == filters["customer_group"]) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) -======= -======= query = apply_conditions(query, si, sii, filters, additional_conditions) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) ->>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) def get_delivery_notes_against_sales_order(item_list): From 9a629f860c6acd0d51aaf39e9fc0cfe1c0f27d98 Mon Sep 17 00:00:00 2001 From: Poorvi Date: Fri, 10 May 2024 11:47:57 +0530 Subject: [PATCH 057/203] fix: fixing Item-wise sales register (cherry picked from commit c90185f5331e2271afb5ddc22340d3704f5bbcd3) (cherry picked from commit f36db7ebaf0c1e3c0de716f2733961be4f5b632e) --- .../accounts/report/purchase_register/purchase_register.py | 6 ++++-- erpnext/accounts/report/sales_register/sales_register.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 504c74babcb..7631506f506 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -376,8 +376,10 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") + pii = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(pi) + .left_join(pii).on(pi.name == pii.parent) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -386,7 +388,7 @@ def get_invoices(filters, additional_query_columns): pi.supplier, pi.supplier_name, pi.tax_id, - pi.bill_no, + pi.bill_no, pi.bill_date, pi.remarks, pi.base_net_total, @@ -395,7 +397,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where(pi.docstatus == 1) + .where((pi.docstatus == 1) & pii.item_code.isnotnull()) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f27569531b1..832845ffe2e 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -414,9 +414,11 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") + sii = frappe.qb.DocType("Sales Invoice Item") query = ( frappe.qb.from_(si) - .select( + .left_join(sii).on(si.name == sii.parent) + .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, si.posting_date, @@ -437,7 +439,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where(si.docstatus == 1) + .where((si.docstatus == 1) & sii.item_code.isnotnull()) .orderby(si.posting_date, si.name, order=Order.desc) ) From 3e0123ba7bf54bd94fd7693ed33b8e94a7625be8 Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Mon, 13 May 2024 09:35:10 +0530 Subject: [PATCH 058/203] fix: Item-wise Sales and Purchase register with no item codes #41373 (cherry picked from commit 1b45ecfcae5ed9587c5d03e14afaf6d39d3fa84f) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py (cherry picked from commit 96405e8e4957c672b7a4aa5f7e9ead081a6e143d) --- .../item_wise_purchase_register.py | 36 +++++++++++++++- .../item_wise_sales_register.py | 43 +++++++++++++++++++ .../purchase_register/purchase_register.py | 4 +- .../report/sales_register/sales_register.py | 6 +-- 4 files changed, 81 insertions(+), 8 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 ebdea0602d1..02fb873c572 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 @@ -311,8 +311,8 @@ def get_conditions(filters): return conditions - def get_items(filters, additional_query_columns): +<<<<<<< HEAD conditions = get_conditions(filters) if additional_query_columns: additional_query_columns = "," + ",".join(additional_query_columns) @@ -339,6 +339,40 @@ def get_items(filters, additional_query_columns): filters, as_dict=1, ) +======= + pi = frappe.qb.DocType('Purchase Invoice') + pii = frappe.qb.DocType('Purchase Invoice Item') + Item = frappe.qb.DocType('Item') + query = (frappe.qb.from_(pi) + .join(pii).on(pi.name == pii.parent) + .left_join(Item).on(pii.item_code == Item.name) + .select( + pii.name.as_('pii_name'), pii.parent, + pi.posting_date, pi.credit_to, pi.company, + pi.supplier, pi.remarks, pi.base_net_total, + pi.unrealized_profit_loss_account, + pii.item_code, pii.description, pii.item_group, + pii.item_name.as_('pi_item_name'), pii.item_group.as_('pi_item_group'), + Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), + pii.project, pii.purchase_order, + pii.purchase_receipt, pii.po_detail, + pii.expense_account, pii.stock_qty, + pii.stock_uom, pii.base_net_amount, + pi.supplier_name, pi.mode_of_payment + ) + .where(pi.docstatus == 1)) + + if additional_query_columns: + query = query.select(*additional_query_columns) + + if filters.get("supplier"): + query = query.where(pi.supplier == filters['supplier']) + if filters.get("company"): + query = query.where(pi.company == filters['company']) + + return query.run(as_dict=True) + +>>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) def get_aii_accounts(): diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index c7819731930..fd538b7bfa6 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -388,6 +388,7 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) +<<<<<<< HEAD def get_items(filters, additional_query_columns, additional_conditions=None): conditions = get_conditions(filters, additional_conditions) if additional_query_columns: @@ -420,6 +421,48 @@ def get_items(filters, additional_query_columns, additional_conditions=None): filters, as_dict=1, ) # nosec +======= +def get_items(filters, additional_query_columns,additional_conditions=None): + si = frappe.qb.DocType('Sales Invoice') + sii = frappe.qb.DocType('Sales Invoice Item') + Item = frappe.qb.DocType('Item') + + query = ( + frappe.qb.from_(si) + .join(sii).on(si.name == sii.parent) + .left_join(Item).on(sii.item_code == Item.name) + .select( + sii.name, sii.parent, + si.posting_date, si.debit_to, + si.unrealized_profit_loss_account, + si.is_internal_customer, + si.customer, si.remarks, + si.territory, si.company, si.base_net_total, + sii.project, + sii.item_code, sii.description, + sii.item_name, sii.item_group, + sii.item_name.as_('si_item_name'), sii.item_group.as_('si_item_group'), + Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), + sii.sales_order, sii.delivery_note, + sii.income_account, sii.cost_center, + sii.enable_deferred_revenue, sii.deferred_revenue_account, + sii.stock_qty, sii.stock_uom, + sii.base_net_rate, sii.base_net_amount, + si.customer_name, si.customer_group, sii.so_detail, + si.update_stock, sii.uom, sii.qty + ) + .where(si.docstatus == 1) + ) + if filters.get("customer"): + query = query.where(si.customer == filters['customer']) + + if filters.get("customer_group"): + query = query.where(si.customer_group == filters['customer_group']) + + return query.run(as_dict=True) + + +>>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) def get_delivery_notes_against_sales_order(item_list): diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 7631506f506..25caa9e03fb 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -376,10 +376,8 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") - pii = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(pi) - .left_join(pii).on(pi.name == pii.parent) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -397,7 +395,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where((pi.docstatus == 1) & pii.item_code.isnotnull()) + .where((pi.docstatus == 1)) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 832845ffe2e..dc557433f7d 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -414,11 +414,9 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") - sii = frappe.qb.DocType("Sales Invoice Item") query = ( frappe.qb.from_(si) - .left_join(sii).on(si.name == sii.parent) - .select( + .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, si.posting_date, @@ -439,7 +437,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where((si.docstatus == 1) & sii.item_code.isnotnull()) + .where((si.docstatus == 1)) .orderby(si.posting_date, si.name, order=Order.desc) ) From 50ef81a997425d35208c25ccfa0137c2f9be34ec Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Wed, 22 May 2024 12:26:02 +0530 Subject: [PATCH 059/203] fix: fixing Item-wise sales register and purchase register #41373 (cherry picked from commit 76073ae228d9f3ebe67eae562e2e48bd54ace252) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py (cherry picked from commit a4b28ba354873a8bb5253a5902970fc780aa9441) --- .../item_wise_purchase_register.py | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 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 02fb873c572..bc2f24b8076 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 @@ -311,7 +311,9 @@ def get_conditions(filters): return conditions + def get_items(filters, additional_query_columns): +<<<<<<< HEAD <<<<<<< HEAD conditions = get_conditions(filters) if additional_query_columns: @@ -361,18 +363,62 @@ def get_items(filters, additional_query_columns): pi.supplier_name, pi.mode_of_payment ) .where(pi.docstatus == 1)) +======= + pi = frappe.qb.DocType("Purchase Invoice") + pii = frappe.qb.DocType("Purchase Invoice Item") + Item = frappe.qb.DocType("Item") + query = ( + frappe.qb.from_(pi) + .join(pii) + .on(pi.name == pii.parent) + # added left join + .left_join(Item) + .on(pii.item_code == Item.name) + .select( + pii.name.as_("pii_name"), + pii.parent, + pi.posting_date, + pi.credit_to, + pi.company, + pi.supplier, + pi.remarks, + pi.base_net_total, + pi.unrealized_profit_loss_account, + pii.item_code, + pii.description, + pii.item_group, + pii.item_name.as_("pi_item_name"), + pii.item_group.as_("pi_item_group"), + Item.item_name.as_("i_item_name"), + Item.item_group.as_("i_item_group"), + pii.project, + pii.purchase_order, + pii.purchase_receipt, + pii.po_detail, + pii.expense_account, + pii.stock_qty, + pii.stock_uom, + pii.base_net_amount, + pi.supplier_name, + pi.mode_of_payment, + ) + .where(pi.docstatus == 1) + ) +>>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) - if additional_query_columns: - query = query.select(*additional_query_columns) + if additional_query_columns: + query = query.select(*additional_query_columns) - if filters.get("supplier"): - query = query.where(pi.supplier == filters['supplier']) - if filters.get("company"): - query = query.where(pi.company == filters['company']) - - return query.run(as_dict=True) + if filters.get("supplier"): + query = query.where(pi.supplier == filters["supplier"]) + if filters.get("company"): + query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) +======= + return query.run(as_dict=True) +>>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) def get_aii_accounts(): From d75d64eb258c3d398e721cea9d7fc5e6725694e4 Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Wed, 22 May 2024 12:39:22 +0530 Subject: [PATCH 060/203] fix: fixing Item-wise sales register #41373 (cherry picked from commit eafa88b8e9da824d89e5673504c0de3422b54b6b) # Conflicts: # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py (cherry picked from commit 5e0fa1b75ec1437516aa190550ccf8fbfab67a6e) --- .../item_wise_purchase_register.py | 1 - .../item_wise_sales_register.py | 66 +++++++++++++++++-- .../purchase_register/purchase_register.py | 6 +- .../report/sales_register/sales_register.py | 4 +- 4 files changed, 67 insertions(+), 10 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 bc2f24b8076..086026efb53 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 @@ -371,7 +371,6 @@ def get_items(filters, additional_query_columns): frappe.qb.from_(pi) .join(pii) .on(pi.name == pii.parent) - # added left join .left_join(Item) .on(pii.item_code == Item.name) .select( diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index fd538b7bfa6..102d9d6a0af 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -388,6 +388,7 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) +<<<<<<< HEAD <<<<<<< HEAD def get_items(filters, additional_query_columns, additional_conditions=None): conditions = get_conditions(filters, additional_conditions) @@ -455,14 +456,71 @@ def get_items(filters, additional_query_columns,additional_conditions=None): ) if filters.get("customer"): query = query.where(si.customer == filters['customer']) +======= +def get_items(filters, additional_query_columns, additional_conditions=None): + si = frappe.qb.DocType("Sales Invoice") + sii = frappe.qb.DocType("Sales Invoice Item") + Item = frappe.qb.DocType("Item") +>>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) - if filters.get("customer_group"): - query = query.where(si.customer_group == filters['customer_group']) - - return query.run(as_dict=True) + query = ( + frappe.qb.from_(si) + .join(sii) + .on(si.name == sii.parent) + # added left join + .left_join(Item) + .on(sii.item_code == Item.name) + .select( + sii.name, + sii.parent, + si.posting_date, + si.debit_to, + si.unrealized_profit_loss_account, + si.is_internal_customer, + si.customer, + si.remarks, + si.territory, + si.company, + si.base_net_total, + sii.project, + sii.item_code, + sii.description, + sii.item_name, + sii.item_group, + sii.item_name.as_("si_item_name"), + sii.item_group.as_("si_item_group"), + Item.item_name.as_("i_item_name"), + Item.item_group.as_("i_item_group"), + sii.sales_order, + sii.delivery_note, + sii.income_account, + sii.cost_center, + sii.enable_deferred_revenue, + sii.deferred_revenue_account, + sii.stock_qty, + sii.stock_uom, + sii.base_net_rate, + sii.base_net_amount, + si.customer_name, + si.customer_group, + sii.so_detail, + si.update_stock, + sii.uom, + sii.qty, + ) + .where(si.docstatus == 1) + ) + if filters.get("customer"): + query = query.where(si.customer == filters["customer"]) + if filters.get("customer_group"): + query = query.where(si.customer_group == filters["customer_group"]) +<<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) +======= + return query.run(as_dict=True) +>>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) def get_delivery_notes_against_sales_order(item_list): diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 25caa9e03fb..1e24d96115d 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -377,7 +377,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") query = ( - frappe.qb.from_(pi) + frappe.qb.from_(pi) # qb .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -386,7 +386,7 @@ def get_invoices(filters, additional_query_columns): pi.supplier, pi.supplier_name, pi.tax_id, - pi.bill_no, + pi.bill_no, pi.bill_date, pi.remarks, pi.base_net_total, @@ -395,7 +395,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where((pi.docstatus == 1)) + .where(pi.docstatus == 1) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index dc557433f7d..f8b94d353f9 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -415,7 +415,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") query = ( - frappe.qb.from_(si) + frappe.qb.from_(si) # qb .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, @@ -437,7 +437,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where((si.docstatus == 1)) + .where(si.docstatus == 1) .orderby(si.posting_date, si.name, order=Order.desc) ) From 7ca848c2db77615f867c161450c613ee02004aab Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 Jun 2024 20:17:24 +0530 Subject: [PATCH 061/203] chore: update condition queries in qb (cherry picked from commit d2af36e1eb2ba395b3bf35f933f40b6906852fa0) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py (cherry picked from commit 209af58f8318c58c3e8bad840aedd21ba8fa0c3c) --- .../item_wise_purchase_register.py | 42 +++--- .../item_wise_sales_register.js | 6 + .../item_wise_sales_register.py | 126 ++++++++++-------- .../purchase_register/purchase_register.py | 2 +- .../report/sales_register/sales_register.py | 2 +- 5 files changed, 105 insertions(+), 73 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 086026efb53..dc4999a3b4e 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 @@ -5,14 +5,15 @@ import frappe from frappe import _ from frappe.utils import flt +from pypika import Order import erpnext from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import ( add_sub_total_row, add_total_row, + apply_group_by_conditions, get_grand_total, get_group_by_and_display_fields, - get_group_by_conditions, get_tax_accounts, ) from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -287,29 +288,27 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters): - conditions = "" +def apply_conditions(query, pi, pii, filters): + for opts in ("company", "supplier", "item_code", "mode_of_payment"): + if filters.get(opts): + query = query.where(pi[opts] == filters[opts]) - for opts in ( - ("company", " and `tabPurchase Invoice`.company=%(company)s"), - ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"), - ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"), - ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"), - ("item_group", " and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(pi.posting_date >= filters.get("from_date")) + + if filters.get("to_date"): + query = query.where(pi.posting_date <= filters.get("to_date")) + + if filters.get("item_group"): + query = query.where(pii.item_group == filters.get("item_group")) if not filters.get("group_by"): - conditions += ( - "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc" - ) + query = query.orderby(pi.posting_date, order=Order.desc) + query = query.orderby(pii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Purchase Invoice") + query = apply_group_by_conditions(filters, "Purchase Invoice") - return conditions + return query def get_items(filters, additional_query_columns): @@ -413,9 +412,14 @@ def get_items(filters, additional_query_columns): if filters.get("company"): query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= +======= + query = apply_conditions(query, pi, pii, filters) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) >>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index 7ed2ebd89ab..1f155de63a0 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -41,6 +41,12 @@ frappe.query_reports["Item-wise Sales Register"] = { label: __("Warehouse"), fieldtype: "Link", options: "Warehouse", + get_query: function () { + const company = frappe.query_report.get_filter_value("company"); + return { + filters: { company: company }, + }; + }, }, { fieldname: "brand", diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 102d9d6a0af..192cbc0e0cc 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -7,6 +7,7 @@ from frappe import _ from frappe.model.meta import get_field_precision from frappe.utils import cstr, flt from frappe.utils.xlsxutils import handle_html +from pypika import Order from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -333,59 +334,69 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters, additional_conditions=None): - conditions = "" +def apply_conditions(query, si, sii, filters, additional_conditions=None): + for opts in ("company", "customer", "item_code"): + if filters.get(opts): + query = query.where(si[opts] == filters[opts]) - for opts in ( - ("company", " and `tabSales Invoice`.company=%(company)s"), - ("customer", " and `tabSales Invoice`.customer = %(customer)s"), - ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(si.posting_date >= filters.get("from_date")) - if additional_conditions: - conditions += additional_conditions + if filters.get("to_date"): + query = query.where(si.posting_date <= filters.get("to_date")) if filters.get("mode_of_payment"): - conditions += """ and exists(select name from `tabSales Invoice Payment` - where parent=`tabSales Invoice`.name - and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" + sales_invoice = frappe.db.get_all( + "Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent" + ) + query = query.where(si.name.isin(sales_invoice)) if filters.get("warehouse"): if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"): lft, rgt = frappe.db.get_all( "Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True )[0] - conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) " + warehouses = frappe.db.get_all("Warehouse", {"lft": (">", lft), "rgt": ("<", rgt)}, pluck="name") + query = query.where(sii.warehouse.isin(warehouses)) else: - conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s""" + query = query.where(sii.warehouse == filters.get("warehouse")) if filters.get("brand"): - conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s""" + query = query.where(sii.brand == filters.get("brand")) if filters.get("item_group"): - conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s""" + query = query.where(sii.item_group == filters.get("item_group")) +<<<<<<< HEAD +======= + if filters.get("income_account"): + query = query.where( + (sii.income_account == filters.get("income_account")) + | (sii.deferred_revenue_account == filters.get("income_account")) + | (si.unrealized_profit_loss_account == filters.get("income_account")) + ) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) if not filters.get("group_by"): - conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc" + query = query.orderby(si.posting_date, order=Order.desc) + query = query.orderby(sii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Sales Invoice") + query = apply_group_by_conditions(query, si, sii, filters) - return conditions + return query -def get_group_by_conditions(filters, doctype): +def apply_group_by_conditions(query, si, ii, filters): if filters.get("group_by") == "Invoice": - return f"ORDER BY `tab{doctype} Item`.parent desc" + query = query.orderby(ii.parent, order=Order.desc) elif filters.get("group_by") == "Item": - return f"ORDER BY `tab{doctype} Item`.`item_code`" + query = query.orderby(ii.item_code) elif filters.get("group_by") == "Item Group": - return "ORDER BY `tab{} Item`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(ii.item_group) elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"): - return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(si[frappe.scrub(filters.get("group_by"))]) + + return query <<<<<<< HEAD @@ -460,16 +471,19 @@ def get_items(filters, additional_query_columns,additional_conditions=None): def get_items(filters, additional_query_columns, additional_conditions=None): si = frappe.qb.DocType("Sales Invoice") sii = frappe.qb.DocType("Sales Invoice Item") +<<<<<<< HEAD Item = frappe.qb.DocType("Item") >>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) +======= + item = frappe.qb.DocType("Item") +>>>>>>> d2af36e1eb (chore: update condition queries in qb) query = ( frappe.qb.from_(si) .join(sii) .on(si.name == sii.parent) - # added left join - .left_join(Item) - .on(sii.item_code == Item.name) + .left_join(item) + .on(sii.item_code == item.name) .select( sii.name, sii.parent, @@ -489,8 +503,8 @@ def get_items(filters, additional_query_columns, additional_conditions=None): sii.item_group, sii.item_name.as_("si_item_name"), sii.item_group.as_("si_item_group"), - Item.item_name.as_("i_item_name"), - Item.item_group.as_("i_item_group"), + item.item_name.as_("i_item_name"), + item.item_group.as_("i_item_group"), sii.sales_order, sii.delivery_note, sii.income_account, @@ -510,15 +524,24 @@ def get_items(filters, additional_query_columns, additional_conditions=None): ) .where(si.docstatus == 1) ) + + if additional_query_columns: + query = query.select(*additional_query_columns) + if filters.get("customer"): query = query.where(si.customer == filters["customer"]) if filters.get("customer_group"): query = query.where(si.customer_group == filters["customer_group"]) +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= +======= + query = apply_conditions(query, si, sii, filters, additional_conditions) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) >>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) @@ -528,16 +551,14 @@ def get_delivery_notes_against_sales_order(item_list): so_item_rows = list(set([d.so_detail for d in item_list])) if so_item_rows: - delivery_notes = frappe.db.sql( - """ - select parent, so_detail - from `tabDelivery Note Item` - where docstatus=1 and so_detail in (%s) - group by so_detail, parent - """ - % (", ".join(["%s"] * len(so_item_rows))), - tuple(so_item_rows), - as_dict=1, + dn_item = frappe.qb.DocType("Delivery Note Item") + delivery_notes = ( + frappe.qb.from_(dn_item) + .select(dn_item.parent, dn_item.so_detail) + .where(dn_item.docstatus == 1) + .where(dn_item.so_detail.isin(so_item_rows)) + .groupby(dn_item.so_detail, dn_item.parent) + .run(as_dict=True) ) for dn in delivery_notes: @@ -547,15 +568,16 @@ def get_delivery_notes_against_sales_order(item_list): def get_grand_total(filters, doctype): - return frappe.db.sql( - f""" SELECT - SUM(`tab{doctype}`.base_grand_total) - FROM `tab{doctype}` - WHERE `tab{doctype}`.docstatus = 1 - and posting_date between %s and %s - """, - (filters.get("from_date"), filters.get("to_date")), - )[0][0] # nosec + return flt( + frappe.db.get_value( + doctype, + { + "docstatus": 1, + "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), + }, + "sum(base_grand_total)", + ) + ) def get_tax_accounts( diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 1e24d96115d..504c74babcb 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -377,7 +377,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") query = ( - frappe.qb.from_(pi) # qb + frappe.qb.from_(pi) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f8b94d353f9..f27569531b1 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -415,7 +415,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") query = ( - frappe.qb.from_(si) # qb + frappe.qb.from_(si) .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, From 45c16f615c6c94c69f4c633cc4a5faef333e1b2c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Jun 2024 18:48:16 +0530 Subject: [PATCH 062/203] fix: Add additional condition application (cherry picked from commit 8ec364df6fe07817fd6c69c6e9db31af08d1a455) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py (cherry picked from commit 1fca6ea5e70da34cdce637bf2a4b1beb94bc3507) --- .../item_wise_purchase_register.py | 20 +++++++++++++++---- .../item_wise_sales_register.py | 12 +++++++++-- 2 files changed, 26 insertions(+), 6 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 dc4999a3b4e..bafd83db37a 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 @@ -30,7 +30,7 @@ def _execute(filters=None, additional_table_columns=None): company_currency = erpnext.get_company_currency(filters.company) - item_list = get_items(filters, get_query_columns(additional_table_columns)) + item_list = get_items(filters, additional_table_columns) aii_account_map = get_aii_accounts() if item_list: itemised_tax, tax_columns = get_tax_accounts( @@ -311,6 +311,7 @@ def apply_conditions(query, pi, pii, filters): return query +<<<<<<< HEAD def get_items(filters, additional_query_columns): <<<<<<< HEAD <<<<<<< HEAD @@ -363,6 +364,9 @@ def get_items(filters, additional_query_columns): ) .where(pi.docstatus == 1)) ======= +======= +def get_items(filters, additional_table_columns): +>>>>>>> 8ec364df6f (fix: Add additional condition application) pi = frappe.qb.DocType("Purchase Invoice") pii = frappe.qb.DocType("Purchase Invoice Item") Item = frappe.qb.DocType("Item") @@ -404,19 +408,27 @@ def get_items(filters, additional_query_columns): ) >>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) - if additional_query_columns: - query = query.select(*additional_query_columns) - if filters.get("supplier"): query = query.where(pi.supplier == filters["supplier"]) if filters.get("company"): query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= ======= +======= + if additional_table_columns: + for column in additional_table_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(pi[column.get("fieldname")]) + +>>>>>>> 8ec364df6f (fix: Add additional condition application) query = apply_conditions(query, pi, pii, filters) >>>>>>> d2af36e1eb (chore: update condition queries in qb) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 192cbc0e0cc..5a989a929ab 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -27,7 +27,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions) + item_list = get_items(filters, additional_table_columns, additional_conditions) if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) @@ -383,6 +383,9 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): else: query = apply_group_by_conditions(query, si, sii, filters) + for key, value in (additional_conditions or {}).items(): + query = query.where(si[key] == value) + return query @@ -526,7 +529,12 @@ def get_items(filters, additional_query_columns, additional_conditions=None): ) if additional_query_columns: - query = query.select(*additional_query_columns) + for column in additional_query_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(si[column.get("fieldname")]) if filters.get("customer"): query = query.where(si.customer == filters["customer"]) From 14c98fe6f0e1b9dbe04522ed6c61cf34656dea4e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 9 Jun 2024 20:02:39 +0530 Subject: [PATCH 063/203] chore: resolve conflicts (cherry picked from commit c29db5afcda6d283c32676c437f525e7aaba5ef6) --- .../item_wise_purchase_register.py | 66 ------------- .../item_wise_sales_register.py | 97 ++----------------- 2 files changed, 10 insertions(+), 153 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 bafd83db37a..80c246cad55 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 @@ -311,62 +311,7 @@ def apply_conditions(query, pi, pii, filters): return query -<<<<<<< HEAD -def get_items(filters, additional_query_columns): -<<<<<<< HEAD -<<<<<<< HEAD - conditions = get_conditions(filters) - if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - f""" - select - `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, - `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, - `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, - `tabPurchase Invoice`.unrealized_profit_loss_account, - `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, - `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, - `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, - `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, - `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, - `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, - `tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`, - `tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {additional_query_columns} - from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem` - where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and - `tabItem`.name = `tabPurchase Invoice Item`.`item_code` and - `tabPurchase Invoice`.docstatus = 1 {conditions} - """, - filters, - as_dict=1, - ) -======= - pi = frappe.qb.DocType('Purchase Invoice') - pii = frappe.qb.DocType('Purchase Invoice Item') - Item = frappe.qb.DocType('Item') - query = (frappe.qb.from_(pi) - .join(pii).on(pi.name == pii.parent) - .left_join(Item).on(pii.item_code == Item.name) - .select( - pii.name.as_('pii_name'), pii.parent, - pi.posting_date, pi.credit_to, pi.company, - pi.supplier, pi.remarks, pi.base_net_total, - pi.unrealized_profit_loss_account, - pii.item_code, pii.description, pii.item_group, - pii.item_name.as_('pi_item_name'), pii.item_group.as_('pi_item_group'), - Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), - pii.project, pii.purchase_order, - pii.purchase_receipt, pii.po_detail, - pii.expense_account, pii.stock_qty, - pii.stock_uom, pii.base_net_amount, - pi.supplier_name, pi.mode_of_payment - ) - .where(pi.docstatus == 1)) -======= -======= def get_items(filters, additional_table_columns): ->>>>>>> 8ec364df6f (fix: Add additional condition application) pi = frappe.qb.DocType("Purchase Invoice") pii = frappe.qb.DocType("Purchase Invoice Item") Item = frappe.qb.DocType("Item") @@ -406,20 +351,12 @@ def get_items(filters, additional_table_columns): ) .where(pi.docstatus == 1) ) ->>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) if filters.get("supplier"): query = query.where(pi.supplier == filters["supplier"]) if filters.get("company"): query = query.where(pi.company == filters["company"]) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) -======= -======= -======= if additional_table_columns: for column in additional_table_columns: if column.get("_doctype"): @@ -428,12 +365,9 @@ def get_items(filters, additional_table_columns): else: query = query.select(pi[column.get("fieldname")]) ->>>>>>> 8ec364df6f (fix: Add additional condition application) query = apply_conditions(query, pi, pii, filters) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) ->>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) def get_aii_accounts(): diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 5a989a929ab..cd50b118715 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -84,9 +84,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= "company": d.company, "sales_order": d.sales_order, "delivery_note": d.delivery_note, - "income_account": d.unrealized_profit_loss_account - if d.is_internal_customer == 1 - else d.income_account, + "income_account": get_income_account(d), "cost_center": d.cost_center, "stock_qty": d.stock_qty, "stock_uom": d.stock_uom, @@ -151,6 +149,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= return columns, data, None, None, None, skip_total_row +def get_income_account(row): + if row.enable_deferred_revenue: + return row.deferred_revenue_account + elif row.is_internal_customer == 1: + return row.unrealized_profit_loss_account + else: + return row.income_account + + def get_columns(additional_table_columns, filters): columns = [] @@ -367,8 +374,6 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): if filters.get("item_group"): query = query.where(sii.item_group == filters.get("item_group")) -<<<<<<< HEAD -======= if filters.get("income_account"): query = query.where( (sii.income_account == filters.get("income_account")) @@ -376,7 +381,6 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): | (si.unrealized_profit_loss_account == filters.get("income_account")) ) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) if not filters.get("group_by"): query = query.orderby(si.posting_date, order=Order.desc) query = query.orderby(sii.item_group, order=Order.desc) @@ -402,84 +406,10 @@ def apply_group_by_conditions(query, si, ii, filters): return query -<<<<<<< HEAD -<<<<<<< HEAD -def get_items(filters, additional_query_columns, additional_conditions=None): - conditions = get_conditions(filters, additional_conditions) - if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - """ - select - `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent, - `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, - `tabSales Invoice`.unrealized_profit_loss_account, - `tabSales Invoice`.is_internal_customer, - `tabSales Invoice`.customer, `tabSales Invoice`.remarks, - `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, - `tabSales Invoice Item`.project, - `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, - `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, - `tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group, - `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, - `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, - `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, - `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, - `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, - `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail, - `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {} - from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem` - where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and - `tabItem`.name = `tabSales Invoice Item`.`item_code` and - `tabSales Invoice`.docstatus = 1 {} - """.format(additional_query_columns, conditions), - filters, - as_dict=1, - ) # nosec -======= -def get_items(filters, additional_query_columns,additional_conditions=None): - si = frappe.qb.DocType('Sales Invoice') - sii = frappe.qb.DocType('Sales Invoice Item') - Item = frappe.qb.DocType('Item') - - query = ( - frappe.qb.from_(si) - .join(sii).on(si.name == sii.parent) - .left_join(Item).on(sii.item_code == Item.name) - .select( - sii.name, sii.parent, - si.posting_date, si.debit_to, - si.unrealized_profit_loss_account, - si.is_internal_customer, - si.customer, si.remarks, - si.territory, si.company, si.base_net_total, - sii.project, - sii.item_code, sii.description, - sii.item_name, sii.item_group, - sii.item_name.as_('si_item_name'), sii.item_group.as_('si_item_group'), - Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), - sii.sales_order, sii.delivery_note, - sii.income_account, sii.cost_center, - sii.enable_deferred_revenue, sii.deferred_revenue_account, - sii.stock_qty, sii.stock_uom, - sii.base_net_rate, sii.base_net_amount, - si.customer_name, si.customer_group, sii.so_detail, - si.update_stock, sii.uom, sii.qty - ) - .where(si.docstatus == 1) - ) - if filters.get("customer"): - query = query.where(si.customer == filters['customer']) -======= def get_items(filters, additional_query_columns, additional_conditions=None): si = frappe.qb.DocType("Sales Invoice") sii = frappe.qb.DocType("Sales Invoice Item") -<<<<<<< HEAD - Item = frappe.qb.DocType("Item") ->>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) -======= item = frappe.qb.DocType("Item") ->>>>>>> d2af36e1eb (chore: update condition queries in qb) query = ( frappe.qb.from_(si) @@ -542,16 +472,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None): if filters.get("customer_group"): query = query.where(si.customer_group == filters["customer_group"]) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) -======= -======= query = apply_conditions(query, si, sii, filters, additional_conditions) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) ->>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) def get_delivery_notes_against_sales_order(item_list): From f2909e9784ec677b0ea116cb9cb2acc60db30da8 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 10 Jun 2024 04:54:40 +0000 Subject: [PATCH 064/203] chore(release): Bumped to Version 15.27.2 ## [15.27.2](https://github.com/frappe/erpnext/compare/v15.27.1...v15.27.2) (2024-06-10) ### Bug Fixes * Add additional condition application ([45c16f6](https://github.com/frappe/erpnext/commit/45c16f615c6c94c69f4c633cc4a5faef333e1b2c)) * fixing Item-wise sales register ([9a629f8](https://github.com/frappe/erpnext/commit/9a629f860c6acd0d51aaf39e9fc0cfe1c0f27d98)) * fixing Item-wise sales register [#41373](https://github.com/frappe/erpnext/issues/41373) ([d75d64e](https://github.com/frappe/erpnext/commit/d75d64eb258c3d398e721cea9d7fc5e6725694e4)) * fixing Item-wise sales register and purchase register [#41373](https://github.com/frappe/erpnext/issues/41373) ([50ef81a](https://github.com/frappe/erpnext/commit/50ef81a997425d35208c25ccfa0137c2f9be34ec)) * Item-wise Sales and Purchase register with no item codes [#41373](https://github.com/frappe/erpnext/issues/41373) ([3e0123b](https://github.com/frappe/erpnext/commit/3e0123ba7bf54bd94fd7693ed33b8e94a7625be8)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 22be77c9b4a..f758814da21 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.1" +__version__ = "15.27.2" def get_default_company(user=None): From 30c0b2bb546c1c78bd5db29311a78d71a02efdf1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:16:57 +0530 Subject: [PATCH 065/203] fix: do not fetch items with no active BOM in PP (backport #41833) (#41835) fix: do not fetch items with no active BOM in PP (#41833) (cherry picked from commit 36413d14d852f2e126f000fbfeda6b1a8ef51d55) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 6 ++++- .../production_plan/test_production_plan.py | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a378d8ae606..7d3aa000c87 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -452,6 +452,10 @@ class ProductionPlan(Document): {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty} ) + bom_no = data.bom_no or item_details and item_details.bom_no or "" + if not bom_no: + continue + pi = self.append( "po_items", { @@ -459,7 +463,7 @@ class ProductionPlan(Document): "item_code": data.item_code, "description": data.description or item_details.description, "stock_uom": item_details and item_details.stock_uom or "", - "bom_no": data.bom_no or item_details and item_details.bom_no or "", + "bom_no": bom_no, "planned_qty": data.pending_qty, "pending_qty": data.pending_qty, "planned_start_date": now_datetime(), diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 8957c11d3f0..71d9b59c677 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -328,6 +328,28 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(pln2.po_items[0].bom_no, bom2.name) + def test_production_plan_with_non_active_bom_item(self): + item = make_item("Test Production Item 1 for Non Active BOM", {"is_stock_item": 1}).name + + so1 = make_sales_order(item_code=item, qty=1) + + pln = frappe.new_doc("Production Plan") + pln.company = so1.company + pln.get_items_from = "Sales Order" + pln.append( + "sales_orders", + { + "sales_order": so1.name, + "sales_order_date": so1.transaction_date, + "customer": so1.customer, + "grand_total": so1.grand_total, + }, + ) + + pln.get_items() + + self.assertFalse(pln.po_items) + def test_production_plan_combine_items(self): "Test combining FG items in Production Plan." item = "Test Production Item 1" From 1ead66721d92a25e8037da2198a80031c69049d4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:17:19 +0530 Subject: [PATCH 066/203] fix: terms and conditions for material request (backport #41834) (#41837) fix: terms and conditions for material request (#41834) (cherry picked from commit 4b026d66dc32de2a8447a8007e56719cf31872dc) Co-authored-by: rohitwaghchaure --- erpnext/selling/doctype/sales_order/sales_order.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d6c08d957fe..e5e67ed38b7 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -794,6 +794,11 @@ def get_requested_item_qty(sales_order): def make_material_request(source_name, target_doc=None): requested_item_qty = get_requested_item_qty(source_name) + def postprocess(source, target): + if source.tc_name and frappe.db.get_value("Terms and Conditions", source.tc_name, "buying") != 1: + target.tc_name = None + target.terms = None + def get_remaining_qty(so_item): return flt( flt(so_item.qty) @@ -849,6 +854,7 @@ def make_material_request(source_name, target_doc=None): }, }, target_doc, + postprocess, ) return doc From 5544309048e905592355dc1f206c31db40126e41 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:49:44 +0530 Subject: [PATCH 067/203] fix: calculate totals and taxes (cherry picked from commit a7ec0c1cec3aa225f0ebc9148a71f86a28c933d5) --- erpnext/public/js/controllers/taxes_and_totals.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index cf6b34c9604..adf05ffb154 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -83,7 +83,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total")); } - this.frm.refresh_field("taxes"); + this.frm.refresh_fields(); } calculate_discount_amount() { @@ -841,7 +841,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { }); } - this.frm.refresh_field("taxes"); + this.frm.refresh_fields(); } set_default_payment(total_amount_to_pay, update_paid_amount) { From c0668373c7fa19e30f0986e168a8e02123fdf7b2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 30 May 2024 11:47:44 +0530 Subject: [PATCH 068/203] refactor: enabling partial TDS application on partial invoice (cherry picked from commit 091c5496b20864577d133b0804e957ff8995606f) --- .../purchase_invoice/purchase_invoice.js | 2 +- .../purchase_invoice/purchase_invoice.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c56a083808d..c0a43395216 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -677,7 +677,7 @@ frappe.ui.form.on("Purchase Invoice", { if (frm.doc.supplier) { frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; } - if (!frm.doc.__onload.supplier_tds) { + if (!frm.doc.__onload.enable_apply_tds) { frm.set_df_property("apply_tds", "read_only", 1); } } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index afccc21c313..df54a63b661 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, throw +from frappe import _, qb, throw from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate @@ -347,6 +347,22 @@ class PurchaseInvoice(BuyingController): self.tax_withholding_category = tds_category self.set_onload("supplier_tds", tds_category) + # If Linked Purchase Order has TDS applied, enable 'apply_tds' checkbox + if purchase_orders := [x.purchase_order for x in self.items if x.purchase_order]: + po = qb.DocType("Purchase Order") + po_with_tds = ( + qb.from_(po) + .select(po.name) + .where( + po.docstatus.eq(1) + & (po.name.isin(purchase_orders)) + & (po.apply_tds.eq(1)) + & (po.tax_withholding_category.notnull()) + ) + .run() + ) + self.set_onload("enable_apply_tds", True if po_with_tds else False) + super().set_missing_values(for_validate) def validate_credit_to_acc(self): From c664a27b1e3b94339c23091fec72ae5e258f9cf8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 31 May 2024 16:53:48 +0530 Subject: [PATCH 069/203] refactor: is_opening in payment entry (cherry picked from commit 05d17d0d7347fcb28e616ed297dfef41206d0960) --- .../doctype/payment_entry/payment_entry.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 6b0115d3f56..c1e58b28e7d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -89,6 +89,7 @@ "custom_remarks", "remarks", "base_in_words", + "is_opening", "column_break_16", "letter_head", "print_heading", @@ -777,6 +778,15 @@ "label": "Reconcile on Advance Payment Date", "no_copy": 1, "read_only": 1 + }, + { + "default": "No", + "fieldname": "is_opening", + "fieldtype": "Select", + "label": "Is Opening", + "options": "No\nYes", + "print_hide": 1, + "search_index": 1 } ], "index_web_pages_for_search": 1, @@ -790,7 +800,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-05-17 10:21:11.199445", + "modified": "2024-05-31 16:54:59.618516", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From 1fc802da6f4f213c22e3a3a61bb9714f6c5364ab Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 31 May 2024 17:11:40 +0530 Subject: [PATCH 070/203] refactor: restrict to 'Advance in Separate Party Account' type (cherry picked from commit c36f0e4a3376f28a82ba94d1fb46337b3fd62232) --- erpnext/accounts/doctype/payment_entry/payment_entry.json | 3 ++- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index c1e58b28e7d..d420bcca342 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -781,6 +781,7 @@ }, { "default": "No", + "depends_on": "eval: doc.book_advance_payments_in_separate_party_account == 1", "fieldname": "is_opening", "fieldtype": "Select", "label": "Is Opening", @@ -800,7 +801,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-05-31 16:54:59.618516", + "modified": "2024-05-31 17:07:06.197249", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e542ff68176..69075e5cf6c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -116,11 +116,13 @@ class PaymentEntry(AccountsController): self.book_advance_payments_in_separate_party_account = False if self.party_type not in ("Customer", "Supplier"): + self.is_opening = "No" return if not frappe.db.get_value( "Company", self.company, "book_advance_payments_in_separate_party_account" ): + self.is_opening = "No" return # Important to set this flag for the gl building logic to work properly @@ -132,6 +134,7 @@ class PaymentEntry(AccountsController): if (account_type == "Payable" and self.party_type == "Customer") or ( account_type == "Receivable" and self.party_type == "Supplier" ): + self.is_opening = "No" return if self.references: @@ -141,6 +144,7 @@ class PaymentEntry(AccountsController): # If there are referencers other than `allowed_types`, treat this as a normal payment entry if reference_types - allowed_types: self.book_advance_payments_in_separate_party_account = False + self.is_opening = "No" return liability_account = get_party_account( From 818a5536689ac936bc0bee42197b45a3220c5387 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Jun 2024 10:08:37 +0530 Subject: [PATCH 071/203] test: is_opening flag for advance in separate party account (cherry picked from commit 17f968e1e18a071e1708534bd28e3e6a037dde79) --- .../payment_entry/test_payment_entry.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index d22350d20fc..4631f8905e6 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1729,6 +1729,68 @@ class TestPaymentEntry(FrappeTestCase): self.check_gl_entries() self.check_pl_entries() + def test_opening_flag_for_advance_as_liability(self): + company = "_Test Company" + + advance_account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Received", + company=company, + account_type="Receivable", + ) + + # Enable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": advance_account, + }, + ) + # Advance Payment + adv = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + adv.is_opening = "Yes" + adv.save() # use save() to trigger set_liability_account() + adv.submit() + + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": adv.name, "is_opening": "Yes"} + ) + # 'Is Opening' can be 'Yes' for Advances in separate party account + self.assertNotEqual(gl_with_opening_set, []) + + # Disable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 0, + "default_advance_received_account": None, + }, + ) + payment = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + payment.is_opening = "Yes" + payment.save() + payment.submit() + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": payment.name, "is_opening": "Yes"} + ) + # 'Is Opening' should always be 'No' for normal advance payments + self.assertEqual(gl_with_opening_set, []) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 9e9bc8b59c35bbf4fbb5548c0341296f858e06c9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Jun 2024 14:47:08 +0530 Subject: [PATCH 072/203] fix: Add timestamp to key for immutable views (cherry picked from commit 6bded59f1c5e7f5f63703ccf8fa03cd3dae935e9) --- erpnext/accounts/report/general_ledger/general_ledger.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 44fad110baa..e884deb2a17 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -421,6 +421,8 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): if filters.get("show_net_values_in_party_account"): account_type_map = get_account_type_map(filters.get("company")) + immutable_ledger = frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger") + def update_value_in_dict(data, key, gle): data[key].debit += gle.debit data[key].credit += gle.credit @@ -489,6 +491,10 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): gle.get("party_type"), gle.get("party"), ] + + if immutable_ledger: + keylist.append(gle.get("creation")) + if filters.get("include_dimensions"): for dim in accounting_dimensions: keylist.append(gle.get(dim)) From 439ef109f305020a5b60f852e4c2d24581ac06be Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Jun 2024 14:47:08 +0530 Subject: [PATCH 073/203] fix: Add timestamp to key for immutable views (cherry picked from commit 6bded59f1c5e7f5f63703ccf8fa03cd3dae935e9) (cherry picked from commit 9e9bc8b59c35bbf4fbb5548c0341296f858e06c9) --- erpnext/accounts/report/general_ledger/general_ledger.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 44fad110baa..e884deb2a17 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -421,6 +421,8 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): if filters.get("show_net_values_in_party_account"): account_type_map = get_account_type_map(filters.get("company")) + immutable_ledger = frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger") + def update_value_in_dict(data, key, gle): data[key].debit += gle.debit data[key].credit += gle.credit @@ -489,6 +491,10 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): gle.get("party_type"), gle.get("party"), ] + + if immutable_ledger: + keylist.append(gle.get("creation")) + if filters.get("include_dimensions"): for dim in accounting_dimensions: keylist.append(gle.get(dim)) From 6157119bcf25b8ddfdf0b6384f43cd11c24e0af9 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 11 Jun 2024 09:50:57 +0000 Subject: [PATCH 074/203] chore(release): Bumped to Version 15.27.3 ## [15.27.3](https://github.com/frappe/erpnext/compare/v15.27.2...v15.27.3) (2024-06-11) ### Bug Fixes * Add timestamp to key for immutable views ([439ef10](https://github.com/frappe/erpnext/commit/439ef109f305020a5b60f852e4c2d24581ac06be)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index f758814da21..2a58be190d1 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.2" +__version__ = "15.27.3" def get_default_company(user=None): From 28b7fad579bfca19f3b0be0331a38215f01521bc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:18:11 +0530 Subject: [PATCH 075/203] fix: asset depreciations and balances report correction (backport #41824) (#41851) fix: asset depreciations and balances report correction (#41824) * fix: asset depreciations and balances report correction * chore: suppress linter warnings with # nosemgrep (cherry picked from commit 857c6894052ed1dad31ab0b44d5c51f44a2c3bec) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../asset_depreciations_and_balances.py | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 4fa485f54b3..f9a008ade7f 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -69,48 +69,50 @@ def get_asset_categories_for_grouped_by_category(filters): condition = "" if filters.get("asset_category"): condition += " and asset_category = %(asset_category)s" + # nosemgrep return frappe.db.sql( f""" - SELECT asset_category, - ifnull(sum(case when purchase_date < %(from_date)s then - case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then - gross_purchase_amount + SELECT a.asset_category, + ifnull(sum(case when a.purchase_date < %(from_date)s then + case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_as_on_from_date, - ifnull(sum(case when purchase_date >= %(from_date)s then - gross_purchase_amount + ifnull(sum(case when a.purchase_date >= %(from_date)s then + a.gross_purchase_amount else 0 end), 0) as cost_of_new_purchase, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Sold" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Sold" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_sold_asset, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Scrapped" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Scrapped" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_scrapped_asset - from `tabAsset` + from `tabAsset` a where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition} - group by asset_category + and not exists(select name from `tabAsset Capitalization Asset Item` where asset = a.name) + group by a.asset_category """, { "to_date": filters.to_date, From 3e4c568ddccfde0dd1a03a2e90dfeedf38521ae3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:29:54 +0530 Subject: [PATCH 076/203] fix: valuation rate for serial and batch bundle for current bundle (backport #41850) (#41860) fix: valuation rate for serial and batch bundle for current bundle (#41850) (cherry picked from commit 7249a691b3ab24fc6ecb80545ff9a17b8d60a12d) Co-authored-by: rohitwaghchaure --- erpnext/controllers/stock_controller.py | 3 +++ .../stock_reconciliation.py | 22 ++++++++++++++----- .../test_stock_reconciliation.py | 14 ++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index bde2f6bd099..cad9178366e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -715,6 +715,9 @@ class StockController(AccountsController): row.db_set("rejected_serial_and_batch_bundle", None) + if row.get("current_serial_and_batch_bundle"): + row.db_set("current_serial_and_batch_bundle", None) + def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False): if not table_name: table_name = "items" diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 89e69c153d4..8301a706183 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -164,7 +164,11 @@ class StockReconciliation(StockController): for item in self.items: if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle: bundle = self.get_bundle_for_specific_serial_batch(item) - item.current_serial_and_batch_bundle = bundle + item.current_serial_and_batch_bundle = bundle.name + item.current_valuation_rate = abs(bundle.avg_rate) + + if not item.valuation_rate: + item.valuation_rate = item.current_valuation_rate continue if not save and item.use_serial_batch_fields: @@ -282,7 +286,12 @@ class StockReconciliation(StockController): from erpnext.stock.serial_batch_bundle import SerialBatchCreation if row.current_serial_and_batch_bundle and not self.has_change_in_serial_batch(row): - return row.current_serial_and_batch_bundle + return frappe._dict( + { + "name": row.current_serial_and_batch_bundle, + "avg_rate": row.current_valuation_rate, + } + ) cls_obj = SerialBatchCreation( { @@ -316,12 +325,11 @@ class StockReconciliation(StockController): total_current_qty += current_qty entry.qty = current_qty * -1 - reco_obj.flags.ignore_validate = True reco_obj.save() row.current_qty = total_current_qty - return reco_obj.name + return reco_obj def has_change_in_serial_batch(self, row) -> bool: bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []} @@ -721,7 +729,7 @@ class StockReconciliation(StockController): for d in serial_nos: frappe.db.set_value("Serial No", d, "purchase_rate", valuation_rate) - def get_sle_for_items(self, row, serial_nos=None): + def get_sle_for_items(self, row, serial_nos=None, current_bundle=True): """Insert Stock Ledger Entries""" if not serial_nos and row.serial_no: @@ -755,7 +763,7 @@ class StockReconciliation(StockController): has_dimensions = True if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle): - if row.current_qty: + if row.current_qty and current_bundle: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) data.previous_qty_after_transaction = flt(row.qty) @@ -785,6 +793,8 @@ class StockReconciliation(StockController): has_serial_no = False for row in self.items: sl_entries.append(self.get_sle_for_items(row)) + if row.serial_and_batch_bundle and row.current_serial_and_batch_bundle: + sl_entries.append(self.get_sle_for_items(row, current_bundle=False)) if sl_entries: if has_serial_no: diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 4397616e30c..8845bdbb753 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1109,6 +1109,8 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): ) sr.reload() + + self.assertTrue(sr.items[0].current_valuation_rate) current_sabb = sr.items[0].current_serial_and_batch_bundle doc = frappe.get_doc("Serial and Batch Bundle", current_sabb) for row in doc.entries: @@ -1118,6 +1120,18 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): batch_qty = get_batch_qty(batches[0].batch_no, warehouse, item.name) self.assertEqual(batch_qty, 100) + for row in frappe.get_all("Repost Item Valuation", filters={"voucher_no": sr.name}): + rdoc = frappe.get_doc("Repost Item Valuation", row.name) + rdoc.cancel() + rdoc.delete() + + sr.cancel() + + for row in frappe.get_all( + "Serial and Batch Bundle", fields=["docstatus"], filters={"voucher_no": sr.name} + ): + self.assertEqual(row.docstatus, 2) + def test_not_reconcile_all_serial_nos(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.utils import get_incoming_rate From 4d2d38cd9dc22b91d0c23d39bafd05b240fa5cb7 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 11 Jun 2024 13:18:31 +0000 Subject: [PATCH 077/203] chore(release): Bumped to Version 15.27.4 ## [15.27.4](https://github.com/frappe/erpnext/compare/v15.27.3...v15.27.4) (2024-06-11) ### Bug Fixes * Add additional condition application ([1fca6ea](https://github.com/frappe/erpnext/commit/1fca6ea5e70da34cdce637bf2a4b1beb94bc3507)) * Add posting date to key for consolidated view ([9472085](https://github.com/frappe/erpnext/commit/94720851c6b819f3466dbae45a566f2d1830e2e3)) * Add timestamp to key for immutable views ([9e9bc8b](https://github.com/frappe/erpnext/commit/9e9bc8b59c35bbf4fbb5548c0341296f858e06c9)) * asset depreciations and balances report correction (backport [#41824](https://github.com/frappe/erpnext/issues/41824)) ([#41851](https://github.com/frappe/erpnext/issues/41851)) ([28b7fad](https://github.com/frappe/erpnext/commit/28b7fad579bfca19f3b0be0331a38215f01521bc)) * calculate totals and taxes ([5544309](https://github.com/frappe/erpnext/commit/5544309048e905592355dc1f206c31db40126e41)) * Do no apply pricing rule on qty change for mapped docs ([822bc58](https://github.com/frappe/erpnext/commit/822bc581f3965f85f0080cfc0b2426520538fccb)) * do not fetch items with no active BOM in PP (backport [#41833](https://github.com/frappe/erpnext/issues/41833)) ([#41835](https://github.com/frappe/erpnext/issues/41835)) ([30c0b2b](https://github.com/frappe/erpnext/commit/30c0b2bb546c1c78bd5db29311a78d71a02efdf1)) * enable no_copy for timesheet in sales invoice ([f1ad0f6](https://github.com/frappe/erpnext/commit/f1ad0f6f269c7eeb9668798f0e6815a620c38a95)) * fixing Item-wise sales register ([f36db7e](https://github.com/frappe/erpnext/commit/f36db7ebaf0c1e3c0de716f2733961be4f5b632e)) * fixing Item-wise sales register [#41373](https://github.com/frappe/erpnext/issues/41373) ([5e0fa1b](https://github.com/frappe/erpnext/commit/5e0fa1b75ec1437516aa190550ccf8fbfab67a6e)) * fixing Item-wise sales register and purchase register [#41373](https://github.com/frappe/erpnext/issues/41373) ([a4b28ba](https://github.com/frappe/erpnext/commit/a4b28ba354873a8bb5253a5902970fc780aa9441)) * Item-wise Sales and Purchase register with no item codes [#41373](https://github.com/frappe/erpnext/issues/41373) ([96405e8](https://github.com/frappe/erpnext/commit/96405e8e4957c672b7a4aa5f7e9ead081a6e143d)) * stock_value_change for legacy serial nos (backport [#41825](https://github.com/frappe/erpnext/issues/41825)) ([#41826](https://github.com/frappe/erpnext/issues/41826)) ([a7971cf](https://github.com/frappe/erpnext/commit/a7971cf8443dac71347369fb38ab2edf0ed8dc27)) * terms and conditions for material request (backport [#41834](https://github.com/frappe/erpnext/issues/41834)) ([#41837](https://github.com/frappe/erpnext/issues/41837)) ([1ead667](https://github.com/frappe/erpnext/commit/1ead66721d92a25e8037da2198a80031c69049d4)) * typo in method - holiday list (backport [#41811](https://github.com/frappe/erpnext/issues/41811)) ([#41812](https://github.com/frappe/erpnext/issues/41812)) ([4d3e883](https://github.com/frappe/erpnext/commit/4d3e88313013af0e3741a0b108c49f6680fc95a5)) * valuation rate for backdated legacy serial/batches (backport [#41788](https://github.com/frappe/erpnext/issues/41788)) ([#41821](https://github.com/frappe/erpnext/issues/41821)) ([8d31243](https://github.com/frappe/erpnext/commit/8d31243a42a37b4dd0947d4d506815e8d999a009)) * valuation rate for serial and batch bundle for current bundle (backport [#41850](https://github.com/frappe/erpnext/issues/41850)) ([#41860](https://github.com/frappe/erpnext/issues/41860)) ([3e4c568](https://github.com/frappe/erpnext/commit/3e4c568ddccfde0dd1a03a2e90dfeedf38521ae3)) * **Vehicle:** allow doc renaming and add test (backport [#41729](https://github.com/frappe/erpnext/issues/41729)) ([#41785](https://github.com/frappe/erpnext/issues/41785)) ([4d37739](https://github.com/frappe/erpnext/commit/4d37739dd3ede937e93e8328fa6a147462efc241)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2a58be190d1..b3e5b4b6880 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.3" +__version__ = "15.27.4" def get_default_company(user=None): From 67d1709ef8e477d39c4d95e6bfb00128e570faa1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Jun 2024 12:12:58 +0530 Subject: [PATCH 078/203] fix: batch qty in the dropdown field (cherry picked from commit eb426c69fe85e27e79cadb4da7ff90b298a111de) --- erpnext/controllers/queries.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index c57b2f3ddd6..a07a00dd03e 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -352,6 +352,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): doctype = "Batch" meta = frappe.get_meta(doctype, cached=True) searchfields = meta.get_search_fields() + page_len = 30 batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len) batches.extend(get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len)) @@ -422,7 +423,7 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, p & (stock_ledger_entry.batch_no.isnotnull()) ) .groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse) - .having(Sum(stock_ledger_entry.actual_qty) > 0) + .having(Sum(stock_ledger_entry.actual_qty) != 0) .offset(start) .limit(page_len) ) @@ -473,7 +474,7 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0 & (stock_ledger_entry.serial_and_batch_bundle.isnotnull()) ) .groupby(bundle.batch_no, bundle.warehouse) - .having(Sum(bundle.qty) > 0) + .having(Sum(bundle.qty) != 0) .offset(start) .limit(page_len) ) From ef2a6c788ee60e33798469bc7e8de436c6d4c2c5 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:26:40 +0530 Subject: [PATCH 079/203] fix: download materials request plan in PP (cherry picked from commit f4acdd9636dd32fcee2bc73495a1c5aa299c5baa) --- .../doctype/production_plan/production_plan.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 6db901c71a4..d89ea4546c1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -400,13 +400,17 @@ frappe.ui.form.on("Production Plan", { }, download_materials_required(frm) { + const warehouses_data = [{ + warehouse: frm.doc.for_warehouse + }]; const fields = [ { fieldname: "warehouses", fieldtype: "Table MultiSelect", label: __("Warehouses"), - default: frm.doc.from_warehouse, + default: warehouses_data, options: "Production Plan Material Request Warehouse", + reqd: 1, get_query: function () { return { filters: { From 7193633773237105e7f461e2e589645a2bc8eab6 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:34:46 +0530 Subject: [PATCH 080/203] fix: download materials request plan in PP (cherry picked from commit cb52b7634215a5f580d0d01cbd0458b6a354b9ec) --- .../doctype/production_plan/production_plan.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index d89ea4546c1..cb3d8c3f96d 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -400,9 +400,11 @@ frappe.ui.form.on("Production Plan", { }, download_materials_required(frm) { - const warehouses_data = [{ - warehouse: frm.doc.for_warehouse - }]; + const warehouses_data = [ + { + warehouse: frm.doc.for_warehouse + }, + ]; const fields = [ { fieldname: "warehouses", From 764235b390ba6ff0b2c794876349c6be2ac63023 Mon Sep 17 00:00:00 2001 From: Himanshu Shivhare Date: Wed, 12 Jun 2024 13:01:28 +0530 Subject: [PATCH 081/203] chore: typo in bom_operation.json (#41650) chore: typo in bom_operation.json (cherry picked from commit 7ee2a19f535010313e7be1e8c99c443446c123a6) --- .../manufacturing/doctype/bom_operation/bom_operation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 5a734d8684f..037a3fe0e81 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -77,7 +77,7 @@ "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, - "label": "Operation Time ", + "label": "Operation Time", "oldfieldname": "time_in_mins", "oldfieldtype": "Currency", "reqd": 1 @@ -203,4 +203,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From 337082512e28bacaa292085b64dde7f5b2ec8526 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:36:53 +0530 Subject: [PATCH 082/203] fix: download materials request plan in PP -prettier (cherry picked from commit a1c4e0e93c7ec449c6e61d0dab8d8ee08d2deb84) --- .../manufacturing/doctype/production_plan/production_plan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index cb3d8c3f96d..f57adc5db2e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -402,7 +402,7 @@ frappe.ui.form.on("Production Plan", { download_materials_required(frm) { const warehouses_data = [ { - warehouse: frm.doc.for_warehouse + warehouse: frm.doc.for_warehouse, }, ]; const fields = [ From 2c9b3908dd61ae51e9c455dc0b4b03fd69ea15c0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Jun 2024 12:12:58 +0530 Subject: [PATCH 083/203] fix: batch qty in the dropdown field (cherry picked from commit eb426c69fe85e27e79cadb4da7ff90b298a111de) (cherry picked from commit 67d1709ef8e477d39c4d95e6bfb00128e570faa1) --- erpnext/controllers/queries.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index c57b2f3ddd6..a07a00dd03e 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -352,6 +352,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): doctype = "Batch" meta = frappe.get_meta(doctype, cached=True) searchfields = meta.get_search_fields() + page_len = 30 batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len) batches.extend(get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len)) @@ -422,7 +423,7 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, p & (stock_ledger_entry.batch_no.isnotnull()) ) .groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse) - .having(Sum(stock_ledger_entry.actual_qty) > 0) + .having(Sum(stock_ledger_entry.actual_qty) != 0) .offset(start) .limit(page_len) ) @@ -473,7 +474,7 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0 & (stock_ledger_entry.serial_and_batch_bundle.isnotnull()) ) .groupby(bundle.batch_no, bundle.warehouse) - .having(Sum(bundle.qty) > 0) + .having(Sum(bundle.qty) != 0) .offset(start) .limit(page_len) ) From 6c57971abc12fd08ca3986f10c8b78d089cbb3c4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Jun 2024 14:33:25 +0530 Subject: [PATCH 084/203] fix: return from accepted and rejected warehouse at a same time not working (cherry picked from commit 17731f09ef614537cf5dc07ad0cce6b8f918bb59) --- .../controllers/sales_and_purchase_return.py | 18 ++++++- erpnext/controllers/stock_controller.py | 9 +++- .../purchase_receipt/test_purchase_receipt.py | 49 +++++++++++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 96af12f2340..3414b5b2520 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -936,6 +936,7 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False if is_rejected: fields.extend(["rejected_serial_and_batch_bundle", "return_qty_from_rejected_warehouse"]) + del filters["rejected_serial_and_batch_bundle"] data = frappe.get_all( doctype, fields=fields, @@ -943,6 +944,9 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False ) for d in data: + if not d.get("serial_and_batch_bundle") and not d.get("rejected_serial_and_batch_bundle"): + continue + if is_rejected: if d.get("return_qty_from_rejected_warehouse"): _bundle_ids.append(d.get("serial_and_batch_bundle")) @@ -1027,7 +1031,7 @@ def get_available_batch_qty(parent_doc, batch_no, warehouse): ) -def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_field=None): +def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_field=None, qty_field=None): from erpnext.stock.serial_batch_bundle import SerialBatchCreation type_of_transaction = "Outward" @@ -1037,11 +1041,21 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f if not warehouse_field: warehouse_field = "warehouse" + if not qty_field: + qty_field = "qty" + warehouse = child_doc.get(warehouse_field) if parent_doc.get("is_internal_customer"): warehouse = child_doc.get("target_warehouse") type_of_transaction = "Outward" + if not child_doc.get(qty_field): + frappe.throw( + _("For the {0}, the quantity is required to make the return entry").format( + frappe.bold(child_doc.item_code) + ) + ) + cls_obj = SerialBatchCreation( { "type_of_transaction": type_of_transaction, @@ -1054,7 +1068,7 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f "voucher_type": parent_doc.doctype, "voucher_no": parent_doc.name, "voucher_detail_no": child_doc.name, - "qty": child_doc.qty, + "qty": child_doc.get(qty_field), "company": parent_doc.company, "do_not_submit": True, } diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index cad9178366e..4f579fd500e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -255,10 +255,17 @@ class StockController(AccountsController): qty_field = "qty" warehouse_field = "warehouse" + if not data.get("qty"): + frappe.throw( + _("For the {0}, no stock is available for the return in the warehouse {1}.").format( + frappe.bold(row.item_code), row.get(warehouse_field) + ) + ) + data = filter_serial_batches( self, data, row, warehouse_field=warehouse_field, qty_field=qty_field ) - bundle = make_serial_batch_bundle_for_return(data, row, self, warehouse_field) + bundle = make_serial_batch_bundle_for_return(data, row, self, warehouse_field, qty_field) if row.get("return_qty_from_rejected_warehouse"): row.db_set( { diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index edfe8152169..88e9c94c2b1 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2864,6 +2864,55 @@ class TestPurchaseReceipt(FrappeTestCase): for row in sabb_doc.entries: self.assertEqual(row.incoming_rate, 0) + def test_purchase_return_from_accepted_and_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return, + ) + + item = make_item( + "_Test PR Item With Return From Accepted and Rejected WH", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SD-TZVRFORBATCH.#####", + "valuation_rate": 200, + }, + ) + + pr = make_purchase_receipt( + qty=10, + rejected_qty=5, + rate=100, + item_code=item.name, + ) + + pr.reload() + self.assertTrue(pr.items[0].serial_and_batch_bundle) + self.assertTrue(pr.items[0].rejected_serial_and_batch_bundle) + + return_pr = make_purchase_return(pr.name) + return_pr.submit() + + return_pr.reload() + self.assertTrue(return_pr.items[0].serial_and_batch_bundle) + self.assertTrue(return_pr.items[0].rejected_serial_and_batch_bundle) + + self.assertEqual( + return_pr.items[0].qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].serial_and_batch_bundle, "total_qty" + ), + ) + + self.assertEqual( + return_pr.items[0].rejected_qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty" + ), + ) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 00b7c49b4b0b831141d830d5f06e42fea283e0b1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Jun 2024 14:33:25 +0530 Subject: [PATCH 085/203] fix: return from accepted and rejected warehouse at a same time not working (cherry picked from commit 17731f09ef614537cf5dc07ad0cce6b8f918bb59) (cherry picked from commit 6c57971abc12fd08ca3986f10c8b78d089cbb3c4) --- .../controllers/sales_and_purchase_return.py | 18 ++++++- erpnext/controllers/stock_controller.py | 9 +++- .../purchase_receipt/test_purchase_receipt.py | 49 +++++++++++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 96af12f2340..3414b5b2520 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -936,6 +936,7 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False if is_rejected: fields.extend(["rejected_serial_and_batch_bundle", "return_qty_from_rejected_warehouse"]) + del filters["rejected_serial_and_batch_bundle"] data = frappe.get_all( doctype, fields=fields, @@ -943,6 +944,9 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False ) for d in data: + if not d.get("serial_and_batch_bundle") and not d.get("rejected_serial_and_batch_bundle"): + continue + if is_rejected: if d.get("return_qty_from_rejected_warehouse"): _bundle_ids.append(d.get("serial_and_batch_bundle")) @@ -1027,7 +1031,7 @@ def get_available_batch_qty(parent_doc, batch_no, warehouse): ) -def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_field=None): +def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_field=None, qty_field=None): from erpnext.stock.serial_batch_bundle import SerialBatchCreation type_of_transaction = "Outward" @@ -1037,11 +1041,21 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f if not warehouse_field: warehouse_field = "warehouse" + if not qty_field: + qty_field = "qty" + warehouse = child_doc.get(warehouse_field) if parent_doc.get("is_internal_customer"): warehouse = child_doc.get("target_warehouse") type_of_transaction = "Outward" + if not child_doc.get(qty_field): + frappe.throw( + _("For the {0}, the quantity is required to make the return entry").format( + frappe.bold(child_doc.item_code) + ) + ) + cls_obj = SerialBatchCreation( { "type_of_transaction": type_of_transaction, @@ -1054,7 +1068,7 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f "voucher_type": parent_doc.doctype, "voucher_no": parent_doc.name, "voucher_detail_no": child_doc.name, - "qty": child_doc.qty, + "qty": child_doc.get(qty_field), "company": parent_doc.company, "do_not_submit": True, } diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index cad9178366e..4f579fd500e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -255,10 +255,17 @@ class StockController(AccountsController): qty_field = "qty" warehouse_field = "warehouse" + if not data.get("qty"): + frappe.throw( + _("For the {0}, no stock is available for the return in the warehouse {1}.").format( + frappe.bold(row.item_code), row.get(warehouse_field) + ) + ) + data = filter_serial_batches( self, data, row, warehouse_field=warehouse_field, qty_field=qty_field ) - bundle = make_serial_batch_bundle_for_return(data, row, self, warehouse_field) + bundle = make_serial_batch_bundle_for_return(data, row, self, warehouse_field, qty_field) if row.get("return_qty_from_rejected_warehouse"): row.db_set( { diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index edfe8152169..88e9c94c2b1 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2864,6 +2864,55 @@ class TestPurchaseReceipt(FrappeTestCase): for row in sabb_doc.entries: self.assertEqual(row.incoming_rate, 0) + def test_purchase_return_from_accepted_and_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return, + ) + + item = make_item( + "_Test PR Item With Return From Accepted and Rejected WH", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SD-TZVRFORBATCH.#####", + "valuation_rate": 200, + }, + ) + + pr = make_purchase_receipt( + qty=10, + rejected_qty=5, + rate=100, + item_code=item.name, + ) + + pr.reload() + self.assertTrue(pr.items[0].serial_and_batch_bundle) + self.assertTrue(pr.items[0].rejected_serial_and_batch_bundle) + + return_pr = make_purchase_return(pr.name) + return_pr.submit() + + return_pr.reload() + self.assertTrue(return_pr.items[0].serial_and_batch_bundle) + self.assertTrue(return_pr.items[0].rejected_serial_and_batch_bundle) + + self.assertEqual( + return_pr.items[0].qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].serial_and_batch_bundle, "total_qty" + ), + ) + + self.assertEqual( + return_pr.items[0].rejected_qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty" + ), + ) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 04f0c96af782d5690124f49782efedafcebddca0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 10 Jun 2024 17:14:27 +0530 Subject: [PATCH 086/203] fix: use invoice outstanding on Dunning (#41817) (cherry picked from commit cf0a29b05f9e120a7a1e648da50164a15c6b5b4e) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 68da9ea1657..cb0803db932 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2678,6 +2678,10 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): target.closing_text = letter_text.get("closing_text") target.language = letter_text.get("language") + # update outstanding + if source.payment_schedule and len(source.payment_schedule) == 1: + target.overdue_payments[0].outstanding = source.get("outstanding_amount") + target.validate() return get_mapped_doc( From a89b4f49fe9870065fee39a154b3a17e2d249f55 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 10 Jun 2024 17:14:27 +0530 Subject: [PATCH 087/203] fix: use invoice outstanding on Dunning (#41817) (cherry picked from commit cf0a29b05f9e120a7a1e648da50164a15c6b5b4e) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 68da9ea1657..cb0803db932 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2678,6 +2678,10 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): target.closing_text = letter_text.get("closing_text") target.language = letter_text.get("language") + # update outstanding + if source.payment_schedule and len(source.payment_schedule) == 1: + target.overdue_payments[0].outstanding = source.get("outstanding_amount") + target.validate() return get_mapped_doc( From b34191e81f486a8ee1f04842490a61a9d99172a8 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 12 Jun 2024 11:32:40 +0000 Subject: [PATCH 088/203] chore(release): Bumped to Version 15.27.5 ## [15.27.5](https://github.com/frappe/erpnext/compare/v15.27.4...v15.27.5) (2024-06-12) ### Bug Fixes * use invoice outstanding on Dunning ([#41817](https://github.com/frappe/erpnext/issues/41817)) ([a89b4f4](https://github.com/frappe/erpnext/commit/a89b4f49fe9870065fee39a154b3a17e2d249f55)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index b3e5b4b6880..a84e8dcc6b3 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.4" +__version__ = "15.27.5" def get_default_company(user=None): From 3bc5076dfc6e9c0d100c778cdee27e0f5fc2c4bf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:19:36 +0530 Subject: [PATCH 089/203] fix: allow Employee role to select Department (backport #41877) (#41881) * fix: allow Employee role to select Department (#41877) (cherry picked from commit 56082f5a2911f8f1766b504ff3b34f3ea949cf97) # Conflicts: # erpnext/setup/doctype/department/department.json * chore: fix conflicts --------- Co-authored-by: Rucha Mahabal --- erpnext/setup/doctype/department/department.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/department/department.json b/erpnext/setup/doctype/department/department.json index 99deca5c19d..fa6b9ad4a55 100644 --- a/erpnext/setup/doctype/department/department.json +++ b/erpnext/setup/doctype/department/department.json @@ -90,7 +90,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2023-08-28 17:26:46.826501", + "modified": "2024-06-12 16:10:31.451257", "modified_by": "Administrator", "module": "Setup", "name": "Department", @@ -132,6 +132,10 @@ "role": "HR Manager", "share": 1, "write": 1 + }, + { + "role": "Employee", + "select": 1 } ], "show_name_in_global_search": 1, From 9ccfbe1bf54c0353f4ee7fa9ee5bd859dc2d5b55 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 12 Jun 2024 11:54:13 +0000 Subject: [PATCH 090/203] chore(release): Bumped to Version 15.27.6 ## [15.27.6](https://github.com/frappe/erpnext/compare/v15.27.5...v15.27.6) (2024-06-12) ### Bug Fixes * batch qty in the dropdown field ([2c9b390](https://github.com/frappe/erpnext/commit/2c9b3908dd61ae51e9c455dc0b4b03fd69ea15c0)) * return from accepted and rejected warehouse at a same time not working ([00b7c49](https://github.com/frappe/erpnext/commit/00b7c49b4b0b831141d830d5f06e42fea283e0b1)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a84e8dcc6b3..61ed40e7e23 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.5" +__version__ = "15.27.6" def get_default_company(user=None): From 9cb7c279a47a2100569983ba9d938d219704f89e Mon Sep 17 00:00:00 2001 From: Kishan0330 Date: Wed, 12 Jun 2024 21:46:30 +0530 Subject: [PATCH 091/203] fix: download materials request plan in production plan (cherry picked from commit 6e55651795d7df39cc7cd2fd052809db22794d53) --- .../doctype/production_plan/production_plan.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index f57adc5db2e..9f7cb32c1c6 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -400,17 +400,18 @@ frappe.ui.form.on("Production Plan", { }, download_materials_required(frm) { - const warehouses_data = [ - { - warehouse: frm.doc.for_warehouse, - }, - ]; + const warehousesData = []; + + if (frm.doc.for_warehouse) { + warehousesData.push({ warehouse: frm.doc.for_warehouse }); + } + const fields = [ { fieldname: "warehouses", fieldtype: "Table MultiSelect", label: __("Warehouses"), - default: warehouses_data, + default: warehousesData, options: "Production Plan Material Request Warehouse", reqd: 1, get_query: function () { From 164973a42cd827e289b95bfc05b0fa18ac636752 Mon Sep 17 00:00:00 2001 From: Kishan0330 Date: Wed, 12 Jun 2024 22:08:29 +0530 Subject: [PATCH 092/203] refactor: change warehousesData variable name (cherry picked from commit f4e369977a6592ffd2ed4e6c3ce7179fc4fa27a0) --- .../doctype/production_plan/production_plan.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 9f7cb32c1c6..5b4ef233926 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -400,10 +400,10 @@ frappe.ui.form.on("Production Plan", { }, download_materials_required(frm) { - const warehousesData = []; + const warehouses_data = []; if (frm.doc.for_warehouse) { - warehousesData.push({ warehouse: frm.doc.for_warehouse }); + warehouses_data.push({ warehouse: frm.doc.for_warehouse }); } const fields = [ @@ -411,7 +411,7 @@ frappe.ui.form.on("Production Plan", { fieldname: "warehouses", fieldtype: "Table MultiSelect", label: __("Warehouses"), - default: warehousesData, + default: warehouses_data, options: "Production Plan Material Request Warehouse", reqd: 1, get_query: function () { From 5ea2cdcaad1787fbbbb1da297012f0ceec5d2e62 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 10 Jun 2024 20:06:05 +0530 Subject: [PATCH 093/203] fix: regional overide for updating item valution (cherry picked from commit 1a10f0bcbd0d9d19e9267a0715b3a5e69c77f2c3) --- erpnext/controllers/buying_controller.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 14bf8ad7436..bf6e3cd663a 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -8,6 +8,7 @@ from frappe.contacts.doctype.address.address import render_address from frappe.utils import cint, flt, getdate from frappe.utils.data import nowtime +import erpnext from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.party import get_party_details from erpnext.buying.utils import update_last_purchase_rate, validate_for_items @@ -332,6 +333,8 @@ class BuyingController(SubcontractingController): else: item.valuation_rate = 0.0 + update_regional_item_valuation_rate(self) + def set_incoming_rate(self): if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"): return @@ -935,3 +938,8 @@ def validate_item_type(doc, fieldname, message): ).format(items, message) frappe.throw(error_message) + + +@erpnext.allow_regional +def update_regional_item_valuation_rate(doc): + pass From 83ccb32be68c55b050e6b1993da2d9a8af5d7c67 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 09:04:24 +0530 Subject: [PATCH 094/203] fix: add LCV flag to determine negative expenses (cherry picked from commit baa3fee1bf9ce814a889ecf264fb6bac4ebaf93c) # Conflicts: # erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py --- erpnext/controllers/stock_controller.py | 4 +-- .../landed_cost_voucher.py | 5 ++++ .../purchase_receipt/purchase_receipt.py | 27 +++++++++---------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4f579fd500e..bc8778b4505 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -97,7 +97,7 @@ class StockController(AccountsController): ) ) - def make_gl_entries(self, gl_entries=None, from_repost=False): + def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False): if self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) @@ -118,7 +118,7 @@ class StockController(AccountsController): if self.docstatus == 1: if not gl_entries: - gl_entries = self.get_gl_entries(warehouse_account) + gl_entries = self.get_gl_entries(warehouse_account, via_landed_cost_voucher) make_gl_entries(gl_entries, from_repost=from_repost) def validate_serialized_batch(self): diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 683e946298a..da974c73999 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -252,8 +252,13 @@ class LandedCostVoucher(Document): doc.docstatus = 1 doc.make_bundle_using_old_serial_batch_fields(via_landed_cost_voucher=True) doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) +<<<<<<< HEAD doc.make_gl_entries() doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) +======= + doc.make_gl_entries(via_landed_cost_voucher=True) + doc.repost_future_sle_and_gle() +>>>>>>> baa3fee1bf (fix: add LCV flag to determine negative expenses) def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): for item in self.get("items"): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 0742ba3b590..6a00fed5588 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -422,13 +422,13 @@ class PurchaseReceipt(BuyingController): self.delete_auto_created_batches() self.set_consumed_qty_in_subcontract_order() - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False): from erpnext.accounts.general_ledger import process_gl_map gl_entries = [] self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) - self.make_tax_gl_entries(gl_entries) + self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher) update_regional_gl_entries(gl_entries, self) return process_gl_map(gl_entries) @@ -776,7 +776,7 @@ class PurchaseReceipt(BuyingController): posting_date=posting_date, ) - def make_tax_gl_entries(self, gl_entries): + def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) # Cost center-wise amount breakup for other charges included for valuation @@ -811,18 +811,17 @@ class PurchaseReceipt(BuyingController): i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): - negative_expense_booked_in_pi = frappe.db.sql( - """select name from `tabPurchase Invoice Item` pi - where docstatus = 1 and purchase_receipt=%s - and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' - and voucher_no=pi.parent and account=%s)""", - (self.name, tax.account_head), - ) - - if negative_expense_booked_in_pi: - account = stock_rbnb - else: + if via_landed_cost_voucher: account = tax.account_head + else: + negative_expense_booked_in_pi = frappe.db.sql( + """select name from `tabPurchase Invoice Item` pi + where docstatus = 1 and purchase_receipt=%s + and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' + and voucher_no=pi.parent and account=%s)""", + (self.name, tax.account_head), + ) + account = stock_rbnb if negative_expense_booked_in_pi else tax.account_head if i == len(valuation_tax): applicable_amount = amount_including_divisional_loss From c7c9d33954b78a8b0f3926cf3daa99bc382cd215 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 11:26:31 +0530 Subject: [PATCH 095/203] test: LCV entries after billing (cherry picked from commit 53642e7417c54197ba526625902d2671a7a564c2) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- erpnext/controllers/stock_controller.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 69 +++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index bc8778b4505..212c9adf4af 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -118,7 +118,11 @@ class StockController(AccountsController): if self.docstatus == 1: if not gl_entries: - gl_entries = self.get_gl_entries(warehouse_account, via_landed_cost_voucher) + gl_entries = ( + self.get_gl_entries(warehouse_account, via_landed_cost_voucher) + if self.doctype == "Purchase Receipt" + else self.get_gl_entries(warehouse_account) + ) make_gl_entries(gl_entries, from_repost=from_repost) def validate_serialized_batch(self): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 88e9c94c2b1..497dd331f7a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2426,6 +2426,7 @@ class TestPurchaseReceipt(FrappeTestCase): pr.reload() self.assertEqual(pr.per_billed, 100) +<<<<<<< HEAD def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self): batch_item = make_item( "_Test Purchase Receipt Batch Item For Rejected Qty", @@ -2912,6 +2913,50 @@ class TestPurchaseReceipt(FrappeTestCase): "Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty" ), ) +======= + def test_valuation_taxes_lcv_repost_after_billing(self): + from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( + make_landed_cost_voucher, + ) + + company = frappe.get_doc("Company", "_Test Company") + company.enable_perpetual_inventory = 1 + company.default_inventory_account = "Stock In Hand - _TC" + company.stock_received_but_not_billed = "Stock Received But Not Billed - _TC" + company.save() + + pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) + pr.append( + "taxes", + { + "category": "Valuation and Total", + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "tax_amount": 2000, + "description": "Test", + }, + ) + pr.submit() + pi = make_purchase_invoice(pr.name) + pi.submit() + lcv = make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=2000, + distribute_charges_based_on="Qty", + expense_account="Expenses Included In Valuation - _TC", + ) + + gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False) + expected_gle = ( + ("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"), + ("Stock In Hand - _TC", 14000, 0, "Main - _TC"), + ("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"), + ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), + ) + self.assertSequenceEqual(expected_gle, gl_entries) +>>>>>>> 53642e7417 (test: LCV entries after billing) def prepare_data_for_internal_transfer(): @@ -2959,14 +3004,24 @@ def get_sl_entries(voucher_type, voucher_no): ) -def get_gl_entries(voucher_type, voucher_no): - return frappe.db.sql( - """select account, debit, credit, cost_center, is_cancelled - from `tabGL Entry` where voucher_type=%s and voucher_no=%s - order by account desc""", - (voucher_type, voucher_no), - as_dict=1, +def get_gl_entries(voucher_type, voucher_no, skip_cancelled=False, as_dict=True): + gl = frappe.qb.DocType("GL Entry") + gl_query = ( + frappe.qb.from_(gl) + .select( + gl.account, + gl.debit, + gl.credit, + gl.cost_center, + ) + .where((gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no)) + .orderby(gl.account, order=frappe.qb.desc) ) + if skip_cancelled: + gl_query = gl_query.where(gl.is_cancelled == 0) + else: + gl_query = gl_query.select(gl.is_cancelled) + return gl_query.run(as_dict=as_dict) def get_taxes(**args): From 8618402829d9553ddc0c7d7ac41d86147ab2a84d Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 11:58:14 +0530 Subject: [PATCH 096/203] fix: parameters for PI references (cherry picked from commit 8b3d46610eacd27edbceaefff6452efb68a83365) # Conflicts: # erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- .../doctype/landed_cost_voucher/landed_cost_voucher.py | 7 +++++++ .../doctype/purchase_receipt/test_purchase_receipt.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index da974c73999..70874af677b 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -252,11 +252,18 @@ class LandedCostVoucher(Document): doc.docstatus = 1 doc.make_bundle_using_old_serial_batch_fields(via_landed_cost_voucher=True) doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) +<<<<<<< HEAD <<<<<<< HEAD doc.make_gl_entries() doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) ======= doc.make_gl_entries(via_landed_cost_voucher=True) +======= + if d.receipt_document_type == "Purchase Receipt": + doc.make_gl_entries(via_landed_cost_voucher=True) + else: + doc.make_gl_entries() +>>>>>>> 8b3d46610e (fix: parameters for PI references) doc.repost_future_sle_and_gle() >>>>>>> baa3fee1bf (fix: add LCV flag to determine negative expenses) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 497dd331f7a..30b72a96c7d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2956,7 +2956,11 @@ class TestPurchaseReceipt(FrappeTestCase): ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), ) self.assertSequenceEqual(expected_gle, gl_entries) +<<<<<<< HEAD >>>>>>> 53642e7417 (test: LCV entries after billing) +======= + frappe.db.rollback() +>>>>>>> 8b3d46610e (fix: parameters for PI references) def prepare_data_for_internal_transfer(): From 4c11f2bff23102eff34146baa15521218e31b77a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 13:04:01 +0530 Subject: [PATCH 097/203] fix: reset perpetual inventory flag after test (cherry picked from commit 0b36cbe307fec68f72ddfdfdeee401134887a884) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- .../purchase_receipt/test_purchase_receipt.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 30b72a96c7d..a7a54f4f5c2 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -9,6 +9,7 @@ from pypika import functions as fn import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.controllers.buying_controller import QtyMismatchError +from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( @@ -1681,7 +1682,6 @@ class TestPurchaseReceipt(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0) def test_internal_pr_gl_entries(self): - from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -2919,11 +2919,14 @@ class TestPurchaseReceipt(FrappeTestCase): make_landed_cost_voucher, ) - company = frappe.get_doc("Company", "_Test Company") - company.enable_perpetual_inventory = 1 - company.default_inventory_account = "Stock In Hand - _TC" - company.stock_received_but_not_billed = "Stock Received But Not Billed - _TC" - company.save() + old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company") + frappe.local.enable_perpetual_inventory["_Test Company"] = 1 + frappe.db.set_value( + "Company", + "_Test Company", + "stock_received_but_not_billed", + "Stock Received But Not Billed - _TC", + ) pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) pr.append( @@ -2939,7 +2942,7 @@ class TestPurchaseReceipt(FrappeTestCase): pr.submit() pi = make_purchase_invoice(pr.name) pi.submit() - lcv = make_landed_cost_voucher( + make_landed_cost_voucher( company=pr.company, receipt_document_type="Purchase Receipt", receipt_document=pr.name, @@ -2949,18 +2952,23 @@ class TestPurchaseReceipt(FrappeTestCase): ) gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False) + warehouse_account = get_warehouse_account_map("_Test Company") expected_gle = ( ("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"), - ("Stock In Hand - _TC", 14000, 0, "Main - _TC"), ("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"), ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), + (warehouse_account[pr.items[0].warehouse]["account"], 14000, 0, "Main - _TC"), ) self.assertSequenceEqual(expected_gle, gl_entries) <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> 53642e7417 (test: LCV entries after billing) ======= frappe.db.rollback() >>>>>>> 8b3d46610e (fix: parameters for PI references) +======= + frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory +>>>>>>> 0b36cbe307 (fix: reset perpetual inventory flag after test) def prepare_data_for_internal_transfer(): From 248e9cac712afbaf92302f11f8baa9ec420fbf3a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 21 Apr 2024 19:07:08 +0530 Subject: [PATCH 098/203] chore: resolve conflicts (cherry picked from commit 54a58e9205af43f4aabe1668e8d8cf925a226536) # Conflicts: # erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py --- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 70874af677b..0d15338c37c 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -263,9 +263,13 @@ class LandedCostVoucher(Document): doc.make_gl_entries(via_landed_cost_voucher=True) else: doc.make_gl_entries() +<<<<<<< HEAD >>>>>>> 8b3d46610e (fix: parameters for PI references) doc.repost_future_sle_and_gle() >>>>>>> baa3fee1bf (fix: add LCV flag to determine negative expenses) +======= + doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) +>>>>>>> 54a58e9205 (chore: resolve conflicts) def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): for item in self.get("items"): From b577d95ecd9893313258b69ed67297e32aea7c40 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 16 Jun 2024 20:47:55 +0530 Subject: [PATCH 099/203] chore: resolve conflicts --- .../landed_cost_voucher/landed_cost_voucher.py | 13 ------------- .../purchase_receipt/test_purchase_receipt.py | 11 +---------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 0d15338c37c..eb84fdbc7c0 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -252,24 +252,11 @@ class LandedCostVoucher(Document): doc.docstatus = 1 doc.make_bundle_using_old_serial_batch_fields(via_landed_cost_voucher=True) doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) -<<<<<<< HEAD -<<<<<<< HEAD - doc.make_gl_entries() - doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) -======= - doc.make_gl_entries(via_landed_cost_voucher=True) -======= if d.receipt_document_type == "Purchase Receipt": doc.make_gl_entries(via_landed_cost_voucher=True) else: doc.make_gl_entries() -<<<<<<< HEAD ->>>>>>> 8b3d46610e (fix: parameters for PI references) - doc.repost_future_sle_and_gle() ->>>>>>> baa3fee1bf (fix: add LCV flag to determine negative expenses) -======= doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) ->>>>>>> 54a58e9205 (chore: resolve conflicts) def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): for item in self.get("items"): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index a7a54f4f5c2..42747818d2b 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2426,7 +2426,6 @@ class TestPurchaseReceipt(FrappeTestCase): pr.reload() self.assertEqual(pr.per_billed, 100) -<<<<<<< HEAD def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self): batch_item = make_item( "_Test Purchase Receipt Batch Item For Rejected Qty", @@ -2913,7 +2912,7 @@ class TestPurchaseReceipt(FrappeTestCase): "Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty" ), ) -======= + def test_valuation_taxes_lcv_repost_after_billing(self): from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( make_landed_cost_voucher, @@ -2960,15 +2959,7 @@ class TestPurchaseReceipt(FrappeTestCase): (warehouse_account[pr.items[0].warehouse]["account"], 14000, 0, "Main - _TC"), ) self.assertSequenceEqual(expected_gle, gl_entries) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 53642e7417 (test: LCV entries after billing) -======= - frappe.db.rollback() ->>>>>>> 8b3d46610e (fix: parameters for PI references) -======= frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory ->>>>>>> 0b36cbe307 (fix: reset perpetual inventory flag after test) def prepare_data_for_internal_transfer(): From b64b4330a34b14b6d544cc18fb0bab8a5c622ce3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Jun 2024 10:15:54 +0530 Subject: [PATCH 100/203] fix: incorrect batch return from sales return (cherry picked from commit 317047c8085a253f41741a18d8a4496ffdebe972) --- .../controllers/sales_and_purchase_return.py | 3 + .../delivery_note/test_delivery_note.py | 104 ++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 3414b5b2520..b61d1773a67 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -883,6 +883,9 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids): if frappe.get_cached_value(row.voucher_type, row.voucher_no, "is_return"): key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field) + if row.voucher_type in ["Sales Invoice", "Delivery Note"]: + row.qty = -1 * row.qty + if key not in available_dict: available_dict[key] = frappe._dict( {"qty": 0.0, "serial_nos": defaultdict(float), "batches": defaultdict(float)} diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 5ebcc795fa0..f24c912e362 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1814,6 +1814,110 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(sle_data.actual_qty, 1 * -1) self.assertEqual(sle_data.stock_value_difference, 200.0 * -1) + def test_sales_return_batch_no_for_batched_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Batched Item for Sales Return 11", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + def test_partial_sales_return_batch_no_for_batched_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Partial Batched Item for Sales Return 11", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BPART11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.items[0].qty = 3 * -1 + dn_return.save().submit() + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + sabb_qty = frappe.db.get_value( + "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" + ) + self.assertEqual(sabb_qty, 3) + + dn_return = make_sales_return(dn.name) + dn_return.items[0].qty = 2 * -1 + dn_return.save().submit() + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + sabb_qty = frappe.db.get_value( + "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" + ) + self.assertEqual(sabb_qty, 2) + + def test_sales_return_serial_no_for_serial_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Serial Item for Sales Return 11", + properties={ + "has_serial_no": 1, + "serial_no_series": "SNN11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + serial_no=serial_nos, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + returned_serial_nos = get_serial_nos_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(serial_nos, returned_serial_nos) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From 4551b4b63820852fd23b76fe98defb46f52d8039 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Fri, 14 Jun 2024 12:39:45 +0530 Subject: [PATCH 101/203] fix: update received qty in MR on purchase invoice submit with update stock (cherry picked from commit 13cb53fa899104ee519c13fe4289e9ea0baf0581) # Conflicts: # erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json --- .../purchase_invoice/purchase_invoice.py | 13 +++++++++ .../purchase_invoice_item.json | 28 +++++++++++++++++++ .../purchase_invoice_item.py | 2 ++ .../doctype/purchase_order/purchase_order.py | 2 ++ 4 files changed, 45 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index df54a63b661..fba60cef632 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -683,6 +683,19 @@ class PurchaseInvoice(BuyingController): where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""", } ) + self.status_updater.append( + { + "source_dt": "Purchase Invoice Item", + "target_dt": "Material Request Item", + "join_field": "material_request_item", + "target_field": "received_qty", + "target_parent_dt": "Material Request", + "target_parent_field": "per_received", + "target_ref_field": "stock_qty", + "source_field": "stock_qty", + "percent_join_field": "material_request", + } + ) if cint(self.is_return): self.status_updater.append( { diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 66df76a3af0..d783478a2cc 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -105,6 +105,8 @@ "purchase_receipt", "pr_detail", "sales_invoice_item", + "material_request", + "material_request_item", "item_weight_details", "weight_per_unit", "total_weight", @@ -934,12 +936,38 @@ { "fieldname": "column_break_vbbb", "fieldtype": "Column Break" + }, + { + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "no_copy": 1, + "options": "Material Request", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "material_request_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Material Request Item", + "no_copy": 1, + "oldfieldname": "pr_detail", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "search_index": 1 } ], "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2024-03-19 19:09:47.210965", +======= + "modified": "2024-06-14 11:57:07.171700", +>>>>>>> 13cb53fa89 (fix: update received qty in MR on purchase invoice submit with update stock) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py index ccbc34749d7..baeece4815c 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py @@ -52,6 +52,8 @@ class PurchaseInvoiceItem(Document): manufacturer_part_no: DF.Data | None margin_rate_or_amount: DF.Float margin_type: DF.Literal["", "Percentage", "Amount"] + material_request: DF.Link | None + material_request_item: DF.Data | None net_amount: DF.Currency net_rate: DF.Currency page_break: DF.Check diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index d1f19841ac5..a280724193d 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -794,6 +794,8 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions "field_map": { "name": "po_detail", "parent": "purchase_order", + "material_request": "material_request", + "material_request_item": "material_request_item", "wip_composite_asset": "wip_composite_asset", }, "postprocess": update_item, From 125d97a182e2e8feebf1bbfc64d9b81234034507 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Fri, 14 Jun 2024 16:44:26 +0530 Subject: [PATCH 102/203] fix: add test case for update received qty in MR (cherry picked from commit ebb0cef601e29cdaca3b5698d624bafbc6a8afcc) --- .../purchase_invoice/test_purchase_invoice.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 3ea88f195d8..8abb089f56a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -31,6 +31,8 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle ) from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction from erpnext.stock.tests.test_utils import StockTestMixin +from erpnext.stock.doctype.material_request.test_material_request import make_material_request +from erpnext.stock.doctype.material_request.material_request import make_purchase_order test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_ignore = ["Serial No"] @@ -72,6 +74,32 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): # teardown pi.delete() + def test_update_received_qty_in_material_request(self): + from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice + """ + Test if the received_qty in Material Request is updated correctly when + a Purchase Invoice with update_stock=True is submitted. + """ + mr = make_material_request(item_code="_Test Item", qty=10) + mr.save() + mr.submit() + frappe.db.commit() + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.save() + po.submit() + + # Create a Purchase Invoice with update_stock=True + pi = make_purchase_invoice(po.name) + pi.update_stock = True + pi.insert() + pi.submit() + frappe.db.commit() + + # Check if the received quantity is updated in Material Request + mr.reload() + self.assertEqual(mr.items[0].received_qty, 10) + def test_gl_entries_without_perpetual_inventory(self): frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC") pi = frappe.copy_doc(test_records[0]) From aaee02835b4104bfc79771a877d1da1b7b3f1543 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Fri, 14 Jun 2024 16:46:23 +0530 Subject: [PATCH 103/203] fix: add test case for update received qty in MR (cherry picked from commit cff105015901af0f5b18497e8110ae394d49f1c6) --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 8abb089f56a..cd3eda99b07 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -83,7 +83,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): mr = make_material_request(item_code="_Test Item", qty=10) mr.save() mr.submit() - frappe.db.commit() po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" po.save() @@ -94,7 +93,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): pi.update_stock = True pi.insert() pi.submit() - frappe.db.commit() # Check if the received quantity is updated in Material Request mr.reload() From 858058c9e1ba457af9a10db041756ad91f7c53ab Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Fri, 14 Jun 2024 16:47:30 +0530 Subject: [PATCH 104/203] fix: add test case for update received qty in MR (cherry picked from commit 62e4e88f1d60cdd6865b77df41a0bba9c0a4f8eb) --- .../doctype/purchase_invoice/test_purchase_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index cd3eda99b07..179d580298e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -17,6 +17,8 @@ from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.exceptions import InvalidCurrency from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.material_request.test_material_request import make_material_request +from erpnext.stock.doctype.material_request.material_request import make_purchase_order from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as create_purchase_invoice_from_receipt, ) @@ -31,8 +33,6 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle ) from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction from erpnext.stock.tests.test_utils import StockTestMixin -from erpnext.stock.doctype.material_request.test_material_request import make_material_request -from erpnext.stock.doctype.material_request.material_request import make_purchase_order test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_ignore = ["Serial No"] From 21596c6c23a66e6bcf976b368082c1384b831ccf Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Fri, 14 Jun 2024 16:50:04 +0530 Subject: [PATCH 105/203] fix: add test case for update received qty in MR - import sorter (cherry picked from commit 73f22ba9a00eeffc444a23fb9b0fa80c05741e24) --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 179d580298e..a7e30e2eb9e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -17,8 +17,8 @@ from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.exceptions import InvalidCurrency from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import create_item -from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.material_request.material_request import make_purchase_order +from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as create_purchase_invoice_from_receipt, ) From 084658e8ff6c5decea94baf7babda52603301223 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Fri, 14 Jun 2024 16:50:49 +0530 Subject: [PATCH 106/203] fix: add test case for update received qty in MR - import sorter (cherry picked from commit 77959596db8a2002e54224e6ad1bf1a5fc9f7e6c) --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index a7e30e2eb9e..3c3b081fa2f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -76,6 +76,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): def test_update_received_qty_in_material_request(self): from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice + """ Test if the received_qty in Material Request is updated correctly when a Purchase Invoice with update_stock=True is submitted. From 7e9f6aee58459b6894678e999ba5d373d1b2c14b Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:39:42 +0530 Subject: [PATCH 107/203] fix: update received qty in MR on purchase invoice submit with update stock --- .../purchase_invoice_item/purchase_invoice_item.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index d783478a2cc..1d7b0c2f461 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -963,11 +963,7 @@ "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-03-19 19:09:47.210965", -======= "modified": "2024-06-14 11:57:07.171700", ->>>>>>> 13cb53fa89 (fix: update received qty in MR on purchase invoice submit with update stock) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -977,4 +973,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From c159e3b06f86b16cbf6ff8b2e28060451e1454f0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Jun 2024 12:48:33 +0530 Subject: [PATCH 108/203] fix: duplicate rows fetching in RFQ (cherry picked from commit 555510c128a2f13181c4b27475bf7326f232ab34) --- erpnext/public/js/utils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index fcb541f71a8..a7d88edcafa 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -933,7 +933,13 @@ erpnext.utils.map_current_doc = function (opts) { frappe.msgprint(__("Please select {0}", [opts.source_doctype])); return; } - opts.source_name = values; + + if (values.constructor === Array) { + opts.source_name = [...new Set(values)]; + } else { + opts.source_name = values; + } + if ( opts.allow_child_item_selection || ["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype) From 98c8feded034f5d23e939bb53aed2935ddac5179 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Mon, 17 Jun 2024 12:33:14 +0530 Subject: [PATCH 109/203] fix: item_code filter in item-wise sales-purchase register (cherry picked from commit 757aef397235707b2a6341c9c0a11070fa647842) --- .../item_wise_purchase_register.py | 5 ++++- .../item_wise_sales_register/item_wise_sales_register.js | 6 ++++++ .../item_wise_sales_register/item_wise_sales_register.py | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 80c246cad55..a75473b165d 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 @@ -289,7 +289,7 @@ def get_columns(additional_table_columns, filters): def apply_conditions(query, pi, pii, filters): - for opts in ("company", "supplier", "item_code", "mode_of_payment"): + for opts in ("company", "supplier", "mode_of_payment"): if filters.get(opts): query = query.where(pi[opts] == filters[opts]) @@ -299,6 +299,9 @@ def apply_conditions(query, pi, pii, filters): if filters.get("to_date"): query = query.where(pi.posting_date <= filters.get("to_date")) + if filters.get("item_code"): + query = query.where(pii.item_code == filters.get("item_code")) + if filters.get("item_group"): query = query.where(pii.item_group == filters.get("item_group")) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index 1f155de63a0..16a97733393 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -54,6 +54,12 @@ frappe.query_reports["Item-wise Sales Register"] = { fieldtype: "Link", options: "Brand", }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + options: "Item", + }, { fieldname: "item_group", label: __("Item Group"), diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index cd50b118715..cf08e45c537 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -342,7 +342,7 @@ def get_columns(additional_table_columns, filters): def apply_conditions(query, si, sii, filters, additional_conditions=None): - for opts in ("company", "customer", "item_code"): + for opts in ("company", "customer"): if filters.get(opts): query = query.where(si[opts] == filters[opts]) @@ -371,6 +371,9 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): if filters.get("brand"): query = query.where(sii.brand == filters.get("brand")) + if filters.get("item_code"): + query = query.where(sii.item_code == filters.get("item_code")) + if filters.get("item_group"): query = query.where(sii.item_group == filters.get("item_group")) From a43c181673d57fa095e5dd3c6e027628089353c8 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 17 Jun 2024 14:30:31 +0530 Subject: [PATCH 110/203] fix: consistent query field name in item wise purchase register with item wise sales register (cherry picked from commit 6d539e0fc068a8386a8aad30d146765185d25ba3) --- .../item_wise_purchase_register/item_wise_purchase_register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a75473b165d..1cd9b872a9b 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 @@ -325,7 +325,7 @@ def get_items(filters, additional_table_columns): .left_join(Item) .on(pii.item_code == Item.name) .select( - pii.name.as_("pii_name"), + pii.name, pii.parent, pi.posting_date, pi.credit_to, From ceafa0ce282cc41cb00a9c8f302a87166ea03595 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 17 Jun 2024 12:32:17 +0530 Subject: [PATCH 111/203] fix: handle NoneType error where customer details are not available in sales register (cherry picked from commit f876fec6f5602744dd1b879ec7abe4c039e162a8) --- erpnext/accounts/report/sales_register/sales_register.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f27569531b1..6c0bf91e3f8 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -80,6 +80,7 @@ def _execute(filters, additional_table_columns=None): delivery_note = list(set(invoice_so_dn_map.get(inv.name, {}).get("delivery_note", []))) cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", []))) warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", []))) + inv_customer_details = customer_details.get(inv.customer, {}) row = { "voucher_type": inv.doctype, @@ -88,9 +89,9 @@ def _execute(filters, additional_table_columns=None): "customer": inv.customer, "customer_name": inv.customer_name, **get_values_for_columns(additional_table_columns, inv), - "customer_group": customer_details.get(inv.customer).get("customer_group"), - "territory": customer_details.get(inv.customer).get("territory"), - "tax_id": customer_details.get(inv.customer).get("tax_id"), + "customer_group": inv_customer_details.get("customer_group"), + "territory": inv_customer_details.get("territory"), + "tax_id": inv_customer_details.get("tax_id"), "receivable_account": inv.debit_to, "mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])), "project": inv.project, From f4852d472a6eba10db722ef8ef0d73ec8c5a2931 Mon Sep 17 00:00:00 2001 From: mahsem <137205921+mahsem@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:40:08 +0200 Subject: [PATCH 112/203] fix: add strings for translation in pos_item_cart.js fix: add strings for translation in pos_item_cart.js (cherry picked from commit 2fb3e5da4a252061d566d025447cf47572371282) --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index fbee9c16267..694f70d4db5 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -857,7 +857,7 @@ erpnext.PointOfSale.ItemCart = class { }); this.$customer_section.find(".customer-details").html( `
-
Contact Details
+
${__("Contact Details")}
@@ -877,7 +877,7 @@ erpnext.PointOfSale.ItemCart = class {
-
Recent Transactions
` +
${__("Recent Transactions")}
` ); // transactions need to be in diff div from sticky elem for scrolling this.$customer_section.append(`
`); From 794ade832f66d9e347d5dd7ab9c0c48eef5aea86 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Jun 2024 16:51:46 +0530 Subject: [PATCH 113/203] fix: reposting validation related PCV (cherry picked from commit 194f1dc67431c4b823289c9603de51d78a40dda8) --- .../repost_item_valuation.py | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index d8b44765251..40767704f4e 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -77,7 +77,7 @@ class RepostItemValuation(Document): def validate_period_closing_voucher(self): # Period Closing Voucher - year_end_date = self.get_max_year_end_date(self.company) + year_end_date = self.get_max_period_closing_date(self.company) if year_end_date and getdate(self.posting_date) <= getdate(year_end_date): date = frappe.format(year_end_date, "Date") msg = f"Due to period closing, you cannot repost item valuation before {date}" @@ -120,24 +120,16 @@ class RepostItemValuation(Document): return frappe.get_all("Closing Stock Balance", fields=["name", "to_date"], filters=filters) @staticmethod - def get_max_year_end_date(company): - data = frappe.get_all( - "Period Closing Voucher", fields=["fiscal_year"], filters={"docstatus": 1, "company": company} - ) - - if not data: - return - - fiscal_years = [d.fiscal_year for d in data] - table = frappe.qb.DocType("Fiscal Year") + def get_max_period_closing_date(company): + table = frappe.qb.DocType("Period Closing Voucher") query = ( frappe.qb.from_(table) - .select(Max(table.year_end_date)) - .where((table.name.isin(fiscal_years)) & (table.disabled == 0)) + .select(Max(table.posting_date)) + .where((table.company == company) & (table.docstatus == 1)) ).run() - return query[0][0] if query else None + return query[0][0] if query and query[0][0] else None def validate_accounts_freeze(self): acc_settings = frappe.db.get_value( From 7cc5579ecaae99336b8164e7aeedc15b990d2257 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 14 Jun 2024 13:13:11 +0530 Subject: [PATCH 114/203] refactor: ignore unreconcile doc for PO and SO on cancel/delete (cherry picked from commit b618d685c62837657fe808d6c30dc3582a12d0ec) --- erpnext/buying/doctype/purchase_order/purchase_order.js | 1 + erpnext/buying/doctype/purchase_order/purchase_order.py | 8 +++++++- erpnext/selling/doctype/sales_order/sales_order.js | 6 +++++- erpnext/selling/doctype/sales_order/sales_order.py | 8 +++++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index cf383021b06..bd92ebef3d7 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -12,6 +12,7 @@ erpnext.buying.setup_buying_controller(); frappe.ui.form.on("Purchase Order", { setup: function (frm) { + frm.ignore_doctypes_on_cancel_all = ["Unreconcile Payment", "Unreconcile Payment Entries"]; if (frm.doc.is_old_subcontracting_flow) { frm.set_query("reserve_warehouse", "supplied_items", function () { return { diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index a280724193d..0508483a0fc 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -484,7 +484,13 @@ class PurchaseOrder(BuyingController): self.auto_create_subcontracting_order() def on_cancel(self): - self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry") + self.ignore_linked_doctypes = ( + "GL Entry", + "Payment Ledger Entry", + "Unreconcile Payment", + "Unreconcile Payment Entries", + ) + super().on_cancel() if self.is_against_so(): diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index d903f7eedf5..5885d092cce 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -217,7 +217,11 @@ frappe.ui.form.on("Sales Order", { frm.set_value("advance_paid", 0); } - frm.ignore_doctypes_on_cancel_all = ["Purchase Order"]; + frm.ignore_doctypes_on_cancel_all = [ + "Purchase Order", + "Unreconcile Payment", + "Unreconcile Payment Entries", + ]; }, delivery_date: function (frm) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e5e67ed38b7..af67f07a360 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -421,7 +421,13 @@ class SalesOrder(SellingController): self.create_stock_reservation_entries() def on_cancel(self): - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry") + self.ignore_linked_doctypes = ( + "GL Entry", + "Stock Ledger Entry", + "Payment Ledger Entry", + "Unreconcile Payment", + "Unreconcile Payment Entries", + ) super().on_cancel() # Cannot cancel closed SO From 5663432bd838e7ef2673c7e4cede6f014a4fff1a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 12 May 2024 23:01:19 +0200 Subject: [PATCH 115/203] fix: migrate lead notes (cherry picked from commit 382d0ff45300fe6e92d1c535b8e6126d5e4057c3) --- .../migrate_existing_lead_notes_as_per_the_new_format.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py index ec72527552c..d740a5b9c8e 100644 --- a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py +++ b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py @@ -9,15 +9,13 @@ def execute(): dt = frappe.qb.DocType(doctype) records = ( - frappe.qb.from_(dt) - .select(dt.name, dt.notes, dt.modified_by, dt.modified) - .where(dt.notes.isnotnull() & dt.notes != "") + frappe.qb.from_(dt).select(dt.name, dt.notes).where(dt.notes.isnotnull() & dt.notes != "") ).run(as_dict=True) for d in records: if strip_html(cstr(d.notes)).strip(): doc = frappe.get_doc(doctype, d.name) - doc.append("notes", {"note": d.notes, "added_by": d.modified_by, "added_on": d.modified}) + doc.append("notes", {"note": d.notes}) doc.update_child_table("notes") frappe.db.sql_ddl(f"alter table `tab{doctype}` drop column `notes`") From 24ee765e6a3edc7a9e45c249e3ed1cc012cfb549 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:47:02 +0200 Subject: [PATCH 116/203] fix: only show user and date if available (cherry picked from commit 895aede5907cd949ae4d93521705e3144e31dfd5) --- erpnext/public/js/templates/crm_notes.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/js/templates/crm_notes.html b/erpnext/public/js/templates/crm_notes.html index 53df9330784..a20e6c2723c 100644 --- a/erpnext/public/js/templates/crm_notes.html +++ b/erpnext/public/js/templates/crm_notes.html @@ -12,6 +12,7 @@ {% for(var i=0, l=notes.length; i
+ {% if (notes[i].added_by && notes[i].added_on) %}
{{ frappe.avatar(notes[i].added_by) }} @@ -25,6 +26,7 @@
+ {% } %}
{{ notes[i].note }} From cd1db37c74b542c7bfd148fa181237d2d5ea689b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Jun 2024 11:25:02 +0530 Subject: [PATCH 117/203] chore: fix linter --- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 37756533f44..42747818d2b 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2961,6 +2961,7 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertSequenceEqual(expected_gle, gl_entries) frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier from erpnext.selling.doctype.customer.test_customer import create_internal_customer From fa6a479ed9a377681ba0e81bb9f478b56e1751d5 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 19 Jun 2024 06:28:10 +0000 Subject: [PATCH 118/203] chore(release): Bumped to Version 15.27.7 ## [15.27.7](https://github.com/frappe/erpnext/compare/v15.27.6...v15.27.7) (2024-06-19) ### Bug Fixes * add LCV flag to determine negative expenses ([83ccb32](https://github.com/frappe/erpnext/commit/83ccb32be68c55b050e6b1993da2d9a8af5d7c67)) * add strings for translation in pos_item_cart.js ([f4852d4](https://github.com/frappe/erpnext/commit/f4852d472a6eba10db722ef8ef0d73ec8c5a2931)) * add test case for update received qty in MR ([858058c](https://github.com/frappe/erpnext/commit/858058c9e1ba457af9a10db041756ad91f7c53ab)) * add test case for update received qty in MR ([aaee028](https://github.com/frappe/erpnext/commit/aaee02835b4104bfc79771a877d1da1b7b3f1543)) * add test case for update received qty in MR ([125d97a](https://github.com/frappe/erpnext/commit/125d97a182e2e8feebf1bbfc64d9b81234034507)) * add test case for update received qty in MR - import sorter ([084658e](https://github.com/frappe/erpnext/commit/084658e8ff6c5decea94baf7babda52603301223)) * add test case for update received qty in MR - import sorter ([21596c6](https://github.com/frappe/erpnext/commit/21596c6c23a66e6bcf976b368082c1384b831ccf)) * allow Employee role to select Department (backport [#41877](https://github.com/frappe/erpnext/issues/41877)) ([#41881](https://github.com/frappe/erpnext/issues/41881)) ([3bc5076](https://github.com/frappe/erpnext/commit/3bc5076dfc6e9c0d100c778cdee27e0f5fc2c4bf)) * batch qty in the dropdown field ([67d1709](https://github.com/frappe/erpnext/commit/67d1709ef8e477d39c4d95e6bfb00128e570faa1)) * consistent query field name in item wise purchase register with item wise sales register ([a43c181](https://github.com/frappe/erpnext/commit/a43c181673d57fa095e5dd3c6e027628089353c8)) * download materials request plan in PP ([7193633](https://github.com/frappe/erpnext/commit/7193633773237105e7f461e2e589645a2bc8eab6)) * download materials request plan in PP ([ef2a6c7](https://github.com/frappe/erpnext/commit/ef2a6c788ee60e33798469bc7e8de436c6d4c2c5)) * download materials request plan in PP -prettier ([3370825](https://github.com/frappe/erpnext/commit/337082512e28bacaa292085b64dde7f5b2ec8526)) * download materials request plan in production plan ([9cb7c27](https://github.com/frappe/erpnext/commit/9cb7c279a47a2100569983ba9d938d219704f89e)) * duplicate rows fetching in RFQ ([c159e3b](https://github.com/frappe/erpnext/commit/c159e3b06f86b16cbf6ff8b2e28060451e1454f0)) * handle NoneType error where customer details are not available in sales register ([ceafa0c](https://github.com/frappe/erpnext/commit/ceafa0ce282cc41cb00a9c8f302a87166ea03595)) * incorrect batch return from sales return ([b64b433](https://github.com/frappe/erpnext/commit/b64b4330a34b14b6d544cc18fb0bab8a5c622ce3)) * item_code filter in item-wise sales-purchase register ([98c8fed](https://github.com/frappe/erpnext/commit/98c8feded034f5d23e939bb53aed2935ddac5179)) * migrate lead notes ([5663432](https://github.com/frappe/erpnext/commit/5663432bd838e7ef2673c7e4cede6f014a4fff1a)) * only show user and date if available ([24ee765](https://github.com/frappe/erpnext/commit/24ee765e6a3edc7a9e45c249e3ed1cc012cfb549)) * parameters for PI references ([8618402](https://github.com/frappe/erpnext/commit/8618402829d9553ddc0c7d7ac41d86147ab2a84d)) * regional overide for updating item valution ([5ea2cdc](https://github.com/frappe/erpnext/commit/5ea2cdcaad1787fbbbb1da297012f0ceec5d2e62)) * reposting validation related PCV ([794ade8](https://github.com/frappe/erpnext/commit/794ade832f66d9e347d5dd7ab9c0c48eef5aea86)) * reset perpetual inventory flag after test ([4c11f2b](https://github.com/frappe/erpnext/commit/4c11f2bff23102eff34146baa15521218e31b77a)) * return from accepted and rejected warehouse at a same time not working ([6c57971](https://github.com/frappe/erpnext/commit/6c57971abc12fd08ca3986f10c8b78d089cbb3c4)) * update received qty in MR on purchase invoice submit with update stock ([7e9f6ae](https://github.com/frappe/erpnext/commit/7e9f6aee58459b6894678e999ba5d373d1b2c14b)) * update received qty in MR on purchase invoice submit with update stock ([4551b4b](https://github.com/frappe/erpnext/commit/4551b4b63820852fd23b76fe98defb46f52d8039)) * use invoice outstanding on Dunning ([#41817](https://github.com/frappe/erpnext/issues/41817)) ([04f0c96](https://github.com/frappe/erpnext/commit/04f0c96af782d5690124f49782efedafcebddca0)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 61ed40e7e23..9d0dfd8d741 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.6" +__version__ = "15.27.7" def get_default_company(user=None): From c10b123a817ba0e1285a607c8514b57747d0ebf5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:04:58 +0530 Subject: [PATCH 119/203] fix: do not show zero balance stock items in stock balance report (backport #41958) (#41961) fix: do not show zero balance stock in stock balance (cherry picked from commit 7f7b363d487983ee396c4921b4047074551ec47a) Co-authored-by: Rohit Waghchaure --- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 64ad36ff5b1..27d9f1164bc 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -137,6 +137,10 @@ class StockBalanceReport: report_data.update( {"reserved_stock": sre_details.get((report_data.item_code, report_data.warehouse), 0.0)} ) + + if report_data and report_data.bal_qty == 0 and report_data.bal_val == 0: + continue + self.data.append(report_data) def get_item_warehouse_map(self): From 48dc24b9bf65e788bffc700f4002f66cef9bcfed Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:05:39 +0530 Subject: [PATCH 120/203] fix: add string for translation (backport #41903) (#41963) fix: add string for translation (#41903) fix: add string for translation (cherry picked from commit f28c692dca5db94460de8828a3f5d6faee9aed05) Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> --- erpnext/stock/doctype/stock_settings/stock_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js index 0443f3f1ece..79638590f9b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.js +++ b/erpnext/stock/doctype/stock_settings/stock_settings.js @@ -41,7 +41,7 @@ frappe.ui.form.on("Stock Settings", { msg += " "; msg += __("This is considered dangerous from accounting point of view."); msg += "
"; - msg += "Do you still want to enable negative inventory?"; + msg += __("Do you still want to enable negative inventory?"); frappe.confirm( msg, From 9bad219f0a755898a798569f90e6b162317f7912 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:28:24 +0200 Subject: [PATCH 121/203] refactor: remove use of can_create for Payment Request (#41647) (cherry picked from commit 47bc5691a1579b88e0a430f6fbe5ff6309486ccf) --- erpnext/selling/doctype/sales_order/sales_order.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 361c8550597..e732dfd42d5 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -774,13 +774,11 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex flt(doc.per_billed, precision("per_billed", doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance ) { - if (frappe.model.can_create("Payment Request")) { - this.frm.add_custom_button( - __("Payment Request"), - () => this.make_payment_request(), - __("Create") - ); - } + this.frm.add_custom_button( + __("Payment Request"), + () => this.make_payment_request(), + __("Create") + ); if (frappe.model.can_create("Payment Entry")) { this.frm.add_custom_button( From 21802396ce0659de9097074916b3144ce576171e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:32:01 +0200 Subject: [PATCH 122/203] fix: move condition for shipment --- erpnext/stock/doctype/delivery_note/delivery_note.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 06881c99c12..3352b53343a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -188,8 +188,8 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } - if (!doc.is_return && doc.status != "Closed" && frappe.model.can_create("Shipment")) { - if (doc.docstatus == 1) { + if (!doc.is_return && doc.status != "Closed") { + if (doc.docstatus == 1 && frappe.model.can_create("Shipment")) { this.frm.add_custom_button( __("Shipment"), function () { From 77f4199e2ad4b1b7b2b4122a67affcc2d845a80b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 20 Jun 2024 12:00:25 +0530 Subject: [PATCH 123/203] fix: incorrect discount on other item When discount is applied on other item, don't update `discount_amount` as the amount is calculated for current item (cherry picked from commit 654764e398a8315a7eeb056f616b939ac98b6646) --- erpnext/public/js/controllers/transaction.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 524d3033579..950914953aa 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1733,6 +1733,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe me.frm.doc.items.forEach(d => { if (in_list(JSON.parse(data.apply_rule_on_other_items), d[data.apply_rule_on])) { for(var k in data) { + if (data.pricing_rule_for == "Discount Percentage" && data.apply_rule_on_other_items && k == "discount_amount") { + continue; + } + if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'Price' || k === 'pricing_rules')) { frappe.model.set_value(d.doctype, d.name, k, data[k]); } From a41577a1cd901f7c66256f9bb877af1ddc610913 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 21 Jun 2024 17:21:32 +0530 Subject: [PATCH 124/203] fix: incorrect against_account upon reposting (cherry picked from commit 20c4098399d5cb276d373074036a738e6cee67b0) --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 6 +++++- .../repost_accounting_ledger/repost_accounting_ledger.py | 4 ++++ erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fba60cef632..dce742d1021 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -593,7 +593,7 @@ class PurchaseInvoice(BuyingController): for item in self.get("items"): validate_account_head(item.idx, item.expense_account, self.company, "Expense") - def set_against_expense_account(self): + def set_against_expense_account(self, force=False): against_accounts = [] for item in self.get("items"): if item.expense_account and (item.expense_account not in against_accounts): @@ -601,6 +601,10 @@ class PurchaseInvoice(BuyingController): self.against_expense_account = ",".join(against_accounts) + def force_set_against_expense_account(self): + self.set_against_expense_account() + frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account) + def po_required(self): if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes": if frappe.get_value( diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 6bc19222c98..8c8ba633df0 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -167,6 +167,10 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries_on_cancel() doc.docstatus = 1 + if doc.doctype == "Sales Invoice": + doc.force_set_against_income_account() + else: + doc.force_set_against_expense_account() doc.make_gl_entries() elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]: diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index cb0803db932..babcb417f23 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -943,6 +943,10 @@ class SalesInvoice(SellingController): against_acc.append(d.income_account) self.against_income_account = ",".join(against_acc) + def force_set_against_income_account(self): + self.set_against_income_account() + frappe.db.set_value(self.doctype, self.name, "against_income_account", self.against_income_account) + def add_remarks(self): if not self.remarks: if self.po_no and self.po_date: From 9945a90b3fdfdbe95c7205c7228372d3b47e0ce6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:26:05 +0530 Subject: [PATCH 125/203] fix: decimal issue in pick list (backport #41972) (#41982) fix: decimal issue in pick list (#41972) (cherry picked from commit 21adc7b63e742389a107d4df6a939ebbf23b196b) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list/pick_list.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 4b5ab3836c5..69a1bdf17d8 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -693,7 +693,8 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus) # if stock qty is zero on submitted entry, show positive remaining qty to recalculate in case of restock. remaining_stock_qty = item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty - while flt(remaining_stock_qty) > 0 and available_locations: + precision = frappe.get_precision("Pick List Item", "qty") + while flt(remaining_stock_qty, precision) > 0 and available_locations: item_location = available_locations.pop(0) item_location = frappe._dict(item_location) @@ -838,6 +839,7 @@ def validate_picked_materials(item_code, required_qty, locations, picked_item_de def filter_locations_by_picked_materials(locations, picked_item_details) -> list[dict]: filterd_locations = [] + precision = frappe.get_precision("Pick List Item", "qty") for row in locations: key = row.warehouse if row.batch_no: @@ -856,7 +858,7 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list if row.serial_nos: row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no"))) - if row.qty > 0: + if flt(row.qty, precision) > 0: filterd_locations.append(row) return filterd_locations From ca343f12d8abcb7441868490249cb29df5f3f6a5 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 28 May 2024 20:37:52 +0530 Subject: [PATCH 126/203] =?UTF-8?q?refactor:=20renamed=20number=20of=20dep?= =?UTF-8?q?reciations=20booked=20to=20opening=20booked=20de=E2=80=A6=20(#4?= =?UTF-8?q?1515)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: renamed number of depreciations booked to opening booked depreciations * feat: introduced new field for showing total number of booked depreciations --- .../doctype/journal_entry/journal_entry.py | 24 ++++++++++++ erpnext/assets/doctype/asset/asset.json | 16 ++++---- erpnext/assets/doctype/asset/asset.py | 27 ++++++++++---- erpnext/assets/doctype/asset/depreciation.py | 1 + erpnext/assets/doctype/asset/test_asset.py | 26 ++++++------- .../asset_depreciation_schedule.json | 6 +-- .../asset_depreciation_schedule.py | 30 ++++++++------- .../test_asset_depreciation_schedule.py | 37 ++++++++++++++++++- .../asset_finance_book.json | 10 ++++- .../asset_finance_book/asset_finance_book.py | 1 + .../doctype/asset_repair/asset_repair.py | 4 +- erpnext/patches.txt | 1 + ...sset_depreciation_schedules_from_assets.py | 2 +- ..._booked_to_opening_booked_depreciations.py | 7 ++++ .../update_gpa_and_ndb_for_assdeprsch.py | 4 +- 15 files changed, 144 insertions(+), 52 deletions(-) create mode 100644 erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 3ec47ff022a..554217ab6b9 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -194,6 +194,7 @@ class JournalEntry(AccountsController): self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() + self.update_booked_depreciation() def on_update_after_submit(self): if hasattr(self, "repost_required"): @@ -225,6 +226,7 @@ class JournalEntry(AccountsController): self.unlink_inter_company_jv() self.unlink_asset_adjustment_entry() self.update_invoice_discounting() + self.update_booked_depreciation() def get_title(self): return self.pay_to_recd_from or self.accounts[0].account @@ -439,6 +441,28 @@ class JournalEntry(AccountsController): if status: inv_disc_doc.set_status(status=status) + def update_booked_depreciation(self): + for d in self.get("accounts"): + if ( + self.voucher_type == "Depreciation Entry" + and d.reference_type == "Asset" + and d.reference_name + and frappe.get_cached_value("Account", d.account, "root_type") == "Expense" + and d.debit + ): + asset = frappe.get_doc("Asset", d.reference_name) + for fb_row in asset.get("finance_books"): + if fb_row.finance_book == self.finance_book: + depr_schedule = get_depr_schedule(asset.name, "Active", fb_row.finance_book) + total_number_of_booked_depreciations = asset.opening_number_of_booked_depreciations + for je in depr_schedule: + if je.journal_entry: + total_number_of_booked_depreciations += 1 + fb_row.db_set( + "total_number_of_booked_depreciations", total_number_of_booked_depreciations + ) + break + def unlink_advance_entry_reference(self): for d in self.get("accounts"): if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"): diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 3a2a942bdf2..99a430cbb40 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -45,7 +45,7 @@ "calculate_depreciation", "column_break_33", "opening_accumulated_depreciation", - "number_of_depreciations_booked", + "opening_number_of_booked_depreciations", "is_fully_depreciated", "section_break_36", "finance_books", @@ -257,12 +257,6 @@ "label": "Opening Accumulated Depreciation", "options": "Company:company:default_currency" }, - { - "depends_on": "eval:(doc.is_existing_asset)", - "fieldname": "number_of_depreciations_booked", - "fieldtype": "Int", - "label": "Number of Depreciations Booked" - }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.calculate_depreciation || doc.is_existing_asset", @@ -546,6 +540,12 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "depends_on": "eval:(doc.is_existing_asset)", + "fieldname": "opening_number_of_booked_depreciations", + "fieldtype": "Int", + "label": "Opening Number of Booked Depreciations" } ], "idx": 72, @@ -589,7 +589,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2024-04-18 16:45:47.306032", + "modified": "2024-05-21 13:46:21.066483", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 063a5447ab5..8641bb33fad 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -89,8 +89,8 @@ class Asset(AccountsController): maintenance_required: DF.Check naming_series: DF.Literal["ACC-ASS-.YYYY.-"] next_depreciation_date: DF.Date | None - number_of_depreciations_booked: DF.Int opening_accumulated_depreciation: DF.Currency + opening_number_of_booked_depreciations: DF.Int policy_number: DF.Data | None purchase_amount: DF.Currency purchase_date: DF.Date | None @@ -145,7 +145,7 @@ class Asset(AccountsController): "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." ).format(asset_depr_schedules_links) ) - + self.set_total_booked_depreciations() self.total_asset_cost = self.gross_purchase_amount self.status = self.get_status() @@ -417,7 +417,7 @@ class Asset(AccountsController): if not self.is_existing_asset: self.opening_accumulated_depreciation = 0 - self.number_of_depreciations_booked = 0 + self.opening_number_of_booked_depreciations = 0 else: depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) if flt(self.opening_accumulated_depreciation) > depreciable_amount: @@ -428,15 +428,15 @@ class Asset(AccountsController): ) if self.opening_accumulated_depreciation: - if not self.number_of_depreciations_booked: - frappe.throw(_("Please set Number of Depreciations Booked")) + if not self.opening_number_of_booked_depreciations: + frappe.throw(_("Please set Opening Number of Booked Depreciations")) else: - self.number_of_depreciations_booked = 0 + self.opening_number_of_booked_depreciations = 0 - if flt(row.total_number_of_depreciations) <= cint(self.number_of_depreciations_booked): + if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations): frappe.throw( _( - "Row {0}: Total Number of Depreciations cannot be less than or equal to Number of Depreciations Booked" + "Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations" ).format(row.idx), title=_("Invalid Schedule"), ) @@ -457,6 +457,17 @@ class Asset(AccountsController): ).format(row.idx) ) + def set_total_booked_depreciations(self): + # set value of total number of booked depreciations field + for fb_row in self.get("finance_books"): + total_number_of_booked_depreciations = self.opening_number_of_booked_depreciations + depr_schedule = get_depr_schedule(self.name, "Active", fb_row.finance_book) + if depr_schedule: + for je in depr_schedule: + if je.journal_entry: + total_number_of_booked_depreciations += 1 + fb_row.db_set("total_number_of_booked_depreciations", total_number_of_booked_depreciations) + def validate_expected_value_after_useful_life(self): for row in self.get("finance_books"): depr_schedule = get_depr_schedule(self.name, "Draft", row.finance_book) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 2c54f55f25c..cc8defc5fe6 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -323,6 +323,7 @@ def _make_journal_entry_for_depreciation( if not je.meta.get_workflow(): je.submit() + asset.reload() idx = cint(asset_depr_schedule_doc.finance_book_id) row = asset.get("finance_books")[idx - 1] row.value_after_depreciation -= depr_schedule.depreciation_amount diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 7e0c3ad6888..07608e7144e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -355,7 +355,7 @@ class TestAsset(AssetSetup): purchase_date="2020-04-01", expected_value_after_useful_life=0, total_number_of_depreciations=5, - number_of_depreciations_booked=2, + opening_number_of_booked_depreciations=2, frequency_of_depreciation=12, depreciation_start_date="2023-03-31", opening_accumulated_depreciation=24000, @@ -453,7 +453,7 @@ class TestAsset(AssetSetup): purchase_date="2020-01-01", expected_value_after_useful_life=0, total_number_of_depreciations=6, - number_of_depreciations_booked=1, + opening_number_of_booked_depreciations=1, frequency_of_depreciation=10, depreciation_start_date="2021-01-01", opening_accumulated_depreciation=20000, @@ -739,7 +739,7 @@ class TestDepreciationMethods(AssetSetup): calculate_depreciation=1, available_for_use_date="2030-06-06", is_existing_asset=1, - number_of_depreciations_booked=2, + opening_number_of_booked_depreciations=2, opening_accumulated_depreciation=47095.89, expected_value_after_useful_life=10000, depreciation_start_date="2032-12-31", @@ -789,7 +789,7 @@ class TestDepreciationMethods(AssetSetup): available_for_use_date="2030-01-01", is_existing_asset=1, depreciation_method="Double Declining Balance", - number_of_depreciations_booked=1, + opening_number_of_booked_depreciations=1, opening_accumulated_depreciation=50000, expected_value_after_useful_life=10000, depreciation_start_date="2031-12-31", @@ -1123,8 +1123,8 @@ class TestDepreciationBasics(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) - def test_number_of_depreciations_booked(self): - """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is.""" + def test_opening_booked_depreciations(self): + """Tests if an error is raised when opening_number_of_booked_depreciations is not specified when opening_accumulated_depreciation is.""" asset = create_asset( item_code="Macbook Pro", @@ -1140,9 +1140,9 @@ class TestDepreciationBasics(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) def test_number_of_depreciations(self): - """Tests if an error is raised when number_of_depreciations_booked >= total_number_of_depreciations.""" + """Tests if an error is raised when opening_number_of_booked_depreciations >= total_number_of_depreciations.""" - # number_of_depreciations_booked > total_number_of_depreciations + # opening_number_of_booked_depreciations > total_number_of_depreciations asset = create_asset( item_code="Macbook Pro", calculate_depreciation=1, @@ -1151,13 +1151,13 @@ class TestDepreciationBasics(AssetSetup): expected_value_after_useful_life=10000, depreciation_start_date="2020-07-01", opening_accumulated_depreciation=10000, - number_of_depreciations_booked=5, + opening_number_of_booked_depreciations=5, do_not_save=1, ) self.assertRaises(frappe.ValidationError, asset.save) - # number_of_depreciations_booked = total_number_of_depreciations + # opening_number_of_booked_depreciations = total_number_of_depreciations asset_2 = create_asset( item_code="Macbook Pro", calculate_depreciation=1, @@ -1166,7 +1166,7 @@ class TestDepreciationBasics(AssetSetup): expected_value_after_useful_life=10000, depreciation_start_date="2020-07-01", opening_accumulated_depreciation=10000, - number_of_depreciations_booked=5, + opening_number_of_booked_depreciations=5, do_not_save=1, ) @@ -1502,7 +1502,7 @@ class TestDepreciationBasics(AssetSetup): asset = create_asset(calculate_depreciation=1) asset.opening_accumulated_depreciation = 2000 - asset.number_of_depreciations_booked = 1 + asset.opening_number_of_booked_depreciations = 1 asset.finance_books[0].expected_value_after_useful_life = 100 asset.save() @@ -1696,7 +1696,7 @@ def create_asset(**args): "purchase_date": args.purchase_date or "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, - "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, + "opening_number_of_booked_depreciations": args.opening_number_of_booked_depreciations or 0, "gross_purchase_amount": args.gross_purchase_amount or 100000, "purchase_amount": args.purchase_amount or 100000, "maintenance_required": args.maintenance_required or 0, diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json index 73838163d3a..36ee7402576 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -13,7 +13,7 @@ "column_break_2", "gross_purchase_amount", "opening_accumulated_depreciation", - "number_of_depreciations_booked", + "opening_number_of_booked_depreciations", "finance_book", "finance_book_id", "depreciation_details_section", @@ -171,10 +171,10 @@ "read_only": 1 }, { - "fieldname": "number_of_depreciations_booked", + "fieldname": "opening_number_of_booked_depreciations", "fieldtype": "Int", "hidden": 1, - "label": "Number of Depreciations Booked", + "label": "Opening Number of Booked Depreciations", "print_hide": 1, "read_only": 1 }, diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index f64e9123dc0..d9fc5b3dd47 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -50,7 +50,7 @@ class AssetDepreciationSchedule(Document): gross_purchase_amount: DF.Currency naming_series: DF.Literal["ACC-ADS-.YYYY.-"] notes: DF.SmallText | None - number_of_depreciations_booked: DF.Int + opening_number_of_booked_depreciations: DF.Int opening_accumulated_depreciation: DF.Currency rate_of_depreciation: DF.Percent shift_based: DF.Check @@ -161,7 +161,7 @@ class AssetDepreciationSchedule(Document): return ( asset_doc.gross_purchase_amount != self.gross_purchase_amount or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation - or asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked + or asset_doc.opening_number_of_booked_depreciations != self.opening_number_of_booked_depreciations ) def not_manual_depr_or_have_manual_depr_details_been_modified(self, row): @@ -194,7 +194,7 @@ class AssetDepreciationSchedule(Document): self.finance_book = row.finance_book self.finance_book_id = row.idx self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0 - self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked or 0 + self.opening_number_of_booked_depreciations = asset_doc.opening_number_of_booked_depreciations or 0 self.gross_purchase_amount = asset_doc.gross_purchase_amount self.depreciation_method = row.depreciation_method self.total_number_of_depreciations = row.total_number_of_depreciations @@ -263,7 +263,7 @@ class AssetDepreciationSchedule(Document): row.db_update() final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint( - self.number_of_depreciations_booked + self.opening_number_of_booked_depreciations ) has_pro_rata = _check_is_pro_rata(asset_doc, row) @@ -328,7 +328,7 @@ class AssetDepreciationSchedule(Document): if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal): from_date = add_months( getdate(asset_doc.available_for_use_date), - (asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation), + (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation), ) if self.depreciation_schedule: from_date = self.depreciation_schedule[-1].schedule_date @@ -378,13 +378,16 @@ class AssetDepreciationSchedule(Document): from_date = get_last_day( add_months( getdate(asset_doc.available_for_use_date), - ((self.number_of_depreciations_booked - 1) * row.frequency_of_depreciation), + ( + (self.opening_number_of_booked_depreciations - 1) + * row.frequency_of_depreciation + ), ) ) else: from_date = add_months( getdate(add_days(asset_doc.available_for_use_date, -1)), - (self.number_of_depreciations_booked * row.frequency_of_depreciation), + (self.opening_number_of_booked_depreciations * row.frequency_of_depreciation), ) depreciation_amount, days, months = _get_pro_rata_amt( row, @@ -400,7 +403,8 @@ class AssetDepreciationSchedule(Document): # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission asset_doc.to_date = add_months( asset_doc.available_for_use_date, - (n + self.number_of_depreciations_booked) * cint(row.frequency_of_depreciation), + (n + self.opening_number_of_booked_depreciations) + * cint(row.frequency_of_depreciation), ) depreciation_amount_without_pro_rata = depreciation_amount @@ -546,7 +550,7 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False): has_pro_rata = False # if not existing asset, from_date = available_for_use_date - # otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12 + # otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12 # from_date = 01/01/2022 from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly) days = date_diff(row.depreciation_start_date, from_date) + 1 @@ -567,12 +571,12 @@ def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=Fa if wdv_or_dd_non_yearly: return add_months( asset_doc.available_for_use_date, - (asset_doc.number_of_depreciations_booked * 12), + (asset_doc.opening_number_of_booked_depreciations * 12), ) else: return add_months( asset_doc.available_for_use_date, - (asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation), + (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation), ) @@ -678,7 +682,7 @@ def get_straight_line_or_manual_depr_amount( flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) + ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations) def get_daily_prorata_based_straight_line_depr( @@ -704,7 +708,7 @@ def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx): flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) + ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations) asset_shift_factors_map = get_asset_shift_factors_map() shift = ( diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index 6e4966ac6cf..6009ac1496c 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -5,6 +5,9 @@ import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import cstr +from erpnext.assets.doctype.asset.depreciation import ( + post_depreciation_entries, +) from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, @@ -28,7 +31,7 @@ class TestAssetDepreciationSchedule(FrappeTestCase): self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert) - def test_daily_prorata_based_depr_on_sl_methond(self): + def test_daily_prorata_based_depr_on_sl_method(self): asset = create_asset( calculate_depreciation=1, depreciation_method="Straight Line", @@ -160,3 +163,35 @@ class TestAssetDepreciationSchedule(FrappeTestCase): for d in get_depr_schedule(asset.name, "Draft") ] self.assertEqual(schedules, expected_schedules) + + def test_update_total_number_of_booked_depreciations(self): + # check if updates total number of booked depreciations when depreciation gets booked + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + opening_accumulated_depreciation=2000, + opening_number_of_booked_depreciations=2, + depreciation_method="Straight Line", + available_for_use_date="2020-03-01", + depreciation_start_date="2020-03-31", + frequency_of_depreciation=1, + total_number_of_depreciations=24, + submit=1, + ) + + post_depreciation_entries(date="2021-03-31") + asset.reload() + """ + opening_number_of_booked_depreciations = 2 + number_of_booked_depreciations till 2021-03-31 = 13 + total_number_of_booked_depreciations = 15 + """ + self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 15) + + # cancel depreciation entry + depr_entry = get_depr_schedule(asset.name, "Active")[0].journal_entry + + frappe.get_doc("Journal Entry", depr_entry).cancel() + asset.reload() + + self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 14) diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index ba5b5f87826..c269948b742 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -8,6 +8,7 @@ "finance_book", "depreciation_method", "total_number_of_depreciations", + "total_number_of_booked_depreciations", "daily_prorata_based", "shift_based", "column_break_5", @@ -104,12 +105,19 @@ "fieldname": "shift_based", "fieldtype": "Check", "label": "Depreciate based on shifts" + }, + { + "default": "0", + "fieldname": "total_number_of_booked_depreciations", + "fieldtype": "Int", + "label": "Total Number of Booked Depreciations ", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-12-29 08:49:39.876439", + "modified": "2024-05-21 15:48:20.907250", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py index f812a0816dd..d06d6355ec3 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py @@ -28,6 +28,7 @@ class AssetFinanceBook(Document): rate_of_depreciation: DF.Percent salvage_value_percentage: DF.Percent shift_based: DF.Check + total_number_of_booked_depreciations: DF.Int total_number_of_depreciations: DF.Int value_after_depreciation: DF.Currency # end: auto-generated types diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 27542bc6de8..ccde836fe0d 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -377,7 +377,7 @@ class AssetRepair(AccountsController): def calculate_last_schedule_date(self, asset, row, extra_months): asset.flags.increase_in_asset_life = True number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset.number_of_depreciations_booked + asset.opening_number_of_booked_depreciations ) depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) @@ -410,7 +410,7 @@ class AssetRepair(AccountsController): def calculate_last_schedule_date_before_modification(self, asset, row, extra_months): asset.flags.increase_in_asset_life = True number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset.number_of_depreciations_booked + asset.opening_number_of_booked_depreciations ) depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2522077e9c3..383989643b7 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -365,3 +365,4 @@ erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 +erpnext.patches.v15_0.rename_number_of_depreciations_booked_to_opening_booked_depreciations diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index c31d754d2cd..a39b3e3cb24 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -43,7 +43,7 @@ def get_details_of_draft_or_submitted_depreciable_assets(): asset.name, asset.opening_accumulated_depreciation, asset.gross_purchase_amount, - asset.number_of_depreciations_booked, + asset.opening_number_of_booked_depreciations, asset.docstatus, ) .where(asset.calculate_depreciation == 1) diff --git a/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py b/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py new file mode 100644 index 00000000000..18183374554 --- /dev/null +++ b/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py @@ -0,0 +1,7 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + if frappe.db.has_column("Asset", "number_of_depreciations_booked"): + rename_field("Asset", "number_of_depreciations_booked", "opening_number_of_booked_depreciations") diff --git a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py index afb59e0f6f5..4399a95fda2 100644 --- a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py +++ b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py @@ -9,12 +9,12 @@ def execute(): ON `tabAsset Depreciation Schedule`.`asset`=`tabAsset`.`name` SET `tabAsset Depreciation Schedule`.`gross_purchase_amount`=`tabAsset`.`gross_purchase_amount`, - `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`=`tabAsset`.`number_of_depreciations_booked` + `tabAsset Depreciation Schedule`.`opening_number_of_booked_depreciations`=`tabAsset`.`opening_number_of_booked_depreciations` WHERE ( `tabAsset Depreciation Schedule`.`gross_purchase_amount`<>`tabAsset`.`gross_purchase_amount` OR - `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`<>`tabAsset`.`number_of_depreciations_booked` + `tabAsset Depreciation Schedule`.`opening_number_of_booked_depreciations`<>`tabAsset`.`opening_number_of_booked_depreciations` ) AND `tabAsset Depreciation Schedule`.`docstatus`<2""" ) From 7b5d5043c5220189240c04f90abb7ffbe75d7cd3 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Sat, 22 Jun 2024 00:57:00 +0530 Subject: [PATCH 127/203] fix: reload asset when creating asset depreciation --- .../v15_0/create_asset_depreciation_schedules_from_assets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index a39b3e3cb24..523b559d734 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -4,6 +4,7 @@ import frappe def execute(): frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule") frappe.reload_doc("assets", "doctype", "Asset Finance Book") + frappe.reload_doc("assets", "doctype", "Asset") assets = get_details_of_draft_or_submitted_depreciable_assets() From f9574366b5ad3e78bb44f01c26e71dc30fd3142b Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Sat, 22 Jun 2024 01:01:03 +0530 Subject: [PATCH 128/203] chore: added nosemgrep for security checks --- erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py index 4399a95fda2..f8cb3e48e7a 100644 --- a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py +++ b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py @@ -3,6 +3,7 @@ import frappe def execute(): # not using frappe.qb because https://github.com/frappe/frappe/issues/20292 + # nosemgrep frappe.db.sql( """UPDATE `tabAsset Depreciation Schedule` JOIN `tabAsset` From 44c16713ba9fdda293d612b10720d63f6a83b0c8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 22 Jun 2024 10:39:53 +0530 Subject: [PATCH 129/203] feat: default account head for operating cost (backport #41985) (#41987) * feat: default account head for operating cost (#41985) (cherry picked from commit fd7666a029a430c0b3cf4d1c9d0b728f4d0e4476) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py # erpnext/setup/doctype/company/company.json * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/manufacturing/doctype/bom/bom.py | 14 +++- .../doctype/work_order/test_work_order.py | 75 +++++++++++++++++++ erpnext/setup/doctype/company/company.js | 6 ++ erpnext/setup/doctype/company/company.json | 19 ++++- erpnext/setup/doctype/company/company.py | 1 + 5 files changed, 108 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 20b9845ef6a..009320c7a18 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1259,12 +1259,18 @@ def get_children(parent=None, is_root=False, **filters): def add_additional_cost(stock_entry, work_order): # Add non stock items cost in the additional cost stock_entry.additional_costs = [] - expenses_included_in_valuation = frappe.get_cached_value( - "Company", work_order.company, "expenses_included_in_valuation" + company_account = frappe.db.get_value( + "Company", + work_order.company, + ["expenses_included_in_valuation", "default_operating_cost_account"], + as_dict=1, ) - add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation) - add_operations_cost(stock_entry, work_order, expenses_included_in_valuation) + expense_account = ( + company_account.default_operating_cost_account or company_account.expenses_included_in_valuation + ) + add_non_stock_items_cost(stock_entry, work_order, expense_account) + add_operations_cost(stock_entry, work_order, expense_account) def add_non_stock_items_cost(stock_entry, work_order, expense_account): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 14a56f21afb..d1f2cdbd1aa 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1748,6 +1748,81 @@ class TestWorkOrder(FrappeTestCase): job_card2.time_logs = [] job_card2.save() + def test_operating_cost_account(self): + operating_cost_account = "Test Operating Cost Account - _TC" + company = "_Test Company" + if not frappe.db.exists("Account", operating_cost_account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Test Operating Cost Account", + "account_type": "Expense Account", + "company": company, + "parent_account": "Expenses - _TC", + "root_type": "Expense", + } + ).insert() + + frappe.db.set_value("Company", company, "default_operating_cost_account", operating_cost_account) + + for item in ["TEST RM OP COST Item 1", "TEST FG OP COST Item"]: + if not frappe.db.exists("Item", item): + make_item(item_code=item, properties={"is_stock_item": 1}) + + fg_item = "TEST FG OP COST Item" + bom_doc = make_bom( + item=fg_item, + raw_materials=["TEST RM OP COST Item 1"], + rate=150, + with_operations=1, + do_not_save=True, + ) + + workstation = "Test Workstation For Capacity Planning 1" + if not frappe.db.exists("Workstation", workstation): + make_workstation(workstation=workstation, production_capacity=1) + + operation = "Test Operation For Capacity Planning 1" + if not frappe.db.exists("Operation", operation): + make_operation(operation=operation, workstation=workstation) + + bom_doc.append( + "operations", + {"operation": operation, "time_in_mins": 60, "hour_rate": 100, "workstation": workstation}, + ) + + bom_doc.save() + bom_doc.submit() + + wo = make_wo_order_test_record( + production_item=fg_item, + bom_no=bom_doc.name, + qty=1, + skip_transfer=1, + ) + + job_cards = frappe.get_all("Job Card", filters={"work_order": wo.name}) + for job_card in job_cards: + job_card_doc = frappe.get_doc("Job Card", job_card.name) + job_card_doc.time_logs = [] + job_card_doc.append( + "time_logs", + { + "from_time": now(), + "to_time": add_to_date(now(), minutes=60), + "time_in_mins": 60, + "completed_qty": 1, + }, + ) + + job_card_doc.submit() + + se_doc = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1)) + se_doc.save() + + for row in se_doc.additional_costs: + self.assertEqual(row.expense_account, operating_cost_account) + def test_op_cost_and_scrap_based_on_sub_assemblies(self): # Make Sub Assembly BOM 1 diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 4712a10cc0a..939eaf25712 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -28,6 +28,12 @@ frappe.ui.form.on("Company", { }; }); + frm.set_query("default_operating_cost_account", function (doc) { + return { + filters: { company: doc.name, root_type: "Expense" }, + }; + }); + frm.set_query("default_selling_terms", function () { return { filters: { selling: 1 } }; }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index b2d6af03a10..787b0826f01 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -109,6 +109,8 @@ "stock_received_but_not_billed", "default_provisional_account", "expenses_included_in_valuation", + "manufacturing_section", + "default_operating_cost_account", "dashboard_tab" ], "fields": [ @@ -773,7 +775,7 @@ { "fieldname": "stock_tab", "fieldtype": "Tab Break", - "label": "Stock" + "label": "Stock and Manufacturing" }, { "fieldname": "dashboard_tab", @@ -788,6 +790,17 @@ "fieldname": "reconcile_on_advance_payment_date", "fieldtype": "Check", "label": "Reconcile on Advance Payment Date" + }, + { + "fieldname": "manufacturing_section", + "fieldtype": "Section Break", + "label": "Manufacturing" + }, + { + "fieldname": "default_operating_cost_account", + "fieldtype": "Link", + "label": "Default Operating Cost Account", + "options": "Account" } ], "icon": "fa fa-building", @@ -795,7 +808,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2024-05-27 17:32:49.057386", + "modified": "2024-06-21 17:46:25.567565", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -862,4 +875,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 2a0b32ed568..87d14e3c6b4 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -62,6 +62,7 @@ class Company(NestedSet): default_income_account: DF.Link | None default_inventory_account: DF.Link | None default_letter_head: DF.Link | None + default_operating_cost_account: DF.Link | None default_payable_account: DF.Link | None default_provisional_account: DF.Link | None default_receivable_account: DF.Link | None From 6f3a6ca2ca43c2ee22385233ebf7f0cb2b6d69db Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 22 Jun 2024 10:39:53 +0530 Subject: [PATCH 130/203] feat: default account head for operating cost (backport #41985) (#41987) * feat: default account head for operating cost (#41985) (cherry picked from commit fd7666a029a430c0b3cf4d1c9d0b728f4d0e4476) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py # erpnext/setup/doctype/company/company.json * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure (cherry picked from commit 44c16713ba9fdda293d612b10720d63f6a83b0c8) --- erpnext/manufacturing/doctype/bom/bom.py | 14 +++- .../doctype/work_order/test_work_order.py | 75 +++++++++++++++++++ erpnext/setup/doctype/company/company.js | 6 ++ erpnext/setup/doctype/company/company.json | 19 ++++- erpnext/setup/doctype/company/company.py | 1 + 5 files changed, 108 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 20b9845ef6a..009320c7a18 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1259,12 +1259,18 @@ def get_children(parent=None, is_root=False, **filters): def add_additional_cost(stock_entry, work_order): # Add non stock items cost in the additional cost stock_entry.additional_costs = [] - expenses_included_in_valuation = frappe.get_cached_value( - "Company", work_order.company, "expenses_included_in_valuation" + company_account = frappe.db.get_value( + "Company", + work_order.company, + ["expenses_included_in_valuation", "default_operating_cost_account"], + as_dict=1, ) - add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation) - add_operations_cost(stock_entry, work_order, expenses_included_in_valuation) + expense_account = ( + company_account.default_operating_cost_account or company_account.expenses_included_in_valuation + ) + add_non_stock_items_cost(stock_entry, work_order, expense_account) + add_operations_cost(stock_entry, work_order, expense_account) def add_non_stock_items_cost(stock_entry, work_order, expense_account): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 14a56f21afb..d1f2cdbd1aa 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1748,6 +1748,81 @@ class TestWorkOrder(FrappeTestCase): job_card2.time_logs = [] job_card2.save() + def test_operating_cost_account(self): + operating_cost_account = "Test Operating Cost Account - _TC" + company = "_Test Company" + if not frappe.db.exists("Account", operating_cost_account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Test Operating Cost Account", + "account_type": "Expense Account", + "company": company, + "parent_account": "Expenses - _TC", + "root_type": "Expense", + } + ).insert() + + frappe.db.set_value("Company", company, "default_operating_cost_account", operating_cost_account) + + for item in ["TEST RM OP COST Item 1", "TEST FG OP COST Item"]: + if not frappe.db.exists("Item", item): + make_item(item_code=item, properties={"is_stock_item": 1}) + + fg_item = "TEST FG OP COST Item" + bom_doc = make_bom( + item=fg_item, + raw_materials=["TEST RM OP COST Item 1"], + rate=150, + with_operations=1, + do_not_save=True, + ) + + workstation = "Test Workstation For Capacity Planning 1" + if not frappe.db.exists("Workstation", workstation): + make_workstation(workstation=workstation, production_capacity=1) + + operation = "Test Operation For Capacity Planning 1" + if not frappe.db.exists("Operation", operation): + make_operation(operation=operation, workstation=workstation) + + bom_doc.append( + "operations", + {"operation": operation, "time_in_mins": 60, "hour_rate": 100, "workstation": workstation}, + ) + + bom_doc.save() + bom_doc.submit() + + wo = make_wo_order_test_record( + production_item=fg_item, + bom_no=bom_doc.name, + qty=1, + skip_transfer=1, + ) + + job_cards = frappe.get_all("Job Card", filters={"work_order": wo.name}) + for job_card in job_cards: + job_card_doc = frappe.get_doc("Job Card", job_card.name) + job_card_doc.time_logs = [] + job_card_doc.append( + "time_logs", + { + "from_time": now(), + "to_time": add_to_date(now(), minutes=60), + "time_in_mins": 60, + "completed_qty": 1, + }, + ) + + job_card_doc.submit() + + se_doc = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1)) + se_doc.save() + + for row in se_doc.additional_costs: + self.assertEqual(row.expense_account, operating_cost_account) + def test_op_cost_and_scrap_based_on_sub_assemblies(self): # Make Sub Assembly BOM 1 diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 4712a10cc0a..939eaf25712 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -28,6 +28,12 @@ frappe.ui.form.on("Company", { }; }); + frm.set_query("default_operating_cost_account", function (doc) { + return { + filters: { company: doc.name, root_type: "Expense" }, + }; + }); + frm.set_query("default_selling_terms", function () { return { filters: { selling: 1 } }; }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index b2d6af03a10..787b0826f01 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -109,6 +109,8 @@ "stock_received_but_not_billed", "default_provisional_account", "expenses_included_in_valuation", + "manufacturing_section", + "default_operating_cost_account", "dashboard_tab" ], "fields": [ @@ -773,7 +775,7 @@ { "fieldname": "stock_tab", "fieldtype": "Tab Break", - "label": "Stock" + "label": "Stock and Manufacturing" }, { "fieldname": "dashboard_tab", @@ -788,6 +790,17 @@ "fieldname": "reconcile_on_advance_payment_date", "fieldtype": "Check", "label": "Reconcile on Advance Payment Date" + }, + { + "fieldname": "manufacturing_section", + "fieldtype": "Section Break", + "label": "Manufacturing" + }, + { + "fieldname": "default_operating_cost_account", + "fieldtype": "Link", + "label": "Default Operating Cost Account", + "options": "Account" } ], "icon": "fa fa-building", @@ -795,7 +808,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2024-05-27 17:32:49.057386", + "modified": "2024-06-21 17:46:25.567565", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -862,4 +875,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 2a0b32ed568..87d14e3c6b4 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -62,6 +62,7 @@ class Company(NestedSet): default_income_account: DF.Link | None default_inventory_account: DF.Link | None default_letter_head: DF.Link | None + default_operating_cost_account: DF.Link | None default_payable_account: DF.Link | None default_provisional_account: DF.Link | None default_receivable_account: DF.Link | None From e99ef572a20fcd9a4d02610d3f945f071c8051ee Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Sat, 22 Jun 2024 05:49:34 +0000 Subject: [PATCH 131/203] chore(release): Bumped to Version 15.28.0 # [15.28.0](https://github.com/frappe/erpnext/compare/v15.27.7...v15.28.0) (2024-06-22) ### Features * default account head for operating cost (backport [#41985](https://github.com/frappe/erpnext/issues/41985)) ([#41987](https://github.com/frappe/erpnext/issues/41987)) ([6f3a6ca](https://github.com/frappe/erpnext/commit/6f3a6ca2ca43c2ee22385233ebf7f0cb2b6d69db)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 9d0dfd8d741..3e0be774866 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.27.7" +__version__ = "15.28.0" def get_default_company(user=None): From b59c91a341d3f4265ae966a12e3a002c0a4dbc0b Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 22 Jun 2024 21:27:00 +0530 Subject: [PATCH 132/203] perf: dont run queries unnecessarily, improved filters (#41993) * perf: dont run queries unnecessarily, improved filters * perf: dont run query if `in` filter is empty (cherry picked from commit ac6d85aed628734820cc055d00e062b4fd90bb8c) --- .../controllers/subcontracting_controller.py | 33 ++++++++++++------- .../serial_and_batch_bundle.py | 7 ++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index e0f1ed77140..d31ee258b27 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -294,16 +294,23 @@ class SubcontractingController(StockController): receipt_items = {item.name: item.get(self.subcontract_data.order_field) for item in receipt_items} consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys()) - voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no] - voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle( - voucher_no=voucher_nos, - is_outward=1, - get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"), - ) - if return_consumed_items: return (consumed_materials, receipt_items) + if not consumed_materials: + return + + voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no] + voucher_bundle_data = ( + get_voucher_wise_serial_batch_from_bundle( + voucher_no=voucher_nos, + is_outward=1, + get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"), + ) + if voucher_nos + else {} + ) + for row in consumed_materials: key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name)) if not self.available_materials.get(key): @@ -350,10 +357,14 @@ class SubcontractingController(StockController): transferred_items = self.__get_transferred_items() voucher_nos = [row.voucher_no for row in transferred_items] - voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle( - voucher_no=voucher_nos, - is_outward=0, - get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"), + voucher_bundle_data = ( + get_voucher_wise_serial_batch_from_bundle( + voucher_no=voucher_nos, + is_outward=0, + get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"), + ) + if voucher_nos + else {} ) for row in transferred_items: diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 87bf2df5f61..e7637aca63e 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -2104,10 +2104,13 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: ) for key, val in kwargs.items(): - if not val: + if val is None: continue - if key in ["get_subcontracted_item"]: + if not val and isinstance(val, list): + return [] + + if key == "get_subcontracted_item": continue if key in ["name", "item_code", "warehouse", "voucher_no", "company", "voucher_detail_no"]: From 199a64937b4d504b1ff3b151d913e47ad7ff5361 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 8 May 2024 09:55:55 +0530 Subject: [PATCH 133/203] chore: remove validation on payment entry (cherry picked from commit e7740033cad4bfbe0ca35ca05795635b2db4257e) --- .../doctype/payment_entry/payment_entry.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 69075e5cf6c..e4f6a735183 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -163,22 +163,6 @@ class PaymentEntry(AccountsController): alert=True, ) - def validate_advance_account_currency(self): - if self.book_advance_payments_in_separate_party_account is True: - company_currency = frappe.get_cached_value("Company", self.company, "default_currency") - if self.payment_type == "Receive" and self.paid_from_account_currency != company_currency: - frappe.throw( - _("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format( - frappe.bold(self.paid_from), frappe.bold(self.paid_from_account_currency) - ) - ) - if self.payment_type == "Pay" and self.paid_to_account_currency != company_currency: - frappe.throw( - _("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format( - frappe.bold(self.paid_to), frappe.bold(self.paid_to_account_currency) - ) - ) - def on_cancel(self): self.ignore_linked_doctypes = ( "GL Entry", From 5d2f296ca8c350f28d6b650dad13a6f9b581f6f2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 May 2024 11:06:06 +0530 Subject: [PATCH 134/203] refactor: convert amount to base currency for advances (cherry picked from commit c9ede1ffbe25d6250b52910372c1d7cfa82096c5) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e4f6a735183..79ed5639cf2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1250,7 +1250,7 @@ class PaymentEntry(AccountsController): dr_or_cr, account = self.get_dr_and_account_for_advances(invoice) args_dict["account"] = account - args_dict[dr_or_cr] = invoice.allocated_amount + args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice) args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount args_dict.update( { @@ -1269,7 +1269,7 @@ class PaymentEntry(AccountsController): args_dict[dr_or_cr + "_in_account_currency"] = 0 dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" args_dict["account"] = self.party_account - args_dict[dr_or_cr] = invoice.allocated_amount + args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice) args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount args_dict.update( { From 47071cec5df815534e72ef4730349d8cd8d0d749 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 May 2024 16:00:39 +0530 Subject: [PATCH 135/203] refactor: for advances uses the party account in references table (cherry picked from commit 7dce6e03c7f8452fa9ca942b38a0cf1bd2264d3e) --- erpnext/controllers/accounts_controller.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 40ce8b6bc57..f576ecc1b11 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1430,10 +1430,13 @@ class AccountsController(TransactionBase): if d.exchange_gain_loss and ( (d.reference_doctype, d.reference_name, str(d.idx)) not in booked ): - if self.payment_type == "Receive": - party_account = self.paid_from - elif self.payment_type == "Pay": - party_account = self.paid_to + if self.book_advance_payments_in_separate_party_account: + party_account = d.account + else: + if self.payment_type == "Receive": + party_account = self.paid_from + elif self.payment_type == "Pay": + party_account = self.paid_to dr_or_cr = "debit" if d.exchange_gain_loss > 0 else "credit" From 6ebd4ba2ccf8de2045f6d9d7691c97b9a11e61e1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 28 May 2024 08:44:19 +0530 Subject: [PATCH 136/203] refactor(test): simpler create_account helper method (cherry picked from commit 475e0ddeeef3700e2c75910886fad3e44f58eb6d) --- .../tests/test_accounts_controller.py | 98 +++++++++++-------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index f91acb2e4ea..37d98549a4d 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -55,6 +55,7 @@ class TestAccountsController(FrappeTestCase): 40 series - Company default Cost center is unset 50 series - Journals against Journals 60 series - Journals against Payment Entries + 70 series - Advances in Separate party account. Both Party and Advance account are in Foreign currency. 90 series - Dimension inheritence """ @@ -114,47 +115,64 @@ class TestAccountsController(FrappeTestCase): self.supplier = make_supplier("_Test MC Supplier USD", "USD") def create_account(self): - account_name = "Debtors USD" - if not frappe.db.get_value( - "Account", filters={"account_name": account_name, "company": self.company} - ): - acc = frappe.new_doc("Account") - acc.account_name = account_name - acc.parent_account = "Accounts Receivable - " + self.company_abbr - acc.company = self.company - acc.account_currency = "USD" - acc.account_type = "Receivable" - acc.insert() - else: - name = frappe.db.get_value( - "Account", - filters={"account_name": account_name, "company": self.company}, - fieldname="name", - pluck=True, - ) - acc = frappe.get_doc("Account", name) - self.debtors_usd = acc.name + accounts = [ + frappe._dict( + { + "attribute_name": "debtors_usd", + "name": "Debtors USD", + "account_type": "Receivable", + "account_currency": "USD", + "parent_account": "Accounts Receivable - " + self.company_abbr, + } + ), + frappe._dict( + { + "attribute_name": "creditors_usd", + "name": "Creditors USD", + "account_type": "Payable", + "account_currency": "USD", + "parent_account": "Accounts Payable - " + self.company_abbr, + } + ), + # Advance accounts under Asset and Liability header + frappe._dict( + { + "attribute_name": "debtors_advance_usd", + "name": "Advance Received USD", + "account_type": "Receivable", + "account_currency": "USD", + "parent_account": "Current Liabilities - " + self.company_abbr, + } + ), + frappe._dict( + { + "attribute_name": "creditors_advance_usd", + "name": "Advance Paid USD", + "account_type": "Payable", + "account_currency": "USD", + "parent_account": "Current Assets - " + self.company_abbr, + } + ), + ] - account_name = "Creditors USD" - if not frappe.db.get_value( - "Account", filters={"account_name": account_name, "company": self.company} - ): - acc = frappe.new_doc("Account") - acc.account_name = account_name - acc.parent_account = "Accounts Payable - " + self.company_abbr - acc.company = self.company - acc.account_currency = "USD" - acc.account_type = "Payable" - acc.insert() - else: - name = frappe.db.get_value( - "Account", - filters={"account_name": account_name, "company": self.company}, - fieldname="name", - pluck=True, - ) - acc = frappe.get_doc("Account", name) - self.creditors_usd = acc.name + for x in accounts: + if not frappe.db.get_value("Account", filters={"account_name": x.name, "company": self.company}): + acc = frappe.new_doc("Account") + acc.account_name = x.name + acc.parent_account = x.parent_account + acc.company = self.company + acc.account_currency = x.account_currency + acc.account_type = x.account_type + acc.insert() + else: + name = frappe.db.get_value( + "Account", + filters={"account_name": x.name, "company": self.company}, + fieldname="name", + pluck=True, + ) + acc = frappe.get_doc("Account", name) + setattr(self, x.attribute_name, acc.name) def create_sales_invoice( self, From 259d7cde39daf7249f19a2d083842c8c0e9e71e9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 28 May 2024 11:20:07 +0530 Subject: [PATCH 137/203] test: exc gain/loss booking on advances under asset/liability (cherry picked from commit 827d67d02f745346bd32bed5428cc33a7baba745) --- .../tests/test_accounts_controller.py | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 37d98549a4d..8a59f0b070c 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -137,7 +137,7 @@ class TestAccountsController(FrappeTestCase): # Advance accounts under Asset and Liability header frappe._dict( { - "attribute_name": "debtors_advance_usd", + "attribute_name": "advance_received_usd", "name": "Advance Received USD", "account_type": "Receivable", "account_currency": "USD", @@ -146,7 +146,7 @@ class TestAccountsController(FrappeTestCase): ), frappe._dict( { - "attribute_name": "creditors_advance_usd", + "attribute_name": "advance_paid_usd", "name": "Advance Paid USD", "account_type": "Payable", "account_currency": "USD", @@ -174,6 +174,18 @@ class TestAccountsController(FrappeTestCase): acc = frappe.get_doc("Account", name) setattr(self, x.attribute_name, acc.name) + def enable_advances_under_asset_and_liability(self): + company = frappe.get_doc("Company", self.company) + company.book_advance_payments_in_separate_party_account = 1 + company.default_advance_received_account = self.advance_received_usd + company.default_advance_paid_account = self.advance_paid_usd + company.save() + + def disable_advances_under_asset_and_liability(self): + company = frappe.get_doc("Company", self.company) + company.book_advance_payments_in_separate_party_account = 0 + company.save() + def create_sales_invoice( self, qty=1, @@ -1716,3 +1728,54 @@ class TestAccountsController(FrappeTestCase): # Exchange Gain/Loss Journal should've been cancelled exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name) self.assertEqual(exc_je_for_je1, []) + + def test_70_advance_payment_against_sales_invoice_in_foreign_currency(self): + """ + Customer advance booked under Liability + """ + self.enable_advances_under_asset_and_liability() + + adv = self.create_payment_entry(amount=1, source_exc_rate=83) + adv.save() # explicit 'save' is needed to trigger set_liability_account() + self.assertEqual(adv.paid_from, self.advance_received_usd) + adv.submit() + + si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True) + si.debit_to = self.debtors_usd + si.save().submit() + self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0) + + pr = self.create_payment_reconciliation() + pr.receivable_payable_account = self.debtors_usd + pr.default_advance_account = self.advance_received_usd + pr.get_unreconciled_entries() + self.assertEqual(pr.invoices[0].invoice_number, si.name) + self.assertEqual(pr.payments[0].reference_name, adv.name) + + # Allocate and Reconcile + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Exc Gain/Loss journal should've been creatad + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_si), 1) + self.assertEqual(len(exc_je_for_adv), 1) + self.assertEqual(exc_je_for_si, exc_je_for_adv) + + adv.reload() + adv.cancel() + si.reload() + self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0) + # Exc Gain/Loss journal should've been cancelled + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_si), 0) + self.assertEqual(len(exc_je_for_adv), 0) + + self.disable_advances_under_asset_and_liability() From 88e1c28e7d070fe028c0e034a2ef483f55c0c664 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 29 May 2024 08:17:02 +0530 Subject: [PATCH 138/203] test: advance against purchase invoice (cherry picked from commit 90c84822d01a9b8ab2aba303846702f08b33c63a) --- .../tests/test_accounts_controller.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 8a59f0b070c..d2144adde0f 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -10,6 +10,7 @@ from frappe.utils import add_days, getdate, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry +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 from erpnext.accounts.party import get_party_account from erpnext.stock.doctype.item.test_item import create_item @@ -248,6 +249,48 @@ class TestAccountsController(FrappeTestCase): payment.posting_date = posting_date return payment + def create_purchase_invoice( + self, + qty=1, + rate=1, + conversion_rate=80, + posting_date=None, + do_not_save=False, + do_not_submit=False, + ): + """ + Helper function to populate default values in purchase invoice + """ + if posting_date is None: + posting_date = nowdate() + + pinv = make_purchase_invoice( + posting_date=posting_date, + qty=qty, + rate=rate, + company=self.company, + supplier=self.supplier, + item_code=self.item, + item_name=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + parent_cost_center=self.cost_center, + update_stock=0, + currency="USD", + conversion_rate=conversion_rate, + is_pos=0, + is_return=0, + income_account=self.income_account, + expense_account=self.expense_account, + do_not_save=True, + ) + pinv.credit_to = self.creditors_usd + if not do_not_save: + pinv.save() + if not do_not_submit: + pinv.submit() + return pinv + def clear_old_entries(self): doctype_list = [ "GL Entry", @@ -1779,3 +1822,72 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(len(exc_je_for_adv), 0) self.disable_advances_under_asset_and_liability() + + def test_71_advance_payment_against_purchase_invoice_in_foreign_currency(self): + """ + Supplier advance booked under Asset + """ + self.enable_advances_under_asset_and_liability() + + usd_amount = 1 + inr_amount = 85 + exc_rate = 85 + adv = create_payment_entry( + company=self.company, + payment_type="Pay", + party_type="Supplier", + party=self.supplier, + paid_from=self.cash, + paid_to=self.advance_paid_usd, + paid_amount=inr_amount, + ) + adv.source_exchange_rate = 1 + adv.target_exchange_rate = exc_rate + adv.received_amount = usd_amount + adv.paid_amount = exc_rate * usd_amount + adv.posting_date = nowdate() + adv.save() + # Make sure that advance account is still set + self.assertEqual(adv.paid_to, self.advance_paid_usd) + adv.submit() + + pi = self.create_purchase_invoice(qty=1, conversion_rate=83, rate=1) + self.assertEqual(pi.credit_to, self.creditors_usd) + self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0) + + pr = self.create_payment_reconciliation() + pr.party_type = "Supplier" + pr.party = self.supplier + pr.receivable_payable_account = self.creditors_usd + pr.default_advance_account = self.advance_paid_usd + pr.get_unreconciled_entries() + self.assertEqual(pr.invoices[0].invoice_number, pi.name) + self.assertEqual(pr.payments[0].reference_name, adv.name) + + # Allocate and Reconcile + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + self.assert_ledger_outstanding(pi.doctype, pi.name, 0.0, 0.0) + + # Exc Gain/Loss journal should've been creatad + exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_pi), 1) + self.assertEqual(len(exc_je_for_adv), 1) + self.assertEqual(exc_je_for_pi, exc_je_for_adv) + + adv.reload() + adv.cancel() + pi.reload() + self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0) + # Exc Gain/Loss journal should've been cancelled + exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_pi), 0) + self.assertEqual(len(exc_je_for_adv), 0) + + self.disable_advances_under_asset_and_liability() From 78ad3f6cdc8689cc6c297eb8ca4d1adcedc17b6a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 29 May 2024 11:26:10 +0530 Subject: [PATCH 139/203] refactor: validation to force accounts to be on same currency (cherry picked from commit 0f0b4d88bcd5e298154748a58c20589c5aa9f531) --- erpnext/buying/doctype/supplier/supplier.py | 1 + erpnext/selling/doctype/customer/customer.py | 1 + erpnext/utilities/transaction_base.py | 57 ++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 0df17fdd4b9..ff5385dd961 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -138,6 +138,7 @@ class Supplier(TransactionBase): validate_party_accounts(self) self.validate_internal_supplier() self.add_role_for_user() + self.validate_currency_for_receivable_payable_and_advance_account() @frappe.whitelist() def get_supplier_group_details(self): diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index f5185c2ff5b..8ce67cc659a 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -144,6 +144,7 @@ class Customer(TransactionBase): self.validate_default_bank_account() self.validate_internal_customer() self.add_role_for_user() + self.validate_currency_for_receivable_payable_and_advance_account() # set loyalty program tier if frappe.db.exists("Customer", self.name): diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 3b7812f96c2..9559c9170cf 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -168,6 +168,63 @@ class TransactionBase(StatusUpdater): if len(child_table_values) > 1: self.set(default_field, None) + def validate_currency_for_receivable_payable_and_advance_account(self): + if self.doctype in ["Customer", "Supplier"]: + account_type = "Receivable" if self.doctype == "Customer" else "Payable" + for x in self.accounts: + company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency") + receivable_payable_account_currency = None + advance_account_currency = None + + if x.account: + receivable_payable_account_currency = frappe.get_cached_value( + "Account", x.account, "account_currency" + ) + + if x.advance_account: + advance_account_currency = frappe.get_cached_value( + "Account", x.advance_account, "account_currency" + ) + if receivable_payable_account_currency and ( + receivable_payable_account_currency != self.default_currency + and receivable_payable_account_currency != company_default_currency + ): + frappe.throw( + _( + "{0} Account must be in either customer billing currency: {1} or Company default currency: {2}" + ).format( + account_type, + frappe.bold(self.default_currency), + frappe.bold(company_default_currency), + ) + ) + + if advance_account_currency and ( + advance_account_currency != self.default_currency + and advance_account_currency != company_default_currency + ): + frappe.throw( + _( + "Advance Account must be in either customer billing currency: {0} or Company default currency: {1}" + ).format(frappe.bold(self.default_currency), frappe.bold(company_default_currency)) + ) + + if ( + receivable_payable_account_currency + and advance_account_currency + and receivable_payable_account_currency != advance_account_currency + ): + frappe.throw( + _( + "Both {0} Account: {1} and Advance Account: {2} must be of same currency for company: {3}" + ).format( + account_type, + frappe.bold(x.account), + frappe.bold(x.advance_account), + frappe.bold(x.company), + ) + ) + def delete_events(ref_type, ref_name): events = ( From 4bde34539996757f04f1c4f23184b32d22d5ddb7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 10 Jun 2024 20:06:21 +0530 Subject: [PATCH 140/203] refactor: validation in customer group (cherry picked from commit 4f9a2281754ad64c845739a29c5225059f1cc11d) --- .../doctype/customer_group/customer_group.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py index 0b783c0c56e..6d95d3bcb24 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.py +++ b/erpnext/setup/doctype/customer_group/customer_group.py @@ -38,6 +38,53 @@ class CustomerGroup(NestedSet): def validate(self): if not self.parent_customer_group: self.parent_customer_group = get_root_of("Customer Group") + self.validate_currency_for_receivable_and_advance_account() + + def validate_currency_for_receivable_and_advance_account(self): + for x in self.accounts: + company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency") + receivable_account_currency = None + advance_account_currency = None + + if x.account: + receivable_account_currency = frappe.get_cached_value( + "Account", x.account, "account_currency" + ) + + if x.advance_account: + advance_account_currency = frappe.get_cached_value( + "Account", x.advance_account, "account_currency" + ) + + if receivable_account_currency and receivable_account_currency != company_default_currency: + frappe.throw( + _("Receivable Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.account), + frappe.bold(company_default_currency), + ) + ) + + if advance_account_currency and advance_account_currency != company_default_currency: + frappe.throw( + _("Advance Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.advance_account), frappe.bold(company_default_currency) + ) + ) + + if ( + receivable_account_currency + and advance_account_currency + and receivable_account_currency != advance_account_currency + ): + frappe.throw( + _( + "Both Receivable Account: {0} and Advance Account: {1} must be of same currency for company: {2}" + ).format( + frappe.bold(x.account), + frappe.bold(x.advance_account), + frappe.bold(x.company), + ) + ) def on_update(self): self.validate_name_with_customer() From 545d0b9730295407a29990b5b9adc0ce9240b51d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 10 Jun 2024 20:08:28 +0530 Subject: [PATCH 141/203] refactor: validation in Supplier Group (cherry picked from commit 107b614518444339a9674287fa6cb2c788716fa8) --- .../doctype/supplier_group/supplier_group.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.py b/erpnext/setup/doctype/supplier_group/supplier_group.py index b639b962509..fa0c6beac49 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.py +++ b/erpnext/setup/doctype/supplier_group/supplier_group.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ from frappe.utils.nestedset import NestedSet, get_root_of @@ -32,6 +33,51 @@ class SupplierGroup(NestedSet): def validate(self): if not self.parent_supplier_group: self.parent_supplier_group = get_root_of("Supplier Group") + self.validate_currency_for_payable_and_advance_account() + + def validate_currency_for_payable_and_advance_account(self): + for x in self.accounts: + company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency") + payable_account_currency = None + advance_account_currency = None + + if x.account: + payable_account_currency = frappe.get_cached_value("Account", x.account, "account_currency") + + if x.advance_account: + advance_account_currency = frappe.get_cached_value( + "Account", x.advance_account, "account_currency" + ) + + if payable_account_currency and payable_account_currency != company_default_currency: + frappe.throw( + _("Payable Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.account), + frappe.bold(company_default_currency), + ) + ) + + if advance_account_currency and advance_account_currency != company_default_currency: + frappe.throw( + _("Advance Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.advance_account), frappe.bold(company_default_currency) + ) + ) + + if ( + payable_account_currency + and advance_account_currency + and payable_account_currency != advance_account_currency + ): + frappe.throw( + _( + "Both Payable Account: {0} and Advance Account: {1} must be of same currency for company: {2}" + ).format( + frappe.bold(x.account), + frappe.bold(x.advance_account), + frappe.bold(x.company), + ) + ) def on_update(self): NestedSet.on_update(self) From 2bd10d388ffe7cddb5614e37f644fc0369d8b060 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Jun 2024 10:18:59 +0530 Subject: [PATCH 142/203] refactor: better error messages (cherry picked from commit 83ff94b9b87772251f663f3be59c44cadb2c3a67) --- erpnext/utilities/transaction_base.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 9559c9170cf..6fab5380c38 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -191,9 +191,11 @@ class TransactionBase(StatusUpdater): ): frappe.throw( _( - "{0} Account must be in either customer billing currency: {1} or Company default currency: {2}" + "{0} Account: {1} ({2}) must be in either customer billing currency: {3} or Company default currency: {4}" ).format( account_type, + frappe.bold(x.account), + frappe.bold(receivable_payable_account_currency), frappe.bold(self.default_currency), frappe.bold(company_default_currency), ) @@ -205,8 +207,12 @@ class TransactionBase(StatusUpdater): ): frappe.throw( _( - "Advance Account must be in either customer billing currency: {0} or Company default currency: {1}" - ).format(frappe.bold(self.default_currency), frappe.bold(company_default_currency)) + "Advance Account: {0} must be in either customer billing currency: {1} or Company default currency: {2}" + ).format( + frappe.bold(x.advance_account), + frappe.bold(self.default_currency), + frappe.bold(company_default_currency), + ) ) if ( From d1679d4663748694c79070587c66e1f951366705 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 17 Jun 2024 07:30:13 +0530 Subject: [PATCH 143/203] chore: fix test data (cherry picked from commit 07d59443b7e679e3dd3498f5837a800983540616) --- erpnext/buying/doctype/supplier/test_records.json | 1 + erpnext/selling/doctype/customer/test_records.json | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json index 1aa63fb5ba0..1bb9899cc85 100644 --- a/erpnext/buying/doctype/supplier/test_records.json +++ b/erpnext/buying/doctype/supplier/test_records.json @@ -35,6 +35,7 @@ "doctype": "Supplier", "supplier_name": "_Test Supplier USD", "supplier_group": "_Test Supplier Group", + "default_currency": "USD", "accounts": [{ "company": "_Test Company", "account": "_Test Payable USD - _TC" diff --git a/erpnext/selling/doctype/customer/test_records.json b/erpnext/selling/doctype/customer/test_records.json index 61cb36b0fae..6040f4dd75b 100644 --- a/erpnext/selling/doctype/customer/test_records.json +++ b/erpnext/selling/doctype/customer/test_records.json @@ -47,6 +47,7 @@ "customer_type": "Individual", "doctype": "Customer", "territory": "_Test Territory", + "default_currency": "USD", "accounts": [{ "company": "_Test Company", "account": "_Test Receivable USD - _TC" From a1ebd162841339ae19c0dc620156749716b4bc52 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 17 Jun 2024 08:45:54 +0530 Subject: [PATCH 144/203] chore: remove dead code (cherry picked from commit 7e318c013257ccb673e5065fac32e25e463be96d) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 79ed5639cf2..a2882206b4a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -76,7 +76,6 @@ class PaymentEntry(AccountsController): self.setup_party_account_field() self.set_missing_values() self.set_liability_account() - self.validate_advance_account_currency() self.set_missing_ref_details(force=True) self.validate_payment_type() self.validate_party_details() From 3b15708f18e695a93f740f03ddb8b1153abc6208 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 18 Jun 2024 14:35:35 +0530 Subject: [PATCH 145/203] fix(test): incorrect field for customer default billing currency (cherry picked from commit c696d13a5edbd62606efd266db9cd95bf18ed7d6) --- erpnext/accounts/doctype/subscription/test_subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index cb4f0200478..ba4963f78ed 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -565,7 +565,7 @@ def create_parties(): if not frappe.db.exists("Customer", "_Test Subscription Customer"): customer = frappe.new_doc("Customer") customer.customer_name = "_Test Subscription Customer" - customer.billing_currency = "USD" + customer.default_currency = "USD" customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"}) customer.insert() From 6dbe820416fdc91b91ed947f691acf12633b935f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 18 Jun 2024 15:10:25 +0530 Subject: [PATCH 146/203] refactor(test): enfore use of customer/supplier master While using advance accounts in foreign currency, always use Customer/Supplier master to maintain them (cherry picked from commit 64e63887bed71db1cb6e880ee2ae7f2e88d34a6c) --- .../tests/test_accounts_controller.py | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index d2144adde0f..3f6830c2021 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -175,17 +175,43 @@ class TestAccountsController(FrappeTestCase): acc = frappe.get_doc("Account", name) setattr(self, x.attribute_name, acc.name) - def enable_advances_under_asset_and_liability(self): + def setup_advance_accounts_in_party_master(self): company = frappe.get_doc("Company", self.company) company.book_advance_payments_in_separate_party_account = 1 - company.default_advance_received_account = self.advance_received_usd - company.default_advance_paid_account = self.advance_paid_usd company.save() - def disable_advances_under_asset_and_liability(self): + customer = frappe.get_doc("Customer", self.customer) + customer.append( + "accounts", + { + "company": self.company, + "account": self.debtors_usd, + "advance_account": self.advance_received_usd, + }, + ) + customer.save() + + supplier = frappe.get_doc("Supplier", self.supplier) + supplier.append( + "accounts", + { + "company": self.company, + "account": self.creditors_usd, + "advance_account": self.advance_paid_usd, + }, + ) + supplier.save() + + def remove_advance_accounts_from_party_master(self): company = frappe.get_doc("Company", self.company) company.book_advance_payments_in_separate_party_account = 0 company.save() + customer = frappe.get_doc("Customer", self.customer) + customer.accounts = [] + customer.save() + supplier = frappe.get_doc("Supplier", self.supplier) + supplier.accounts = [] + supplier.save() def create_sales_invoice( self, @@ -1776,7 +1802,7 @@ class TestAccountsController(FrappeTestCase): """ Customer advance booked under Liability """ - self.enable_advances_under_asset_and_liability() + self.setup_advance_accounts_in_party_master() adv = self.create_payment_entry(amount=1, source_exc_rate=83) adv.save() # explicit 'save' is needed to trigger set_liability_account() @@ -1821,13 +1847,13 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(len(exc_je_for_si), 0) self.assertEqual(len(exc_je_for_adv), 0) - self.disable_advances_under_asset_and_liability() + self.remove_advance_accounts_from_party_master() def test_71_advance_payment_against_purchase_invoice_in_foreign_currency(self): """ Supplier advance booked under Asset """ - self.enable_advances_under_asset_and_liability() + self.setup_advance_accounts_in_party_master() usd_amount = 1 inr_amount = 85 @@ -1890,4 +1916,4 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(len(exc_je_for_pi), 0) self.assertEqual(len(exc_je_for_adv), 0) - self.disable_advances_under_asset_and_liability() + self.remove_advance_accounts_from_party_master() From c45ce75f57bf475152f7ac0af020717919c8758a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jun 2024 19:32:07 +0530 Subject: [PATCH 147/203] refactor(test): make and use a different party for subscription (cherry picked from commit 3fabf4aaa48943f29429931cf0d3e9a3c08a65e5) --- .../accounts/doctype/subscription/test_subscription.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index ba4963f78ed..af3916ae469 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -476,7 +476,7 @@ class TestSubscription(FrappeTestCase): start_date="2021-01-01", submit_invoice=0, generate_new_invoices_past_due_date=1, - party="_Test Subscription Customer", + party="_Test Subscription Customer John Doe", ) # create invoices for the first two moths @@ -569,6 +569,12 @@ def create_parties(): customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"}) customer.insert() + if not frappe.db.exists("Customer", "_Test Subscription Customer John Doe"): + customer = frappe.new_doc("Customer") + customer.customer_name = "_Test Subscription Customer John Doe" + customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable - _TC"}) + customer.insert() + def reset_settings(): settings = frappe.get_single("Subscription Settings") From f3aa8854880173dd89a911ca4198c9fc605e35f1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 21 Jun 2024 15:46:16 +0530 Subject: [PATCH 148/203] fix: pricing rule with and without 'apply multiple' and priority Either all of the pricing rules identified for an item should have 'apply multiple' enabled. If not, Priority is applied and only the highest priority is applied (cherry picked from commit 5e875b238c9d1a1c376f2fa98c0d2451da7e80b7) --- erpnext/accounts/doctype/pricing_rule/utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 60c9e26aabe..9c7911d7cae 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -174,12 +174,9 @@ def _get_pricing_rules(apply_on, args, values): def apply_multiple_pricing_rules(pricing_rules): - apply_multiple_rule = [ - d.apply_multiple_pricing_rules for d in pricing_rules if d.apply_multiple_pricing_rules - ] - - if not apply_multiple_rule: - return False + for d in pricing_rules: + if not d.apply_multiple_pricing_rules: + return False return True From f52f726e0601dfdf3e2fd329e82ad3b933035df4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jun 2024 20:26:23 +0530 Subject: [PATCH 149/203] test: priority takes effect on with and without apply multiple (cherry picked from commit efebc3662e8b76ea82866f940ed0bc9032c819e2) --- .../doctype/pricing_rule/test_pricing_rule.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 7f9c55ff24f..72961a6b6ec 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1237,6 +1237,68 @@ class TestPricingRule(unittest.TestCase): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + def test_pricing_rules_with_and_without_apply_multiple(self): + item = make_item("PR Item 99") + + test_records = [ + { + "doctype": "Pricing Rule", + "title": "_Test discount on item group", + "name": "_Test discount on item group", + "apply_on": "Item Group", + "item_groups": [ + { + "item_group": "Products", + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Percentage", + "discount_percentage": 60, + "has_priority": 1, + "company": "_Test Company", + "apply_multiple_pricing_rules": True, + }, + { + "doctype": "Pricing Rule", + "title": "_Test fixed rate on item code", + "name": "_Test fixed rate on item code", + "apply_on": "Item Code", + "items": [ + { + "item_code": item.name, + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Rate", + "rate": 25, + "has_priority": 1, + "company": "_Test Company", + "apply_multiple_pricing_rules": False, + }, + ] + + for item_group_priority, item_code_priority in [(2, 4), (4, 2)]: + item_group_rule = frappe.get_doc(test_records[0].copy()) + item_group_rule.priority = item_group_priority + item_group_rule.insert() + + item_code_rule = frappe.get_doc(test_records[1].copy()) + item_code_rule.priority = item_code_priority + item_code_rule.insert() + + si = create_sales_invoice(qty=5, customer="_Test Customer 1", item=item.name, do_not_submit=True) + si.save() + self.assertEqual(len(si.pricing_rules), 1) + # Item Code rule should've applied as it has higher priority + expected_rule = item_group_rule if item_group_priority > item_code_priority else item_code_rule + self.assertEqual(si.pricing_rules[0].pricing_rule, expected_rule.name) + + si.delete() + item_group_rule.delete() + item_code_rule.delete() + test_dependencies = ["Campaign"] From fe9dffb271021e554331f048931fe793b05bbaf1 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 3 Jun 2024 13:45:55 +0000 Subject: [PATCH 150/203] feat: accounting dimension filters in gp report (cherry picked from commit d165638bbbd49ef260760528ccca1eb04b71628b) --- .../report/gross_profit/gross_profit.js | 22 ++++++++++++++ .../report/gross_profit/gross_profit.py | 30 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index fad6586466e..4f65aa615ee 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -63,6 +63,26 @@ frappe.query_reports["Gross Profit"] = { }; }, }, + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, ], tree: true, name_field: "parent", @@ -85,3 +105,5 @@ frappe.query_reports["Gross Profit"] = { return value; }, }; + +erpnext.utils.add_dimensions("Gross Profit", 15); diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index c8c8dd9b494..6ddb95eb140 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -8,6 +8,11 @@ from frappe import _, qb, scrub from frappe.query_builder import Order from frappe.utils import cint, flt, formatdate +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, + get_dimension_with_children, +) +from erpnext.accounts.report.financial_statements import get_cost_centers_with_children from erpnext.controllers.queries import get_match_cond from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition from erpnext.stock.utils import get_incoming_rate @@ -787,6 +792,31 @@ class GrossProfitGenerator: if self.filters.get("item_code"): conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s" + if self.filters.get("cost_center"): + self.filters.cost_center = frappe.parse_json(self.filters.get("cost_center")) + self.filters.cost_center = get_cost_centers_with_children(self.filters.cost_center) + conditions += " and `tabSales Invoice Item`.cost_center in %(cost_center)s" + + if self.filters.get("project"): + self.filters.project = frappe.parse_json(self.filters.get("project")) + conditions += " and `tabSales Invoice Item`.project in %(project)s" + + accounting_dimensions = get_accounting_dimensions(as_list=False) + if accounting_dimensions: + for dimension in accounting_dimensions: + if self.filters.get(dimension.fieldname): + if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): + self.filters[dimension.fieldname] = get_dimension_with_children( + dimension.document_type, self.filters.get(dimension.fieldname) + ) + conditions += ( + f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s" + ) + else: + conditions += ( + f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s" + ) + if self.filters.get("warehouse"): warehouse_details = frappe.db.get_value( "Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1 From 068ae87b8d56cb1161a9fd42136b72da33f6313f Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 3 Jun 2024 13:55:12 +0000 Subject: [PATCH 151/203] feat(gp): group by cost center (cherry picked from commit e26bc17c75f1dfea1659172216e3449f2271da14) --- .../accounts/report/gross_profit/gross_profit.js | 2 +- .../accounts/report/gross_profit/gross_profit.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 4f65aa615ee..ad194ee90a2 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -36,7 +36,7 @@ frappe.query_reports["Gross Profit"] = { label: __("Group By"), fieldtype: "Select", options: - "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term", + "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nCost Center\nMonthly\nPayment Term", default: "Invoice", }, { diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 6ddb95eb140..fe2746660eb 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -125,6 +125,13 @@ def execute(filters=None): "gross_profit_percent", ], "project": ["project", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"], + "cost_center": [ + "cost_center", + "base_amount", + "buying_amount", + "gross_profit", + "gross_profit_percent", + ], "territory": [ "territory", "base_amount", @@ -304,7 +311,14 @@ def get_columns(group_wise_columns, filters): "fieldname": "project", "fieldtype": "Link", "options": "Project", - "width": 100, + "width": 140, + }, + "cost_center": { + "label": _("Cost Center"), + "fieldname": "cost_center", + "fieldtype": "Link", + "options": "Cost Center", + "width": 140, }, "sales_person": { "label": _("Sales Person"), From 68b318a94b679fef505090fa1ebb4adecb3862a8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:04:13 +0530 Subject: [PATCH 152/203] fix: Wrong Delete Batch on Purchase Receipt (backport #42007) (#42012) fix: Wrong Delete Batch on Purchase Receipt (#42007) (cherry picked from commit d50487ce530605bd311da372a09b9e2fb339f00d) Co-authored-by: rohitwaghchaure --- erpnext/public/js/utils/serial_no_batch_selector.js | 1 + .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 4928f2dc1a5..78efb46f4c3 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -635,6 +635,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { set_data(data) { data.forEach((d) => { d.qty = Math.abs(d.qty); + d.name = d.child_row || d.name; this.dialog.fields_dict.entries.df.data.push(d); }); diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index e7637aca63e..63e1e5084d6 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1220,6 +1220,7 @@ def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, na "`tabSerial and Batch Entry`.`warehouse`", "`tabSerial and Batch Entry`.`batch_no`", "`tabSerial and Batch Entry`.`serial_no`", + "`tabSerial and Batch Entry`.`name` as `child_row`", ] if not child_row: From 838cc5b72aff39d53093c71a52fe2645c69fe1a8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:04:24 +0530 Subject: [PATCH 153/203] fix: incorrect Difference Amount (backport #42008) (#42013) fix: incorrect Difference Amount (#42008) (cherry picked from commit 7d91c6cbd5faf3f0d9d507b76964acf98282786a) Co-authored-by: rohitwaghchaure --- .../stock_reconciliation.py | 17 ++++++++++++----- .../test_stock_reconciliation.py | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 8301a706183..674624e184b 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -404,7 +404,9 @@ class StockReconciliation(StockController): fields=["total_qty as qty", "avg_rate as rate"], )[0] + bundle_data.qty = abs(bundle_data.qty) self.calculate_difference_amount(item, bundle_data) + return True inventory_dimensions_dict = {} @@ -464,11 +466,16 @@ class StockReconciliation(StockController): frappe.msgprint(_("Removed items with no change in quantity or value.")) def calculate_difference_amount(self, item, item_dict): - self.difference_amount += flt(item.qty, item.precision("qty")) * flt( - item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate") - ) - flt(item_dict.get("qty"), item.precision("qty")) * flt( - item_dict.get("rate"), item.precision("valuation_rate") - ) + qty_precision = item.precision("qty") + val_precision = item.precision("valuation_rate") + + new_qty = flt(item.qty, qty_precision) + new_valuation_rate = flt(item.valuation_rate or item_dict.get("rate"), val_precision) + + current_qty = flt(item_dict.get("qty"), qty_precision) + current_valuation_rate = flt(item_dict.get("rate"), val_precision) + + self.difference_amount += (new_qty * new_valuation_rate) - (current_qty * current_valuation_rate) def validate_data(self): def _get_msg(row_num, msg): diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 8845bdbb753..a41db6cf611 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1109,6 +1109,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): ) sr.reload() + self.assertEqual(sr.difference_amount, 98900.0) self.assertTrue(sr.items[0].current_valuation_rate) current_sabb = sr.items[0].current_serial_and_batch_bundle From f6be19cb7c46e67dde6c8dc43afe08d57d87acc5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:07:03 +0530 Subject: [PATCH 154/203] fix: valuation rate for the legacy batches (backport #42011) (#42020) fix: valuation rate for the legacy batches (#42011) (cherry picked from commit 9ab333d10584745e67ff3bcfe3385d890fc1075a) Co-authored-by: rohitwaghchaure --- erpnext/stock/deprecated_serial_batch.py | 86 ++++++++++++------- .../test_stock_reconciliation.py | 4 +- erpnext/stock/utils.py | 6 +- .../test_subcontracting_receipt.py | 8 +- 4 files changed, 63 insertions(+), 41 deletions(-) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index e43e6f21c92..d10833b6d46 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -1,3 +1,6 @@ +import datetime +from collections import defaultdict + import frappe from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import flt @@ -8,12 +11,7 @@ from pypika import Order class DeprecatedSerialNoValuation: @deprecated def calculate_stock_value_from_deprecarated_ledgers(self): - if not frappe.db.get_all( - "Stock Ledger Entry", - fields=["name"], - filters={"serial_no": ("is", "set"), "is_cancelled": 0, "item_code": self.sle.item_code}, - limit=1, - ): + if not has_sle_for_serial_nos(self.sle.item_code): return serial_nos = self.get_filterd_serial_nos() @@ -82,6 +80,20 @@ class DeprecatedSerialNoValuation: return incoming_values +@frappe.request_cache +def has_sle_for_serial_nos(item_code): + serial_nos = frappe.db.get_all( + "Stock Ledger Entry", + fields=["name"], + filters={"serial_no": ("is", "set"), "is_cancelled": 0, "item_code": item_code}, + limit=1, + ) + if serial_nos: + return True + + return False + + class DeprecatedBatchNoValuation: @deprecated def calculate_avg_rate_from_deprecarated_ledgers(self): @@ -92,19 +104,25 @@ class DeprecatedBatchNoValuation: @deprecated def get_sle_for_batches(self): + from erpnext.stock.utils import get_combine_datetime + if not self.batchwise_valuation_batches: return [] sle = frappe.qb.DocType("Stock Ledger Entry") - timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime( - self.sle.posting_date, self.sle.posting_time - ) - if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(sle.posting_date, sle.posting_time) - == CombineDatetime(self.sle.posting_date, self.sle.posting_time) - ) & (sle.creation < self.sle.creation) + timestamp_condition = None + if self.sle.posting_date and self.sle.posting_time: + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if not self.sle.creation: + posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) + + timestamp_condition = sle.posting_datetime < posting_datetime + + if self.sle.creation: + timestamp_condition |= (sle.posting_datetime == posting_datetime) & ( + sle.creation < self.sle.creation + ) query = ( frappe.qb.from_(sle) @@ -120,10 +138,12 @@ class DeprecatedBatchNoValuation: & (sle.batch_no.isnotnull()) & (sle.is_cancelled == 0) ) - .where(timestamp_condition) .groupby(sle.batch_no) ) + if timestamp_condition: + query = query.where(timestamp_condition) + if self.sle.name: query = query.where(sle.name != self.sle.name) @@ -134,8 +154,8 @@ class DeprecatedBatchNoValuation: if not self.non_batchwise_valuation_batches: return - self.non_batchwise_balance_value = 0.0 - self.non_batchwise_balance_qty = 0.0 + self.non_batchwise_balance_value = defaultdict(float) + self.non_batchwise_balance_qty = defaultdict(float) self.set_balance_value_for_non_batchwise_valuation_batches() @@ -146,12 +166,12 @@ class DeprecatedBatchNoValuation: if not self.non_batchwise_balance_qty: continue - if self.non_batchwise_balance_value == 0: + if self.non_batchwise_balance_qty.get(batch_no) == 0: self.batch_avg_rate[batch_no] = 0.0 self.stock_value_differece[batch_no] = 0.0 else: self.batch_avg_rate[batch_no] = ( - self.non_batchwise_balance_value / self.non_batchwise_balance_qty + self.non_batchwise_balance_value[batch_no] / self.non_batchwise_balance_qty[batch_no] ) self.stock_value_differece[batch_no] = self.non_batchwise_balance_value @@ -174,17 +194,21 @@ class DeprecatedBatchNoValuation: @deprecated def set_balance_value_from_sl_entries(self) -> None: + from erpnext.stock.utils import get_combine_datetime + sle = frappe.qb.DocType("Stock Ledger Entry") batch = frappe.qb.DocType("Batch") - timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime( - self.sle.posting_date, self.sle.posting_time - ) + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if not self.sle.creation: + posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) + + timestamp_condition = sle.posting_datetime < posting_datetime + if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(sle.posting_date, sle.posting_time) - == CombineDatetime(self.sle.posting_date, self.sle.posting_time) - ) & (sle.creation < self.sle.creation) + timestamp_condition |= (sle.posting_datetime == posting_datetime) & ( + sle.creation < self.sle.creation + ) query = ( frappe.qb.from_(sle) @@ -201,6 +225,7 @@ class DeprecatedBatchNoValuation: & (sle.batch_no.isnotnull()) & (batch.use_batchwise_valuation == 0) & (sle.is_cancelled == 0) + & (sle.batch_no.isin(self.non_batchwise_valuation_batches)) ) .where(timestamp_condition) .groupby(sle.batch_no) @@ -210,8 +235,8 @@ class DeprecatedBatchNoValuation: query = query.where(sle.name != self.sle.name) for d in query.run(as_dict=True): - self.non_batchwise_balance_value += flt(d.batch_value) - self.non_batchwise_balance_qty += flt(d.batch_qty) + self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value) + self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty) self.available_qty[d.batch_no] += flt(d.batch_qty) @deprecated @@ -249,6 +274,7 @@ class DeprecatedBatchNoValuation: & (bundle.is_cancelled == 0) & (bundle.docstatus == 1) & (bundle.type_of_transaction.isin(["Inward", "Outward"])) + & (bundle_child.batch_no.isin(self.non_batchwise_valuation_batches)) ) .where(timestamp_condition) .groupby(bundle_child.batch_no) @@ -260,6 +286,6 @@ class DeprecatedBatchNoValuation: query = query.where(bundle.voucher_type != "Pick List") for d in query.run(as_dict=True): - self.non_batchwise_balance_value += flt(d.batch_value) - self.non_batchwise_balance_qty += flt(d.batch_qty) + self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value) + self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty) self.available_qty[d.batch_no] += flt(d.batch_qty) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index a41db6cf611..48d67c2cf46 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -647,7 +647,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): "has_serial_no": 1, "has_batch_no": 1, "serial_no_series": "SRS9.####", - "batch_number_series": "BNS9.####", + "batch_number_series": "BNS90.####", "create_new_batch": 1, }, ) @@ -680,7 +680,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): { "is_stock_item": 1, "has_batch_no": 1, - "batch_number_series": "BNS9.####", + "batch_number_series": "BNS91.####", "create_new_batch": 1, }, ).name diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index eca01a57640..f2388e5737a 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -276,11 +276,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): sn_obj = SerialNoValuation(sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code")) return sn_obj.get_incoming_rate() - elif ( - args.get("batch_no") - and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True) - and not args.get("serial_and_batch_bundle") - ): + elif args.get("batch_no") and not args.get("serial_and_batch_bundle"): args.actual_qty = args.qty args.batch_nos = frappe._dict({args.batch_no: args}) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 81662a6257b..0f5fe7ab958 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -623,8 +623,8 @@ class TestSubcontractingReceipt(FrappeTestCase): "has_batch_no": 1, "has_serial_no": 1, "create_new_batch": 1, - "batch_number_series": "BNGS-.####", - "serial_no_series": "BNSS-.####", + "batch_number_series": "BNGS0-.####", + "serial_no_series": "BNSS90-.####", } ).name @@ -715,8 +715,8 @@ class TestSubcontractingReceipt(FrappeTestCase): "has_batch_no": 1, "has_serial_no": 1, "create_new_batch": 1, - "batch_number_series": "BNGS-.####", - "serial_no_series": "BNSS-.####", + "batch_number_series": "BNGS91-.####", + "serial_no_series": "BNSS91-.####", } ).name From 068de08bbbcbe885523b484ed964e73ef38c4fdc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 18:42:43 +0530 Subject: [PATCH 155/203] fix: timeout while cancelling LCV (backport #42030) (backport #42031) (#42032) fix: timeout while cancelling LCV (backport #42030) (#42031) fix: timeout while cancelling LCV (#42030) fix: timeout while canelling LCV (cherry picked from commit 21bf7fd1f8c9d4c1cd9f3b419089782e82658165) Co-authored-by: rohitwaghchaure (cherry picked from commit 2e76b9f9db51b70341bbbdeeeb43bfbd6675abd4) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- erpnext/stock/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e804ae18016..d0195843c5f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1794,6 +1794,7 @@ def get_next_stock_reco(kwargs): sle.actual_qty, sle.has_batch_no, ) + .force_index("item_warehouse") .where( (sle.item_code == kwargs.get("item_code")) & (sle.warehouse == kwargs.get("warehouse")) From e278fc683f9cad5d709c1719d2bae502193387a5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 09:11:01 +0530 Subject: [PATCH 156/203] fix: Stock Reservation Entry was not getting created (backport #42033) (#42035) fix: Stock Reservation Entry was not getting created (#42033) (cherry picked from commit 1a9899b32bc574873ef27e5e56dc07eea9385178) Co-authored-by: Poorvi-R-Bhat --- erpnext/selling/doctype/sales_order/sales_order.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index e732dfd42d5..cdcd1047bd8 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -75,8 +75,7 @@ frappe.ui.form.on("Sales Order", { if ( frm.doc.__onload && frm.doc.__onload.has_unreserved_stock && - flt(frm.doc.per_picked) === 0 && - frappe.model.can_create("Stock Reservation Entry") + flt(frm.doc.per_picked) === 0 ) { frm.add_custom_button( __("Reserve"), From a981633d949fb43054bb22b78c77c5bde3296742 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:00:36 +0530 Subject: [PATCH 157/203] fix: manufacturing date issue in the batch (backport #42034) (#42037) * fix: manufacturing date issue in the batch (#42034) (cherry picked from commit eca3e02f8da4b5d7d1b15a3c42de225168f3bf5e) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/batch/batch.py | 16 +++++++++- .../purchase_receipt/test_purchase_receipt.py | 31 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 0be85e46015..e490badfc40 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -161,11 +161,25 @@ class Batch(Document): self.use_batchwise_valuation = 1 def before_save(self): + self.set_expiry_date() + + def set_expiry_date(self): has_expiry_date, shelf_life_in_days = frappe.db.get_value( "Item", self.item, ["has_expiry_date", "shelf_life_in_days"] ) + if not self.expiry_date and has_expiry_date and shelf_life_in_days: - self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days) + if ( + not self.manufacturing_date + and self.reference_doctype in ["Stock Entry", "Purchase Receipt", "Purchase Invoice"] + and self.reference_name + ): + self.manufacturing_date = frappe.db.get_value( + self.reference_doctype, self.reference_name, "posting_date" + ) + + if self.manufacturing_date: + self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days) if has_expiry_date and not self.expiry_date: frappe.throw( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 42747818d2b..cae58de9303 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3,7 +3,7 @@ import frappe from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, cint, cstr, flt, nowtime, today +from frappe.utils import add_days, cint, cstr, flt, getdate, nowtime, today from pypika import functions as fn import erpnext @@ -2961,6 +2961,35 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertSequenceEqual(expected_gle, gl_entries) frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory + def test_manufacturing_and_expiry_date_for_batch(self): + item = make_item( + "_Test Manufacturing and Expiry Date For Batch", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B-MEBATCH.#####", + "has_expiry_date": 1, + "shelf_life_in_days": 5, + }, + ) + + pr = make_purchase_receipt( + qty=10, + rate=100, + item_code=item.name, + posting_date=today(), + ) + + pr.reload() + self.assertTrue(pr.items[0].serial_and_batch_bundle) + + batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle) + batch = frappe.get_doc("Batch", batch_no) + self.assertEqual(batch.manufacturing_date, getdate(today())) + self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5))) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From f2feeaf264febdbb3b9489b9d363c1e2798dd5f9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:49:55 +0530 Subject: [PATCH 158/203] fix: fixed asset value in Fixed Asset Register (backport #41930) (#42027) fix: fixed asset value in Fixed Asset Register (#41930) (cherry picked from commit 1c643a0ead6ab7f894e9fcf286a53fe566633a8b) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../fixed_asset_register.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 5d4ef4e3845..8ebf9d6d389 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -125,9 +125,10 @@ def get_data(filters): if assets_linked_to_fb and asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb: continue - asset_value = get_asset_value_after_depreciation( - asset.asset_id, finance_book - ) or get_asset_value_after_depreciation(asset.asset_id) + depreciation_amount = depreciation_amount_map.get(asset.asset_id) or 0.0 + asset_value = ( + asset.gross_purchase_amount - asset.opening_accumulated_depreciation - depreciation_amount + ) row = { "asset_id": asset.asset_id, @@ -139,7 +140,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, + "depreciated_amount": depreciation_amount, "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, @@ -185,11 +186,12 @@ def prepare_chart_data(data, filters): ) for d in data: - date = d.get(date_field) - belongs_to_month = formatdate(date, "MMM YYYY") + if d.get(date_field): + date = d.get(date_field) + belongs_to_month = formatdate(date, "MMM YYYY") - labels_values_map[belongs_to_month].asset_value += d.get("asset_value") - labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount") + labels_values_map[belongs_to_month].asset_value += d.get("asset_value") + labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount") return { "data": { From 63b26e679b05d8f2c334257ba4f8921ad72a48db Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:50:41 +0530 Subject: [PATCH 159/203] feat: Turkish Chart Of Accounts (backport #41756) (#42028) * feat: Create Turkish Chart Of Accounts (cherry picked from commit 5c8ea86a3f039077c50b0b9e6ecbae6e29bf574a) * feat: Create Turkish Chart Of Accounts (cherry picked from commit b401ba2c268fc22b99514d599e477c999293a773) --------- Co-authored-by: fzozyurt --- .../unverified/tr_l10ntr_tek_duzen_hesap.json | 531 ------ .../verified/tr_chart_of_accounts.json | 1473 +++++++++++++++++ 2 files changed, 1473 insertions(+), 531 deletions(-) delete mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json create mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json deleted file mode 100644 index dfc821eb53e..00000000000 --- a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json +++ /dev/null @@ -1,531 +0,0 @@ -{ - "country_code": "tr", - "name": "Turkey - Tek D\u00fczen Hesap Plan\u0131", - "tree": { - "Duran Varl\u0131klar": { - "Di\u011fer Alacaklar": { - "Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {}, - "Di\u011fer Alacak Senetleri Reeskontu(-)": {}, - "Di\u011fer \u00c7e\u015fitli Alacaklar": {}, - "Ortaklardan Alacaklar": {}, - "Personelden Alacaklar": {}, - "\u0130\u015ftiraklerden Alacaklar": {}, - "\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "Di\u011fer Duran Varl\u0131klar": { - "Birikmi\u015f Amortismanlar(-)": {}, - "Di\u011fer KDV": {}, - "Di\u011fer \u00c7e\u015fitli Duran Varl\u0131klar": {}, - "Elden \u00c7\u0131kar\u0131lacak Stoklar Ve Maddi Duran Varl\u0131klar": {}, - "Gelecek Y\u0131llar \u0130htiyac\u0131 Stoklar": {}, - "Gelecek Y\u0131llarda \u0130ndirilecek KDV": {}, - "Pe\u015fin \u00d6denen Vergi Ve Fonlar": {}, - "Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "Gelecek Y\u0131llara Ait Giderler ve Gelir Tahakkuklar\u0131": { - "Gelecek Y\u0131llara Ait Giderler": {}, - "Gelir Tahakkuklar\u0131": {} - }, - "Maddi Duran Varl\u0131klar": { - "Arazi Ve Arsalar": {}, - "Binalar": {}, - "Birikmi\u015f Amortismanlar(-)": {}, - "Demirba\u015flar": {}, - "Di\u011fer Maddi Duran Varl\u0131klar": {}, - "Ta\u015f\u0131tlar": {}, - "Tesis, Makine Ve Cihazlar": {}, - "Verilen Avanslar": {}, - "Yap\u0131lmakta Olan Yat\u0131r\u0131mlar": {}, - "Yer Alt\u0131 Ve Yer \u00dcst\u00fc D\u00fczenleri": {} - }, - "Maddi Olmayan Duran Varl\u0131klar": { - "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {}, - "Birikmi\u015f Amortismanlar(-)": {}, - "Di\u011fer Maddi Olmayan Duran Varl\u0131klar": {}, - "Haklar": {}, - "Kurulu\u015f Ve \u00d6rg\u00fctlenme Giderleri": {}, - "Verilen Avanslar": {}, - "\u00d6zel Maliyetler": {}, - "\u015eerefiye": {} - }, - "Mali Duran Varl\u0131klar": { - "Ba\u011fl\u0131 Menkul K\u0131ymetler": {}, - "Ba\u011fl\u0131 Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Ba\u011fl\u0131 Ortakl\u0131klar": {}, - "Ba\u011fl\u0131 Ortakl\u0131klar Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Ba\u011fl\u0131 Ortakl\u0131klara Sermaye Taahh\u00fctleri(-)": {}, - "Di\u011fer Mali Duran Varl\u0131klar": {}, - "Di\u011fer Mali Duran Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "\u0130\u015ftirakler": {}, - "\u0130\u015ftirakler Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "\u0130\u015ftiraklere Sermaye Taahh\u00fctleri(-)": {} - }, - "Ticari Alacaklar": { - "Alacak Senetleri": {}, - "Alacak Senetleri Reeskontu(-)": {}, - "Al\u0131c\u0131lar": {}, - "Kazaqn\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {}, - "Verilen Depozito Ve Teminatlar": {}, - "\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "root_type": "", - "\u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": { - "Arama Giderleri": {}, - "Birikmi\u015f T\u00fckenme Paylar\u0131(-)": {}, - "Di\u011fer \u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {}, - "Haz\u0131rl\u0131k Ve Geli\u015ftirme Giderleri": {}, - "Verilen Avanslar": {} - } - }, - "D\u00f6nen Varl\u0131klar": { - "Di\u011fer Alacaklar": { - "Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {}, - "Di\u011fer Alacak Senetleri Reeskontu(-)": {}, - "Di\u011fer \u00c7e\u015fitli Alacaklar": {}, - "Ortaklardan Alacaklar": {}, - "Personelden Alacaklar": {}, - "\u0130\u015ftiraklerden Alacaklar": {}, - "\u015e\u00fcpheli Di\u011fer Alacaklar": {}, - "\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "Di\u011fer D\u00f6nen Varl\u0131klar": { - "Devreden KDV": {}, - "Di\u011fer D\u00f6nen Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Di\u011fer KDV": {}, - "Di\u011fer \u00c7e\u015fitli D\u00f6nen Varl\u0131klar": {}, - "Personel Avanslar\u0131": {}, - "Pe\u015fin \u00d6denen Vergiler Ve Fonlar": {}, - "Say\u0131m Ve Tesell\u00fcm Noksanlar\u0131": {}, - "\u0130ndirilecek KDV": {}, - "\u0130\u015f Avanslar\u0131": {} - }, - "Gelecek Aylara Ait Giderler ve Gelir Tahakkuklar\u0131": { - "Gelecek Aylara Ait Giderler": {}, - "Gelir Tahakkuklar\u0131": {} - }, - "Haz\u0131r De\u011ferler": { - "Al\u0131nan \u00c7ekler": {}, - "Bankalar": { - "account_type": "Bank" - }, - "Di\u011fer Haz\u0131r De\u011ferler": {}, - "Kasa": { - "account_type": "Cash" - }, - "Verilen \u00c7ekler ve \u00d6deme Emirleri(-)": {} - }, - "Menkul K\u0131ymetler": { - "Di\u011fer Menkul K\u0131ymetler": {}, - "Hisse Senetleri": {}, - "Kamu Kesimi Tahvil, Senet ve Bonolar\u0131": {}, - "Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "\u00d6zel Kesim Tahvil Senet Ve Bonolar\u0131": {} - }, - "Stoklar": { - "Mamuller": {}, - "Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Ticari Mallar": {}, - "Verilen Sipari\u015f Avanslar\u0131": {}, - "Yar\u0131 Mamuller": {}, - "\u0130lk Madde Malzeme": {} - }, - "Ticari Alacaklar": { - "Alacak Senetleri": {}, - "Alacak Senetleri Reeskontu(-)": {}, - "Al\u0131c\u0131lar": {}, - "Di\u011fer Ticari Alacaklar": {}, - "Kazan\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {}, - "Verilen Depozito ve Teminatlar": {}, - "\u015e\u00fcpheli Ticari Alacaklar": {}, - "\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131": {} - }, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat ve Onar\u0131m Maliyetleri": { - "Ta\u015feronlara Verilen Avanslar": {}, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Maliyetleri": {} - }, - "root_type": "" - }, - "Gelir Tablosu Hesaplar\u0131": { - "Br\u00fct Sat\u0131\u015flar": { - "Di\u011fer Gelirler": {}, - "Yurt D\u0131\u015f\u0131 Sat\u0131\u015flar": {}, - "Yurt \u0130\u00e7i Sat\u0131\u015flar": {} - }, - "Di\u011fer Faaliyetlerden Olu\u015fan Gelir ve K\u00e2rlar": { - "Ba\u011fl\u0131 Ortakl\u0131klardan Temett\u00fc Gelirleri": {}, - "Di\u011fer Ola\u011fan Gelir Ve K\u00e2rlar": {}, - "Enflasyon D\u00fczeltme K\u00e2rlar\u0131": {}, - "Faiz Gelirleri": {}, - "Kambiyo K\u00e2rlar\u0131": {}, - "Komisyon Gelirleri": {}, - "Konusu Kalmayan Kar\u015f\u0131l\u0131klar": {}, - "Menkul K\u0131ymet Sat\u0131\u015f K\u00e2rlar\u0131": {}, - "Reeskont Faiz Gelirleri": {}, - "\u0130\u015ftiraklerden Temett\u00fc Gelirleri": {} - }, - "Di\u011fer Faaliyetlerden Olu\u015fan Gider ve Zararlar (-)": { - "Di\u011fer Ola\u011fan Gider Ve Zararlar(-)": {}, - "Enflasyon D\u00fczeltmesi Zararlar\u0131(-)": {}, - "Kambiyo Zararlar\u0131(-)": {}, - "Kar\u015f\u0131l\u0131k Giderleri(-)": {}, - "Komisyon Giderleri(-)": {}, - "Menkul K\u0131ymet Sat\u0131\u015f Zararlar\u0131(-)": {}, - "Reeskont Faiz Giderleri(-)": {} - }, - "D\u00f6nem Net K\u00e2r\u0131 Ve Zarar\u0131": { - "D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131(-)": {}, - "D\u00f6nem K\u00e2r\u0131 Veya Zarar\u0131": {}, - "D\u00f6nem Net K\u00e2r\u0131 Veya Zarar\u0131": {}, - "Enflasyon D\u00fczeltme Hesab\u0131": {}, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Enflasyon D\u00fczeltme Hesab\u0131": {} - }, - "Faaliyet Giderleri(-)": { - "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri(-)": {}, - "Genel Y\u00f6netim Giderleri(-)": {}, - "Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri(-)": {} - }, - "Finansman Giderleri": { - "K\u0131sa Vadeli Bor\u00e7lanma Giderleri(-)": {}, - "Uzun Vadeli Bor\u00e7lanma Giderleri(-)": {} - }, - "Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": { - "Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {}, - "\u00d6nceki D\u00f6nem Gelir Ve K\u00e2rlar\u0131": {} - }, - "Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zaralar(-)": { - "Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zararlar(-)": {}, - "\u00c7al\u0131\u015fmayan K\u0131s\u0131m Gider Ve Zararlar\u0131(-)": {}, - "\u00d6nceki D\u00f6nem Gider Ve Zararlar\u0131(-)": {} - }, - "Sat\u0131\u015f \u0130ndirimleri (-)": { - "Di\u011fer \u0130ndirimler": {}, - "Sat\u0131\u015f \u0130ndirimleri(-)": {}, - "Sat\u0131\u015ftan \u0130adeler(-)": {} - }, - "Sat\u0131\u015flar\u0131n Maliyeti(-)": { - "Di\u011fer Sat\u0131\u015flar\u0131n Maliyeti(-)": {}, - "Sat\u0131lan Hizmet Maliyeti(-)": {}, - "Sat\u0131lan Mamuller Maliyeti(-)": {}, - "Sat\u0131lan Ticari Mallar Maliyeti(-)": {} - }, - "root_type": "" - }, - "K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": { - "Al\u0131nan Avanslar": { - "Al\u0131nan Di\u011fer Avanslar": { - "account_type": "Payable" - }, - "Al\u0131nan Sipari\u015f Avanslar\u0131": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Bor\u00e7 ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "account_type": "Payable" - }, - "D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131": { - "account_type": "Tax" - }, - "D\u00f6nem K\u00e2r\u0131n\u0131n Pe\u015fin \u00d6denen Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler(-)": { - "account_type": "Tax" - }, - "K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {}, - "Maliyet Giderleri Kar\u015f\u0131l\u0131\u011f\u0131": {}, - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7lar": { - "Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": { - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ortaklara Bor\u00e7lar": { - "account_type": "Payable" - }, - "Personele Bor\u00e7lar": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u0130\u015ftiraklere Bor\u00e7lar": { - "account_type": "Payable" - } - }, - "Di\u011fer K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": { - "Di\u011fer KDV": { - "account_type": "Tax" - }, - "Di\u011fer \u00c7e\u015fitli Yabanc\u0131 Kaynaklar": {}, - "Hesaplanan KDV": { - "account_type": "Tax" - }, - "Merkez Ve \u015eubeler Cari Hesab\u0131": {}, - "Say\u0131m Ve Tesell\u00fcm Fazlalar\u0131": {}, - "account_type": "Payable" - }, - "Gelecek Aylara Ait Gelirler Ve Gider Tahakkuklar\u0131": { - "Gelecek Aylara Ait Gelirler": {}, - "Gider Tahakkuklar\u0131": {} - }, - "Mali Bor\u00e7lar": { - "Banka Kredileri": { - "account_type": "Payable" - }, - "Di\u011fer Mali Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": { - "account_type": "Payable" - }, - "Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": { - "account_type": "Payable" - }, - "Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": { - "account_type": "Payable" - }, - "Tahvil Anapara Bor\u00e7, Taksit Ve Faizleri": { - "account_type": "Payable" - }, - "Uzun Vadeli Kredilerin Anapara Taksitleri Ve Faizleri": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u00c7\u0131kar\u0131lan Bonolar Ve Senetler": { - "account_type": "Payable" - }, - "\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": { - "account_type": "Payable" - } - }, - "Ticari Bor\u00e7lar": { - "Al\u0131nan Depozito Ve Teminatlar": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer Ticari Bor\u00e7lar": { - "account_type": "Payable" - }, - "Sat\u0131c\u0131lar": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri": { - "350 Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri Bedelleri": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "root_type": "", - "\u00d6denecek Vergi ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": { - "Vadesi Ge\u00e7mi\u015f, Ertelenmi\u015f Veya Taksitlendirilmi\u015f Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": { - "account_type": "Tax" - }, - "account_type": "Tax", - "\u00d6denecek Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": { - "account_type": "Tax" - }, - "\u00d6denecek Sosyal G\u00fcvenl\u00fck Kesintileri": { - "account_type": "Tax" - }, - "\u00d6denecek Vergi Ve Fonlar": { - "account_type": "Tax" - } - } - }, - "Maliyet Hesaplar\u0131": { - "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {}, - "Direkt \u0130lk Madde Ve Malzeme Giderleri": { - "Direk \u0130lk Madde Ve Malzeme Giderleri Hesab\u0131": {}, - "Direkt \u0130lk Madde Ve Malzeme Fiyat Fark\u0131": {}, - "Direkt \u0130lk Madde Ve Malzeme Miktar Fark\u0131": {}, - "Direkt \u0130lk Madde Ve Malzeme Yans\u0131tma Hesab\u0131": {} - }, - "Direkt \u0130\u015f\u00e7ilik Giderleri": { - "Direkt \u0130\u015f\u00e7ilik Giderleri": {}, - "Direkt \u0130\u015f\u00e7ilik Giderleri Yans\u0131tma Hesab\u0131": {}, - "Direkt \u0130\u015f\u00e7ilik S\u00fcre Farklar\u0131": {}, - "Direkt \u0130\u015f\u00e7ilik \u00dccret Farklar\u0131": {} - }, - "Finansman Giderleri": { - "Finansman Giderleri": {}, - "Finansman Giderleri Fark Hesab\u0131": {}, - "Finansman Giderleri Yans\u0131tma Hesab\u0131": {} - }, - "Genel Y\u00f6netim Giderleri": { - "Genel Y\u00f6netim Gider Farklar\u0131 Hesab\u0131": {}, - "Genel Y\u00f6netim Giderleri": {}, - "Genel Y\u00f6netim Giderleri Yans\u0131tma Hesab\u0131": {} - }, - "Genel \u00dcretim Giderleri": { - "Genel \u00dcretim Giderleri": {}, - "Genel \u00dcretim Giderleri B\u00fct\u00e7e Farklar\u0131": {}, - "Genel \u00dcretim Giderleri Kapasite Farklar\u0131": {}, - "Genel \u00dcretim Giderleri Verimlilik Giderleri": {}, - "Genel \u00dcretim Giderleri Yans\u0131tma Hesab\u0131": {} - }, - "Hizmet \u00dcretim Maliyeti": { - "Hizmet \u00dcretim Maliyeti": {}, - "Hizmet \u00dcretim Maliyeti Fark Hesaplar\u0131": {}, - "Hizmet \u00dcretim Maliyeti Yans\u0131tma Hesab\u0131": {} - }, - "Maliyet Muhasebesi Ba\u011flant\u0131 Hesaplar\u0131": { - "Maliyet Muhasebesi Ba\u011flant\u0131 Hesab\u0131": {}, - "Maliyet Muhasebesi Yans\u0131tma Hesab\u0131": {} - }, - "Pazarlama, Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri": { - "Atra\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {}, - "Pazarlama Sat\u0131\u015f Ve Dag\u0131t\u0131m Giderleri Yans\u0131tma Hesab\u0131": {}, - "Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri Fark Hesab\u0131": {} - }, - "root_type": "" - }, - "Naz\u0131m Hesaplar": { - "root_type": "" - }, - "Serbest Hesaplar": { - "root_type": "" - }, - "Uzun Vadeli Yabanc\u0131 Kaynaklar": { - "Al\u0131nan Avanslar": { - "Al\u0131nan Di\u011fer Avanslar": { - "account_type": "Payable" - }, - "Al\u0131nan Sipari\u015f Avanslar\u0131": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "account_type": "Payable" - }, - "K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {}, - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7lar": { - "Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": { - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": { - "account_type": "Payable" - }, - "Kamuya Olan Ertelenmi\u015f Veya Taksitlendirilmi\u015f Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ortaklara Bor\u00e7lar": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u0130\u015ftiraklere Bor\u00e7lar": { - "account_type": "Payable" - } - }, - "Di\u011fer Uzun Vadeli Yabanc\u0131 Kaynaklar": { - "Di\u011fer \u00c7e\u015fitli Uzun Vadeli Yabanc\u0131 Kaynaklar": { - "account_type": "Payable" - }, - "Gelecek Y\u0131llara Ertelenmi\u015f Veya Terkin Edilecek KDV": { - "account_type": "Payable" - }, - "Tesise Kat\u0131lma Paylar\u0131": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Gelecek Y\u0131llara Ait Gelirler Ve Gider Tahakkuklar\u0131": { - "Gelecek Y\u0131llara Ait Gelirler": {}, - "Gider Tahakkuklar\u0131": {} - }, - "Mali Bor\u00e7lar": { - "Banka Kredileri": { - "account_type": "Payable" - }, - "Di\u011fer Mali Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": { - "account_type": "Payable" - }, - "Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": { - "account_type": "Payable" - }, - "Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": { - "account_type": "Payable" - }, - "\u00c7\u0131kar\u0131lm\u0131\u015f Tahviller": { - "account_type": "Payable" - } - }, - "Ticari Bor\u00e7lar": { - "Al\u0131nan Depozito Ve Teminatlar": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer Ticari Bor\u00e7lar": { - "account_type": "Payable" - }, - "Sat\u0131c\u0131lar": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "root_type": "" - }, - "\u00d6z Kaynaklar": { - "D\u00f6nem Net K\u00e2r\u0131 (Zarar\u0131)": { - "D\u00f6nem Net K\u00e2r\u0131": {}, - "D\u00f6nem Net Zarar\u0131(-)": {} - }, - "Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": { - "Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {} - }, - "Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": { - "Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {} - }, - "K\u00e2r Yedekleri": { - "Di\u011fer K\u00e2r Yedekleri": {}, - "Ola\u011fan\u00fcst\u00fc Yedekler": {}, - "Stat\u00fc Yedekleri": {}, - "Yasal Yedekler": {}, - "\u00d6zel Fonlar": {} - }, - "Sermaye Yedekleri": { - "Di\u011fer Sermaye Yedekleri": {}, - "Hisse Senedi \u0130ptal K\u00e2rlar\u0131": {}, - "Hisse Senetleri \u0130hra\u00e7 Primleri": {}, - "Maddi Duran Varl\u0131k Yeniden De\u011ferlenme Art\u0131\u015flar\u0131": {}, - "Maliyet Art\u0131\u015flar\u0131 Fonu": {}, - "\u0130\u015ftirakler Yeniden De\u011ferleme Art\u0131\u015flar\u0131": {} - }, - "root_type": "", - "\u00d6denmi\u015f Sermaye": { - "Sermaye": {}, - "\u00d6denmi\u015f Sermaye(-)": { - "account_type": "Payable" - } - } - } - } -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json new file mode 100644 index 00000000000..fa8860883d2 --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json @@ -0,0 +1,1473 @@ +{ + "country_code": "tr", + "name": "Turkey - Chart of Accounts", + "tree": { + "D\u00d6NEN VARLIKLAR": { + "account_number": "1", + "root_type": "Asset", + "HAZIR DE\u011eERLER": { + "account_number": "10", + "KASA": { + "account_number": "100", + "is_group": 1, + "account_type": "Cash", + "TL KASA": { + "account_number": "100.01", + "account_type": "Cash" + }, + "EUR KASA": { + "account_number": "100.02", + "account_type": "Cash" + }, + "USD KASA": { + "account_number": "100.03", + "account_type": "Cash" + } + }, + "ALINAN \u00c7EKLER": { + "account_number": "101", + "is_group": 1 + }, + "BANKALAR": { + "account_number": "102", + "is_group": 1, + "account_type": "Bank" + }, + "VER\u0130LEN \u00c7EKLER VE \u00d6DEME EM\u0130RLER\u0130(-)": { + "account_number": "103", + "is_group": 1 + }, + "D\u0130\u011eER HAZIR DE\u011eERLER": { + "account_number": "108", + "is_group": 1 + } + }, + "MENKUL KIYMETLER": { + "account_number": "11", + "H\u0130SSE SENETLER\u0130": { + "account_number": "110", + "is_group": 1 + }, + "\u00d6ZEL KES\u0130M TAHV\u0130L SENET VE BONOLARI": { + "account_number": "111", + "is_group": 1 + }, + "KAMU KES\u0130M\u0130 TAHV\u0130L, SENET VE BONOLARI": { + "account_number": "112", + "is_group": 1 + }, + "D\u0130\u011eER MENKUL KIYMETLER": { + "account_number": "118", + "is_group": 1 + }, + "MENKUL KIYMETLER DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "119", + "is_group": 1 + } + }, + "T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "12", + "ALICILAR": { + "account_number": "120", + "ALICILAR": { + "account_number": "120.01", + "account_type": "Receivable" + } + }, + "ALACAK SENETLER\u0130": { + "account_number": "121", + "is_group": 1 + }, + "ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "122", + "is_group": 1 + }, + "KAZANILMAMI\u015e F\u0130NANSAL K\u0130RALAMA FA\u0130Z GEL\u0130RLER\u0130 (-)": { + "account_number": "124", + "is_group": 1 + }, + "VER\u0130LEN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "126", + "is_group": 1 + }, + "D\u0130\u011eER T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "127", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "128", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "129", + "is_group": 1 + } + }, + "D\u0130\u011eER ALACAKLAR": { + "account_number": "13", + "ORTAKLARDAN ALACAKLAR": { + "account_number": "131", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERDEN ALACAKLAR": { + "account_number": "132", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARDAN ALACAKLAR": { + "account_number": "133", + "is_group": 1 + }, + "PERSONELDEN ALACAKLAR": { + "account_number": "135", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 ALACAKLAR": { + "account_number": "136", + "is_group": 1 + }, + "D\u0130\u011eER ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "137", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR": { + "account_number": "138", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "139", + "is_group": 1 + } + }, + "STOKLAR": { + "account_number": "15", + "\u0130LK MADDE MALZEME": { + "account_number": "150", + "is_group": 1 + }, + "YARI MAMULLER": { + "account_number": "151", + "is_group": 1 + }, + "MAMULLER": { + "account_number": "152", + "is_group": 1 + }, + "T\u0130CAR\u0130 MALLAR": { + "account_number": "153", + "is_group": 1 + }, + "D\u0130\u011eER STOKLAR": { + "account_number": "157", + "D\u0130\u011eR. STOK.": { + "account_number": "157.01", + "account_type": "Stock" + } + }, + "STOK DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "158", + "is_group": 1 + }, + "VER\u0130LEN S\u0130PAR\u0130\u015e AVANSLARI": { + "account_number": "159", + "is_group": 1 + } + }, + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130": { + "account_number": "17", + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130": { + "account_number": "170", + "is_group": 1 + }, + "(YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130)": { + "account_number": "177", + "is_group": 1 + }, + "(YILLARA YAYGIN \u0130N\u015eAAT ENFLASYON D\u00dcZELTME)": { + "account_number": "178", + "is_group": 1 + }, + "TA\u015eERONLARA VER\u0130LEN AVANSLAR": { + "account_number": "179", + "is_group": 1 + } + }, + "GELECEK AYLARA A\u0130T G\u0130DERLER VE GEL\u0130R TAHAKKUKLARI": { + "account_number": "18", + "GELECEK AYLARA A\u0130T G\u0130DERLER": { + "account_number": "180", + "is_group": 1 + }, + "GEL\u0130R TAHAKKUKLARI": { + "account_number": "181", + "is_group": 1 + } + }, + "D\u0130\u011eER D\u00d6NEN VARLIKLAR": { + "account_number": "19", + "DEVREDEN KDV": { + "account_number": "190", + "is_group": 1 + }, + "\u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191", + "\u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01", + "% 18'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.01", + "account_type": "Tax" + }, + "% 8'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.02", + "account_type": "Tax" + }, + "% 1'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.03", + "account_type": "Tax" + }, + "% 20'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.04", + "account_type": "Tax" + }, + "% 10'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.05", + "account_type": "Tax" + } + } + }, + "D\u0130\u011eER KDV": { + "account_number": "192", + "is_group": 1 + }, + "PE\u015e\u0130N \u00d6DENEN VERG\u0130LER VE FONLAR": { + "account_number": "193", + "is_group": 1 + }, + "\u0130\u015e AVANSLARI": { + "account_number": "195", + "is_group": 1 + }, + "PERSONEL AVANSLARI": { + "account_number": "196", + "is_group": 1 + }, + "SAYIM VE TESELL\u00dcM NOKSANLARI": { + "account_number": "197", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 D\u00d6NEN VARLIKLAR": { + "account_number": "198", + "is_group": 1 + }, + "D\u0130\u011eER D\u00d6NEN VARLIKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "199", + "is_group": 1 + } + } + }, + "DURAN VARLIKLAR": { + "account_number": "2", + "root_type": "Asset", + "T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "22", + "ALICILAR": { + "account_number": "220", + "is_group": 1 + }, + "ALACAK SENETLER\u0130": { + "account_number": "221", + "is_group": 1 + }, + "ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "222", + "is_group": 1 + }, + "KAZANILMAMI\u015e F\u0130NANSAL K\u0130RALAMA FA\u0130Z GEL\u0130RLER\u0130 (-)-": { + "account_number": "224", + "is_group": 1 + }, + "VER\u0130LEN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "226", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "229", + "is_group": 1 + } + }, + "D\u0130\u011eER ALACAKLAR": { + "account_number": "23", + "ORTAKLARDAN ALACAKLAR": { + "account_number": "231", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERDEN ALACAKLAR": { + "account_number": "232", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARDAN ALACAKLAR": { + "account_number": "233", + "is_group": 1 + }, + "PERSONELDEN ALACAKLAR": { + "account_number": "235", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 ALACAKLAR": { + "account_number": "236", + "is_group": 1 + }, + "D\u0130\u011eER ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "237", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "239", + "is_group": 1 + } + }, + "MAL\u0130 DURAN VARLIKLAR": { + "account_number": "24", + "BA\u011eLI MENKUL KIYMETLER": { + "account_number": "240", + "BA\u011eL. MEKL. KIYM.": { + "account_number": "240.01", + "account_type": "Fixed Asset" + } + }, + "BA\u011eLI MENKUL KIYMETLER DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "241", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLER": { + "account_number": "242", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERE SERMAYE TAAHH\u00dcTLER\u0130(-)": { + "account_number": "243", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLER SERMAYE PAYLARI DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "244", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLAR": { + "account_number": "245", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARA SERMAYE TAAHH\u00dcTLER\u0130(-)": { + "account_number": "246", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLAR SERMAYE PAYLARI DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "247", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 DURAN VARLIKLAR": { + "account_number": "248", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 DURAN VARLIKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "249", + "is_group": 1 + } + }, + "MADD\u0130 DURAN VARLIKLAR": { + "account_number": "25", + "ARAZ\u0130 VE ARSALAR": { + "account_number": "250", + "is_group": 1 + }, + "YER ALTI VE YER \u00dcST\u00dc D\u00dcZENLER\u0130": { + "account_number": "251", + "is_group": 1 + }, + "B\u0130NALAR": { + "account_number": "252", + "is_group": 1 + }, + "TES\u0130S, MAK\u0130NE VE C\u0130HAZLAR": { + "account_number": "253", + "is_group": 1 + }, + "TA\u015eITLAR": { + "account_number": "254", + "is_group": 1 + }, + "DEM\u0130RBA\u015eLAR": { + "account_number": "255", + "is_group": 1 + }, + "D\u0130\u011eER MADD\u0130 DURAN VARLIKLAR": { + "account_number": "256", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": { + "account_number": "257", + "is_group": 1 + }, + "YAPILMAKTA OLAN YATIRIMLAR": { + "account_number": "258", + "is_group": 1 + }, + "VER\u0130LEN AVANSLAR": { + "account_number": "259", + "is_group": 1 + } + }, + "MADD\u0130 OLMAYAN DURAN VARLIKLAR": { + "account_number": "26", + "HAKLAR": { + "account_number": "260", + "is_group": 1 + }, + "\u015eEREF\u0130YE": { + "account_number": "261", + "is_group": 1 + }, + "KURULU\u015e VE \u00d6RG\u00dcTLENME G\u0130DERLER\u0130": { + "account_number": "262", + "is_group": 1 + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "263", + "is_group": 1 + }, + "\u00d6ZEL MAL\u0130YETLER": { + "account_number": "264", + "\u00d6ZL. MAL\u0130YT.": { + "account_number": "264.01", + "account_type": "Depreciation" + } + }, + "D\u0130\u011eER MADD\u0130 OLMAYAN DURAN VARLIKLAR": { + "account_number": "267", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": { + "account_number": "268", + "is_group": 1 + }, + "VER\u0130LEN AVANSLAR": { + "account_number": "269", + "is_group": 1 + } + }, + "\u00d6ZEL T\u00dcKENMEYE TAB\u0130 VARLIKLAR": { + "account_number": "27", + "ARAMA G\u0130DERLER\u0130": { + "account_number": "271", + "is_group": 1 + }, + "HAZIRLIK VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "272", + "is_group": 1 + }, + "D\u0130\u011eER \u00d6ZEL T\u00dcKENMEYE TAB\u0130 VARLIKLAR": { + "account_number": "277", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e T\u00dcKENME PAYLARI(-)": { + "account_number": "278", + "is_group": 1 + }, + "VER\u0130LEN AVANSLAR": { + "account_number": "279", + "is_group": 1 + } + }, + "GELECEK YILLARA A\u0130T G\u0130DERLER VE GEL\u0130R TAHAKKUKLARI": { + "account_number": "28", + "GELECEK YILLARA A\u0130T G\u0130DERLER": { + "account_number": "280", + "is_group": 1 + }, + "GEL\u0130R TAHAKKUKLARI": { + "account_number": "281", + "is_group": 1 + } + }, + "D\u0130\u011eER DURAN VARLIKLAR": { + "account_number": "29", + "GELECEK YILLARDA \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "291", + "is_group": 1 + }, + "D\u0130\u011eER KDV": { + "account_number": "292", + "is_group": 1 + }, + "GELECEK YILLAR \u0130HT\u0130YACI STOKLAR": { + "account_number": "293", + "is_group": 1 + }, + "ELDEN \u00c7IKARILACAK STOKLAR VE MADD\u0130 DURAN VARLIKLAR": { + "account_number": "294", + "is_group": 1 + }, + "PE\u015e\u0130N \u00d6DENEN VERG\u0130 VE FONLAR": { + "account_number": "295", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 DURAN VARLIKLAR": { + "account_number": "297", + "is_group": 1 + }, + "STOK DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "298", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": { + "account_number": "299", + "is_group": 1 + } + } + }, + "KISA VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "3", + "root_type": "Liability", + "MAL\u0130 BOR\u00c7LAR": { + "account_number": "30", + "BANKA KRED\u0130LER\u0130": { + "account_number": "300", + "is_group": 1 + }, + "F\u0130NANSAL K\u0130RALAMA \u0130\u015eLEMLER\u0130NDEN BOR\u00c7LAR": { + "account_number": "301", + "is_group": 1 + }, + "ERTELENM\u0130\u015e F\u0130NANSAL K\u0130RALAMA BOR\u00c7LANMA MAL\u0130YETLER\u0130(-)": { + "account_number": "302", + "is_group": 1 + }, + "UZUN VADEL\u0130 KRED\u0130LER\u0130N ANAPARA TAKS\u0130TLER\u0130 VE FA\u0130ZLER\u0130": { + "account_number": "303", + "is_group": 1 + }, + "TAHV\u0130L ANAPARA BOR\u00c7, TAKS\u0130T VE FA\u0130ZLER\u0130": { + "account_number": "304", + "is_group": 1 + }, + "\u00c7IKARILAN BONOLAR VE SENETLER": { + "account_number": "305", + "is_group": 1 + }, + "\u00c7IKARILMI\u015e D\u0130\u011eER MENKUL KIYMETLER": { + "account_number": "306", + "is_group": 1 + }, + "MENKUL KIYMETLER \u0130HRA\u00c7 FARKI(-)": { + "account_number": "308", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 BOR\u00c7LAR": { + "account_number": "309", + "is_group": 1 + } + }, + "T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "32", + "SATICILAR": { + "account_number": "320", + "SATICILAR TRY": { + "account_number": "320.01" + } + }, + "BOR\u00c7 SENETLER\u0130": { + "account_number": "321", + "is_group": 1 + }, + "BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "322", + "is_group": 1 + }, + "ALINAN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "326", + "is_group": 1 + }, + "D\u0130\u011eER T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "329", + "is_group": 1 + } + }, + "D\u0130\u011eER BOR\u00c7LAR": { + "account_number": "33", + "ORTAKLARA BOR\u00c7LAR": { + "account_number": "331", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERE BOR\u00c7LAR": { + "account_number": "332", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARA BOR\u00c7LAR": { + "account_number": "333", + "is_group": 1 + }, + "PERSONELE BOR\u00c7LAR": { + "account_number": "335", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 BOR\u00c7LAR": { + "account_number": "336", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "337", + "is_group": 1 + } + }, + "ALINAN AVANSLAR": { + "account_number": "34", + "ALINAN S\u0130PAR\u0130\u015e AVANSLARI": { + "account_number": "340", + "is_group": 1 + }, + "ALINAN D\u0130\u011eER AVANSLAR": { + "account_number": "349", + "is_group": 1 + } + }, + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM HAKED\u0130\u015eLER\u0130": { + "account_number": "35", + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM HAKED\u0130\u015e BEDELLER\u0130": { + "account_number": "358", + "is_group": 1 + } + }, + "\u00d6DENECEK VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": { + "account_number": "36", + "\u00d6DENECEK VERG\u0130 VE FONLAR": { + "account_number": "360", + "is_group": 1 + }, + "\u00d6DENECEK SOSYAL G\u00dcVENL\u0130K KES\u0130NT\u0130LER\u0130": { + "account_number": "361", + "is_group": 1 + }, + "VADES\u0130 GE\u00c7M\u0130\u015e, ERTELENM\u0130\u015e VEYA TAKS\u0130TLEND\u0130R\u0130LM\u0130\u015e VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": { + "account_number": "368", + "is_group": 1 + }, + "\u00d6DENECEK D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": { + "account_number": "369", + "is_group": 1 + } + }, + "BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "37", + "D\u00d6NEM K\u00c2RI VERG\u0130 VE D\u0130\u011eER YASAL Y\u00dcK\u00dcML\u00dcL\u00dcK KAR\u015eILIKLARI": { + "account_number": "370", + "is_group": 1 + }, + "D\u00d6NEM K\u00c2RININ PE\u015e\u0130N \u00d6DENEN VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER\u0130(-)": { + "account_number": "371", + "is_group": 1 + }, + "KIDEM TAZM\u0130NATI KAR\u015eILI\u011eI": { + "account_number": "372", + "is_group": 1 + }, + "MAL\u0130YET G\u0130DERLER\u0130 KAR\u015eILI\u011eI": { + "account_number": "373", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "379", + "is_group": 1 + } + }, + "GELECEK AYLARA A\u0130T GEL\u0130RLER VE G\u0130DER TAHAKKUKLARI": { + "account_number": "38", + "GELECEK AYLARA A\u0130T GEL\u0130RLER": { + "account_number": "380", + "is_group": 1 + }, + "G\u0130DER TAHAKKUKLARI": { + "account_number": "381", + "is_group": 1 + } + }, + "D\u0130\u011eER KISA VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "39", + "HESAPLANAN KDV": { + "account_number": "391", + "HESAPL. KDV": { + "account_number": "391.01", + "% 18 HESAPLAN KDV": { + "account_number": "391.01.01", + "account_type": "Tax" + }, + "% 8 HESAPLANAN KDV": { + "account_number": "391.01.02", + "account_type": "Tax" + }, + "% 1 HESAPLANAN KDV": { + "account_number": "391.01.03", + "account_type": "Tax" + }, + "% 20 HESAPLANAN KDV": { + "account_number": "391.01.04", + "account_type": "Tax" + }, + "% 10 HESAPLANAN KDV": { + "account_number": "391.01.05", + "account_type": "Tax" + } + } + }, + "D\u0130\u011eER KDV": { + "account_number": "392", + "is_group": 1 + }, + "MERKEZ VE \u015eUBELER CAR\u0130 HESABI": { + "account_number": "393", + "is_group": 1 + }, + "SAYIM VE TESELL\u00dcM FAZLALARI": { + "account_number": "397", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 YABANCI KAYNAKLAR": { + "account_number": "399", + "is_group": 1 + } + } + }, + "UZUN VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "4", + "root_type": "Liability", + "MAL\u0130 BOR\u00c7LAR": { + "account_number": "40", + "BANKA KRED\u0130LER\u0130": { + "account_number": "400", + "is_group": 1 + }, + "F\u0130NANSAL K\u0130RALAMA \u0130\u015eLEMLER\u0130NDEN BOR\u00c7LAR-": { + "account_number": "401", + "is_group": 1 + }, + "ERTELENM\u0130\u015e F\u0130NANSAL K\u0130RALAMA BOR\u00c7LANMA MAL\u0130YETLER\u0130 (-)": { + "account_number": "402", + "is_group": 1 + }, + "\u00c7IKARILMI\u015e TAHV\u0130LLER": { + "account_number": "405", + "is_group": 1 + }, + "\u00c7IKARILMI\u015e D\u0130\u011eER MENKUL KIYMETLER": { + "account_number": "407", + "is_group": 1 + }, + "MENKUL KIYMETLER \u0130HRA\u00c7 FARKI(-)": { + "account_number": "408", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 BOR\u00c7LAR": { + "account_number": "409", + "is_group": 1 + } + }, + "T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "42", + "SATICILAR": { + "account_number": "420", + "is_group": 1 + }, + "BOR\u00c7 SENETLER\u0130": { + "account_number": "421", + "is_group": 1 + }, + "BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "422", + "is_group": 1 + }, + "ALINAN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "426", + "is_group": 1 + }, + "D\u0130\u011eER T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "429", + "is_group": 1 + } + }, + "D\u0130\u011eER BOR\u00c7LAR": { + "account_number": "43", + "ORTAKLARA BOR\u00c7LAR": { + "account_number": "431", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERE BOR\u00c7LAR": { + "account_number": "432", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARA BOR\u00c7LAR": { + "account_number": "433", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 BOR\u00c7LAR": { + "account_number": "436", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "437", + "is_group": 1 + }, + "KAMUYA OLAN ERTELENM\u0130\u015e VEYA TAKS\u0130TLEND\u0130R\u0130LM\u0130\u015e BOR\u00c7LAR": { + "account_number": "438", + "is_group": 1 + } + }, + "ALINAN AVANSLAR": { + "account_number": "44", + "ALINAN S\u0130PAR\u0130\u015e AVANSLARI": { + "account_number": "440", + "is_group": 1 + }, + "ALINAN D\u0130\u011eER AVANSLAR": { + "account_number": "449", + "is_group": 1 + } + }, + "BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "47", + "KIDEM TAZM\u0130NATI KAR\u015eILI\u011eI": { + "account_number": "472", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "479", + "is_group": 1 + } + }, + "GELECEK YILLARA A\u0130T GEL\u0130RLER VE G\u0130DER TAHAKKUKLARI": { + "account_number": "48", + "GELECEK YILLARA A\u0130T GEL\u0130RLER": { + "account_number": "480", + "is_group": 1 + }, + "G\u0130DER TAHAKKUKLARI": { + "account_number": "481", + "is_group": 1 + } + }, + "D\u0130\u011eER UZUN VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "49", + "GELECEK YILLARA ERTELENM\u0130\u015e VEYA TERK\u0130N ED\u0130LECEK KDV": { + "account_number": "492", + "is_group": 1 + }, + "TES\u0130SE KATILMA PAYLARI": { + "account_number": "493", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 UZUN VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "499", + "is_group": 1 + } + } + }, + "\u00d6Z KAYNAKLAR": { + "account_number": "5", + "root_type": "Equity", + "\u00d6DENM\u0130\u015e SERMAYE": { + "account_number": "50", + "SERMAYE": { + "account_number": "500", + "is_group": 1 + }, + "\u00d6DENMEM\u0130\u015e SERMAYE(-)": { + "account_number": "501", + "is_group": 1 + }, + "SERMAYE D\u00dcZELTMES\u0130 OLUMLU FARKLARI": { + "account_number": "502", + "is_group": 1 + }, + "SERMAYE D\u00dcZELTMES\u0130 OLUMSUZ FARKLARI (-)": { + "account_number": "503", + "is_group": 1 + } + }, + "SERMAYE YEDEKLER\u0130": { + "account_number": "52", + "H\u0130SSE SENETLER\u0130 \u0130HRA\u00c7 PR\u0130MLER\u0130": { + "account_number": "520", + "is_group": 1 + }, + "H\u0130SSE SENED\u0130 \u0130PTAL K\u00c2RLARI": { + "account_number": "521", + "is_group": 1 + }, + "MADD\u0130 DURAN VARLIK YEN\u0130DEN DE\u011eERLEME ARTI\u015eLARI": { + "account_number": "522", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLER YEN\u0130DEN DE\u011eERLEME ARTI\u015eLARI": { + "account_number": "523", + "is_group": 1 + }, + "D\u0130\u011eER SERMAYE YEDEKLER\u0130": { + "account_number": "529", + "is_group": 1 + } + }, + "K\u00c2R YEDEKLER\u0130": { + "account_number": "54", + "YASAL YEDEKLER": { + "account_number": "540", + "is_group": 1 + }, + "STAT\u00dc YEDEKLER\u0130": { + "account_number": "541", + "is_group": 1 + }, + "OLA\u011eAN\u00dcST\u00dc YEDEKLER": { + "account_number": "542", + "is_group": 1 + }, + "D\u0130\u011eER K\u00c2R YEDEKLER\u0130": { + "account_number": "548", + "is_group": 1 + }, + "\u00d6ZEL FONLAR": { + "account_number": "549", + "is_group": 1 + } + }, + "GE\u00c7M\u0130\u015e YILLAR K\u00c2RLARI": { + "account_number": "57", + "GE\u00c7M\u0130\u015e YILLAR K\u00c2RLARI": { + "account_number": "570", + "is_group": 1 + } + }, + "GE\u00c7M\u0130\u015e YILLAR ZARARLARI(-)": { + "account_number": "58", + "GE\u00c7M\u0130\u015e YILLAR ZARARLARI(-)": { + "account_number": "580", + "is_group": 1 + } + }, + "D\u00d6NEM NET K\u00c2RI (ZARARI)": { + "account_number": "59", + "D\u00d6NEM NET K\u00c2RI": { + "account_number": "590", + "is_group": 1 + }, + "D\u00d6NEM NET ZARARI(-)": { + "account_number": "591", + "is_group": 1 + } + } + }, + "GEL\u0130R TABLOSU HESAPLARI": { + "account_number": "6", + "root_type": "Income", + "BR\u00dcT SATI\u015eLAR": { + "account_number": "60", + "YURT \u0130\u00c7\u0130 SATI\u015eLAR": { + "account_number": "600", + "is_group": 1 + }, + "YURT DI\u015eI SATI\u015eLAR": { + "account_number": "601", + "is_group": 1 + }, + "D\u0130\u011eER GEL\u0130RLER": { + "account_number": "602", + "is_group": 1 + } + }, + "SATI\u015e \u0130ND\u0130R\u0130MLER\u0130(-)": { + "account_number": "61", + "SATI\u015eTAN \u0130ADELER(-)": { + "account_number": "610", + "is_group": 1 + }, + "SATI\u015e \u0130ND\u0130R\u0130MLER\u0130(-)": { + "account_number": "611", + "is_group": 1 + }, + "D\u0130\u011eER \u0130ND\u0130R\u0130MLER(-)": { + "account_number": "612", + "is_group": 1 + } + }, + "SATI\u015eLARIN MAL\u0130YET\u0130(-)": { + "account_number": "62", + "SATILAN MAMULLER MAL\u0130YET\u0130(-)": { + "account_number": "620", + "is_group": 1 + }, + "SATILAN T\u0130CAR\u0130 MALLAR MAL\u0130YET\u0130(-)": { + "account_number": "621", + "is_group": 1 + }, + "SATILAN H\u0130ZMET MAL\u0130YET\u0130(-)": { + "account_number": "622", + "is_group": 1 + }, + "D\u0130\u011eER SATI\u015eLARIN MAL\u0130YET\u0130(-)": { + "account_number": "623", + "D\u0130\u011eR. SAT\u015e. MALYT.": { + "account_number": "623.01" + } + } + }, + "FAAL\u0130YET G\u0130DERLER\u0130(-)": { + "account_number": "63", + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130(-)": { + "account_number": "630", + "is_group": 1 + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130(-)": { + "account_number": "631", + "is_group": 1 + }, + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130(-)": { + "account_number": "632", + "is_group": 1 + } + }, + "D\u0130\u011eER FAAL\u0130YETLERDEN OLA\u011eAN GEL\u0130R VE K\u00c2RLAR": { + "account_number": "64", + "\u0130\u015eT\u0130RAKLERDEN TEMETT\u00dc GEL\u0130RLER\u0130": { + "account_number": "640", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARDAN TEMETT\u00dc GEL\u0130RLER\u0130": { + "account_number": "641", + "is_group": 1 + }, + "FA\u0130Z GEL\u0130RLER\u0130": { + "account_number": "642", + "is_group": 1 + }, + "KOM\u0130SYON GEL\u0130RLER\u0130": { + "account_number": "643", + "is_group": 1 + }, + "KONUSU KALMAYAN KAR\u015eILIKLAR": { + "account_number": "644", + "is_group": 1 + }, + "MENKUL KIYMET SATI\u015e K\u00c2RLARI": { + "account_number": "645", + "is_group": 1 + }, + "KAMB\u0130YO K\u00c2RLARI": { + "account_number": "646", + "is_group": 1 + }, + "REESKONT FA\u0130Z GEL\u0130RLER\u0130": { + "account_number": "647", + "is_group": 1 + }, + "ENFLASYON D\u00dcZELTME K\u00c2RLARI": { + "account_number": "648", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN GEL\u0130R VE K\u00c2RLAR": { + "account_number": "649", + "is_group": 1 + } + }, + "D\u0130\u011eER FAAL\u0130YETLERDEN OLA\u011eAN G\u0130DER VE ZARARLAR(-)": { + "account_number": "65", + "KOM\u0130SYON G\u0130DERLER\u0130(-)": { + "account_number": "653", + "is_group": 1 + }, + "KAR\u015eILIK G\u0130DERLER\u0130(-)": { + "account_number": "654", + "is_group": 1 + }, + "MENKUL KIYMET SATI\u015e ZARARLARI(-)": { + "account_number": "655", + "is_group": 1 + }, + "KAMB\u0130YO ZARARLARI(-)": { + "account_number": "656", + "is_group": 1 + }, + "REESKONT FA\u0130Z G\u0130DERLER\u0130(-)": { + "account_number": "657", + "is_group": 1 + }, + "ENFLASYON D\u00dcZELTMES\u0130 ZARARLARI(-)": { + "account_number": "658", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN G\u0130DER VE ZARARLAR(-)": { + "account_number": "659", + "is_group": 1 + } + }, + "F\u0130NANSMAN G\u0130DERLER\u0130(-)": { + "account_number": "66", + "KISA VADEL\u0130 BOR\u00c7LANMA G\u0130DERLER\u0130(-)": { + "account_number": "660", + "is_group": 1 + }, + "UZUN VADEL\u0130 BOR\u00c7LANMA G\u0130DERLER\u0130(-)": { + "account_number": "661", + "is_group": 1 + } + }, + "OLA\u011eAN DI\u015eI GEL\u0130R VE K\u00c2RLAR": { + "account_number": "67", + "\u00d6NCEK\u0130 D\u00d6NEM GEL\u0130R VE K\u00c2RLARI": { + "account_number": "671", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN DI\u015eI GEL\u0130R VE K\u00c2RLAR": { + "account_number": "679", + "is_group": 1 + } + }, + "OLA\u011eAN DI\u015eI G\u0130DER VE ZARARLAR(-)": { + "account_number": "68", + "\u00c7ALI\u015eMAYAN KISIM G\u0130DER VE ZARARLARI(-)": { + "account_number": "680", + "is_group": 1 + }, + "\u00d6NCEK\u0130 D\u00d6NEM G\u0130DER VE ZARARLARI(-)": { + "account_number": "681", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN DI\u015eI G\u0130DER VE ZARARLAR(-)": { + "account_number": "689", + "is_group": 1 + } + }, + "D\u00d6NEM NET K\u00c2RI VEYA ZARARI": { + "account_number": "69", + "D\u00d6NEM K\u00c2RI VEYA ZARARI": { + "account_number": "690", + "is_group": 1 + }, + "D\u00d6NEM K\u00c2RI VERG\u0130 VE D\u0130\u011eER YASAL Y\u00dcK\u00dcML\u00dcL\u00dcK KAR\u015eILIKLARI(-)": { + "account_number": "691", + "is_group": 1 + }, + "D\u00d6NEM NET K\u00c2RI VEYA ZARARI": { + "account_number": "692", + "is_group": 1 + }, + "YILLARA YAYGIN \u0130N\u015eAAT VE ENFLASYON D\u00dcZELTME HESABI": { + "account_number": "697", + "is_group": 1 + }, + "ENFLASYON D\u00dcZELTME HESABI": { + "account_number": "698", + "is_group": 1 + } + } + }, + "MAL\u0130YET HESAPLARI (7/A SE\u00c7ENE\u011e\u0130)": { + "account_number": "7", + "root_type": "Expense", + "MAL\u0130YET MUHASEBES\u0130 BA\u011eLANTI HESAPLARI": { + "account_number": "70", + "MAL\u0130YET MUHASEBES\u0130 BA\u011eLANTI HESABI": { + "account_number": "700", + "is_group": 1 + }, + "MAL\u0130YET MUHASEBES\u0130 YANSITMA HESABI": { + "account_number": "701", + "is_group": 1 + } + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME G\u0130DERLER\u0130": { + "account_number": "71", + "D\u0130REKT \u0130LK MADDE VE MALZEME G\u0130DERLER\u0130 HESABI": { + "account_number": "710", + "is_group": 1 + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME YANSITMA HESABI": { + "account_number": "711", + "is_group": 1 + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME F\u0130YAT FARKI": { + "account_number": "712", + "is_group": 1 + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME M\u0130KTAR FARKI": { + "account_number": "713", + "is_group": 1 + } + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130": { + "account_number": "72", + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130": { + "account_number": "720", + "is_group": 1 + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "721", + "is_group": 1 + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K \u00dcCRET FARKLARI": { + "account_number": "722", + "is_group": 1 + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K S\u00dcRE FARKLARI": { + "account_number": "723", + "is_group": 1 + } + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130": { + "account_number": "73", + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130": { + "account_number": "730", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "731", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 B\u00dcT\u00c7E FARKLARI": { + "account_number": "732", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 VER\u0130ML\u0130L\u0130K G\u0130DERLER\u0130": { + "account_number": "733", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 KAPAS\u0130TE FARKLARI": { + "account_number": "734", + "is_group": 1 + } + }, + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130": { + "account_number": "74", + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130": { + "account_number": "740", + "is_group": 1 + }, + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130 YANSITMA HESABI": { + "account_number": "741", + "is_group": 1 + }, + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130 FARK HESAPLARI": { + "account_number": "742", + "is_group": 1 + } + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "75", + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "750", + "is_group": 1 + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "751", + "is_group": 1 + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DER FARKLARI": { + "account_number": "752", + "is_group": 1 + } + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130": { + "account_number": "76", + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130": { + "account_number": "760", + "is_group": 1 + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "761", + "is_group": 1 + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130 FARK HESABI": { + "account_number": "762", + "is_group": 1 + } + }, + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130": { + "account_number": "77", + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130": { + "account_number": "770", + "is_group": 1 + }, + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "771", + "is_group": 1 + }, + "GENEL Y\u00d6NET\u0130M G\u0130DER FARKLARI HESABI": { + "account_number": "772", + "is_group": 1 + } + }, + "F\u0130NANSMAN G\u0130DERLER\u0130": { + "account_number": "78", + "F\u0130NANSMAN G\u0130DERLER\u0130": { + "account_number": "780", + "is_group": 1 + }, + "F\u0130NANSMAN G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "781", + "is_group": 1 + }, + "F\u0130NANSMAN G\u0130DERLER\u0130 FARK HESABI": { + "account_number": "782", + "is_group": 1 + } + }, + "G\u0130DER \u00c7E\u015e\u0130TLER\u0130": { + "account_number": "79", + "\u0130LK MADDE VE MALZEME G\u0130DERLER\u0130": { + "account_number": "790", + "is_group": 1 + }, + "\u0130\u015e\u00c7\u0130 \u00dcCRET VE G\u0130DERLER\u0130": { + "account_number": "791", + "is_group": 1 + }, + "MEMUR \u00dcCRET VE G\u0130DERLER\u0130": { + "account_number": "792", + "is_group": 1 + }, + "DI\u015eARIDAN SA\u011eLANAN FAYDA VE H\u0130ZMETLER": { + "account_number": "793", + "is_group": 1 + }, + "\u00c7E\u015e\u0130TL\u0130 G\u0130DERLER": { + "account_number": "794", + "is_group": 1 + }, + "VERG\u0130, RES\u0130M VE HAR\u00c7LAR": { + "account_number": "795", + "is_group": 1 + }, + "AMORT\u0130SMAN VE T\u00dcKENME PAYLARI": { + "account_number": "796", + "is_group": 1 + }, + "F\u0130NANSMAN G\u0130DERLER\u0130": { + "account_number": "797", + "is_group": 1 + }, + "G\u0130DER \u00c7E\u015e\u0130TLER\u0130 YANSITMA HESABI": { + "account_number": "798", + "is_group": 1 + }, + "\u00dcRET\u0130M MAL\u0130YET HESABI": { + "account_number": "799", + "is_group": 1 + } + } + }, + "SERBEST HESAPLAR": { + "account_number": "8", + "root_type": "Asset", + "B\u00dcT\u00c7E GEL\u0130RLER\u0130 HESABI": { + "account_number": "800", + "is_group": 1 + }, + "GEL\u0130R YANSITMA HESABI": { + "account_number": "805", + "is_group": 1 + }, + "B\u00dcT\u00c7E GEL\u0130RLER\u0130NDEN RET VE \u0130ADELER HESABI": { + "account_number": "810", + "is_group": 1 + }, + "B\u00dcT\u00c7E G\u0130DERLER\u0130 HESABI": { + "account_number": "830", + "is_group": 1 + }, + "B\u00dcT\u00c7EDEN MAHSUP ED\u0130LECEK \u00d6DEMELER HESABI": { + "account_number": "833", + "is_group": 1 + }, + "GE\u00c7EN YIL B\u00dcT\u00c7E MAHSUPLARI HESABI": { + "account_number": "834", + "is_group": 1 + }, + "G\u0130DER YANSITMA HESABI": { + "account_number": "835", + "is_group": 1 + }, + "B\u00dcT\u00c7E UYGULAMA SONU\u00c7LARI HESABI": { + "account_number": "895", + "is_group": 1 + } + }, + "NAZIM HESAPLAR": { + "account_number": "9", + "root_type": "Expense", + "G\u00d6NDER\u0130LECEK B\u00dcT\u00c7E \u00d6DENEKLER\u0130 HESABI": { + "account_number": "900", + "is_group": 1 + }, + "B\u00dcT\u00c7E \u00d6DENEKLER\u0130 HESABI": { + "account_number": "901", + "is_group": 1 + }, + "B\u00dcT\u00c7E \u00d6DENEK HAREKETLER\u0130 HESABI": { + "account_number": "902", + "is_group": 1 + }, + "KULLANILACAK \u00d6DENEKLER HESABI": { + "account_number": "903", + "is_group": 1 + }, + "\u00d6DENEKLER HESABI": { + "account_number": "904", + "is_group": 1 + }, + "\u00d6DENEKL\u0130 G\u0130DERLER HESABI": { + "account_number": "905", + "is_group": 1 + }, + "MAHSUP D\u00d6NEM\u0130NE AKTARILAN KULLANILACAK \u00d6DENEKLER HESABI": { + "account_number": "906", + "is_group": 1 + }, + "MAHSUP D\u00d6NEM\u0130NE AKTARILAN \u00d6DENEKLER HESABI": { + "account_number": "907", + "is_group": 1 + }, + "TEM\u0130NAT MEKTUPLARI HESABI": { + "account_number": "910", + "is_group": 1 + }, + "TEM\u0130NAT MEKTUPLARI EMANETLER\u0130 HESABI": { + "account_number": "911", + "is_group": 1 + }, + "K\u0130\u015e\u0130LERE A\u0130T MENKUL KIYMETLER HESABI": { + "account_number": "912", + "is_group": 1 + }, + "K\u0130\u015e\u0130LERE A\u0130T MENKUL KIYMET EMANETLER\u0130 HESABI": { + "account_number": "913", + "is_group": 1 + }, + "G\u0130DER TAAHH\u00dcTLER\u0130 HESABI": { + "account_number": "920", + "is_group": 1 + }, + "G\u0130DER TAAHH\u00dcTLER\u0130 KAR\u015eILI\u011eI HESABI": { + "account_number": "921", + "is_group": 1 + }, + "VER\u0130LEN GARANT\u0130LER HESABI": { + "account_number": "930", + "is_group": 1 + }, + "VER\u0130LEN GARANT\u0130LER KAR\u015eILI\u011eI HESABI": { + "account_number": "931", + "is_group": 1 + }, + "STOK AYARLAMA": { + "account_number": "940", + "STOK SAYIM AYARLAMA": { + "account_number": "940.01", + "account_type": "Stock Adjustment" + } + } + } + } +} \ No newline at end of file From c27f272f061732e9fc58d35a95f5c34c52c0a4ab Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:51:07 +0530 Subject: [PATCH 160/203] perf: code optimization to handle large asset creation (backport #42018) (#42025) perf: code optimization to handle large asset creation (#42018) (cherry picked from commit 5738d93f95f7c891a01e43349abcba72d23f9e69) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- erpnext/controllers/buying_controller.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index bf6e3cd663a..30a5f38400e 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -659,10 +659,7 @@ class BuyingController(SubcontractingController): return if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: - field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt" - self.process_fixed_asset() - self.update_fixed_asset(field) if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value( "Buying Settings", "disable_last_purchase_rate" @@ -772,7 +769,7 @@ class BuyingController(SubcontractingController): if not row.asset_location: frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code)) - item_data = frappe.db.get_value( + item_data = frappe.get_cached_value( "Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1 ) asset_quantity = row.qty if is_grouped_asset else 1 @@ -801,7 +798,7 @@ class BuyingController(SubcontractingController): asset.flags.ignore_validate = True asset.flags.ignore_mandatory = True asset.set_missing_values() - asset.insert() + asset.db_insert() return asset.name @@ -827,11 +824,7 @@ class BuyingController(SubcontractingController): frappe.delete_doc("Asset", asset.name, force=1) continue - if self.docstatus in [0, 1] and not asset.get(field): - asset.set(field, self.name) - asset.purchase_date = self.posting_date - asset.supplier = self.supplier - elif self.docstatus == 2: + if self.docstatus == 2: if asset.docstatus == 2: continue if asset.docstatus == 0: From cf4d4ba3e97094bbe5d61b2f119ab157d4ffac08 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:51:16 +0530 Subject: [PATCH 161/203] fix: incorrect time period in asset depreciation schedule (backport #41805) (#42043) fix: incorrect time period in asset depreciation schedule (#41805) * fix(wip): depreciation calculation for existing asset * fix(wip): added validation for incorrect depreciation period * fix: depreciation schedule time period issue for existing asset * chore: run pre-commit checks and apply fixes * style: apply formatting changes * style: made some necessary changes * chore: modified test (cherry picked from commit 625f16dee0e77e40d0162b46ac4361879ef605a5) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- erpnext/assets/doctype/asset/test_asset.py | 6 +-- .../asset_depreciation_schedule.py | 45 ++++++++++++------- .../test_asset_depreciation_schedule.py | 38 +++++++++++++++- 3 files changed, 67 insertions(+), 22 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 07608e7144e..742cc3eaa49 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1501,19 +1501,17 @@ class TestDepreciationBasics(AssetSetup): """ asset = create_asset(calculate_depreciation=1) - asset.opening_accumulated_depreciation = 2000 - asset.opening_number_of_booked_depreciations = 1 asset.finance_books[0].expected_value_after_useful_life = 100 asset.save() asset.reload() - self.assertEqual(asset.finance_books[0].value_after_depreciation, 98000.0) + self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0) # changing expected_value_after_useful_life shouldn't affect value_after_depreciation asset.finance_books[0].expected_value_after_useful_life = 200 asset.save() asset.reload() - self.assertEqual(asset.finance_books[0].value_after_depreciation, 98000.0) + self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0) def test_asset_cost_center(self): asset = create_asset(is_existing_asset=1, do_not_save=1) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index d9fc5b3dd47..bd67a173343 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -552,32 +552,45 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False): # if not existing asset, from_date = available_for_use_date # otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12 # from_date = 01/01/2022 - from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly) + from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False) days = date_diff(row.depreciation_start_date, from_date) + 1 - - if wdv_or_dd_non_yearly: - total_days = get_total_days(row.depreciation_start_date, 12) - else: - # if frequency_of_depreciation is 12 months, total_days = 365 - total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) - + total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) + if days <= 0: + frappe.throw( + _( + """Error: This asset already has {0} depreciation periods booked. + The `depreciation start` date must be at least {1} periods after the `available for use` date. + Please correct the dates accordingly.""" + ).format( + asset_doc.opening_number_of_booked_depreciations, + asset_doc.opening_number_of_booked_depreciations, + ) + ) if days < total_days: has_pro_rata = True - return has_pro_rata def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False): - if wdv_or_dd_non_yearly: - return add_months( + """ + if Asset has opening booked depreciations = 9, + available for use date = 17-07-2023, + depreciation start date = 30-04-2024 + then from date should be 01-04-2024 + """ + if asset_doc.opening_number_of_booked_depreciations > 0: + from_date = add_months( asset_doc.available_for_use_date, - (asset_doc.opening_number_of_booked_depreciations * 12), + (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation) - 1, ) + if is_last_day_of_the_month(row.depreciation_start_date): + return add_days(get_last_day(from_date), 1) + + # get from date when depreciation start date is not last day of the month + months_difference = month_diff(row.depreciation_start_date, from_date) - 1 + return add_days(add_months(row.depreciation_start_date, -1 * months_difference), 1) else: - return add_months( - asset_doc.available_for_use_date, - (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation), - ) + return asset_doc.available_for_use_date def _get_pro_rata_amt( diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index 6009ac1496c..c359715571e 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -3,7 +3,7 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import cstr +from frappe.utils import cstr, flt from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, @@ -172,7 +172,7 @@ class TestAssetDepreciationSchedule(FrappeTestCase): opening_accumulated_depreciation=2000, opening_number_of_booked_depreciations=2, depreciation_method="Straight Line", - available_for_use_date="2020-03-01", + available_for_use_date="2020-01-01", depreciation_start_date="2020-03-31", frequency_of_depreciation=1, total_number_of_depreciations=24, @@ -195,3 +195,37 @@ class TestAssetDepreciationSchedule(FrappeTestCase): asset.reload() self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 14) + + def test_schedule_for_wdv_method_for_existing_asset(self): + asset = create_asset( + calculate_depreciation=1, + depreciation_method="Written Down Value", + available_for_use_date="2020-07-17", + is_existing_asset=1, + opening_number_of_booked_depreciations=2, + opening_accumulated_depreciation=11666.67, + depreciation_start_date="2021-04-30", + total_number_of_depreciations=12, + frequency_of_depreciation=3, + gross_purchase_amount=50000, + rate_of_depreciation=40, + ) + + self.assertEqual(asset.status, "Draft") + expected_schedules = [ + ["2021-04-30", 3833.33, 15500.0], + ["2021-07-31", 3833.33, 19333.33], + ["2021-10-31", 3833.33, 23166.66], + ["2022-01-31", 3833.33, 26999.99], + ["2022-04-30", 2300.0, 29299.99], + ["2022-07-31", 2300.0, 31599.99], + ["2022-10-31", 2300.0, 33899.99], + ["2023-01-31", 2300.0, 36199.99], + ["2023-04-30", 1380.0, 37579.99], + ["2023-07-31", 12420.01, 50000.0], + ] + schedules = [ + [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) From b29435744fee8c824f70d05a8a2748df93e51573 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:43:57 +0530 Subject: [PATCH 162/203] chore: patch to enable total number of booked depreciations field (backport #41940) (#42042) * chore: patch to enable total number of booked depreciations field (#41940) * chore: patch to enable total number of booked depreciations field * fix: conflict resolved * refactor: replaced fb_row.db_set with set_value (cherry picked from commit 5fdd1d3278d1c1765129551d975cf51064d79439) # Conflicts: # erpnext/patches.txt * fix: resolved conflicts * fix: removed unmerged patches --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../doctype/journal_entry/journal_entry.py | 17 +++++------ erpnext/patches.txt | 1 + ...te_total_number_of_booked_depreciations.py | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 554217ab6b9..39f9aaf7a6c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -226,7 +226,7 @@ class JournalEntry(AccountsController): self.unlink_inter_company_jv() self.unlink_asset_adjustment_entry() self.update_invoice_discounting() - self.update_booked_depreciation() + self.update_booked_depreciation(1) def get_title(self): return self.pay_to_recd_from or self.accounts[0].account @@ -441,7 +441,7 @@ class JournalEntry(AccountsController): if status: inv_disc_doc.set_status(status=status) - def update_booked_depreciation(self): + def update_booked_depreciation(self, cancel=0): for d in self.get("accounts"): if ( self.voucher_type == "Depreciation Entry" @@ -453,14 +453,11 @@ class JournalEntry(AccountsController): asset = frappe.get_doc("Asset", d.reference_name) for fb_row in asset.get("finance_books"): if fb_row.finance_book == self.finance_book: - depr_schedule = get_depr_schedule(asset.name, "Active", fb_row.finance_book) - total_number_of_booked_depreciations = asset.opening_number_of_booked_depreciations - for je in depr_schedule: - if je.journal_entry: - total_number_of_booked_depreciations += 1 - fb_row.db_set( - "total_number_of_booked_depreciations", total_number_of_booked_depreciations - ) + if cancel: + fb_row.total_number_of_booked_depreciations -= 1 + else: + fb_row.total_number_of_booked_depreciations += 1 + fb_row.db_update() break def unlink_advance_entry_reference(self): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 383989643b7..dba491a728d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -366,3 +366,4 @@ erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v15_0.rename_number_of_depreciations_booked_to_opening_booked_depreciations +erpnext.patches.v15_0.update_total_number_of_booked_depreciations diff --git a/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py new file mode 100644 index 00000000000..4b556c2b512 --- /dev/null +++ b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py @@ -0,0 +1,29 @@ +import frappe + +from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( + get_depr_schedule, +) + + +def execute(): + if frappe.db.has_column("Asset Finance Book", "total_number_of_booked_depreciations"): + assets = frappe.get_all( + "Asset", filters={"docstatus": 1}, fields=["name", "opening_number_of_booked_depreciations"] + ) + + for asset in assets: + asset_doc = frappe.get_doc("Asset", asset.name) + + for fb_row in asset_doc.get("finance_books"): + depr_schedule = get_depr_schedule(asset.name, "Active", fb_row.finance_book) + total_number_of_booked_depreciations = asset.opening_number_of_booked_depreciations or 0 + + for je in depr_schedule: + if je.journal_entry: + total_number_of_booked_depreciations += 1 + frappe.db.set_value( + "Asset Finance Book", + fb_row.name, + "total_number_of_booked_depreciations", + total_number_of_booked_depreciations, + ) From 3536a754ff8dd778e72679dd906e7703f1ef7c0f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:47:30 +0530 Subject: [PATCH 163/203] fix: lead status filter (backport #41816) (#42046) fix: lead status filter (#41816) (cherry picked from commit 8ae2b8ff8cb9070304e4961d290fc14c97ac900a) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- erpnext/crm/doctype/lead/lead_list.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/crm/doctype/lead/lead_list.js b/erpnext/crm/doctype/lead/lead_list.js index 97415251a93..beef2c934dc 100644 --- a/erpnext/crm/doctype/lead/lead_list.js +++ b/erpnext/crm/doctype/lead/lead_list.js @@ -1,4 +1,8 @@ frappe.listview_settings["Lead"] = { + get_indicator: function (doc) { + var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status]; + return indicator; + }, onload: function (listview) { if (frappe.boot.user.can_create.includes("Prospect")) { listview.page.add_action_item(__("Create Prospect"), function () { From 482832f3c2be1b7cd63e5ffea595a88abdd3869b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 18:01:33 +0530 Subject: [PATCH 164/203] fix: unhide serial no field (backport #42045) (#42047) * fix: unhide serial no field (#42045) (cherry picked from commit 80c6981cfaff082ae05d64fd183e29fea04e0b81) # Conflicts: # erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json * fix: resolved conflicts --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../asset_capitalization_stock_item.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json index c838f8b0f26..da05e930eab 100644 --- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json @@ -108,7 +108,6 @@ "depends_on": "eval:doc.use_serial_batch_fields === 1", "fieldname": "serial_no", "fieldtype": "Text", - "hidden": 1, "label": "Serial No", "print_hide": 1 }, @@ -178,7 +177,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-05 11:22:57.346889", + "modified": "2024-06-26 17:06:22.564438", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Stock Item", @@ -188,4 +187,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From b7eabc3112c57e4db95eafaaa1039e660fa49dbf Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 26 Jun 2024 21:52:35 +0530 Subject: [PATCH 165/203] chore: release v15 (#42024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(Sales Order): only show permitted actions (cherry picked from commit c29d95537185d909612103b65573242a91ef0d70) * fix(Delivery Note): only show permitted actions (cherry picked from commit 418bdc1dcc0c8c8eaaa6555b3689436515270c7c) * fix: do not show zero balance stock items in stock balance report (backport #41958) (#41961) fix: do not show zero balance stock in stock balance (cherry picked from commit 7f7b363d487983ee396c4921b4047074551ec47a) Co-authored-by: Rohit Waghchaure * fix: add string for translation (backport #41903) (#41963) fix: add string for translation (#41903) fix: add string for translation (cherry picked from commit f28c692dca5db94460de8828a3f5d6faee9aed05) Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> * refactor: remove use of can_create for Payment Request (#41647) (cherry picked from commit 47bc5691a1579b88e0a430f6fbe5ff6309486ccf) * fix: move condition for shipment * fix: incorrect discount on other item When discount is applied on other item, don't update `discount_amount` as the amount is calculated for current item (cherry picked from commit 654764e398a8315a7eeb056f616b939ac98b6646) * fix: incorrect against_account upon reposting (cherry picked from commit 20c4098399d5cb276d373074036a738e6cee67b0) * fix: decimal issue in pick list (backport #41972) (#41982) fix: decimal issue in pick list (#41972) (cherry picked from commit 21adc7b63e742389a107d4df6a939ebbf23b196b) Co-authored-by: rohitwaghchaure * refactor: renamed number of depreciations booked to opening booked de… (#41515) * refactor: renamed number of depreciations booked to opening booked depreciations * feat: introduced new field for showing total number of booked depreciations * fix: reload asset when creating asset depreciation * chore: added nosemgrep for security checks * feat: default account head for operating cost (backport #41985) (#41987) * feat: default account head for operating cost (#41985) (cherry picked from commit fd7666a029a430c0b3cf4d1c9d0b728f4d0e4476) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py # erpnext/setup/doctype/company/company.json * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure * perf: dont run queries unnecessarily, improved filters (#41993) * perf: dont run queries unnecessarily, improved filters * perf: dont run query if `in` filter is empty (cherry picked from commit ac6d85aed628734820cc055d00e062b4fd90bb8c) * chore: remove validation on payment entry (cherry picked from commit e7740033cad4bfbe0ca35ca05795635b2db4257e) * refactor: convert amount to base currency for advances (cherry picked from commit c9ede1ffbe25d6250b52910372c1d7cfa82096c5) * refactor: for advances uses the party account in references table (cherry picked from commit 7dce6e03c7f8452fa9ca942b38a0cf1bd2264d3e) * refactor(test): simpler create_account helper method (cherry picked from commit 475e0ddeeef3700e2c75910886fad3e44f58eb6d) * test: exc gain/loss booking on advances under asset/liability (cherry picked from commit 827d67d02f745346bd32bed5428cc33a7baba745) * test: advance against purchase invoice (cherry picked from commit 90c84822d01a9b8ab2aba303846702f08b33c63a) * refactor: validation to force accounts to be on same currency (cherry picked from commit 0f0b4d88bcd5e298154748a58c20589c5aa9f531) * refactor: validation in customer group (cherry picked from commit 4f9a2281754ad64c845739a29c5225059f1cc11d) * refactor: validation in Supplier Group (cherry picked from commit 107b614518444339a9674287fa6cb2c788716fa8) * refactor: better error messages (cherry picked from commit 83ff94b9b87772251f663f3be59c44cadb2c3a67) * chore: fix test data (cherry picked from commit 07d59443b7e679e3dd3498f5837a800983540616) * chore: remove dead code (cherry picked from commit 7e318c013257ccb673e5065fac32e25e463be96d) * fix(test): incorrect field for customer default billing currency (cherry picked from commit c696d13a5edbd62606efd266db9cd95bf18ed7d6) * refactor(test): enfore use of customer/supplier master While using advance accounts in foreign currency, always use Customer/Supplier master to maintain them (cherry picked from commit 64e63887bed71db1cb6e880ee2ae7f2e88d34a6c) * refactor(test): make and use a different party for subscription (cherry picked from commit 3fabf4aaa48943f29429931cf0d3e9a3c08a65e5) * fix: pricing rule with and without 'apply multiple' and priority Either all of the pricing rules identified for an item should have 'apply multiple' enabled. If not, Priority is applied and only the highest priority is applied (cherry picked from commit 5e875b238c9d1a1c376f2fa98c0d2451da7e80b7) * test: priority takes effect on with and without apply multiple (cherry picked from commit efebc3662e8b76ea82866f940ed0bc9032c819e2) * feat: accounting dimension filters in gp report (cherry picked from commit d165638bbbd49ef260760528ccca1eb04b71628b) * feat(gp): group by cost center (cherry picked from commit e26bc17c75f1dfea1659172216e3449f2271da14) * fix: Wrong Delete Batch on Purchase Receipt (backport #42007) (#42012) fix: Wrong Delete Batch on Purchase Receipt (#42007) (cherry picked from commit d50487ce530605bd311da372a09b9e2fb339f00d) Co-authored-by: rohitwaghchaure * fix: incorrect Difference Amount (backport #42008) (#42013) fix: incorrect Difference Amount (#42008) (cherry picked from commit 7d91c6cbd5faf3f0d9d507b76964acf98282786a) Co-authored-by: rohitwaghchaure * fix: valuation rate for the legacy batches (backport #42011) (#42020) fix: valuation rate for the legacy batches (#42011) (cherry picked from commit 9ab333d10584745e67ff3bcfe3385d890fc1075a) Co-authored-by: rohitwaghchaure * fix: timeout while cancelling LCV (backport #42030) (backport #42031) (#42032) fix: timeout while cancelling LCV (backport #42030) (#42031) fix: timeout while cancelling LCV (#42030) fix: timeout while canelling LCV (cherry picked from commit 21bf7fd1f8c9d4c1cd9f3b419089782e82658165) Co-authored-by: rohitwaghchaure (cherry picked from commit 2e76b9f9db51b70341bbbdeeeb43bfbd6675abd4) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * fix: Stock Reservation Entry was not getting created (backport #42033) (#42035) fix: Stock Reservation Entry was not getting created (#42033) (cherry picked from commit 1a9899b32bc574873ef27e5e56dc07eea9385178) Co-authored-by: Poorvi-R-Bhat * fix: manufacturing date issue in the batch (backport #42034) (#42037) * fix: manufacturing date issue in the batch (#42034) (cherry picked from commit eca3e02f8da4b5d7d1b15a3c42de225168f3bf5e) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure * fix: fixed asset value in Fixed Asset Register (backport #41930) (#42027) fix: fixed asset value in Fixed Asset Register (#41930) (cherry picked from commit 1c643a0ead6ab7f894e9fcf286a53fe566633a8b) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * feat: Turkish Chart Of Accounts (backport #41756) (#42028) * feat: Create Turkish Chart Of Accounts (cherry picked from commit 5c8ea86a3f039077c50b0b9e6ecbae6e29bf574a) * feat: Create Turkish Chart Of Accounts (cherry picked from commit b401ba2c268fc22b99514d599e477c999293a773) --------- Co-authored-by: fzozyurt * perf: code optimization to handle large asset creation (backport #42018) (#42025) perf: code optimization to handle large asset creation (#42018) (cherry picked from commit 5738d93f95f7c891a01e43349abcba72d23f9e69) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * fix: incorrect time period in asset depreciation schedule (backport #41805) (#42043) fix: incorrect time period in asset depreciation schedule (#41805) * fix(wip): depreciation calculation for existing asset * fix(wip): added validation for incorrect depreciation period * fix: depreciation schedule time period issue for existing asset * chore: run pre-commit checks and apply fixes * style: apply formatting changes * style: made some necessary changes * chore: modified test (cherry picked from commit 625f16dee0e77e40d0162b46ac4361879ef605a5) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * chore: patch to enable total number of booked depreciations field (backport #41940) (#42042) * chore: patch to enable total number of booked depreciations field (#41940) * chore: patch to enable total number of booked depreciations field * fix: conflict resolved * refactor: replaced fb_row.db_set with set_value (cherry picked from commit 5fdd1d3278d1c1765129551d975cf51064d79439) # Conflicts: # erpnext/patches.txt * fix: resolved conflicts * fix: removed unmerged patches --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * fix: lead status filter (backport #41816) (#42046) fix: lead status filter (#41816) (cherry picked from commit 8ae2b8ff8cb9070304e4961d290fc14c97ac900a) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> * fix: unhide serial no field (backport #42045) (#42047) * fix: unhide serial no field (#42045) (cherry picked from commit 80c6981cfaff082ae05d64fd183e29fea04e0b81) # Conflicts: # erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json * fix: resolved conflicts --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Rohit Waghchaure Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> Co-authored-by: ruthra kumar Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Co-authored-by: Sagar Vora Co-authored-by: Dany Robert Co-authored-by: Deepesh Garg Co-authored-by: Poorvi-R-Bhat Co-authored-by: fzozyurt Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- .../unverified/tr_l10ntr_tek_duzen_hesap.json | 531 ------ .../verified/tr_chart_of_accounts.json | 1473 +++++++++++++++++ .../doctype/journal_entry/journal_entry.py | 21 + .../doctype/payment_entry/payment_entry.py | 21 +- .../doctype/pricing_rule/test_pricing_rule.py | 62 + .../accounts/doctype/pricing_rule/utils.py | 9 +- .../purchase_invoice/purchase_invoice.py | 6 +- .../repost_accounting_ledger.py | 4 + .../doctype/sales_invoice/sales_invoice.py | 4 + .../doctype/subscription/test_subscription.py | 10 +- .../report/gross_profit/gross_profit.js | 24 +- .../report/gross_profit/gross_profit.py | 46 +- erpnext/assets/doctype/asset/asset.json | 16 +- erpnext/assets/doctype/asset/asset.py | 27 +- erpnext/assets/doctype/asset/depreciation.py | 1 + erpnext/assets/doctype/asset/test_asset.py | 30 +- .../asset_capitalization_stock_item.json | 5 +- .../asset_depreciation_schedule.json | 6 +- .../asset_depreciation_schedule.py | 71 +- .../test_asset_depreciation_schedule.py | 73 +- .../asset_finance_book.json | 10 +- .../asset_finance_book/asset_finance_book.py | 1 + .../doctype/asset_repair/asset_repair.py | 4 +- .../fixed_asset_register.py | 18 +- erpnext/buying/doctype/supplier/supplier.py | 1 + .../buying/doctype/supplier/test_records.json | 1 + erpnext/controllers/accounts_controller.py | 11 +- erpnext/controllers/buying_controller.py | 13 +- .../controllers/subcontracting_controller.py | 33 +- .../tests/test_accounts_controller.py | 299 +++- erpnext/crm/doctype/lead/lead_list.js | 4 + erpnext/patches.txt | 2 + ...sset_depreciation_schedules_from_assets.py | 3 +- ..._booked_to_opening_booked_depreciations.py | 7 + .../update_gpa_and_ndb_for_assdeprsch.py | 5 +- ...te_total_number_of_booked_depreciations.py | 29 + erpnext/public/js/controllers/transaction.js | 6 +- .../js/utils/serial_no_batch_selector.js | 1 + erpnext/selling/doctype/customer/customer.py | 1 + .../doctype/customer/test_records.json | 1 + .../doctype/sales_order/sales_order.js | 113 +- .../doctype/customer_group/customer_group.py | 47 + .../doctype/supplier_group/supplier_group.py | 46 + erpnext/stock/deprecated_serial_batch.py | 86 +- erpnext/stock/doctype/batch/batch.py | 16 +- .../doctype/delivery_note/delivery_note.js | 132 +- erpnext/stock/doctype/pick_list/pick_list.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 31 +- .../serial_and_batch_bundle.py | 8 +- .../stock_reconciliation.py | 17 +- .../test_stock_reconciliation.py | 5 +- .../doctype/stock_settings/stock_settings.js | 2 +- .../report/stock_balance/stock_balance.py | 4 + erpnext/stock/stock_ledger.py | 1 + erpnext/stock/utils.py | 6 +- .../test_subcontracting_receipt.py | 8 +- erpnext/utilities/transaction_base.py | 63 + 57 files changed, 2628 insertions(+), 853 deletions(-) delete mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json create mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json create mode 100644 erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py create mode 100644 erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json deleted file mode 100644 index dfc821eb53e..00000000000 --- a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json +++ /dev/null @@ -1,531 +0,0 @@ -{ - "country_code": "tr", - "name": "Turkey - Tek D\u00fczen Hesap Plan\u0131", - "tree": { - "Duran Varl\u0131klar": { - "Di\u011fer Alacaklar": { - "Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {}, - "Di\u011fer Alacak Senetleri Reeskontu(-)": {}, - "Di\u011fer \u00c7e\u015fitli Alacaklar": {}, - "Ortaklardan Alacaklar": {}, - "Personelden Alacaklar": {}, - "\u0130\u015ftiraklerden Alacaklar": {}, - "\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "Di\u011fer Duran Varl\u0131klar": { - "Birikmi\u015f Amortismanlar(-)": {}, - "Di\u011fer KDV": {}, - "Di\u011fer \u00c7e\u015fitli Duran Varl\u0131klar": {}, - "Elden \u00c7\u0131kar\u0131lacak Stoklar Ve Maddi Duran Varl\u0131klar": {}, - "Gelecek Y\u0131llar \u0130htiyac\u0131 Stoklar": {}, - "Gelecek Y\u0131llarda \u0130ndirilecek KDV": {}, - "Pe\u015fin \u00d6denen Vergi Ve Fonlar": {}, - "Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "Gelecek Y\u0131llara Ait Giderler ve Gelir Tahakkuklar\u0131": { - "Gelecek Y\u0131llara Ait Giderler": {}, - "Gelir Tahakkuklar\u0131": {} - }, - "Maddi Duran Varl\u0131klar": { - "Arazi Ve Arsalar": {}, - "Binalar": {}, - "Birikmi\u015f Amortismanlar(-)": {}, - "Demirba\u015flar": {}, - "Di\u011fer Maddi Duran Varl\u0131klar": {}, - "Ta\u015f\u0131tlar": {}, - "Tesis, Makine Ve Cihazlar": {}, - "Verilen Avanslar": {}, - "Yap\u0131lmakta Olan Yat\u0131r\u0131mlar": {}, - "Yer Alt\u0131 Ve Yer \u00dcst\u00fc D\u00fczenleri": {} - }, - "Maddi Olmayan Duran Varl\u0131klar": { - "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {}, - "Birikmi\u015f Amortismanlar(-)": {}, - "Di\u011fer Maddi Olmayan Duran Varl\u0131klar": {}, - "Haklar": {}, - "Kurulu\u015f Ve \u00d6rg\u00fctlenme Giderleri": {}, - "Verilen Avanslar": {}, - "\u00d6zel Maliyetler": {}, - "\u015eerefiye": {} - }, - "Mali Duran Varl\u0131klar": { - "Ba\u011fl\u0131 Menkul K\u0131ymetler": {}, - "Ba\u011fl\u0131 Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Ba\u011fl\u0131 Ortakl\u0131klar": {}, - "Ba\u011fl\u0131 Ortakl\u0131klar Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Ba\u011fl\u0131 Ortakl\u0131klara Sermaye Taahh\u00fctleri(-)": {}, - "Di\u011fer Mali Duran Varl\u0131klar": {}, - "Di\u011fer Mali Duran Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "\u0130\u015ftirakler": {}, - "\u0130\u015ftirakler Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "\u0130\u015ftiraklere Sermaye Taahh\u00fctleri(-)": {} - }, - "Ticari Alacaklar": { - "Alacak Senetleri": {}, - "Alacak Senetleri Reeskontu(-)": {}, - "Al\u0131c\u0131lar": {}, - "Kazaqn\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {}, - "Verilen Depozito Ve Teminatlar": {}, - "\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "root_type": "", - "\u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": { - "Arama Giderleri": {}, - "Birikmi\u015f T\u00fckenme Paylar\u0131(-)": {}, - "Di\u011fer \u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {}, - "Haz\u0131rl\u0131k Ve Geli\u015ftirme Giderleri": {}, - "Verilen Avanslar": {} - } - }, - "D\u00f6nen Varl\u0131klar": { - "Di\u011fer Alacaklar": { - "Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {}, - "Di\u011fer Alacak Senetleri Reeskontu(-)": {}, - "Di\u011fer \u00c7e\u015fitli Alacaklar": {}, - "Ortaklardan Alacaklar": {}, - "Personelden Alacaklar": {}, - "\u0130\u015ftiraklerden Alacaklar": {}, - "\u015e\u00fcpheli Di\u011fer Alacaklar": {}, - "\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {} - }, - "Di\u011fer D\u00f6nen Varl\u0131klar": { - "Devreden KDV": {}, - "Di\u011fer D\u00f6nen Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Di\u011fer KDV": {}, - "Di\u011fer \u00c7e\u015fitli D\u00f6nen Varl\u0131klar": {}, - "Personel Avanslar\u0131": {}, - "Pe\u015fin \u00d6denen Vergiler Ve Fonlar": {}, - "Say\u0131m Ve Tesell\u00fcm Noksanlar\u0131": {}, - "\u0130ndirilecek KDV": {}, - "\u0130\u015f Avanslar\u0131": {} - }, - "Gelecek Aylara Ait Giderler ve Gelir Tahakkuklar\u0131": { - "Gelecek Aylara Ait Giderler": {}, - "Gelir Tahakkuklar\u0131": {} - }, - "Haz\u0131r De\u011ferler": { - "Al\u0131nan \u00c7ekler": {}, - "Bankalar": { - "account_type": "Bank" - }, - "Di\u011fer Haz\u0131r De\u011ferler": {}, - "Kasa": { - "account_type": "Cash" - }, - "Verilen \u00c7ekler ve \u00d6deme Emirleri(-)": {} - }, - "Menkul K\u0131ymetler": { - "Di\u011fer Menkul K\u0131ymetler": {}, - "Hisse Senetleri": {}, - "Kamu Kesimi Tahvil, Senet ve Bonolar\u0131": {}, - "Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "\u00d6zel Kesim Tahvil Senet Ve Bonolar\u0131": {} - }, - "Stoklar": { - "Mamuller": {}, - "Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}, - "Ticari Mallar": {}, - "Verilen Sipari\u015f Avanslar\u0131": {}, - "Yar\u0131 Mamuller": {}, - "\u0130lk Madde Malzeme": {} - }, - "Ticari Alacaklar": { - "Alacak Senetleri": {}, - "Alacak Senetleri Reeskontu(-)": {}, - "Al\u0131c\u0131lar": {}, - "Di\u011fer Ticari Alacaklar": {}, - "Kazan\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {}, - "Verilen Depozito ve Teminatlar": {}, - "\u015e\u00fcpheli Ticari Alacaklar": {}, - "\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131": {} - }, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat ve Onar\u0131m Maliyetleri": { - "Ta\u015feronlara Verilen Avanslar": {}, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Maliyetleri": {} - }, - "root_type": "" - }, - "Gelir Tablosu Hesaplar\u0131": { - "Br\u00fct Sat\u0131\u015flar": { - "Di\u011fer Gelirler": {}, - "Yurt D\u0131\u015f\u0131 Sat\u0131\u015flar": {}, - "Yurt \u0130\u00e7i Sat\u0131\u015flar": {} - }, - "Di\u011fer Faaliyetlerden Olu\u015fan Gelir ve K\u00e2rlar": { - "Ba\u011fl\u0131 Ortakl\u0131klardan Temett\u00fc Gelirleri": {}, - "Di\u011fer Ola\u011fan Gelir Ve K\u00e2rlar": {}, - "Enflasyon D\u00fczeltme K\u00e2rlar\u0131": {}, - "Faiz Gelirleri": {}, - "Kambiyo K\u00e2rlar\u0131": {}, - "Komisyon Gelirleri": {}, - "Konusu Kalmayan Kar\u015f\u0131l\u0131klar": {}, - "Menkul K\u0131ymet Sat\u0131\u015f K\u00e2rlar\u0131": {}, - "Reeskont Faiz Gelirleri": {}, - "\u0130\u015ftiraklerden Temett\u00fc Gelirleri": {} - }, - "Di\u011fer Faaliyetlerden Olu\u015fan Gider ve Zararlar (-)": { - "Di\u011fer Ola\u011fan Gider Ve Zararlar(-)": {}, - "Enflasyon D\u00fczeltmesi Zararlar\u0131(-)": {}, - "Kambiyo Zararlar\u0131(-)": {}, - "Kar\u015f\u0131l\u0131k Giderleri(-)": {}, - "Komisyon Giderleri(-)": {}, - "Menkul K\u0131ymet Sat\u0131\u015f Zararlar\u0131(-)": {}, - "Reeskont Faiz Giderleri(-)": {} - }, - "D\u00f6nem Net K\u00e2r\u0131 Ve Zarar\u0131": { - "D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131(-)": {}, - "D\u00f6nem K\u00e2r\u0131 Veya Zarar\u0131": {}, - "D\u00f6nem Net K\u00e2r\u0131 Veya Zarar\u0131": {}, - "Enflasyon D\u00fczeltme Hesab\u0131": {}, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Enflasyon D\u00fczeltme Hesab\u0131": {} - }, - "Faaliyet Giderleri(-)": { - "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri(-)": {}, - "Genel Y\u00f6netim Giderleri(-)": {}, - "Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri(-)": {} - }, - "Finansman Giderleri": { - "K\u0131sa Vadeli Bor\u00e7lanma Giderleri(-)": {}, - "Uzun Vadeli Bor\u00e7lanma Giderleri(-)": {} - }, - "Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": { - "Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {}, - "\u00d6nceki D\u00f6nem Gelir Ve K\u00e2rlar\u0131": {} - }, - "Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zaralar(-)": { - "Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zararlar(-)": {}, - "\u00c7al\u0131\u015fmayan K\u0131s\u0131m Gider Ve Zararlar\u0131(-)": {}, - "\u00d6nceki D\u00f6nem Gider Ve Zararlar\u0131(-)": {} - }, - "Sat\u0131\u015f \u0130ndirimleri (-)": { - "Di\u011fer \u0130ndirimler": {}, - "Sat\u0131\u015f \u0130ndirimleri(-)": {}, - "Sat\u0131\u015ftan \u0130adeler(-)": {} - }, - "Sat\u0131\u015flar\u0131n Maliyeti(-)": { - "Di\u011fer Sat\u0131\u015flar\u0131n Maliyeti(-)": {}, - "Sat\u0131lan Hizmet Maliyeti(-)": {}, - "Sat\u0131lan Mamuller Maliyeti(-)": {}, - "Sat\u0131lan Ticari Mallar Maliyeti(-)": {} - }, - "root_type": "" - }, - "K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": { - "Al\u0131nan Avanslar": { - "Al\u0131nan Di\u011fer Avanslar": { - "account_type": "Payable" - }, - "Al\u0131nan Sipari\u015f Avanslar\u0131": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Bor\u00e7 ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "account_type": "Payable" - }, - "D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131": { - "account_type": "Tax" - }, - "D\u00f6nem K\u00e2r\u0131n\u0131n Pe\u015fin \u00d6denen Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler(-)": { - "account_type": "Tax" - }, - "K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {}, - "Maliyet Giderleri Kar\u015f\u0131l\u0131\u011f\u0131": {}, - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7lar": { - "Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": { - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ortaklara Bor\u00e7lar": { - "account_type": "Payable" - }, - "Personele Bor\u00e7lar": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u0130\u015ftiraklere Bor\u00e7lar": { - "account_type": "Payable" - } - }, - "Di\u011fer K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": { - "Di\u011fer KDV": { - "account_type": "Tax" - }, - "Di\u011fer \u00c7e\u015fitli Yabanc\u0131 Kaynaklar": {}, - "Hesaplanan KDV": { - "account_type": "Tax" - }, - "Merkez Ve \u015eubeler Cari Hesab\u0131": {}, - "Say\u0131m Ve Tesell\u00fcm Fazlalar\u0131": {}, - "account_type": "Payable" - }, - "Gelecek Aylara Ait Gelirler Ve Gider Tahakkuklar\u0131": { - "Gelecek Aylara Ait Gelirler": {}, - "Gider Tahakkuklar\u0131": {} - }, - "Mali Bor\u00e7lar": { - "Banka Kredileri": { - "account_type": "Payable" - }, - "Di\u011fer Mali Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": { - "account_type": "Payable" - }, - "Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": { - "account_type": "Payable" - }, - "Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": { - "account_type": "Payable" - }, - "Tahvil Anapara Bor\u00e7, Taksit Ve Faizleri": { - "account_type": "Payable" - }, - "Uzun Vadeli Kredilerin Anapara Taksitleri Ve Faizleri": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u00c7\u0131kar\u0131lan Bonolar Ve Senetler": { - "account_type": "Payable" - }, - "\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": { - "account_type": "Payable" - } - }, - "Ticari Bor\u00e7lar": { - "Al\u0131nan Depozito Ve Teminatlar": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer Ticari Bor\u00e7lar": { - "account_type": "Payable" - }, - "Sat\u0131c\u0131lar": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri": { - "350 Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri Bedelleri": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "root_type": "", - "\u00d6denecek Vergi ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": { - "Vadesi Ge\u00e7mi\u015f, Ertelenmi\u015f Veya Taksitlendirilmi\u015f Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": { - "account_type": "Tax" - }, - "account_type": "Tax", - "\u00d6denecek Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": { - "account_type": "Tax" - }, - "\u00d6denecek Sosyal G\u00fcvenl\u00fck Kesintileri": { - "account_type": "Tax" - }, - "\u00d6denecek Vergi Ve Fonlar": { - "account_type": "Tax" - } - } - }, - "Maliyet Hesaplar\u0131": { - "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {}, - "Direkt \u0130lk Madde Ve Malzeme Giderleri": { - "Direk \u0130lk Madde Ve Malzeme Giderleri Hesab\u0131": {}, - "Direkt \u0130lk Madde Ve Malzeme Fiyat Fark\u0131": {}, - "Direkt \u0130lk Madde Ve Malzeme Miktar Fark\u0131": {}, - "Direkt \u0130lk Madde Ve Malzeme Yans\u0131tma Hesab\u0131": {} - }, - "Direkt \u0130\u015f\u00e7ilik Giderleri": { - "Direkt \u0130\u015f\u00e7ilik Giderleri": {}, - "Direkt \u0130\u015f\u00e7ilik Giderleri Yans\u0131tma Hesab\u0131": {}, - "Direkt \u0130\u015f\u00e7ilik S\u00fcre Farklar\u0131": {}, - "Direkt \u0130\u015f\u00e7ilik \u00dccret Farklar\u0131": {} - }, - "Finansman Giderleri": { - "Finansman Giderleri": {}, - "Finansman Giderleri Fark Hesab\u0131": {}, - "Finansman Giderleri Yans\u0131tma Hesab\u0131": {} - }, - "Genel Y\u00f6netim Giderleri": { - "Genel Y\u00f6netim Gider Farklar\u0131 Hesab\u0131": {}, - "Genel Y\u00f6netim Giderleri": {}, - "Genel Y\u00f6netim Giderleri Yans\u0131tma Hesab\u0131": {} - }, - "Genel \u00dcretim Giderleri": { - "Genel \u00dcretim Giderleri": {}, - "Genel \u00dcretim Giderleri B\u00fct\u00e7e Farklar\u0131": {}, - "Genel \u00dcretim Giderleri Kapasite Farklar\u0131": {}, - "Genel \u00dcretim Giderleri Verimlilik Giderleri": {}, - "Genel \u00dcretim Giderleri Yans\u0131tma Hesab\u0131": {} - }, - "Hizmet \u00dcretim Maliyeti": { - "Hizmet \u00dcretim Maliyeti": {}, - "Hizmet \u00dcretim Maliyeti Fark Hesaplar\u0131": {}, - "Hizmet \u00dcretim Maliyeti Yans\u0131tma Hesab\u0131": {} - }, - "Maliyet Muhasebesi Ba\u011flant\u0131 Hesaplar\u0131": { - "Maliyet Muhasebesi Ba\u011flant\u0131 Hesab\u0131": {}, - "Maliyet Muhasebesi Yans\u0131tma Hesab\u0131": {} - }, - "Pazarlama, Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri": { - "Atra\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {}, - "Pazarlama Sat\u0131\u015f Ve Dag\u0131t\u0131m Giderleri Yans\u0131tma Hesab\u0131": {}, - "Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri Fark Hesab\u0131": {} - }, - "root_type": "" - }, - "Naz\u0131m Hesaplar": { - "root_type": "" - }, - "Serbest Hesaplar": { - "root_type": "" - }, - "Uzun Vadeli Yabanc\u0131 Kaynaklar": { - "Al\u0131nan Avanslar": { - "Al\u0131nan Di\u011fer Avanslar": { - "account_type": "Payable" - }, - "Al\u0131nan Sipari\u015f Avanslar\u0131": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": { - "account_type": "Payable" - }, - "K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {}, - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7lar": { - "Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": { - "account_type": "Payable" - }, - "Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": { - "account_type": "Payable" - }, - "Kamuya Olan Ertelenmi\u015f Veya Taksitlendirilmi\u015f Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ortaklara Bor\u00e7lar": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u0130\u015ftiraklere Bor\u00e7lar": { - "account_type": "Payable" - } - }, - "Di\u011fer Uzun Vadeli Yabanc\u0131 Kaynaklar": { - "Di\u011fer \u00c7e\u015fitli Uzun Vadeli Yabanc\u0131 Kaynaklar": { - "account_type": "Payable" - }, - "Gelecek Y\u0131llara Ertelenmi\u015f Veya Terkin Edilecek KDV": { - "account_type": "Payable" - }, - "Tesise Kat\u0131lma Paylar\u0131": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "Gelecek Y\u0131llara Ait Gelirler Ve Gider Tahakkuklar\u0131": { - "Gelecek Y\u0131llara Ait Gelirler": {}, - "Gider Tahakkuklar\u0131": {} - }, - "Mali Bor\u00e7lar": { - "Banka Kredileri": { - "account_type": "Payable" - }, - "Di\u011fer Mali Bor\u00e7lar": { - "account_type": "Payable" - }, - "Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": { - "account_type": "Payable" - }, - "Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": { - "account_type": "Payable" - }, - "Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": { - "account_type": "Payable" - }, - "account_type": "Payable", - "\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": { - "account_type": "Payable" - }, - "\u00c7\u0131kar\u0131lm\u0131\u015f Tahviller": { - "account_type": "Payable" - } - }, - "Ticari Bor\u00e7lar": { - "Al\u0131nan Depozito Ve Teminatlar": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri": { - "account_type": "Payable" - }, - "Bor\u00e7 Senetleri Reeskontu(-)": { - "account_type": "Payable" - }, - "Di\u011fer Ticari Bor\u00e7lar": { - "account_type": "Payable" - }, - "Sat\u0131c\u0131lar": { - "account_type": "Payable" - }, - "account_type": "Payable" - }, - "root_type": "" - }, - "\u00d6z Kaynaklar": { - "D\u00f6nem Net K\u00e2r\u0131 (Zarar\u0131)": { - "D\u00f6nem Net K\u00e2r\u0131": {}, - "D\u00f6nem Net Zarar\u0131(-)": {} - }, - "Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": { - "Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {} - }, - "Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": { - "Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {} - }, - "K\u00e2r Yedekleri": { - "Di\u011fer K\u00e2r Yedekleri": {}, - "Ola\u011fan\u00fcst\u00fc Yedekler": {}, - "Stat\u00fc Yedekleri": {}, - "Yasal Yedekler": {}, - "\u00d6zel Fonlar": {} - }, - "Sermaye Yedekleri": { - "Di\u011fer Sermaye Yedekleri": {}, - "Hisse Senedi \u0130ptal K\u00e2rlar\u0131": {}, - "Hisse Senetleri \u0130hra\u00e7 Primleri": {}, - "Maddi Duran Varl\u0131k Yeniden De\u011ferlenme Art\u0131\u015flar\u0131": {}, - "Maliyet Art\u0131\u015flar\u0131 Fonu": {}, - "\u0130\u015ftirakler Yeniden De\u011ferleme Art\u0131\u015flar\u0131": {} - }, - "root_type": "", - "\u00d6denmi\u015f Sermaye": { - "Sermaye": {}, - "\u00d6denmi\u015f Sermaye(-)": { - "account_type": "Payable" - } - } - } - } -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json new file mode 100644 index 00000000000..fa8860883d2 --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json @@ -0,0 +1,1473 @@ +{ + "country_code": "tr", + "name": "Turkey - Chart of Accounts", + "tree": { + "D\u00d6NEN VARLIKLAR": { + "account_number": "1", + "root_type": "Asset", + "HAZIR DE\u011eERLER": { + "account_number": "10", + "KASA": { + "account_number": "100", + "is_group": 1, + "account_type": "Cash", + "TL KASA": { + "account_number": "100.01", + "account_type": "Cash" + }, + "EUR KASA": { + "account_number": "100.02", + "account_type": "Cash" + }, + "USD KASA": { + "account_number": "100.03", + "account_type": "Cash" + } + }, + "ALINAN \u00c7EKLER": { + "account_number": "101", + "is_group": 1 + }, + "BANKALAR": { + "account_number": "102", + "is_group": 1, + "account_type": "Bank" + }, + "VER\u0130LEN \u00c7EKLER VE \u00d6DEME EM\u0130RLER\u0130(-)": { + "account_number": "103", + "is_group": 1 + }, + "D\u0130\u011eER HAZIR DE\u011eERLER": { + "account_number": "108", + "is_group": 1 + } + }, + "MENKUL KIYMETLER": { + "account_number": "11", + "H\u0130SSE SENETLER\u0130": { + "account_number": "110", + "is_group": 1 + }, + "\u00d6ZEL KES\u0130M TAHV\u0130L SENET VE BONOLARI": { + "account_number": "111", + "is_group": 1 + }, + "KAMU KES\u0130M\u0130 TAHV\u0130L, SENET VE BONOLARI": { + "account_number": "112", + "is_group": 1 + }, + "D\u0130\u011eER MENKUL KIYMETLER": { + "account_number": "118", + "is_group": 1 + }, + "MENKUL KIYMETLER DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "119", + "is_group": 1 + } + }, + "T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "12", + "ALICILAR": { + "account_number": "120", + "ALICILAR": { + "account_number": "120.01", + "account_type": "Receivable" + } + }, + "ALACAK SENETLER\u0130": { + "account_number": "121", + "is_group": 1 + }, + "ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "122", + "is_group": 1 + }, + "KAZANILMAMI\u015e F\u0130NANSAL K\u0130RALAMA FA\u0130Z GEL\u0130RLER\u0130 (-)": { + "account_number": "124", + "is_group": 1 + }, + "VER\u0130LEN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "126", + "is_group": 1 + }, + "D\u0130\u011eER T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "127", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "128", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "129", + "is_group": 1 + } + }, + "D\u0130\u011eER ALACAKLAR": { + "account_number": "13", + "ORTAKLARDAN ALACAKLAR": { + "account_number": "131", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERDEN ALACAKLAR": { + "account_number": "132", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARDAN ALACAKLAR": { + "account_number": "133", + "is_group": 1 + }, + "PERSONELDEN ALACAKLAR": { + "account_number": "135", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 ALACAKLAR": { + "account_number": "136", + "is_group": 1 + }, + "D\u0130\u011eER ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "137", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR": { + "account_number": "138", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "139", + "is_group": 1 + } + }, + "STOKLAR": { + "account_number": "15", + "\u0130LK MADDE MALZEME": { + "account_number": "150", + "is_group": 1 + }, + "YARI MAMULLER": { + "account_number": "151", + "is_group": 1 + }, + "MAMULLER": { + "account_number": "152", + "is_group": 1 + }, + "T\u0130CAR\u0130 MALLAR": { + "account_number": "153", + "is_group": 1 + }, + "D\u0130\u011eER STOKLAR": { + "account_number": "157", + "D\u0130\u011eR. STOK.": { + "account_number": "157.01", + "account_type": "Stock" + } + }, + "STOK DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "158", + "is_group": 1 + }, + "VER\u0130LEN S\u0130PAR\u0130\u015e AVANSLARI": { + "account_number": "159", + "is_group": 1 + } + }, + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130": { + "account_number": "17", + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130": { + "account_number": "170", + "is_group": 1 + }, + "(YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130)": { + "account_number": "177", + "is_group": 1 + }, + "(YILLARA YAYGIN \u0130N\u015eAAT ENFLASYON D\u00dcZELTME)": { + "account_number": "178", + "is_group": 1 + }, + "TA\u015eERONLARA VER\u0130LEN AVANSLAR": { + "account_number": "179", + "is_group": 1 + } + }, + "GELECEK AYLARA A\u0130T G\u0130DERLER VE GEL\u0130R TAHAKKUKLARI": { + "account_number": "18", + "GELECEK AYLARA A\u0130T G\u0130DERLER": { + "account_number": "180", + "is_group": 1 + }, + "GEL\u0130R TAHAKKUKLARI": { + "account_number": "181", + "is_group": 1 + } + }, + "D\u0130\u011eER D\u00d6NEN VARLIKLAR": { + "account_number": "19", + "DEVREDEN KDV": { + "account_number": "190", + "is_group": 1 + }, + "\u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191", + "\u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01", + "% 18'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.01", + "account_type": "Tax" + }, + "% 8'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.02", + "account_type": "Tax" + }, + "% 1'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.03", + "account_type": "Tax" + }, + "% 20'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.04", + "account_type": "Tax" + }, + "% 10'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "191.01.05", + "account_type": "Tax" + } + } + }, + "D\u0130\u011eER KDV": { + "account_number": "192", + "is_group": 1 + }, + "PE\u015e\u0130N \u00d6DENEN VERG\u0130LER VE FONLAR": { + "account_number": "193", + "is_group": 1 + }, + "\u0130\u015e AVANSLARI": { + "account_number": "195", + "is_group": 1 + }, + "PERSONEL AVANSLARI": { + "account_number": "196", + "is_group": 1 + }, + "SAYIM VE TESELL\u00dcM NOKSANLARI": { + "account_number": "197", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 D\u00d6NEN VARLIKLAR": { + "account_number": "198", + "is_group": 1 + }, + "D\u0130\u011eER D\u00d6NEN VARLIKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "199", + "is_group": 1 + } + } + }, + "DURAN VARLIKLAR": { + "account_number": "2", + "root_type": "Asset", + "T\u0130CAR\u0130 ALACAKLAR": { + "account_number": "22", + "ALICILAR": { + "account_number": "220", + "is_group": 1 + }, + "ALACAK SENETLER\u0130": { + "account_number": "221", + "is_group": 1 + }, + "ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "222", + "is_group": 1 + }, + "KAZANILMAMI\u015e F\u0130NANSAL K\u0130RALAMA FA\u0130Z GEL\u0130RLER\u0130 (-)-": { + "account_number": "224", + "is_group": 1 + }, + "VER\u0130LEN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "226", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "229", + "is_group": 1 + } + }, + "D\u0130\u011eER ALACAKLAR": { + "account_number": "23", + "ORTAKLARDAN ALACAKLAR": { + "account_number": "231", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERDEN ALACAKLAR": { + "account_number": "232", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARDAN ALACAKLAR": { + "account_number": "233", + "is_group": 1 + }, + "PERSONELDEN ALACAKLAR": { + "account_number": "235", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 ALACAKLAR": { + "account_number": "236", + "is_group": 1 + }, + "D\u0130\u011eER ALACAK SENETLER\u0130 REESKONTU(-)": { + "account_number": "237", + "is_group": 1 + }, + "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "239", + "is_group": 1 + } + }, + "MAL\u0130 DURAN VARLIKLAR": { + "account_number": "24", + "BA\u011eLI MENKUL KIYMETLER": { + "account_number": "240", + "BA\u011eL. MEKL. KIYM.": { + "account_number": "240.01", + "account_type": "Fixed Asset" + } + }, + "BA\u011eLI MENKUL KIYMETLER DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "241", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLER": { + "account_number": "242", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERE SERMAYE TAAHH\u00dcTLER\u0130(-)": { + "account_number": "243", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLER SERMAYE PAYLARI DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "244", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLAR": { + "account_number": "245", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARA SERMAYE TAAHH\u00dcTLER\u0130(-)": { + "account_number": "246", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLAR SERMAYE PAYLARI DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "247", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 DURAN VARLIKLAR": { + "account_number": "248", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 DURAN VARLIKLAR KAR\u015eILI\u011eI(-)": { + "account_number": "249", + "is_group": 1 + } + }, + "MADD\u0130 DURAN VARLIKLAR": { + "account_number": "25", + "ARAZ\u0130 VE ARSALAR": { + "account_number": "250", + "is_group": 1 + }, + "YER ALTI VE YER \u00dcST\u00dc D\u00dcZENLER\u0130": { + "account_number": "251", + "is_group": 1 + }, + "B\u0130NALAR": { + "account_number": "252", + "is_group": 1 + }, + "TES\u0130S, MAK\u0130NE VE C\u0130HAZLAR": { + "account_number": "253", + "is_group": 1 + }, + "TA\u015eITLAR": { + "account_number": "254", + "is_group": 1 + }, + "DEM\u0130RBA\u015eLAR": { + "account_number": "255", + "is_group": 1 + }, + "D\u0130\u011eER MADD\u0130 DURAN VARLIKLAR": { + "account_number": "256", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": { + "account_number": "257", + "is_group": 1 + }, + "YAPILMAKTA OLAN YATIRIMLAR": { + "account_number": "258", + "is_group": 1 + }, + "VER\u0130LEN AVANSLAR": { + "account_number": "259", + "is_group": 1 + } + }, + "MADD\u0130 OLMAYAN DURAN VARLIKLAR": { + "account_number": "26", + "HAKLAR": { + "account_number": "260", + "is_group": 1 + }, + "\u015eEREF\u0130YE": { + "account_number": "261", + "is_group": 1 + }, + "KURULU\u015e VE \u00d6RG\u00dcTLENME G\u0130DERLER\u0130": { + "account_number": "262", + "is_group": 1 + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "263", + "is_group": 1 + }, + "\u00d6ZEL MAL\u0130YETLER": { + "account_number": "264", + "\u00d6ZL. MAL\u0130YT.": { + "account_number": "264.01", + "account_type": "Depreciation" + } + }, + "D\u0130\u011eER MADD\u0130 OLMAYAN DURAN VARLIKLAR": { + "account_number": "267", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": { + "account_number": "268", + "is_group": 1 + }, + "VER\u0130LEN AVANSLAR": { + "account_number": "269", + "is_group": 1 + } + }, + "\u00d6ZEL T\u00dcKENMEYE TAB\u0130 VARLIKLAR": { + "account_number": "27", + "ARAMA G\u0130DERLER\u0130": { + "account_number": "271", + "is_group": 1 + }, + "HAZIRLIK VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "272", + "is_group": 1 + }, + "D\u0130\u011eER \u00d6ZEL T\u00dcKENMEYE TAB\u0130 VARLIKLAR": { + "account_number": "277", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e T\u00dcKENME PAYLARI(-)": { + "account_number": "278", + "is_group": 1 + }, + "VER\u0130LEN AVANSLAR": { + "account_number": "279", + "is_group": 1 + } + }, + "GELECEK YILLARA A\u0130T G\u0130DERLER VE GEL\u0130R TAHAKKUKLARI": { + "account_number": "28", + "GELECEK YILLARA A\u0130T G\u0130DERLER": { + "account_number": "280", + "is_group": 1 + }, + "GEL\u0130R TAHAKKUKLARI": { + "account_number": "281", + "is_group": 1 + } + }, + "D\u0130\u011eER DURAN VARLIKLAR": { + "account_number": "29", + "GELECEK YILLARDA \u0130ND\u0130R\u0130LECEK KDV": { + "account_number": "291", + "is_group": 1 + }, + "D\u0130\u011eER KDV": { + "account_number": "292", + "is_group": 1 + }, + "GELECEK YILLAR \u0130HT\u0130YACI STOKLAR": { + "account_number": "293", + "is_group": 1 + }, + "ELDEN \u00c7IKARILACAK STOKLAR VE MADD\u0130 DURAN VARLIKLAR": { + "account_number": "294", + "is_group": 1 + }, + "PE\u015e\u0130N \u00d6DENEN VERG\u0130 VE FONLAR": { + "account_number": "295", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 DURAN VARLIKLAR": { + "account_number": "297", + "is_group": 1 + }, + "STOK DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": { + "account_number": "298", + "is_group": 1 + }, + "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": { + "account_number": "299", + "is_group": 1 + } + } + }, + "KISA VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "3", + "root_type": "Liability", + "MAL\u0130 BOR\u00c7LAR": { + "account_number": "30", + "BANKA KRED\u0130LER\u0130": { + "account_number": "300", + "is_group": 1 + }, + "F\u0130NANSAL K\u0130RALAMA \u0130\u015eLEMLER\u0130NDEN BOR\u00c7LAR": { + "account_number": "301", + "is_group": 1 + }, + "ERTELENM\u0130\u015e F\u0130NANSAL K\u0130RALAMA BOR\u00c7LANMA MAL\u0130YETLER\u0130(-)": { + "account_number": "302", + "is_group": 1 + }, + "UZUN VADEL\u0130 KRED\u0130LER\u0130N ANAPARA TAKS\u0130TLER\u0130 VE FA\u0130ZLER\u0130": { + "account_number": "303", + "is_group": 1 + }, + "TAHV\u0130L ANAPARA BOR\u00c7, TAKS\u0130T VE FA\u0130ZLER\u0130": { + "account_number": "304", + "is_group": 1 + }, + "\u00c7IKARILAN BONOLAR VE SENETLER": { + "account_number": "305", + "is_group": 1 + }, + "\u00c7IKARILMI\u015e D\u0130\u011eER MENKUL KIYMETLER": { + "account_number": "306", + "is_group": 1 + }, + "MENKUL KIYMETLER \u0130HRA\u00c7 FARKI(-)": { + "account_number": "308", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 BOR\u00c7LAR": { + "account_number": "309", + "is_group": 1 + } + }, + "T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "32", + "SATICILAR": { + "account_number": "320", + "SATICILAR TRY": { + "account_number": "320.01" + } + }, + "BOR\u00c7 SENETLER\u0130": { + "account_number": "321", + "is_group": 1 + }, + "BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "322", + "is_group": 1 + }, + "ALINAN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "326", + "is_group": 1 + }, + "D\u0130\u011eER T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "329", + "is_group": 1 + } + }, + "D\u0130\u011eER BOR\u00c7LAR": { + "account_number": "33", + "ORTAKLARA BOR\u00c7LAR": { + "account_number": "331", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERE BOR\u00c7LAR": { + "account_number": "332", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARA BOR\u00c7LAR": { + "account_number": "333", + "is_group": 1 + }, + "PERSONELE BOR\u00c7LAR": { + "account_number": "335", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 BOR\u00c7LAR": { + "account_number": "336", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "337", + "is_group": 1 + } + }, + "ALINAN AVANSLAR": { + "account_number": "34", + "ALINAN S\u0130PAR\u0130\u015e AVANSLARI": { + "account_number": "340", + "is_group": 1 + }, + "ALINAN D\u0130\u011eER AVANSLAR": { + "account_number": "349", + "is_group": 1 + } + }, + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM HAKED\u0130\u015eLER\u0130": { + "account_number": "35", + "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM HAKED\u0130\u015e BEDELLER\u0130": { + "account_number": "358", + "is_group": 1 + } + }, + "\u00d6DENECEK VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": { + "account_number": "36", + "\u00d6DENECEK VERG\u0130 VE FONLAR": { + "account_number": "360", + "is_group": 1 + }, + "\u00d6DENECEK SOSYAL G\u00dcVENL\u0130K KES\u0130NT\u0130LER\u0130": { + "account_number": "361", + "is_group": 1 + }, + "VADES\u0130 GE\u00c7M\u0130\u015e, ERTELENM\u0130\u015e VEYA TAKS\u0130TLEND\u0130R\u0130LM\u0130\u015e VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": { + "account_number": "368", + "is_group": 1 + }, + "\u00d6DENECEK D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": { + "account_number": "369", + "is_group": 1 + } + }, + "BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "37", + "D\u00d6NEM K\u00c2RI VERG\u0130 VE D\u0130\u011eER YASAL Y\u00dcK\u00dcML\u00dcL\u00dcK KAR\u015eILIKLARI": { + "account_number": "370", + "is_group": 1 + }, + "D\u00d6NEM K\u00c2RININ PE\u015e\u0130N \u00d6DENEN VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER\u0130(-)": { + "account_number": "371", + "is_group": 1 + }, + "KIDEM TAZM\u0130NATI KAR\u015eILI\u011eI": { + "account_number": "372", + "is_group": 1 + }, + "MAL\u0130YET G\u0130DERLER\u0130 KAR\u015eILI\u011eI": { + "account_number": "373", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "379", + "is_group": 1 + } + }, + "GELECEK AYLARA A\u0130T GEL\u0130RLER VE G\u0130DER TAHAKKUKLARI": { + "account_number": "38", + "GELECEK AYLARA A\u0130T GEL\u0130RLER": { + "account_number": "380", + "is_group": 1 + }, + "G\u0130DER TAHAKKUKLARI": { + "account_number": "381", + "is_group": 1 + } + }, + "D\u0130\u011eER KISA VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "39", + "HESAPLANAN KDV": { + "account_number": "391", + "HESAPL. KDV": { + "account_number": "391.01", + "% 18 HESAPLAN KDV": { + "account_number": "391.01.01", + "account_type": "Tax" + }, + "% 8 HESAPLANAN KDV": { + "account_number": "391.01.02", + "account_type": "Tax" + }, + "% 1 HESAPLANAN KDV": { + "account_number": "391.01.03", + "account_type": "Tax" + }, + "% 20 HESAPLANAN KDV": { + "account_number": "391.01.04", + "account_type": "Tax" + }, + "% 10 HESAPLANAN KDV": { + "account_number": "391.01.05", + "account_type": "Tax" + } + } + }, + "D\u0130\u011eER KDV": { + "account_number": "392", + "is_group": 1 + }, + "MERKEZ VE \u015eUBELER CAR\u0130 HESABI": { + "account_number": "393", + "is_group": 1 + }, + "SAYIM VE TESELL\u00dcM FAZLALARI": { + "account_number": "397", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 YABANCI KAYNAKLAR": { + "account_number": "399", + "is_group": 1 + } + } + }, + "UZUN VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "4", + "root_type": "Liability", + "MAL\u0130 BOR\u00c7LAR": { + "account_number": "40", + "BANKA KRED\u0130LER\u0130": { + "account_number": "400", + "is_group": 1 + }, + "F\u0130NANSAL K\u0130RALAMA \u0130\u015eLEMLER\u0130NDEN BOR\u00c7LAR-": { + "account_number": "401", + "is_group": 1 + }, + "ERTELENM\u0130\u015e F\u0130NANSAL K\u0130RALAMA BOR\u00c7LANMA MAL\u0130YETLER\u0130 (-)": { + "account_number": "402", + "is_group": 1 + }, + "\u00c7IKARILMI\u015e TAHV\u0130LLER": { + "account_number": "405", + "is_group": 1 + }, + "\u00c7IKARILMI\u015e D\u0130\u011eER MENKUL KIYMETLER": { + "account_number": "407", + "is_group": 1 + }, + "MENKUL KIYMETLER \u0130HRA\u00c7 FARKI(-)": { + "account_number": "408", + "is_group": 1 + }, + "D\u0130\u011eER MAL\u0130 BOR\u00c7LAR": { + "account_number": "409", + "is_group": 1 + } + }, + "T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "42", + "SATICILAR": { + "account_number": "420", + "is_group": 1 + }, + "BOR\u00c7 SENETLER\u0130": { + "account_number": "421", + "is_group": 1 + }, + "BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "422", + "is_group": 1 + }, + "ALINAN DEPOZ\u0130TO VE TEM\u0130NATLAR": { + "account_number": "426", + "is_group": 1 + }, + "D\u0130\u011eER T\u0130CAR\u0130 BOR\u00c7LAR": { + "account_number": "429", + "is_group": 1 + } + }, + "D\u0130\u011eER BOR\u00c7LAR": { + "account_number": "43", + "ORTAKLARA BOR\u00c7LAR": { + "account_number": "431", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLERE BOR\u00c7LAR": { + "account_number": "432", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARA BOR\u00c7LAR": { + "account_number": "433", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 BOR\u00c7LAR": { + "account_number": "436", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 SENETLER\u0130 REESKONTU(-)": { + "account_number": "437", + "is_group": 1 + }, + "KAMUYA OLAN ERTELENM\u0130\u015e VEYA TAKS\u0130TLEND\u0130R\u0130LM\u0130\u015e BOR\u00c7LAR": { + "account_number": "438", + "is_group": 1 + } + }, + "ALINAN AVANSLAR": { + "account_number": "44", + "ALINAN S\u0130PAR\u0130\u015e AVANSLARI": { + "account_number": "440", + "is_group": 1 + }, + "ALINAN D\u0130\u011eER AVANSLAR": { + "account_number": "449", + "is_group": 1 + } + }, + "BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "47", + "KIDEM TAZM\u0130NATI KAR\u015eILI\u011eI": { + "account_number": "472", + "is_group": 1 + }, + "D\u0130\u011eER BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": { + "account_number": "479", + "is_group": 1 + } + }, + "GELECEK YILLARA A\u0130T GEL\u0130RLER VE G\u0130DER TAHAKKUKLARI": { + "account_number": "48", + "GELECEK YILLARA A\u0130T GEL\u0130RLER": { + "account_number": "480", + "is_group": 1 + }, + "G\u0130DER TAHAKKUKLARI": { + "account_number": "481", + "is_group": 1 + } + }, + "D\u0130\u011eER UZUN VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "49", + "GELECEK YILLARA ERTELENM\u0130\u015e VEYA TERK\u0130N ED\u0130LECEK KDV": { + "account_number": "492", + "is_group": 1 + }, + "TES\u0130SE KATILMA PAYLARI": { + "account_number": "493", + "is_group": 1 + }, + "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 UZUN VADEL\u0130 YABANCI KAYNAKLAR": { + "account_number": "499", + "is_group": 1 + } + } + }, + "\u00d6Z KAYNAKLAR": { + "account_number": "5", + "root_type": "Equity", + "\u00d6DENM\u0130\u015e SERMAYE": { + "account_number": "50", + "SERMAYE": { + "account_number": "500", + "is_group": 1 + }, + "\u00d6DENMEM\u0130\u015e SERMAYE(-)": { + "account_number": "501", + "is_group": 1 + }, + "SERMAYE D\u00dcZELTMES\u0130 OLUMLU FARKLARI": { + "account_number": "502", + "is_group": 1 + }, + "SERMAYE D\u00dcZELTMES\u0130 OLUMSUZ FARKLARI (-)": { + "account_number": "503", + "is_group": 1 + } + }, + "SERMAYE YEDEKLER\u0130": { + "account_number": "52", + "H\u0130SSE SENETLER\u0130 \u0130HRA\u00c7 PR\u0130MLER\u0130": { + "account_number": "520", + "is_group": 1 + }, + "H\u0130SSE SENED\u0130 \u0130PTAL K\u00c2RLARI": { + "account_number": "521", + "is_group": 1 + }, + "MADD\u0130 DURAN VARLIK YEN\u0130DEN DE\u011eERLEME ARTI\u015eLARI": { + "account_number": "522", + "is_group": 1 + }, + "\u0130\u015eT\u0130RAKLER YEN\u0130DEN DE\u011eERLEME ARTI\u015eLARI": { + "account_number": "523", + "is_group": 1 + }, + "D\u0130\u011eER SERMAYE YEDEKLER\u0130": { + "account_number": "529", + "is_group": 1 + } + }, + "K\u00c2R YEDEKLER\u0130": { + "account_number": "54", + "YASAL YEDEKLER": { + "account_number": "540", + "is_group": 1 + }, + "STAT\u00dc YEDEKLER\u0130": { + "account_number": "541", + "is_group": 1 + }, + "OLA\u011eAN\u00dcST\u00dc YEDEKLER": { + "account_number": "542", + "is_group": 1 + }, + "D\u0130\u011eER K\u00c2R YEDEKLER\u0130": { + "account_number": "548", + "is_group": 1 + }, + "\u00d6ZEL FONLAR": { + "account_number": "549", + "is_group": 1 + } + }, + "GE\u00c7M\u0130\u015e YILLAR K\u00c2RLARI": { + "account_number": "57", + "GE\u00c7M\u0130\u015e YILLAR K\u00c2RLARI": { + "account_number": "570", + "is_group": 1 + } + }, + "GE\u00c7M\u0130\u015e YILLAR ZARARLARI(-)": { + "account_number": "58", + "GE\u00c7M\u0130\u015e YILLAR ZARARLARI(-)": { + "account_number": "580", + "is_group": 1 + } + }, + "D\u00d6NEM NET K\u00c2RI (ZARARI)": { + "account_number": "59", + "D\u00d6NEM NET K\u00c2RI": { + "account_number": "590", + "is_group": 1 + }, + "D\u00d6NEM NET ZARARI(-)": { + "account_number": "591", + "is_group": 1 + } + } + }, + "GEL\u0130R TABLOSU HESAPLARI": { + "account_number": "6", + "root_type": "Income", + "BR\u00dcT SATI\u015eLAR": { + "account_number": "60", + "YURT \u0130\u00c7\u0130 SATI\u015eLAR": { + "account_number": "600", + "is_group": 1 + }, + "YURT DI\u015eI SATI\u015eLAR": { + "account_number": "601", + "is_group": 1 + }, + "D\u0130\u011eER GEL\u0130RLER": { + "account_number": "602", + "is_group": 1 + } + }, + "SATI\u015e \u0130ND\u0130R\u0130MLER\u0130(-)": { + "account_number": "61", + "SATI\u015eTAN \u0130ADELER(-)": { + "account_number": "610", + "is_group": 1 + }, + "SATI\u015e \u0130ND\u0130R\u0130MLER\u0130(-)": { + "account_number": "611", + "is_group": 1 + }, + "D\u0130\u011eER \u0130ND\u0130R\u0130MLER(-)": { + "account_number": "612", + "is_group": 1 + } + }, + "SATI\u015eLARIN MAL\u0130YET\u0130(-)": { + "account_number": "62", + "SATILAN MAMULLER MAL\u0130YET\u0130(-)": { + "account_number": "620", + "is_group": 1 + }, + "SATILAN T\u0130CAR\u0130 MALLAR MAL\u0130YET\u0130(-)": { + "account_number": "621", + "is_group": 1 + }, + "SATILAN H\u0130ZMET MAL\u0130YET\u0130(-)": { + "account_number": "622", + "is_group": 1 + }, + "D\u0130\u011eER SATI\u015eLARIN MAL\u0130YET\u0130(-)": { + "account_number": "623", + "D\u0130\u011eR. SAT\u015e. MALYT.": { + "account_number": "623.01" + } + } + }, + "FAAL\u0130YET G\u0130DERLER\u0130(-)": { + "account_number": "63", + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130(-)": { + "account_number": "630", + "is_group": 1 + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130(-)": { + "account_number": "631", + "is_group": 1 + }, + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130(-)": { + "account_number": "632", + "is_group": 1 + } + }, + "D\u0130\u011eER FAAL\u0130YETLERDEN OLA\u011eAN GEL\u0130R VE K\u00c2RLAR": { + "account_number": "64", + "\u0130\u015eT\u0130RAKLERDEN TEMETT\u00dc GEL\u0130RLER\u0130": { + "account_number": "640", + "is_group": 1 + }, + "BA\u011eLI ORTAKLIKLARDAN TEMETT\u00dc GEL\u0130RLER\u0130": { + "account_number": "641", + "is_group": 1 + }, + "FA\u0130Z GEL\u0130RLER\u0130": { + "account_number": "642", + "is_group": 1 + }, + "KOM\u0130SYON GEL\u0130RLER\u0130": { + "account_number": "643", + "is_group": 1 + }, + "KONUSU KALMAYAN KAR\u015eILIKLAR": { + "account_number": "644", + "is_group": 1 + }, + "MENKUL KIYMET SATI\u015e K\u00c2RLARI": { + "account_number": "645", + "is_group": 1 + }, + "KAMB\u0130YO K\u00c2RLARI": { + "account_number": "646", + "is_group": 1 + }, + "REESKONT FA\u0130Z GEL\u0130RLER\u0130": { + "account_number": "647", + "is_group": 1 + }, + "ENFLASYON D\u00dcZELTME K\u00c2RLARI": { + "account_number": "648", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN GEL\u0130R VE K\u00c2RLAR": { + "account_number": "649", + "is_group": 1 + } + }, + "D\u0130\u011eER FAAL\u0130YETLERDEN OLA\u011eAN G\u0130DER VE ZARARLAR(-)": { + "account_number": "65", + "KOM\u0130SYON G\u0130DERLER\u0130(-)": { + "account_number": "653", + "is_group": 1 + }, + "KAR\u015eILIK G\u0130DERLER\u0130(-)": { + "account_number": "654", + "is_group": 1 + }, + "MENKUL KIYMET SATI\u015e ZARARLARI(-)": { + "account_number": "655", + "is_group": 1 + }, + "KAMB\u0130YO ZARARLARI(-)": { + "account_number": "656", + "is_group": 1 + }, + "REESKONT FA\u0130Z G\u0130DERLER\u0130(-)": { + "account_number": "657", + "is_group": 1 + }, + "ENFLASYON D\u00dcZELTMES\u0130 ZARARLARI(-)": { + "account_number": "658", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN G\u0130DER VE ZARARLAR(-)": { + "account_number": "659", + "is_group": 1 + } + }, + "F\u0130NANSMAN G\u0130DERLER\u0130(-)": { + "account_number": "66", + "KISA VADEL\u0130 BOR\u00c7LANMA G\u0130DERLER\u0130(-)": { + "account_number": "660", + "is_group": 1 + }, + "UZUN VADEL\u0130 BOR\u00c7LANMA G\u0130DERLER\u0130(-)": { + "account_number": "661", + "is_group": 1 + } + }, + "OLA\u011eAN DI\u015eI GEL\u0130R VE K\u00c2RLAR": { + "account_number": "67", + "\u00d6NCEK\u0130 D\u00d6NEM GEL\u0130R VE K\u00c2RLARI": { + "account_number": "671", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN DI\u015eI GEL\u0130R VE K\u00c2RLAR": { + "account_number": "679", + "is_group": 1 + } + }, + "OLA\u011eAN DI\u015eI G\u0130DER VE ZARARLAR(-)": { + "account_number": "68", + "\u00c7ALI\u015eMAYAN KISIM G\u0130DER VE ZARARLARI(-)": { + "account_number": "680", + "is_group": 1 + }, + "\u00d6NCEK\u0130 D\u00d6NEM G\u0130DER VE ZARARLARI(-)": { + "account_number": "681", + "is_group": 1 + }, + "D\u0130\u011eER OLA\u011eAN DI\u015eI G\u0130DER VE ZARARLAR(-)": { + "account_number": "689", + "is_group": 1 + } + }, + "D\u00d6NEM NET K\u00c2RI VEYA ZARARI": { + "account_number": "69", + "D\u00d6NEM K\u00c2RI VEYA ZARARI": { + "account_number": "690", + "is_group": 1 + }, + "D\u00d6NEM K\u00c2RI VERG\u0130 VE D\u0130\u011eER YASAL Y\u00dcK\u00dcML\u00dcL\u00dcK KAR\u015eILIKLARI(-)": { + "account_number": "691", + "is_group": 1 + }, + "D\u00d6NEM NET K\u00c2RI VEYA ZARARI": { + "account_number": "692", + "is_group": 1 + }, + "YILLARA YAYGIN \u0130N\u015eAAT VE ENFLASYON D\u00dcZELTME HESABI": { + "account_number": "697", + "is_group": 1 + }, + "ENFLASYON D\u00dcZELTME HESABI": { + "account_number": "698", + "is_group": 1 + } + } + }, + "MAL\u0130YET HESAPLARI (7/A SE\u00c7ENE\u011e\u0130)": { + "account_number": "7", + "root_type": "Expense", + "MAL\u0130YET MUHASEBES\u0130 BA\u011eLANTI HESAPLARI": { + "account_number": "70", + "MAL\u0130YET MUHASEBES\u0130 BA\u011eLANTI HESABI": { + "account_number": "700", + "is_group": 1 + }, + "MAL\u0130YET MUHASEBES\u0130 YANSITMA HESABI": { + "account_number": "701", + "is_group": 1 + } + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME G\u0130DERLER\u0130": { + "account_number": "71", + "D\u0130REKT \u0130LK MADDE VE MALZEME G\u0130DERLER\u0130 HESABI": { + "account_number": "710", + "is_group": 1 + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME YANSITMA HESABI": { + "account_number": "711", + "is_group": 1 + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME F\u0130YAT FARKI": { + "account_number": "712", + "is_group": 1 + }, + "D\u0130REKT \u0130LK MADDE VE MALZEME M\u0130KTAR FARKI": { + "account_number": "713", + "is_group": 1 + } + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130": { + "account_number": "72", + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130": { + "account_number": "720", + "is_group": 1 + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "721", + "is_group": 1 + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K \u00dcCRET FARKLARI": { + "account_number": "722", + "is_group": 1 + }, + "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K S\u00dcRE FARKLARI": { + "account_number": "723", + "is_group": 1 + } + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130": { + "account_number": "73", + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130": { + "account_number": "730", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "731", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 B\u00dcT\u00c7E FARKLARI": { + "account_number": "732", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 VER\u0130ML\u0130L\u0130K G\u0130DERLER\u0130": { + "account_number": "733", + "is_group": 1 + }, + "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 KAPAS\u0130TE FARKLARI": { + "account_number": "734", + "is_group": 1 + } + }, + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130": { + "account_number": "74", + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130": { + "account_number": "740", + "is_group": 1 + }, + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130 YANSITMA HESABI": { + "account_number": "741", + "is_group": 1 + }, + "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130 FARK HESAPLARI": { + "account_number": "742", + "is_group": 1 + } + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "75", + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": { + "account_number": "750", + "is_group": 1 + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "751", + "is_group": 1 + }, + "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DER FARKLARI": { + "account_number": "752", + "is_group": 1 + } + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130": { + "account_number": "76", + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130": { + "account_number": "760", + "is_group": 1 + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "761", + "is_group": 1 + }, + "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130 FARK HESABI": { + "account_number": "762", + "is_group": 1 + } + }, + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130": { + "account_number": "77", + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130": { + "account_number": "770", + "is_group": 1 + }, + "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "771", + "is_group": 1 + }, + "GENEL Y\u00d6NET\u0130M G\u0130DER FARKLARI HESABI": { + "account_number": "772", + "is_group": 1 + } + }, + "F\u0130NANSMAN G\u0130DERLER\u0130": { + "account_number": "78", + "F\u0130NANSMAN G\u0130DERLER\u0130": { + "account_number": "780", + "is_group": 1 + }, + "F\u0130NANSMAN G\u0130DERLER\u0130 YANSITMA HESABI": { + "account_number": "781", + "is_group": 1 + }, + "F\u0130NANSMAN G\u0130DERLER\u0130 FARK HESABI": { + "account_number": "782", + "is_group": 1 + } + }, + "G\u0130DER \u00c7E\u015e\u0130TLER\u0130": { + "account_number": "79", + "\u0130LK MADDE VE MALZEME G\u0130DERLER\u0130": { + "account_number": "790", + "is_group": 1 + }, + "\u0130\u015e\u00c7\u0130 \u00dcCRET VE G\u0130DERLER\u0130": { + "account_number": "791", + "is_group": 1 + }, + "MEMUR \u00dcCRET VE G\u0130DERLER\u0130": { + "account_number": "792", + "is_group": 1 + }, + "DI\u015eARIDAN SA\u011eLANAN FAYDA VE H\u0130ZMETLER": { + "account_number": "793", + "is_group": 1 + }, + "\u00c7E\u015e\u0130TL\u0130 G\u0130DERLER": { + "account_number": "794", + "is_group": 1 + }, + "VERG\u0130, RES\u0130M VE HAR\u00c7LAR": { + "account_number": "795", + "is_group": 1 + }, + "AMORT\u0130SMAN VE T\u00dcKENME PAYLARI": { + "account_number": "796", + "is_group": 1 + }, + "F\u0130NANSMAN G\u0130DERLER\u0130": { + "account_number": "797", + "is_group": 1 + }, + "G\u0130DER \u00c7E\u015e\u0130TLER\u0130 YANSITMA HESABI": { + "account_number": "798", + "is_group": 1 + }, + "\u00dcRET\u0130M MAL\u0130YET HESABI": { + "account_number": "799", + "is_group": 1 + } + } + }, + "SERBEST HESAPLAR": { + "account_number": "8", + "root_type": "Asset", + "B\u00dcT\u00c7E GEL\u0130RLER\u0130 HESABI": { + "account_number": "800", + "is_group": 1 + }, + "GEL\u0130R YANSITMA HESABI": { + "account_number": "805", + "is_group": 1 + }, + "B\u00dcT\u00c7E GEL\u0130RLER\u0130NDEN RET VE \u0130ADELER HESABI": { + "account_number": "810", + "is_group": 1 + }, + "B\u00dcT\u00c7E G\u0130DERLER\u0130 HESABI": { + "account_number": "830", + "is_group": 1 + }, + "B\u00dcT\u00c7EDEN MAHSUP ED\u0130LECEK \u00d6DEMELER HESABI": { + "account_number": "833", + "is_group": 1 + }, + "GE\u00c7EN YIL B\u00dcT\u00c7E MAHSUPLARI HESABI": { + "account_number": "834", + "is_group": 1 + }, + "G\u0130DER YANSITMA HESABI": { + "account_number": "835", + "is_group": 1 + }, + "B\u00dcT\u00c7E UYGULAMA SONU\u00c7LARI HESABI": { + "account_number": "895", + "is_group": 1 + } + }, + "NAZIM HESAPLAR": { + "account_number": "9", + "root_type": "Expense", + "G\u00d6NDER\u0130LECEK B\u00dcT\u00c7E \u00d6DENEKLER\u0130 HESABI": { + "account_number": "900", + "is_group": 1 + }, + "B\u00dcT\u00c7E \u00d6DENEKLER\u0130 HESABI": { + "account_number": "901", + "is_group": 1 + }, + "B\u00dcT\u00c7E \u00d6DENEK HAREKETLER\u0130 HESABI": { + "account_number": "902", + "is_group": 1 + }, + "KULLANILACAK \u00d6DENEKLER HESABI": { + "account_number": "903", + "is_group": 1 + }, + "\u00d6DENEKLER HESABI": { + "account_number": "904", + "is_group": 1 + }, + "\u00d6DENEKL\u0130 G\u0130DERLER HESABI": { + "account_number": "905", + "is_group": 1 + }, + "MAHSUP D\u00d6NEM\u0130NE AKTARILAN KULLANILACAK \u00d6DENEKLER HESABI": { + "account_number": "906", + "is_group": 1 + }, + "MAHSUP D\u00d6NEM\u0130NE AKTARILAN \u00d6DENEKLER HESABI": { + "account_number": "907", + "is_group": 1 + }, + "TEM\u0130NAT MEKTUPLARI HESABI": { + "account_number": "910", + "is_group": 1 + }, + "TEM\u0130NAT MEKTUPLARI EMANETLER\u0130 HESABI": { + "account_number": "911", + "is_group": 1 + }, + "K\u0130\u015e\u0130LERE A\u0130T MENKUL KIYMETLER HESABI": { + "account_number": "912", + "is_group": 1 + }, + "K\u0130\u015e\u0130LERE A\u0130T MENKUL KIYMET EMANETLER\u0130 HESABI": { + "account_number": "913", + "is_group": 1 + }, + "G\u0130DER TAAHH\u00dcTLER\u0130 HESABI": { + "account_number": "920", + "is_group": 1 + }, + "G\u0130DER TAAHH\u00dcTLER\u0130 KAR\u015eILI\u011eI HESABI": { + "account_number": "921", + "is_group": 1 + }, + "VER\u0130LEN GARANT\u0130LER HESABI": { + "account_number": "930", + "is_group": 1 + }, + "VER\u0130LEN GARANT\u0130LER KAR\u015eILI\u011eI HESABI": { + "account_number": "931", + "is_group": 1 + }, + "STOK AYARLAMA": { + "account_number": "940", + "STOK SAYIM AYARLAMA": { + "account_number": "940.01", + "account_type": "Stock Adjustment" + } + } + } + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 3ec47ff022a..39f9aaf7a6c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -194,6 +194,7 @@ class JournalEntry(AccountsController): self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() + self.update_booked_depreciation() def on_update_after_submit(self): if hasattr(self, "repost_required"): @@ -225,6 +226,7 @@ class JournalEntry(AccountsController): self.unlink_inter_company_jv() self.unlink_asset_adjustment_entry() self.update_invoice_discounting() + self.update_booked_depreciation(1) def get_title(self): return self.pay_to_recd_from or self.accounts[0].account @@ -439,6 +441,25 @@ class JournalEntry(AccountsController): if status: inv_disc_doc.set_status(status=status) + def update_booked_depreciation(self, cancel=0): + for d in self.get("accounts"): + if ( + self.voucher_type == "Depreciation Entry" + and d.reference_type == "Asset" + and d.reference_name + and frappe.get_cached_value("Account", d.account, "root_type") == "Expense" + and d.debit + ): + asset = frappe.get_doc("Asset", d.reference_name) + for fb_row in asset.get("finance_books"): + if fb_row.finance_book == self.finance_book: + if cancel: + fb_row.total_number_of_booked_depreciations -= 1 + else: + fb_row.total_number_of_booked_depreciations += 1 + fb_row.db_update() + break + def unlink_advance_entry_reference(self): for d in self.get("accounts"): if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 69075e5cf6c..a2882206b4a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -76,7 +76,6 @@ class PaymentEntry(AccountsController): self.setup_party_account_field() self.set_missing_values() self.set_liability_account() - self.validate_advance_account_currency() self.set_missing_ref_details(force=True) self.validate_payment_type() self.validate_party_details() @@ -163,22 +162,6 @@ class PaymentEntry(AccountsController): alert=True, ) - def validate_advance_account_currency(self): - if self.book_advance_payments_in_separate_party_account is True: - company_currency = frappe.get_cached_value("Company", self.company, "default_currency") - if self.payment_type == "Receive" and self.paid_from_account_currency != company_currency: - frappe.throw( - _("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format( - frappe.bold(self.paid_from), frappe.bold(self.paid_from_account_currency) - ) - ) - if self.payment_type == "Pay" and self.paid_to_account_currency != company_currency: - frappe.throw( - _("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format( - frappe.bold(self.paid_to), frappe.bold(self.paid_to_account_currency) - ) - ) - def on_cancel(self): self.ignore_linked_doctypes = ( "GL Entry", @@ -1266,7 +1249,7 @@ class PaymentEntry(AccountsController): dr_or_cr, account = self.get_dr_and_account_for_advances(invoice) args_dict["account"] = account - args_dict[dr_or_cr] = invoice.allocated_amount + args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice) args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount args_dict.update( { @@ -1285,7 +1268,7 @@ class PaymentEntry(AccountsController): args_dict[dr_or_cr + "_in_account_currency"] = 0 dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" args_dict["account"] = self.party_account - args_dict[dr_or_cr] = invoice.allocated_amount + args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice) args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount args_dict.update( { diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 7f9c55ff24f..72961a6b6ec 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1237,6 +1237,68 @@ class TestPricingRule(unittest.TestCase): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + def test_pricing_rules_with_and_without_apply_multiple(self): + item = make_item("PR Item 99") + + test_records = [ + { + "doctype": "Pricing Rule", + "title": "_Test discount on item group", + "name": "_Test discount on item group", + "apply_on": "Item Group", + "item_groups": [ + { + "item_group": "Products", + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Percentage", + "discount_percentage": 60, + "has_priority": 1, + "company": "_Test Company", + "apply_multiple_pricing_rules": True, + }, + { + "doctype": "Pricing Rule", + "title": "_Test fixed rate on item code", + "name": "_Test fixed rate on item code", + "apply_on": "Item Code", + "items": [ + { + "item_code": item.name, + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Rate", + "rate": 25, + "has_priority": 1, + "company": "_Test Company", + "apply_multiple_pricing_rules": False, + }, + ] + + for item_group_priority, item_code_priority in [(2, 4), (4, 2)]: + item_group_rule = frappe.get_doc(test_records[0].copy()) + item_group_rule.priority = item_group_priority + item_group_rule.insert() + + item_code_rule = frappe.get_doc(test_records[1].copy()) + item_code_rule.priority = item_code_priority + item_code_rule.insert() + + si = create_sales_invoice(qty=5, customer="_Test Customer 1", item=item.name, do_not_submit=True) + si.save() + self.assertEqual(len(si.pricing_rules), 1) + # Item Code rule should've applied as it has higher priority + expected_rule = item_group_rule if item_group_priority > item_code_priority else item_code_rule + self.assertEqual(si.pricing_rules[0].pricing_rule, expected_rule.name) + + si.delete() + item_group_rule.delete() + item_code_rule.delete() + test_dependencies = ["Campaign"] diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 60c9e26aabe..9c7911d7cae 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -174,12 +174,9 @@ def _get_pricing_rules(apply_on, args, values): def apply_multiple_pricing_rules(pricing_rules): - apply_multiple_rule = [ - d.apply_multiple_pricing_rules for d in pricing_rules if d.apply_multiple_pricing_rules - ] - - if not apply_multiple_rule: - return False + for d in pricing_rules: + if not d.apply_multiple_pricing_rules: + return False return True diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fba60cef632..dce742d1021 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -593,7 +593,7 @@ class PurchaseInvoice(BuyingController): for item in self.get("items"): validate_account_head(item.idx, item.expense_account, self.company, "Expense") - def set_against_expense_account(self): + def set_against_expense_account(self, force=False): against_accounts = [] for item in self.get("items"): if item.expense_account and (item.expense_account not in against_accounts): @@ -601,6 +601,10 @@ class PurchaseInvoice(BuyingController): self.against_expense_account = ",".join(against_accounts) + def force_set_against_expense_account(self): + self.set_against_expense_account() + frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account) + def po_required(self): if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes": if frappe.get_value( diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 6bc19222c98..8c8ba633df0 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -167,6 +167,10 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries_on_cancel() doc.docstatus = 1 + if doc.doctype == "Sales Invoice": + doc.force_set_against_income_account() + else: + doc.force_set_against_expense_account() doc.make_gl_entries() elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]: diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index cb0803db932..babcb417f23 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -943,6 +943,10 @@ class SalesInvoice(SellingController): against_acc.append(d.income_account) self.against_income_account = ",".join(against_acc) + def force_set_against_income_account(self): + self.set_against_income_account() + frappe.db.set_value(self.doctype, self.name, "against_income_account", self.against_income_account) + def add_remarks(self): if not self.remarks: if self.po_no and self.po_date: diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index cb4f0200478..af3916ae469 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -476,7 +476,7 @@ class TestSubscription(FrappeTestCase): start_date="2021-01-01", submit_invoice=0, generate_new_invoices_past_due_date=1, - party="_Test Subscription Customer", + party="_Test Subscription Customer John Doe", ) # create invoices for the first two moths @@ -565,10 +565,16 @@ def create_parties(): if not frappe.db.exists("Customer", "_Test Subscription Customer"): customer = frappe.new_doc("Customer") customer.customer_name = "_Test Subscription Customer" - customer.billing_currency = "USD" + customer.default_currency = "USD" customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"}) customer.insert() + if not frappe.db.exists("Customer", "_Test Subscription Customer John Doe"): + customer = frappe.new_doc("Customer") + customer.customer_name = "_Test Subscription Customer John Doe" + customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable - _TC"}) + customer.insert() + def reset_settings(): settings = frappe.get_single("Subscription Settings") diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index fad6586466e..ad194ee90a2 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -36,7 +36,7 @@ frappe.query_reports["Gross Profit"] = { label: __("Group By"), fieldtype: "Select", options: - "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term", + "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nCost Center\nMonthly\nPayment Term", default: "Invoice", }, { @@ -63,6 +63,26 @@ frappe.query_reports["Gross Profit"] = { }; }, }, + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, ], tree: true, name_field: "parent", @@ -85,3 +105,5 @@ frappe.query_reports["Gross Profit"] = { return value; }, }; + +erpnext.utils.add_dimensions("Gross Profit", 15); diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index c8c8dd9b494..fe2746660eb 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -8,6 +8,11 @@ from frappe import _, qb, scrub from frappe.query_builder import Order from frappe.utils import cint, flt, formatdate +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, + get_dimension_with_children, +) +from erpnext.accounts.report.financial_statements import get_cost_centers_with_children from erpnext.controllers.queries import get_match_cond from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition from erpnext.stock.utils import get_incoming_rate @@ -120,6 +125,13 @@ def execute(filters=None): "gross_profit_percent", ], "project": ["project", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"], + "cost_center": [ + "cost_center", + "base_amount", + "buying_amount", + "gross_profit", + "gross_profit_percent", + ], "territory": [ "territory", "base_amount", @@ -299,7 +311,14 @@ def get_columns(group_wise_columns, filters): "fieldname": "project", "fieldtype": "Link", "options": "Project", - "width": 100, + "width": 140, + }, + "cost_center": { + "label": _("Cost Center"), + "fieldname": "cost_center", + "fieldtype": "Link", + "options": "Cost Center", + "width": 140, }, "sales_person": { "label": _("Sales Person"), @@ -787,6 +806,31 @@ class GrossProfitGenerator: if self.filters.get("item_code"): conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s" + if self.filters.get("cost_center"): + self.filters.cost_center = frappe.parse_json(self.filters.get("cost_center")) + self.filters.cost_center = get_cost_centers_with_children(self.filters.cost_center) + conditions += " and `tabSales Invoice Item`.cost_center in %(cost_center)s" + + if self.filters.get("project"): + self.filters.project = frappe.parse_json(self.filters.get("project")) + conditions += " and `tabSales Invoice Item`.project in %(project)s" + + accounting_dimensions = get_accounting_dimensions(as_list=False) + if accounting_dimensions: + for dimension in accounting_dimensions: + if self.filters.get(dimension.fieldname): + if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): + self.filters[dimension.fieldname] = get_dimension_with_children( + dimension.document_type, self.filters.get(dimension.fieldname) + ) + conditions += ( + f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s" + ) + else: + conditions += ( + f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s" + ) + if self.filters.get("warehouse"): warehouse_details = frappe.db.get_value( "Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1 diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 3a2a942bdf2..99a430cbb40 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -45,7 +45,7 @@ "calculate_depreciation", "column_break_33", "opening_accumulated_depreciation", - "number_of_depreciations_booked", + "opening_number_of_booked_depreciations", "is_fully_depreciated", "section_break_36", "finance_books", @@ -257,12 +257,6 @@ "label": "Opening Accumulated Depreciation", "options": "Company:company:default_currency" }, - { - "depends_on": "eval:(doc.is_existing_asset)", - "fieldname": "number_of_depreciations_booked", - "fieldtype": "Int", - "label": "Number of Depreciations Booked" - }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.calculate_depreciation || doc.is_existing_asset", @@ -546,6 +540,12 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "depends_on": "eval:(doc.is_existing_asset)", + "fieldname": "opening_number_of_booked_depreciations", + "fieldtype": "Int", + "label": "Opening Number of Booked Depreciations" } ], "idx": 72, @@ -589,7 +589,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2024-04-18 16:45:47.306032", + "modified": "2024-05-21 13:46:21.066483", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 063a5447ab5..8641bb33fad 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -89,8 +89,8 @@ class Asset(AccountsController): maintenance_required: DF.Check naming_series: DF.Literal["ACC-ASS-.YYYY.-"] next_depreciation_date: DF.Date | None - number_of_depreciations_booked: DF.Int opening_accumulated_depreciation: DF.Currency + opening_number_of_booked_depreciations: DF.Int policy_number: DF.Data | None purchase_amount: DF.Currency purchase_date: DF.Date | None @@ -145,7 +145,7 @@ class Asset(AccountsController): "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." ).format(asset_depr_schedules_links) ) - + self.set_total_booked_depreciations() self.total_asset_cost = self.gross_purchase_amount self.status = self.get_status() @@ -417,7 +417,7 @@ class Asset(AccountsController): if not self.is_existing_asset: self.opening_accumulated_depreciation = 0 - self.number_of_depreciations_booked = 0 + self.opening_number_of_booked_depreciations = 0 else: depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) if flt(self.opening_accumulated_depreciation) > depreciable_amount: @@ -428,15 +428,15 @@ class Asset(AccountsController): ) if self.opening_accumulated_depreciation: - if not self.number_of_depreciations_booked: - frappe.throw(_("Please set Number of Depreciations Booked")) + if not self.opening_number_of_booked_depreciations: + frappe.throw(_("Please set Opening Number of Booked Depreciations")) else: - self.number_of_depreciations_booked = 0 + self.opening_number_of_booked_depreciations = 0 - if flt(row.total_number_of_depreciations) <= cint(self.number_of_depreciations_booked): + if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations): frappe.throw( _( - "Row {0}: Total Number of Depreciations cannot be less than or equal to Number of Depreciations Booked" + "Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations" ).format(row.idx), title=_("Invalid Schedule"), ) @@ -457,6 +457,17 @@ class Asset(AccountsController): ).format(row.idx) ) + def set_total_booked_depreciations(self): + # set value of total number of booked depreciations field + for fb_row in self.get("finance_books"): + total_number_of_booked_depreciations = self.opening_number_of_booked_depreciations + depr_schedule = get_depr_schedule(self.name, "Active", fb_row.finance_book) + if depr_schedule: + for je in depr_schedule: + if je.journal_entry: + total_number_of_booked_depreciations += 1 + fb_row.db_set("total_number_of_booked_depreciations", total_number_of_booked_depreciations) + def validate_expected_value_after_useful_life(self): for row in self.get("finance_books"): depr_schedule = get_depr_schedule(self.name, "Draft", row.finance_book) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 2c54f55f25c..cc8defc5fe6 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -323,6 +323,7 @@ def _make_journal_entry_for_depreciation( if not je.meta.get_workflow(): je.submit() + asset.reload() idx = cint(asset_depr_schedule_doc.finance_book_id) row = asset.get("finance_books")[idx - 1] row.value_after_depreciation -= depr_schedule.depreciation_amount diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 7e0c3ad6888..742cc3eaa49 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -355,7 +355,7 @@ class TestAsset(AssetSetup): purchase_date="2020-04-01", expected_value_after_useful_life=0, total_number_of_depreciations=5, - number_of_depreciations_booked=2, + opening_number_of_booked_depreciations=2, frequency_of_depreciation=12, depreciation_start_date="2023-03-31", opening_accumulated_depreciation=24000, @@ -453,7 +453,7 @@ class TestAsset(AssetSetup): purchase_date="2020-01-01", expected_value_after_useful_life=0, total_number_of_depreciations=6, - number_of_depreciations_booked=1, + opening_number_of_booked_depreciations=1, frequency_of_depreciation=10, depreciation_start_date="2021-01-01", opening_accumulated_depreciation=20000, @@ -739,7 +739,7 @@ class TestDepreciationMethods(AssetSetup): calculate_depreciation=1, available_for_use_date="2030-06-06", is_existing_asset=1, - number_of_depreciations_booked=2, + opening_number_of_booked_depreciations=2, opening_accumulated_depreciation=47095.89, expected_value_after_useful_life=10000, depreciation_start_date="2032-12-31", @@ -789,7 +789,7 @@ class TestDepreciationMethods(AssetSetup): available_for_use_date="2030-01-01", is_existing_asset=1, depreciation_method="Double Declining Balance", - number_of_depreciations_booked=1, + opening_number_of_booked_depreciations=1, opening_accumulated_depreciation=50000, expected_value_after_useful_life=10000, depreciation_start_date="2031-12-31", @@ -1123,8 +1123,8 @@ class TestDepreciationBasics(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) - def test_number_of_depreciations_booked(self): - """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is.""" + def test_opening_booked_depreciations(self): + """Tests if an error is raised when opening_number_of_booked_depreciations is not specified when opening_accumulated_depreciation is.""" asset = create_asset( item_code="Macbook Pro", @@ -1140,9 +1140,9 @@ class TestDepreciationBasics(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) def test_number_of_depreciations(self): - """Tests if an error is raised when number_of_depreciations_booked >= total_number_of_depreciations.""" + """Tests if an error is raised when opening_number_of_booked_depreciations >= total_number_of_depreciations.""" - # number_of_depreciations_booked > total_number_of_depreciations + # opening_number_of_booked_depreciations > total_number_of_depreciations asset = create_asset( item_code="Macbook Pro", calculate_depreciation=1, @@ -1151,13 +1151,13 @@ class TestDepreciationBasics(AssetSetup): expected_value_after_useful_life=10000, depreciation_start_date="2020-07-01", opening_accumulated_depreciation=10000, - number_of_depreciations_booked=5, + opening_number_of_booked_depreciations=5, do_not_save=1, ) self.assertRaises(frappe.ValidationError, asset.save) - # number_of_depreciations_booked = total_number_of_depreciations + # opening_number_of_booked_depreciations = total_number_of_depreciations asset_2 = create_asset( item_code="Macbook Pro", calculate_depreciation=1, @@ -1166,7 +1166,7 @@ class TestDepreciationBasics(AssetSetup): expected_value_after_useful_life=10000, depreciation_start_date="2020-07-01", opening_accumulated_depreciation=10000, - number_of_depreciations_booked=5, + opening_number_of_booked_depreciations=5, do_not_save=1, ) @@ -1501,19 +1501,17 @@ class TestDepreciationBasics(AssetSetup): """ asset = create_asset(calculate_depreciation=1) - asset.opening_accumulated_depreciation = 2000 - asset.number_of_depreciations_booked = 1 asset.finance_books[0].expected_value_after_useful_life = 100 asset.save() asset.reload() - self.assertEqual(asset.finance_books[0].value_after_depreciation, 98000.0) + self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0) # changing expected_value_after_useful_life shouldn't affect value_after_depreciation asset.finance_books[0].expected_value_after_useful_life = 200 asset.save() asset.reload() - self.assertEqual(asset.finance_books[0].value_after_depreciation, 98000.0) + self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0) def test_asset_cost_center(self): asset = create_asset(is_existing_asset=1, do_not_save=1) @@ -1696,7 +1694,7 @@ def create_asset(**args): "purchase_date": args.purchase_date or "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, - "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, + "opening_number_of_booked_depreciations": args.opening_number_of_booked_depreciations or 0, "gross_purchase_amount": args.gross_purchase_amount or 100000, "purchase_amount": args.purchase_amount or 100000, "maintenance_required": args.maintenance_required or 0, diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json index c838f8b0f26..da05e930eab 100644 --- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json @@ -108,7 +108,6 @@ "depends_on": "eval:doc.use_serial_batch_fields === 1", "fieldname": "serial_no", "fieldtype": "Text", - "hidden": 1, "label": "Serial No", "print_hide": 1 }, @@ -178,7 +177,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-05 11:22:57.346889", + "modified": "2024-06-26 17:06:22.564438", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Stock Item", @@ -188,4 +187,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json index 73838163d3a..36ee7402576 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -13,7 +13,7 @@ "column_break_2", "gross_purchase_amount", "opening_accumulated_depreciation", - "number_of_depreciations_booked", + "opening_number_of_booked_depreciations", "finance_book", "finance_book_id", "depreciation_details_section", @@ -171,10 +171,10 @@ "read_only": 1 }, { - "fieldname": "number_of_depreciations_booked", + "fieldname": "opening_number_of_booked_depreciations", "fieldtype": "Int", "hidden": 1, - "label": "Number of Depreciations Booked", + "label": "Opening Number of Booked Depreciations", "print_hide": 1, "read_only": 1 }, diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index f64e9123dc0..bd67a173343 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -50,7 +50,7 @@ class AssetDepreciationSchedule(Document): gross_purchase_amount: DF.Currency naming_series: DF.Literal["ACC-ADS-.YYYY.-"] notes: DF.SmallText | None - number_of_depreciations_booked: DF.Int + opening_number_of_booked_depreciations: DF.Int opening_accumulated_depreciation: DF.Currency rate_of_depreciation: DF.Percent shift_based: DF.Check @@ -161,7 +161,7 @@ class AssetDepreciationSchedule(Document): return ( asset_doc.gross_purchase_amount != self.gross_purchase_amount or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation - or asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked + or asset_doc.opening_number_of_booked_depreciations != self.opening_number_of_booked_depreciations ) def not_manual_depr_or_have_manual_depr_details_been_modified(self, row): @@ -194,7 +194,7 @@ class AssetDepreciationSchedule(Document): self.finance_book = row.finance_book self.finance_book_id = row.idx self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0 - self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked or 0 + self.opening_number_of_booked_depreciations = asset_doc.opening_number_of_booked_depreciations or 0 self.gross_purchase_amount = asset_doc.gross_purchase_amount self.depreciation_method = row.depreciation_method self.total_number_of_depreciations = row.total_number_of_depreciations @@ -263,7 +263,7 @@ class AssetDepreciationSchedule(Document): row.db_update() final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint( - self.number_of_depreciations_booked + self.opening_number_of_booked_depreciations ) has_pro_rata = _check_is_pro_rata(asset_doc, row) @@ -328,7 +328,7 @@ class AssetDepreciationSchedule(Document): if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal): from_date = add_months( getdate(asset_doc.available_for_use_date), - (asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation), + (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation), ) if self.depreciation_schedule: from_date = self.depreciation_schedule[-1].schedule_date @@ -378,13 +378,16 @@ class AssetDepreciationSchedule(Document): from_date = get_last_day( add_months( getdate(asset_doc.available_for_use_date), - ((self.number_of_depreciations_booked - 1) * row.frequency_of_depreciation), + ( + (self.opening_number_of_booked_depreciations - 1) + * row.frequency_of_depreciation + ), ) ) else: from_date = add_months( getdate(add_days(asset_doc.available_for_use_date, -1)), - (self.number_of_depreciations_booked * row.frequency_of_depreciation), + (self.opening_number_of_booked_depreciations * row.frequency_of_depreciation), ) depreciation_amount, days, months = _get_pro_rata_amt( row, @@ -400,7 +403,8 @@ class AssetDepreciationSchedule(Document): # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission asset_doc.to_date = add_months( asset_doc.available_for_use_date, - (n + self.number_of_depreciations_booked) * cint(row.frequency_of_depreciation), + (n + self.opening_number_of_booked_depreciations) + * cint(row.frequency_of_depreciation), ) depreciation_amount_without_pro_rata = depreciation_amount @@ -546,34 +550,47 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False): has_pro_rata = False # if not existing asset, from_date = available_for_use_date - # otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12 + # otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12 # from_date = 01/01/2022 - from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly) + from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False) days = date_diff(row.depreciation_start_date, from_date) + 1 - - if wdv_or_dd_non_yearly: - total_days = get_total_days(row.depreciation_start_date, 12) - else: - # if frequency_of_depreciation is 12 months, total_days = 365 - total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) - + total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) + if days <= 0: + frappe.throw( + _( + """Error: This asset already has {0} depreciation periods booked. + The `depreciation start` date must be at least {1} periods after the `available for use` date. + Please correct the dates accordingly.""" + ).format( + asset_doc.opening_number_of_booked_depreciations, + asset_doc.opening_number_of_booked_depreciations, + ) + ) if days < total_days: has_pro_rata = True - return has_pro_rata def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False): - if wdv_or_dd_non_yearly: - return add_months( + """ + if Asset has opening booked depreciations = 9, + available for use date = 17-07-2023, + depreciation start date = 30-04-2024 + then from date should be 01-04-2024 + """ + if asset_doc.opening_number_of_booked_depreciations > 0: + from_date = add_months( asset_doc.available_for_use_date, - (asset_doc.number_of_depreciations_booked * 12), + (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation) - 1, ) + if is_last_day_of_the_month(row.depreciation_start_date): + return add_days(get_last_day(from_date), 1) + + # get from date when depreciation start date is not last day of the month + months_difference = month_diff(row.depreciation_start_date, from_date) - 1 + return add_days(add_months(row.depreciation_start_date, -1 * months_difference), 1) else: - return add_months( - asset_doc.available_for_use_date, - (asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation), - ) + return asset_doc.available_for_use_date def _get_pro_rata_amt( @@ -678,7 +695,7 @@ def get_straight_line_or_manual_depr_amount( flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) + ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations) def get_daily_prorata_based_straight_line_depr( @@ -704,7 +721,7 @@ def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx): flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) + ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations) asset_shift_factors_map = get_asset_shift_factors_map() shift = ( diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index 6e4966ac6cf..c359715571e 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -3,8 +3,11 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import cstr +from frappe.utils import cstr, flt +from erpnext.assets.doctype.asset.depreciation import ( + post_depreciation_entries, +) from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, @@ -28,7 +31,7 @@ class TestAssetDepreciationSchedule(FrappeTestCase): self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert) - def test_daily_prorata_based_depr_on_sl_methond(self): + def test_daily_prorata_based_depr_on_sl_method(self): asset = create_asset( calculate_depreciation=1, depreciation_method="Straight Line", @@ -160,3 +163,69 @@ class TestAssetDepreciationSchedule(FrappeTestCase): for d in get_depr_schedule(asset.name, "Draft") ] self.assertEqual(schedules, expected_schedules) + + def test_update_total_number_of_booked_depreciations(self): + # check if updates total number of booked depreciations when depreciation gets booked + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + opening_accumulated_depreciation=2000, + opening_number_of_booked_depreciations=2, + depreciation_method="Straight Line", + available_for_use_date="2020-01-01", + depreciation_start_date="2020-03-31", + frequency_of_depreciation=1, + total_number_of_depreciations=24, + submit=1, + ) + + post_depreciation_entries(date="2021-03-31") + asset.reload() + """ + opening_number_of_booked_depreciations = 2 + number_of_booked_depreciations till 2021-03-31 = 13 + total_number_of_booked_depreciations = 15 + """ + self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 15) + + # cancel depreciation entry + depr_entry = get_depr_schedule(asset.name, "Active")[0].journal_entry + + frappe.get_doc("Journal Entry", depr_entry).cancel() + asset.reload() + + self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 14) + + def test_schedule_for_wdv_method_for_existing_asset(self): + asset = create_asset( + calculate_depreciation=1, + depreciation_method="Written Down Value", + available_for_use_date="2020-07-17", + is_existing_asset=1, + opening_number_of_booked_depreciations=2, + opening_accumulated_depreciation=11666.67, + depreciation_start_date="2021-04-30", + total_number_of_depreciations=12, + frequency_of_depreciation=3, + gross_purchase_amount=50000, + rate_of_depreciation=40, + ) + + self.assertEqual(asset.status, "Draft") + expected_schedules = [ + ["2021-04-30", 3833.33, 15500.0], + ["2021-07-31", 3833.33, 19333.33], + ["2021-10-31", 3833.33, 23166.66], + ["2022-01-31", 3833.33, 26999.99], + ["2022-04-30", 2300.0, 29299.99], + ["2022-07-31", 2300.0, 31599.99], + ["2022-10-31", 2300.0, 33899.99], + ["2023-01-31", 2300.0, 36199.99], + ["2023-04-30", 1380.0, 37579.99], + ["2023-07-31", 12420.01, 50000.0], + ] + schedules = [ + [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index ba5b5f87826..c269948b742 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -8,6 +8,7 @@ "finance_book", "depreciation_method", "total_number_of_depreciations", + "total_number_of_booked_depreciations", "daily_prorata_based", "shift_based", "column_break_5", @@ -104,12 +105,19 @@ "fieldname": "shift_based", "fieldtype": "Check", "label": "Depreciate based on shifts" + }, + { + "default": "0", + "fieldname": "total_number_of_booked_depreciations", + "fieldtype": "Int", + "label": "Total Number of Booked Depreciations ", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-12-29 08:49:39.876439", + "modified": "2024-05-21 15:48:20.907250", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py index f812a0816dd..d06d6355ec3 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py @@ -28,6 +28,7 @@ class AssetFinanceBook(Document): rate_of_depreciation: DF.Percent salvage_value_percentage: DF.Percent shift_based: DF.Check + total_number_of_booked_depreciations: DF.Int total_number_of_depreciations: DF.Int value_after_depreciation: DF.Currency # end: auto-generated types diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 27542bc6de8..ccde836fe0d 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -377,7 +377,7 @@ class AssetRepair(AccountsController): def calculate_last_schedule_date(self, asset, row, extra_months): asset.flags.increase_in_asset_life = True number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset.number_of_depreciations_booked + asset.opening_number_of_booked_depreciations ) depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) @@ -410,7 +410,7 @@ class AssetRepair(AccountsController): def calculate_last_schedule_date_before_modification(self, asset, row, extra_months): asset.flags.increase_in_asset_life = True number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset.number_of_depreciations_booked + asset.opening_number_of_booked_depreciations ) depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 5d4ef4e3845..8ebf9d6d389 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -125,9 +125,10 @@ def get_data(filters): if assets_linked_to_fb and asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb: continue - asset_value = get_asset_value_after_depreciation( - asset.asset_id, finance_book - ) or get_asset_value_after_depreciation(asset.asset_id) + depreciation_amount = depreciation_amount_map.get(asset.asset_id) or 0.0 + asset_value = ( + asset.gross_purchase_amount - asset.opening_accumulated_depreciation - depreciation_amount + ) row = { "asset_id": asset.asset_id, @@ -139,7 +140,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, + "depreciated_amount": depreciation_amount, "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, @@ -185,11 +186,12 @@ def prepare_chart_data(data, filters): ) for d in data: - date = d.get(date_field) - belongs_to_month = formatdate(date, "MMM YYYY") + if d.get(date_field): + date = d.get(date_field) + belongs_to_month = formatdate(date, "MMM YYYY") - labels_values_map[belongs_to_month].asset_value += d.get("asset_value") - labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount") + labels_values_map[belongs_to_month].asset_value += d.get("asset_value") + labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount") return { "data": { diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 0df17fdd4b9..ff5385dd961 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -138,6 +138,7 @@ class Supplier(TransactionBase): validate_party_accounts(self) self.validate_internal_supplier() self.add_role_for_user() + self.validate_currency_for_receivable_payable_and_advance_account() @frappe.whitelist() def get_supplier_group_details(self): diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json index 1aa63fb5ba0..1bb9899cc85 100644 --- a/erpnext/buying/doctype/supplier/test_records.json +++ b/erpnext/buying/doctype/supplier/test_records.json @@ -35,6 +35,7 @@ "doctype": "Supplier", "supplier_name": "_Test Supplier USD", "supplier_group": "_Test Supplier Group", + "default_currency": "USD", "accounts": [{ "company": "_Test Company", "account": "_Test Payable USD - _TC" diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 40ce8b6bc57..f576ecc1b11 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1430,10 +1430,13 @@ class AccountsController(TransactionBase): if d.exchange_gain_loss and ( (d.reference_doctype, d.reference_name, str(d.idx)) not in booked ): - if self.payment_type == "Receive": - party_account = self.paid_from - elif self.payment_type == "Pay": - party_account = self.paid_to + if self.book_advance_payments_in_separate_party_account: + party_account = d.account + else: + if self.payment_type == "Receive": + party_account = self.paid_from + elif self.payment_type == "Pay": + party_account = self.paid_to dr_or_cr = "debit" if d.exchange_gain_loss > 0 else "credit" diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index bf6e3cd663a..30a5f38400e 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -659,10 +659,7 @@ class BuyingController(SubcontractingController): return if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: - field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt" - self.process_fixed_asset() - self.update_fixed_asset(field) if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value( "Buying Settings", "disable_last_purchase_rate" @@ -772,7 +769,7 @@ class BuyingController(SubcontractingController): if not row.asset_location: frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code)) - item_data = frappe.db.get_value( + item_data = frappe.get_cached_value( "Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1 ) asset_quantity = row.qty if is_grouped_asset else 1 @@ -801,7 +798,7 @@ class BuyingController(SubcontractingController): asset.flags.ignore_validate = True asset.flags.ignore_mandatory = True asset.set_missing_values() - asset.insert() + asset.db_insert() return asset.name @@ -827,11 +824,7 @@ class BuyingController(SubcontractingController): frappe.delete_doc("Asset", asset.name, force=1) continue - if self.docstatus in [0, 1] and not asset.get(field): - asset.set(field, self.name) - asset.purchase_date = self.posting_date - asset.supplier = self.supplier - elif self.docstatus == 2: + if self.docstatus == 2: if asset.docstatus == 2: continue if asset.docstatus == 0: diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index e0f1ed77140..d31ee258b27 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -294,16 +294,23 @@ class SubcontractingController(StockController): receipt_items = {item.name: item.get(self.subcontract_data.order_field) for item in receipt_items} consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys()) - voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no] - voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle( - voucher_no=voucher_nos, - is_outward=1, - get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"), - ) - if return_consumed_items: return (consumed_materials, receipt_items) + if not consumed_materials: + return + + voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no] + voucher_bundle_data = ( + get_voucher_wise_serial_batch_from_bundle( + voucher_no=voucher_nos, + is_outward=1, + get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"), + ) + if voucher_nos + else {} + ) + for row in consumed_materials: key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name)) if not self.available_materials.get(key): @@ -350,10 +357,14 @@ class SubcontractingController(StockController): transferred_items = self.__get_transferred_items() voucher_nos = [row.voucher_no for row in transferred_items] - voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle( - voucher_no=voucher_nos, - is_outward=0, - get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"), + voucher_bundle_data = ( + get_voucher_wise_serial_batch_from_bundle( + voucher_no=voucher_nos, + is_outward=0, + get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"), + ) + if voucher_nos + else {} ) for row in transferred_items: diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index f91acb2e4ea..3f6830c2021 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -10,6 +10,7 @@ from frappe.utils import add_days, getdate, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry +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 from erpnext.accounts.party import get_party_account from erpnext.stock.doctype.item.test_item import create_item @@ -55,6 +56,7 @@ class TestAccountsController(FrappeTestCase): 40 series - Company default Cost center is unset 50 series - Journals against Journals 60 series - Journals against Payment Entries + 70 series - Advances in Separate party account. Both Party and Advance account are in Foreign currency. 90 series - Dimension inheritence """ @@ -114,47 +116,102 @@ class TestAccountsController(FrappeTestCase): self.supplier = make_supplier("_Test MC Supplier USD", "USD") def create_account(self): - account_name = "Debtors USD" - if not frappe.db.get_value( - "Account", filters={"account_name": account_name, "company": self.company} - ): - acc = frappe.new_doc("Account") - acc.account_name = account_name - acc.parent_account = "Accounts Receivable - " + self.company_abbr - acc.company = self.company - acc.account_currency = "USD" - acc.account_type = "Receivable" - acc.insert() - else: - name = frappe.db.get_value( - "Account", - filters={"account_name": account_name, "company": self.company}, - fieldname="name", - pluck=True, - ) - acc = frappe.get_doc("Account", name) - self.debtors_usd = acc.name + accounts = [ + frappe._dict( + { + "attribute_name": "debtors_usd", + "name": "Debtors USD", + "account_type": "Receivable", + "account_currency": "USD", + "parent_account": "Accounts Receivable - " + self.company_abbr, + } + ), + frappe._dict( + { + "attribute_name": "creditors_usd", + "name": "Creditors USD", + "account_type": "Payable", + "account_currency": "USD", + "parent_account": "Accounts Payable - " + self.company_abbr, + } + ), + # Advance accounts under Asset and Liability header + frappe._dict( + { + "attribute_name": "advance_received_usd", + "name": "Advance Received USD", + "account_type": "Receivable", + "account_currency": "USD", + "parent_account": "Current Liabilities - " + self.company_abbr, + } + ), + frappe._dict( + { + "attribute_name": "advance_paid_usd", + "name": "Advance Paid USD", + "account_type": "Payable", + "account_currency": "USD", + "parent_account": "Current Assets - " + self.company_abbr, + } + ), + ] - account_name = "Creditors USD" - if not frappe.db.get_value( - "Account", filters={"account_name": account_name, "company": self.company} - ): - acc = frappe.new_doc("Account") - acc.account_name = account_name - acc.parent_account = "Accounts Payable - " + self.company_abbr - acc.company = self.company - acc.account_currency = "USD" - acc.account_type = "Payable" - acc.insert() - else: - name = frappe.db.get_value( - "Account", - filters={"account_name": account_name, "company": self.company}, - fieldname="name", - pluck=True, - ) - acc = frappe.get_doc("Account", name) - self.creditors_usd = acc.name + for x in accounts: + if not frappe.db.get_value("Account", filters={"account_name": x.name, "company": self.company}): + acc = frappe.new_doc("Account") + acc.account_name = x.name + acc.parent_account = x.parent_account + acc.company = self.company + acc.account_currency = x.account_currency + acc.account_type = x.account_type + acc.insert() + else: + name = frappe.db.get_value( + "Account", + filters={"account_name": x.name, "company": self.company}, + fieldname="name", + pluck=True, + ) + acc = frappe.get_doc("Account", name) + setattr(self, x.attribute_name, acc.name) + + def setup_advance_accounts_in_party_master(self): + company = frappe.get_doc("Company", self.company) + company.book_advance_payments_in_separate_party_account = 1 + company.save() + + customer = frappe.get_doc("Customer", self.customer) + customer.append( + "accounts", + { + "company": self.company, + "account": self.debtors_usd, + "advance_account": self.advance_received_usd, + }, + ) + customer.save() + + supplier = frappe.get_doc("Supplier", self.supplier) + supplier.append( + "accounts", + { + "company": self.company, + "account": self.creditors_usd, + "advance_account": self.advance_paid_usd, + }, + ) + supplier.save() + + def remove_advance_accounts_from_party_master(self): + company = frappe.get_doc("Company", self.company) + company.book_advance_payments_in_separate_party_account = 0 + company.save() + customer = frappe.get_doc("Customer", self.customer) + customer.accounts = [] + customer.save() + supplier = frappe.get_doc("Supplier", self.supplier) + supplier.accounts = [] + supplier.save() def create_sales_invoice( self, @@ -218,6 +275,48 @@ class TestAccountsController(FrappeTestCase): payment.posting_date = posting_date return payment + def create_purchase_invoice( + self, + qty=1, + rate=1, + conversion_rate=80, + posting_date=None, + do_not_save=False, + do_not_submit=False, + ): + """ + Helper function to populate default values in purchase invoice + """ + if posting_date is None: + posting_date = nowdate() + + pinv = make_purchase_invoice( + posting_date=posting_date, + qty=qty, + rate=rate, + company=self.company, + supplier=self.supplier, + item_code=self.item, + item_name=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + parent_cost_center=self.cost_center, + update_stock=0, + currency="USD", + conversion_rate=conversion_rate, + is_pos=0, + is_return=0, + income_account=self.income_account, + expense_account=self.expense_account, + do_not_save=True, + ) + pinv.credit_to = self.creditors_usd + if not do_not_save: + pinv.save() + if not do_not_submit: + pinv.submit() + return pinv + def clear_old_entries(self): doctype_list = [ "GL Entry", @@ -1698,3 +1797,123 @@ class TestAccountsController(FrappeTestCase): # Exchange Gain/Loss Journal should've been cancelled exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name) self.assertEqual(exc_je_for_je1, []) + + def test_70_advance_payment_against_sales_invoice_in_foreign_currency(self): + """ + Customer advance booked under Liability + """ + self.setup_advance_accounts_in_party_master() + + adv = self.create_payment_entry(amount=1, source_exc_rate=83) + adv.save() # explicit 'save' is needed to trigger set_liability_account() + self.assertEqual(adv.paid_from, self.advance_received_usd) + adv.submit() + + si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True) + si.debit_to = self.debtors_usd + si.save().submit() + self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0) + + pr = self.create_payment_reconciliation() + pr.receivable_payable_account = self.debtors_usd + pr.default_advance_account = self.advance_received_usd + pr.get_unreconciled_entries() + self.assertEqual(pr.invoices[0].invoice_number, si.name) + self.assertEqual(pr.payments[0].reference_name, adv.name) + + # Allocate and Reconcile + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Exc Gain/Loss journal should've been creatad + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_si), 1) + self.assertEqual(len(exc_je_for_adv), 1) + self.assertEqual(exc_je_for_si, exc_je_for_adv) + + adv.reload() + adv.cancel() + si.reload() + self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0) + # Exc Gain/Loss journal should've been cancelled + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_si), 0) + self.assertEqual(len(exc_je_for_adv), 0) + + self.remove_advance_accounts_from_party_master() + + def test_71_advance_payment_against_purchase_invoice_in_foreign_currency(self): + """ + Supplier advance booked under Asset + """ + self.setup_advance_accounts_in_party_master() + + usd_amount = 1 + inr_amount = 85 + exc_rate = 85 + adv = create_payment_entry( + company=self.company, + payment_type="Pay", + party_type="Supplier", + party=self.supplier, + paid_from=self.cash, + paid_to=self.advance_paid_usd, + paid_amount=inr_amount, + ) + adv.source_exchange_rate = 1 + adv.target_exchange_rate = exc_rate + adv.received_amount = usd_amount + adv.paid_amount = exc_rate * usd_amount + adv.posting_date = nowdate() + adv.save() + # Make sure that advance account is still set + self.assertEqual(adv.paid_to, self.advance_paid_usd) + adv.submit() + + pi = self.create_purchase_invoice(qty=1, conversion_rate=83, rate=1) + self.assertEqual(pi.credit_to, self.creditors_usd) + self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0) + + pr = self.create_payment_reconciliation() + pr.party_type = "Supplier" + pr.party = self.supplier + pr.receivable_payable_account = self.creditors_usd + pr.default_advance_account = self.advance_paid_usd + pr.get_unreconciled_entries() + self.assertEqual(pr.invoices[0].invoice_number, pi.name) + self.assertEqual(pr.payments[0].reference_name, adv.name) + + # Allocate and Reconcile + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + self.assert_ledger_outstanding(pi.doctype, pi.name, 0.0, 0.0) + + # Exc Gain/Loss journal should've been creatad + exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_pi), 1) + self.assertEqual(len(exc_je_for_adv), 1) + self.assertEqual(exc_je_for_pi, exc_je_for_adv) + + adv.reload() + adv.cancel() + pi.reload() + self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0) + # Exc Gain/Loss journal should've been cancelled + exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name) + exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name) + self.assertEqual(len(exc_je_for_pi), 0) + self.assertEqual(len(exc_je_for_adv), 0) + + self.remove_advance_accounts_from_party_master() diff --git a/erpnext/crm/doctype/lead/lead_list.js b/erpnext/crm/doctype/lead/lead_list.js index 97415251a93..beef2c934dc 100644 --- a/erpnext/crm/doctype/lead/lead_list.js +++ b/erpnext/crm/doctype/lead/lead_list.js @@ -1,4 +1,8 @@ frappe.listview_settings["Lead"] = { + get_indicator: function (doc) { + var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status]; + return indicator; + }, onload: function (listview) { if (frappe.boot.user.can_create.includes("Prospect")) { listview.page.add_action_item(__("Create Prospect"), function () { diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2522077e9c3..dba491a728d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -365,3 +365,5 @@ erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 +erpnext.patches.v15_0.rename_number_of_depreciations_booked_to_opening_booked_depreciations +erpnext.patches.v15_0.update_total_number_of_booked_depreciations diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index c31d754d2cd..523b559d734 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -4,6 +4,7 @@ import frappe def execute(): frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule") frappe.reload_doc("assets", "doctype", "Asset Finance Book") + frappe.reload_doc("assets", "doctype", "Asset") assets = get_details_of_draft_or_submitted_depreciable_assets() @@ -43,7 +44,7 @@ def get_details_of_draft_or_submitted_depreciable_assets(): asset.name, asset.opening_accumulated_depreciation, asset.gross_purchase_amount, - asset.number_of_depreciations_booked, + asset.opening_number_of_booked_depreciations, asset.docstatus, ) .where(asset.calculate_depreciation == 1) diff --git a/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py b/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py new file mode 100644 index 00000000000..18183374554 --- /dev/null +++ b/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py @@ -0,0 +1,7 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + if frappe.db.has_column("Asset", "number_of_depreciations_booked"): + rename_field("Asset", "number_of_depreciations_booked", "opening_number_of_booked_depreciations") diff --git a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py index afb59e0f6f5..f8cb3e48e7a 100644 --- a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py +++ b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py @@ -3,18 +3,19 @@ import frappe def execute(): # not using frappe.qb because https://github.com/frappe/frappe/issues/20292 + # nosemgrep frappe.db.sql( """UPDATE `tabAsset Depreciation Schedule` JOIN `tabAsset` ON `tabAsset Depreciation Schedule`.`asset`=`tabAsset`.`name` SET `tabAsset Depreciation Schedule`.`gross_purchase_amount`=`tabAsset`.`gross_purchase_amount`, - `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`=`tabAsset`.`number_of_depreciations_booked` + `tabAsset Depreciation Schedule`.`opening_number_of_booked_depreciations`=`tabAsset`.`opening_number_of_booked_depreciations` WHERE ( `tabAsset Depreciation Schedule`.`gross_purchase_amount`<>`tabAsset`.`gross_purchase_amount` OR - `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`<>`tabAsset`.`number_of_depreciations_booked` + `tabAsset Depreciation Schedule`.`opening_number_of_booked_depreciations`<>`tabAsset`.`opening_number_of_booked_depreciations` ) AND `tabAsset Depreciation Schedule`.`docstatus`<2""" ) diff --git a/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py new file mode 100644 index 00000000000..4b556c2b512 --- /dev/null +++ b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py @@ -0,0 +1,29 @@ +import frappe + +from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( + get_depr_schedule, +) + + +def execute(): + if frappe.db.has_column("Asset Finance Book", "total_number_of_booked_depreciations"): + assets = frappe.get_all( + "Asset", filters={"docstatus": 1}, fields=["name", "opening_number_of_booked_depreciations"] + ) + + for asset in assets: + asset_doc = frappe.get_doc("Asset", asset.name) + + for fb_row in asset_doc.get("finance_books"): + depr_schedule = get_depr_schedule(asset.name, "Active", fb_row.finance_book) + total_number_of_booked_depreciations = asset.opening_number_of_booked_depreciations or 0 + + for je in depr_schedule: + if je.journal_entry: + total_number_of_booked_depreciations += 1 + frappe.db.set_value( + "Asset Finance Book", + fb_row.name, + "total_number_of_booked_depreciations", + total_number_of_booked_depreciations, + ) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 139ab4bff0f..950914953aa 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -325,7 +325,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } const me = this; - if (!this.frm.is_new() && this.frm.doc.docstatus === 0) { + if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) { this.frm.add_custom_button(__("Quality Inspection(s)"), () => { me.make_quality_inspection(); }, __("Create")); @@ -1733,6 +1733,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe me.frm.doc.items.forEach(d => { if (in_list(JSON.parse(data.apply_rule_on_other_items), d[data.apply_rule_on])) { for(var k in data) { + if (data.pricing_rule_for == "Discount Percentage" && data.apply_rule_on_other_items && k == "discount_amount") { + continue; + } + if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'Price' || k === 'pricing_rules')) { frappe.model.set_value(d.doctype, d.name, k, data[k]); } diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 4928f2dc1a5..78efb46f4c3 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -635,6 +635,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { set_data(data) { data.forEach((d) => { d.qty = Math.abs(d.qty); + d.name = d.child_row || d.name; this.dialog.fields_dict.entries.df.data.push(d); }); diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index f5185c2ff5b..8ce67cc659a 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -144,6 +144,7 @@ class Customer(TransactionBase): self.validate_default_bank_account() self.validate_internal_customer() self.add_role_for_user() + self.validate_currency_for_receivable_payable_and_advance_account() # set loyalty program tier if frappe.db.exists("Customer", self.name): diff --git a/erpnext/selling/doctype/customer/test_records.json b/erpnext/selling/doctype/customer/test_records.json index 61cb36b0fae..6040f4dd75b 100644 --- a/erpnext/selling/doctype/customer/test_records.json +++ b/erpnext/selling/doctype/customer/test_records.json @@ -47,6 +47,7 @@ "customer_type": "Individual", "doctype": "Customer", "territory": "_Test Territory", + "default_currency": "USD", "accounts": [{ "company": "_Test Company", "account": "_Test Receivable USD - _TC" diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 5885d092cce..cdcd1047bd8 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -58,7 +58,8 @@ frappe.ui.form.on("Sales Order", { if ( frm.doc.status !== "Closed" && flt(frm.doc.per_delivered, 2) < 100 && - flt(frm.doc.per_billed, 2) < 100 + flt(frm.doc.per_billed, 2) < 100 && + frm.has_perm("write") ) { frm.add_custom_button(__("Update Items"), () => { erpnext.utils.update_child_items({ @@ -85,7 +86,11 @@ frappe.ui.form.on("Sales Order", { } // Stock Reservation > Unreserve button will be only visible if the SO has un-delivered reserved stock. - if (frm.doc.__onload && frm.doc.__onload.has_reserved_stock) { + if ( + frm.doc.__onload && + frm.doc.__onload.has_reserved_stock && + frappe.model.can_cancel("Stock Reservation Entry") + ) { frm.add_custom_button( __("Unreserve"), () => frm.events.cancel_stock_reservation_entries(frm), @@ -94,7 +99,7 @@ frappe.ui.form.on("Sales Order", { } frm.doc.items.forEach((item) => { - if (flt(item.stock_reserved_qty) > 0) { + if (flt(item.stock_reserved_qty) > 0 && frappe.model.can_read("Stock Reservation Entry")) { frm.add_custom_button( __("Reserved Stock"), () => frm.events.show_reserved_stock(frm), @@ -142,6 +147,10 @@ frappe.ui.form.on("Sales Order", { }, get_items_from_internal_purchase_order(frm) { + if (!frappe.model.can_read("Purchase Order")) { + return; + } + frm.add_custom_button( __("Purchase Order"), () => { @@ -634,15 +643,17 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } - if (!doc.__onload || !doc.__onload.has_reserved_stock) { - // Don't show the `Reserve` button if the Sales Order has Picked Items. - if (flt(doc.per_picked, 2) < 100 && flt(doc.per_delivered, 2) < 100) { - this.frm.add_custom_button( - __("Pick List"), - () => this.create_pick_list(), - __("Create") - ); - } + if ( + (!doc.__onload || !doc.__onload.has_reserved_stock) && + flt(doc.per_picked, 2) < 100 && + flt(doc.per_delivered, 2) < 100 && + frappe.model.can_create("Pick List") + ) { + this.frm.add_custom_button( + __("Pick List"), + () => this.create_pick_list(), + __("Create") + ); } const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1; @@ -657,20 +668,25 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex (order_is_a_sale || order_is_a_custom_sale) && allow_delivery ) { - this.frm.add_custom_button( - __("Delivery Note"), - () => this.make_delivery_note_based_on_delivery_date(true), - __("Create") - ); - this.frm.add_custom_button( - __("Work Order"), - () => this.make_work_order(), - __("Create") - ); + if (frappe.model.can_create("Delivery Note")) { + this.frm.add_custom_button( + __("Delivery Note"), + () => this.make_delivery_note_based_on_delivery_date(true), + __("Create") + ); + } + + if (frappe.model.can_create("Work Order")) { + this.frm.add_custom_button( + __("Work Order"), + () => this.make_work_order(), + __("Create") + ); + } } // sales invoice - if (flt(doc.per_billed, 2) < 100) { + if (flt(doc.per_billed, 2) < 100 && frappe.model.can_create("Sales Invoice")) { this.frm.add_custom_button( __("Sales Invoice"), () => me.make_sales_invoice(), @@ -680,8 +696,10 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex // material request if ( - !doc.order_type || - ((order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered, 2) < 100) + (!doc.order_type || + ((order_is_a_sale || order_is_a_custom_sale) && + flt(doc.per_delivered, 2) < 100)) && + frappe.model.can_create("Material Request") ) { this.frm.add_custom_button( __("Material Request"), @@ -696,7 +714,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } // Make Purchase Order - if (!this.frm.doc.is_internal_customer) { + if (!this.frm.doc.is_internal_customer && frappe.model.can_create("Purchase Order")) { this.frm.add_custom_button( __("Purchase Order"), () => this.make_purchase_order(), @@ -706,24 +724,32 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex // maintenance if (flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) { - this.frm.add_custom_button( - __("Maintenance Visit"), - () => this.make_maintenance_visit(), - __("Create") - ); - this.frm.add_custom_button( - __("Maintenance Schedule"), - () => this.make_maintenance_schedule(), - __("Create") - ); + if (frappe.model.can_create("Maintenance Visit")) { + this.frm.add_custom_button( + __("Maintenance Visit"), + () => this.make_maintenance_visit(), + __("Create") + ); + } + if (frappe.model.can_create("Maintenance Schedule")) { + this.frm.add_custom_button( + __("Maintenance Schedule"), + () => this.make_maintenance_schedule(), + __("Create") + ); + } } // project - if (flt(doc.per_delivered, 2) < 100) { + if (flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) { this.frm.add_custom_button(__("Project"), () => this.make_project(), __("Create")); } - if (doc.docstatus === 1 && !doc.inter_company_order_reference) { + if ( + doc.docstatus === 1 && + !doc.inter_company_order_reference && + frappe.model.can_create("Purchase Order") + ) { let me = this; let internal = me.frm.doc.is_internal_customer; if (internal) { @@ -752,13 +778,20 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex () => this.make_payment_request(), __("Create") ); - this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create")); + + if (frappe.model.can_create("Payment Entry")) { + this.frm.add_custom_button( + __("Payment"), + () => this.make_payment_entry(), + __("Create") + ); + } } this.frm.page.set_inner_btn_group_as_primary(__("Create")); } } - if (this.frm.doc.docstatus === 0) { + if (this.frm.doc.docstatus === 0 && frappe.model.can_read("Quotation")) { this.frm.add_custom_button( __("Quotation"), function () { diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py index 0b783c0c56e..6d95d3bcb24 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.py +++ b/erpnext/setup/doctype/customer_group/customer_group.py @@ -38,6 +38,53 @@ class CustomerGroup(NestedSet): def validate(self): if not self.parent_customer_group: self.parent_customer_group = get_root_of("Customer Group") + self.validate_currency_for_receivable_and_advance_account() + + def validate_currency_for_receivable_and_advance_account(self): + for x in self.accounts: + company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency") + receivable_account_currency = None + advance_account_currency = None + + if x.account: + receivable_account_currency = frappe.get_cached_value( + "Account", x.account, "account_currency" + ) + + if x.advance_account: + advance_account_currency = frappe.get_cached_value( + "Account", x.advance_account, "account_currency" + ) + + if receivable_account_currency and receivable_account_currency != company_default_currency: + frappe.throw( + _("Receivable Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.account), + frappe.bold(company_default_currency), + ) + ) + + if advance_account_currency and advance_account_currency != company_default_currency: + frappe.throw( + _("Advance Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.advance_account), frappe.bold(company_default_currency) + ) + ) + + if ( + receivable_account_currency + and advance_account_currency + and receivable_account_currency != advance_account_currency + ): + frappe.throw( + _( + "Both Receivable Account: {0} and Advance Account: {1} must be of same currency for company: {2}" + ).format( + frappe.bold(x.account), + frappe.bold(x.advance_account), + frappe.bold(x.company), + ) + ) def on_update(self): self.validate_name_with_customer() diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.py b/erpnext/setup/doctype/supplier_group/supplier_group.py index b639b962509..fa0c6beac49 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.py +++ b/erpnext/setup/doctype/supplier_group/supplier_group.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ from frappe.utils.nestedset import NestedSet, get_root_of @@ -32,6 +33,51 @@ class SupplierGroup(NestedSet): def validate(self): if not self.parent_supplier_group: self.parent_supplier_group = get_root_of("Supplier Group") + self.validate_currency_for_payable_and_advance_account() + + def validate_currency_for_payable_and_advance_account(self): + for x in self.accounts: + company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency") + payable_account_currency = None + advance_account_currency = None + + if x.account: + payable_account_currency = frappe.get_cached_value("Account", x.account, "account_currency") + + if x.advance_account: + advance_account_currency = frappe.get_cached_value( + "Account", x.advance_account, "account_currency" + ) + + if payable_account_currency and payable_account_currency != company_default_currency: + frappe.throw( + _("Payable Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.account), + frappe.bold(company_default_currency), + ) + ) + + if advance_account_currency and advance_account_currency != company_default_currency: + frappe.throw( + _("Advance Account: {0} must be in Company default currency: {1}").format( + frappe.bold(x.advance_account), frappe.bold(company_default_currency) + ) + ) + + if ( + payable_account_currency + and advance_account_currency + and payable_account_currency != advance_account_currency + ): + frappe.throw( + _( + "Both Payable Account: {0} and Advance Account: {1} must be of same currency for company: {2}" + ).format( + frappe.bold(x.account), + frappe.bold(x.advance_account), + frappe.bold(x.company), + ) + ) def on_update(self): NestedSet.on_update(self) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index e43e6f21c92..d10833b6d46 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -1,3 +1,6 @@ +import datetime +from collections import defaultdict + import frappe from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import flt @@ -8,12 +11,7 @@ from pypika import Order class DeprecatedSerialNoValuation: @deprecated def calculate_stock_value_from_deprecarated_ledgers(self): - if not frappe.db.get_all( - "Stock Ledger Entry", - fields=["name"], - filters={"serial_no": ("is", "set"), "is_cancelled": 0, "item_code": self.sle.item_code}, - limit=1, - ): + if not has_sle_for_serial_nos(self.sle.item_code): return serial_nos = self.get_filterd_serial_nos() @@ -82,6 +80,20 @@ class DeprecatedSerialNoValuation: return incoming_values +@frappe.request_cache +def has_sle_for_serial_nos(item_code): + serial_nos = frappe.db.get_all( + "Stock Ledger Entry", + fields=["name"], + filters={"serial_no": ("is", "set"), "is_cancelled": 0, "item_code": item_code}, + limit=1, + ) + if serial_nos: + return True + + return False + + class DeprecatedBatchNoValuation: @deprecated def calculate_avg_rate_from_deprecarated_ledgers(self): @@ -92,19 +104,25 @@ class DeprecatedBatchNoValuation: @deprecated def get_sle_for_batches(self): + from erpnext.stock.utils import get_combine_datetime + if not self.batchwise_valuation_batches: return [] sle = frappe.qb.DocType("Stock Ledger Entry") - timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime( - self.sle.posting_date, self.sle.posting_time - ) - if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(sle.posting_date, sle.posting_time) - == CombineDatetime(self.sle.posting_date, self.sle.posting_time) - ) & (sle.creation < self.sle.creation) + timestamp_condition = None + if self.sle.posting_date and self.sle.posting_time: + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if not self.sle.creation: + posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) + + timestamp_condition = sle.posting_datetime < posting_datetime + + if self.sle.creation: + timestamp_condition |= (sle.posting_datetime == posting_datetime) & ( + sle.creation < self.sle.creation + ) query = ( frappe.qb.from_(sle) @@ -120,10 +138,12 @@ class DeprecatedBatchNoValuation: & (sle.batch_no.isnotnull()) & (sle.is_cancelled == 0) ) - .where(timestamp_condition) .groupby(sle.batch_no) ) + if timestamp_condition: + query = query.where(timestamp_condition) + if self.sle.name: query = query.where(sle.name != self.sle.name) @@ -134,8 +154,8 @@ class DeprecatedBatchNoValuation: if not self.non_batchwise_valuation_batches: return - self.non_batchwise_balance_value = 0.0 - self.non_batchwise_balance_qty = 0.0 + self.non_batchwise_balance_value = defaultdict(float) + self.non_batchwise_balance_qty = defaultdict(float) self.set_balance_value_for_non_batchwise_valuation_batches() @@ -146,12 +166,12 @@ class DeprecatedBatchNoValuation: if not self.non_batchwise_balance_qty: continue - if self.non_batchwise_balance_value == 0: + if self.non_batchwise_balance_qty.get(batch_no) == 0: self.batch_avg_rate[batch_no] = 0.0 self.stock_value_differece[batch_no] = 0.0 else: self.batch_avg_rate[batch_no] = ( - self.non_batchwise_balance_value / self.non_batchwise_balance_qty + self.non_batchwise_balance_value[batch_no] / self.non_batchwise_balance_qty[batch_no] ) self.stock_value_differece[batch_no] = self.non_batchwise_balance_value @@ -174,17 +194,21 @@ class DeprecatedBatchNoValuation: @deprecated def set_balance_value_from_sl_entries(self) -> None: + from erpnext.stock.utils import get_combine_datetime + sle = frappe.qb.DocType("Stock Ledger Entry") batch = frappe.qb.DocType("Batch") - timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime( - self.sle.posting_date, self.sle.posting_time - ) + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if not self.sle.creation: + posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) + + timestamp_condition = sle.posting_datetime < posting_datetime + if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(sle.posting_date, sle.posting_time) - == CombineDatetime(self.sle.posting_date, self.sle.posting_time) - ) & (sle.creation < self.sle.creation) + timestamp_condition |= (sle.posting_datetime == posting_datetime) & ( + sle.creation < self.sle.creation + ) query = ( frappe.qb.from_(sle) @@ -201,6 +225,7 @@ class DeprecatedBatchNoValuation: & (sle.batch_no.isnotnull()) & (batch.use_batchwise_valuation == 0) & (sle.is_cancelled == 0) + & (sle.batch_no.isin(self.non_batchwise_valuation_batches)) ) .where(timestamp_condition) .groupby(sle.batch_no) @@ -210,8 +235,8 @@ class DeprecatedBatchNoValuation: query = query.where(sle.name != self.sle.name) for d in query.run(as_dict=True): - self.non_batchwise_balance_value += flt(d.batch_value) - self.non_batchwise_balance_qty += flt(d.batch_qty) + self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value) + self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty) self.available_qty[d.batch_no] += flt(d.batch_qty) @deprecated @@ -249,6 +274,7 @@ class DeprecatedBatchNoValuation: & (bundle.is_cancelled == 0) & (bundle.docstatus == 1) & (bundle.type_of_transaction.isin(["Inward", "Outward"])) + & (bundle_child.batch_no.isin(self.non_batchwise_valuation_batches)) ) .where(timestamp_condition) .groupby(bundle_child.batch_no) @@ -260,6 +286,6 @@ class DeprecatedBatchNoValuation: query = query.where(bundle.voucher_type != "Pick List") for d in query.run(as_dict=True): - self.non_batchwise_balance_value += flt(d.batch_value) - self.non_batchwise_balance_qty += flt(d.batch_qty) + self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value) + self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty) self.available_qty[d.batch_no] += flt(d.batch_qty) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 0be85e46015..e490badfc40 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -161,11 +161,25 @@ class Batch(Document): self.use_batchwise_valuation = 1 def before_save(self): + self.set_expiry_date() + + def set_expiry_date(self): has_expiry_date, shelf_life_in_days = frappe.db.get_value( "Item", self.item, ["has_expiry_date", "shelf_life_in_days"] ) + if not self.expiry_date and has_expiry_date and shelf_life_in_days: - self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days) + if ( + not self.manufacturing_date + and self.reference_doctype in ["Stock Entry", "Purchase Receipt", "Purchase Invoice"] + and self.reference_name + ): + self.manufacturing_date = frappe.db.get_value( + self.reference_doctype, self.reference_name, "posting_date" + ) + + if self.manufacturing_date: + self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days) if has_expiry_date and not self.expiry_date: frappe.throw( diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 23d0adc5708..3352b53343a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -79,7 +79,12 @@ frappe.ui.form.on("Delivery Note", { }, refresh: function (frm) { - if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100) { + if ( + frm.doc.docstatus === 1 && + frm.doc.is_return === 1 && + frm.doc.per_billed !== 100 && + frappe.model.can_create("Sales Invoice") + ) { frm.add_custom_button( __("Credit Note"), function () { @@ -93,7 +98,11 @@ frappe.ui.form.on("Delivery Note", { frm.page.set_inner_btn_group_as_primary(__("Create")); } - if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) { + if ( + frm.doc.docstatus == 1 && + !frm.doc.inter_company_reference && + frappe.model.can_create("Purchase Receipt") + ) { let internal = frm.doc.is_internal_customer; if (internal) { let button_label = @@ -140,43 +149,47 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( refresh(doc, dt, dn) { var me = this; super.refresh(); - if (!doc.is_return && (doc.status != "Closed" || this.frm.is_new())) { - if (this.frm.doc.docstatus === 0) { - this.frm.add_custom_button( - __("Sales Order"), - function () { - if (!me.frm.doc.customer) { - frappe.throw({ - title: __("Mandatory"), - message: __("Please Select a Customer"), - }); - } - erpnext.utils.map_current_doc({ - method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - args: { - for_reserved_stock: 1, - }, - source_doctype: "Sales Order", - target: me.frm, - setters: { - customer: me.frm.doc.customer, - }, - get_query_filters: { - docstatus: 1, - status: ["not in", ["Closed", "On Hold"]], - per_delivered: ["<", 99.99], - company: me.frm.doc.company, - project: me.frm.doc.project || undefined, - }, + if ( + !doc.is_return && + (doc.status != "Closed" || this.frm.is_new()) && + this.frm.has_perm("write") && + frappe.model.can_read("Sales Order") && + this.frm.doc.docstatus === 0 + ) { + this.frm.add_custom_button( + __("Sales Order"), + function () { + if (!me.frm.doc.customer) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Customer"), }); - }, - __("Get Items From") - ); - } + } + erpnext.utils.map_current_doc({ + method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", + args: { + for_reserved_stock: 1, + }, + source_doctype: "Sales Order", + target: me.frm, + setters: { + customer: me.frm.doc.customer, + }, + get_query_filters: { + docstatus: 1, + status: ["not in", ["Closed", "On Hold"]], + per_delivered: ["<", 99.99], + company: me.frm.doc.company, + project: me.frm.doc.project || undefined, + }, + }); + }, + __("Get Items From") + ); } if (!doc.is_return && doc.status != "Closed") { - if (doc.docstatus == 1) { + if (doc.docstatus == 1 && frappe.model.can_create("Shipment")) { this.frm.add_custom_button( __("Shipment"), function () { @@ -186,7 +199,11 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } - if (flt(doc.per_installed, 2) < 100 && doc.docstatus == 1) + if ( + flt(doc.per_installed, 2) < 100 && + doc.docstatus == 1 && + frappe.model.can_create("Installation Note") + ) { this.frm.add_custom_button( __("Installation Note"), function () { @@ -194,8 +211,9 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( }, __("Create") ); + } - if (doc.docstatus == 1) { + if (doc.docstatus == 1 && this.frm.has_perm("create")) { this.frm.add_custom_button( __("Sales Return"), function () { @@ -205,7 +223,7 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } - if (doc.docstatus == 1) { + if (doc.docstatus == 1 && frappe.model.can_create("Delivery Trip")) { this.frm.add_custom_button( __("Delivery Trip"), function () { @@ -215,19 +233,23 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } - if (doc.docstatus == 0 && !doc.__islocal) { - if (doc.__onload && doc.__onload.has_unpacked_items) { - this.frm.add_custom_button( - __("Packing Slip"), - function () { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", - frm: me.frm, - }); - }, - __("Create") - ); - } + if ( + doc.docstatus == 0 && + !doc.__islocal && + doc.__onload && + doc.__onload.has_unpacked_items && + frappe.model.can_create("Packing Slip") + ) { + this.frm.add_custom_button( + __("Packing Slip"), + function () { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", + frm: me.frm, + }); + }, + __("Create") + ); } if (!doc.__islocal && doc.docstatus == 1) { @@ -254,7 +276,13 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( } } - if (doc.docstatus == 1 && !doc.is_return && doc.status != "Closed" && flt(doc.per_billed) < 100) { + if ( + doc.docstatus == 1 && + !doc.is_return && + doc.status != "Closed" && + flt(doc.per_billed) < 100 && + frappe.model.can_create("Sales Invoice") + ) { // show Make Invoice button only if Delivery Note is not created from Sales Invoice var from_sales_invoice = false; from_sales_invoice = me.frm.doc.items.some(function (item) { diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 4b5ab3836c5..69a1bdf17d8 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -693,7 +693,8 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus) # if stock qty is zero on submitted entry, show positive remaining qty to recalculate in case of restock. remaining_stock_qty = item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty - while flt(remaining_stock_qty) > 0 and available_locations: + precision = frappe.get_precision("Pick List Item", "qty") + while flt(remaining_stock_qty, precision) > 0 and available_locations: item_location = available_locations.pop(0) item_location = frappe._dict(item_location) @@ -838,6 +839,7 @@ def validate_picked_materials(item_code, required_qty, locations, picked_item_de def filter_locations_by_picked_materials(locations, picked_item_details) -> list[dict]: filterd_locations = [] + precision = frappe.get_precision("Pick List Item", "qty") for row in locations: key = row.warehouse if row.batch_no: @@ -856,7 +858,7 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list if row.serial_nos: row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no"))) - if row.qty > 0: + if flt(row.qty, precision) > 0: filterd_locations.append(row) return filterd_locations diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 42747818d2b..cae58de9303 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3,7 +3,7 @@ import frappe from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, cint, cstr, flt, nowtime, today +from frappe.utils import add_days, cint, cstr, flt, getdate, nowtime, today from pypika import functions as fn import erpnext @@ -2961,6 +2961,35 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertSequenceEqual(expected_gle, gl_entries) frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory + def test_manufacturing_and_expiry_date_for_batch(self): + item = make_item( + "_Test Manufacturing and Expiry Date For Batch", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B-MEBATCH.#####", + "has_expiry_date": 1, + "shelf_life_in_days": 5, + }, + ) + + pr = make_purchase_receipt( + qty=10, + rate=100, + item_code=item.name, + posting_date=today(), + ) + + pr.reload() + self.assertTrue(pr.items[0].serial_and_batch_bundle) + + batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle) + batch = frappe.get_doc("Batch", batch_no) + self.assertEqual(batch.manufacturing_date, getdate(today())) + self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5))) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 87bf2df5f61..63e1e5084d6 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1220,6 +1220,7 @@ def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, na "`tabSerial and Batch Entry`.`warehouse`", "`tabSerial and Batch Entry`.`batch_no`", "`tabSerial and Batch Entry`.`serial_no`", + "`tabSerial and Batch Entry`.`name` as `child_row`", ] if not child_row: @@ -2104,10 +2105,13 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: ) for key, val in kwargs.items(): - if not val: + if val is None: continue - if key in ["get_subcontracted_item"]: + if not val and isinstance(val, list): + return [] + + if key == "get_subcontracted_item": continue if key in ["name", "item_code", "warehouse", "voucher_no", "company", "voucher_detail_no"]: diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 8301a706183..674624e184b 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -404,7 +404,9 @@ class StockReconciliation(StockController): fields=["total_qty as qty", "avg_rate as rate"], )[0] + bundle_data.qty = abs(bundle_data.qty) self.calculate_difference_amount(item, bundle_data) + return True inventory_dimensions_dict = {} @@ -464,11 +466,16 @@ class StockReconciliation(StockController): frappe.msgprint(_("Removed items with no change in quantity or value.")) def calculate_difference_amount(self, item, item_dict): - self.difference_amount += flt(item.qty, item.precision("qty")) * flt( - item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate") - ) - flt(item_dict.get("qty"), item.precision("qty")) * flt( - item_dict.get("rate"), item.precision("valuation_rate") - ) + qty_precision = item.precision("qty") + val_precision = item.precision("valuation_rate") + + new_qty = flt(item.qty, qty_precision) + new_valuation_rate = flt(item.valuation_rate or item_dict.get("rate"), val_precision) + + current_qty = flt(item_dict.get("qty"), qty_precision) + current_valuation_rate = flt(item_dict.get("rate"), val_precision) + + self.difference_amount += (new_qty * new_valuation_rate) - (current_qty * current_valuation_rate) def validate_data(self): def _get_msg(row_num, msg): diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 8845bdbb753..48d67c2cf46 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -647,7 +647,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): "has_serial_no": 1, "has_batch_no": 1, "serial_no_series": "SRS9.####", - "batch_number_series": "BNS9.####", + "batch_number_series": "BNS90.####", "create_new_batch": 1, }, ) @@ -680,7 +680,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): { "is_stock_item": 1, "has_batch_no": 1, - "batch_number_series": "BNS9.####", + "batch_number_series": "BNS91.####", "create_new_batch": 1, }, ).name @@ -1109,6 +1109,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): ) sr.reload() + self.assertEqual(sr.difference_amount, 98900.0) self.assertTrue(sr.items[0].current_valuation_rate) current_sabb = sr.items[0].current_serial_and_batch_bundle diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js index 0443f3f1ece..79638590f9b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.js +++ b/erpnext/stock/doctype/stock_settings/stock_settings.js @@ -41,7 +41,7 @@ frappe.ui.form.on("Stock Settings", { msg += " "; msg += __("This is considered dangerous from accounting point of view."); msg += "
"; - msg += "Do you still want to enable negative inventory?"; + msg += __("Do you still want to enable negative inventory?"); frappe.confirm( msg, diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 64ad36ff5b1..27d9f1164bc 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -137,6 +137,10 @@ class StockBalanceReport: report_data.update( {"reserved_stock": sre_details.get((report_data.item_code, report_data.warehouse), 0.0)} ) + + if report_data and report_data.bal_qty == 0 and report_data.bal_val == 0: + continue + self.data.append(report_data) def get_item_warehouse_map(self): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e804ae18016..d0195843c5f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1794,6 +1794,7 @@ def get_next_stock_reco(kwargs): sle.actual_qty, sle.has_batch_no, ) + .force_index("item_warehouse") .where( (sle.item_code == kwargs.get("item_code")) & (sle.warehouse == kwargs.get("warehouse")) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index eca01a57640..f2388e5737a 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -276,11 +276,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): sn_obj = SerialNoValuation(sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code")) return sn_obj.get_incoming_rate() - elif ( - args.get("batch_no") - and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True) - and not args.get("serial_and_batch_bundle") - ): + elif args.get("batch_no") and not args.get("serial_and_batch_bundle"): args.actual_qty = args.qty args.batch_nos = frappe._dict({args.batch_no: args}) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 81662a6257b..0f5fe7ab958 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -623,8 +623,8 @@ class TestSubcontractingReceipt(FrappeTestCase): "has_batch_no": 1, "has_serial_no": 1, "create_new_batch": 1, - "batch_number_series": "BNGS-.####", - "serial_no_series": "BNSS-.####", + "batch_number_series": "BNGS0-.####", + "serial_no_series": "BNSS90-.####", } ).name @@ -715,8 +715,8 @@ class TestSubcontractingReceipt(FrappeTestCase): "has_batch_no": 1, "has_serial_no": 1, "create_new_batch": 1, - "batch_number_series": "BNGS-.####", - "serial_no_series": "BNSS-.####", + "batch_number_series": "BNGS91-.####", + "serial_no_series": "BNSS91-.####", } ).name diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 3b7812f96c2..6fab5380c38 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -168,6 +168,69 @@ class TransactionBase(StatusUpdater): if len(child_table_values) > 1: self.set(default_field, None) + def validate_currency_for_receivable_payable_and_advance_account(self): + if self.doctype in ["Customer", "Supplier"]: + account_type = "Receivable" if self.doctype == "Customer" else "Payable" + for x in self.accounts: + company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency") + receivable_payable_account_currency = None + advance_account_currency = None + + if x.account: + receivable_payable_account_currency = frappe.get_cached_value( + "Account", x.account, "account_currency" + ) + + if x.advance_account: + advance_account_currency = frappe.get_cached_value( + "Account", x.advance_account, "account_currency" + ) + if receivable_payable_account_currency and ( + receivable_payable_account_currency != self.default_currency + and receivable_payable_account_currency != company_default_currency + ): + frappe.throw( + _( + "{0} Account: {1} ({2}) must be in either customer billing currency: {3} or Company default currency: {4}" + ).format( + account_type, + frappe.bold(x.account), + frappe.bold(receivable_payable_account_currency), + frappe.bold(self.default_currency), + frappe.bold(company_default_currency), + ) + ) + + if advance_account_currency and ( + advance_account_currency != self.default_currency + and advance_account_currency != company_default_currency + ): + frappe.throw( + _( + "Advance Account: {0} must be in either customer billing currency: {1} or Company default currency: {2}" + ).format( + frappe.bold(x.advance_account), + frappe.bold(self.default_currency), + frappe.bold(company_default_currency), + ) + ) + + if ( + receivable_payable_account_currency + and advance_account_currency + and receivable_payable_account_currency != advance_account_currency + ): + frappe.throw( + _( + "Both {0} Account: {1} and Advance Account: {2} must be of same currency for company: {3}" + ).format( + account_type, + frappe.bold(x.account), + frappe.bold(x.advance_account), + frappe.bold(x.company), + ) + ) + def delete_events(ref_type, ref_name): events = ( From 8a91bf315491e6b3b54c1b0f681397bb5acd4a66 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:31:06 +0530 Subject: [PATCH 166/203] fix: not able to make purchase return (backport #42053) (#42055) fix: not able to make purchase return (#42053) (cherry picked from commit 9738c04ef0e795dbf9b11531fb9a2b42ada935a7) Co-authored-by: rohitwaghchaure --- .../controllers/sales_and_purchase_return.py | 10 +++++-- .../purchase_receipt/test_purchase_receipt.py | 30 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index b61d1773a67..76d04c04d6a 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -146,7 +146,10 @@ def validate_returned_items(doc): def validate_quantity(doc, args, ref, valid_items, already_returned_items): fields = ["stock_qty"] if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]: - fields.extend(["received_qty", "rejected_qty"]) + if not args.get("return_qty_from_rejected_warehouse"): + fields.extend(["received_qty", "rejected_qty"]) + else: + fields.extend(["received_qty"]) already_returned_data = already_returned_items.get(args.item_code) or {} @@ -158,9 +161,12 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items): for column in fields: returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0 - if column == "stock_qty": + if column == "stock_qty" and not args.get("return_qty_from_rejected_warehouse"): reference_qty = ref.get(column) current_stock_qty = args.get(column) + elif args.get("return_qty_from_rejected_warehouse"): + reference_qty = ref.get("rejected_qty") * ref.get("conversion_factor", 1.0) + current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0) else: reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0) current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index cae58de9303..16553749d0a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2990,6 +2990,36 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(batch.manufacturing_date, getdate(today())) self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5))) + def test_purchase_return_from_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return_against_rejected_warehouse, + ) + + item_code = "_Test Item Return from Rejected Warehouse 11" + create_item(item_code) + + warehouse = create_warehouse("_Test Warehouse Return Qty Warehouse 11") + rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Qty Warehouse 11") + + # Step 1: Create Purchase Receipt with valuation rate 100 + pr = make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + qty=24, + rate=100, + rejected_qty=31, + rejected_warehouse=rejected_warehouse, + ) + + pr_return = make_purchase_return_against_rejected_warehouse(pr.name) + pr_return.save() + pr_return.submit() + + self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse) + self.assertEqual(pr_return.items[0].qty, 31 * -1) + self.assertEqual(pr_return.items[0].rejected_qty, 0.0) + self.assertEqual(pr_return.items[0].rejected_warehouse, "") + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 460acc3e2269966ab031f59381b72f5e0f34debf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:31:06 +0530 Subject: [PATCH 167/203] fix: not able to make purchase return (backport #42053) (#42055) fix: not able to make purchase return (#42053) (cherry picked from commit 9738c04ef0e795dbf9b11531fb9a2b42ada935a7) Co-authored-by: rohitwaghchaure (cherry picked from commit 8a91bf315491e6b3b54c1b0f681397bb5acd4a66) --- .../controllers/sales_and_purchase_return.py | 10 +++++-- .../purchase_receipt/test_purchase_receipt.py | 30 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index b61d1773a67..76d04c04d6a 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -146,7 +146,10 @@ def validate_returned_items(doc): def validate_quantity(doc, args, ref, valid_items, already_returned_items): fields = ["stock_qty"] if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]: - fields.extend(["received_qty", "rejected_qty"]) + if not args.get("return_qty_from_rejected_warehouse"): + fields.extend(["received_qty", "rejected_qty"]) + else: + fields.extend(["received_qty"]) already_returned_data = already_returned_items.get(args.item_code) or {} @@ -158,9 +161,12 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items): for column in fields: returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0 - if column == "stock_qty": + if column == "stock_qty" and not args.get("return_qty_from_rejected_warehouse"): reference_qty = ref.get(column) current_stock_qty = args.get(column) + elif args.get("return_qty_from_rejected_warehouse"): + reference_qty = ref.get("rejected_qty") * ref.get("conversion_factor", 1.0) + current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0) else: reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0) current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index cae58de9303..16553749d0a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2990,6 +2990,36 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(batch.manufacturing_date, getdate(today())) self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5))) + def test_purchase_return_from_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return_against_rejected_warehouse, + ) + + item_code = "_Test Item Return from Rejected Warehouse 11" + create_item(item_code) + + warehouse = create_warehouse("_Test Warehouse Return Qty Warehouse 11") + rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Qty Warehouse 11") + + # Step 1: Create Purchase Receipt with valuation rate 100 + pr = make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + qty=24, + rate=100, + rejected_qty=31, + rejected_warehouse=rejected_warehouse, + ) + + pr_return = make_purchase_return_against_rejected_warehouse(pr.name) + pr_return.save() + pr_return.submit() + + self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse) + self.assertEqual(pr_return.items[0].qty, 31 * -1) + self.assertEqual(pr_return.items[0].rejected_qty, 0.0) + self.assertEqual(pr_return.items[0].rejected_warehouse, "") + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From d396c18689e530d3f11a791ef1064c2d9775466e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:32:43 +0530 Subject: [PATCH 168/203] perf: Performance optmization for Purchase Invoice submission (backport #40263) (#41946) * perf: Optimization for providional gl entries (cherry picked from commit d7b738ff61e0ebab62ff2c15247f90b79401e9a4) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py * perf: Performance optimization for validating budget (cherry picked from commit f204d810bba5a6fdb3137b780489907c30b650fc) # Conflicts: # erpnext/accounts/doctype/budget/budget.py * perf: Cached accounting dimensions details (cherry picked from commit 8cd8b8f885419fac8297b171e90be7657357a0e3) * perf: Optimzed code for merging similar gl entries (cherry picked from commit aa75a6014264853668acd15e996e8fd0af8e7ebe) * fix: linter issues (cherry picked from commit acc0b2faf82c2b352cc2caf14ef26e6e55f34230) * perf: Cache accounting dimension filter map (cherry picked from commit e4bd1738752896e9a50231561c8ca5f3a2ee15c5) # Conflicts: # erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py * fix: minor fixes (cherry picked from commit 5cd9bf3bda1c63fc04b6b4db8ddfdb2f9c7b456e) * perf: skip unnecessary validation while transaction cancellation (cherry picked from commit 05385e4acb0f3af7e9cd19c752df8d7287f73401) * perf: refactored handling provisional gl entries for non-stock items (cherry picked from commit 49c74369a58c313f114a8a9d99bb3b316febe4cf) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py * perf: validate expense against budget only if budget exists (cherry picked from commit c15b2d5490975a2eb69ee0819611bc46fcd0dd3e) * perf: Get bin details only for stock items (cherry picked from commit 6ff9e6ee848e68c1d84bc17ad682b376214c5d59) # Conflicts: # erpnext/stock/get_item_details.py * fix: added index for price_list column in Item Price (cherry picked from commit d279e23623f13f93c6f7c870fef0c2335e6d2b15) # Conflicts: # erpnext/stock/doctype/item_price/item_price.json * perf: Caching in checking allowance for qty and amount (cherry picked from commit 8d682fa8840ef69a1d8a78e4bb418668f251585b) * perf: Caching in gl entry (cherry picked from commit b07769d8d7890580a9c47598bd166347ca8c1aec) # Conflicts: # erpnext/accounts/doctype/gl_entry/gl_entry.py * chore: resolve conflicts * chore: resolve conflict in purchase_invoice.py --------- Co-authored-by: Nabin Hait Co-authored-by: ruthra kumar --- .../accounting_dimension.py | 16 +- .../test_accounting_dimension.py | 2 + .../accounting_dimension_filter.py | 62 ++++--- .../test_accounting_dimension_filter.py | 2 + erpnext/accounts/doctype/budget/budget.py | 20 +- .../budget_account/budget_account.json | 124 ++++--------- erpnext/accounts/doctype/gl_entry/gl_entry.py | 10 +- .../payment_ledger_entry.py | 9 +- .../purchase_invoice/purchase_invoice.py | 173 ++++++++++-------- erpnext/accounts/general_ledger.py | 45 ++--- erpnext/controllers/accounts_controller.py | 4 +- erpnext/controllers/status_updater.py | 7 +- .../stock/doctype/item_price/item_price.json | 3 +- erpnext/stock/get_item_details.py | 28 +-- 14 files changed, 254 insertions(+), 251 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 7a2f4e4b71b..db99bcd223b 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -255,14 +255,16 @@ def get_accounting_dimensions(as_list=True, filters=None): def get_checks_for_pl_and_bs_accounts(): - dimensions = frappe.db.sql( - """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs - FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c - WHERE p.name = c.parent""", - as_dict=1, - ) + if frappe.flags.accounting_dimensions_details is None: + # nosemgrep + frappe.flags.accounting_dimensions_details = frappe.db.sql( + """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs + FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c + WHERE p.name = c.parent""", + as_dict=1, + ) - return dimensions + return frappe.flags.accounting_dimensions_details def get_dimension_with_children(doctype, dimensions): diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index cb7f5f5da78..10dbe3bab0f 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -78,6 +78,8 @@ class TestAccountingDimension(unittest.TestCase): def tearDown(self): disable_dimension() + frappe.flags.accounting_dimensions_details = None + frappe.flags.dimension_filter_map = None def create_dimension(): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py index 01f6e60bf3b..1954b4b0efe 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py @@ -66,37 +66,41 @@ class AccountingDimensionFilter(Document): def get_dimension_filter_map(): - filters = frappe.db.sql( - """ - SELECT - a.applicable_on_account, d.dimension_value, p.accounting_dimension, - p.allow_or_restrict, a.is_mandatory - FROM - `tabApplicable On Account` a, - `tabAccounting Dimension Filter` p - LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name - WHERE - p.name = a.parent - AND p.disabled = 0 - """, - as_dict=1, - ) - - dimension_filter_map = {} - - for f in filters: - f.fieldname = scrub(f.accounting_dimension) - - build_map( - dimension_filter_map, - f.fieldname, - f.applicable_on_account, - f.dimension_value, - f.allow_or_restrict, - f.is_mandatory, + if not frappe.flags.get("dimension_filter_map"): + # nosemgrep + filters = frappe.db.sql( + """ + SELECT + a.applicable_on_account, d.dimension_value, p.accounting_dimension, + p.allow_or_restrict, a.is_mandatory + FROM + `tabApplicable On Account` a, `tabAllowed Dimension` d, + `tabAccounting Dimension Filter` p + WHERE + p.name = a.parent + AND p.disabled = 0 + AND p.name = d.parent + """, + as_dict=1, ) - return dimension_filter_map + dimension_filter_map = {} + + for f in filters: + f.fieldname = scrub(f.accounting_dimension) + + build_map( + dimension_filter_map, + f.fieldname, + f.applicable_on_account, + f.dimension_value, + f.allow_or_restrict, + f.is_mandatory, + ) + + frappe.flags.dimension_filter_map = dimension_filter_map + + return frappe.flags.dimension_filter_map def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index 3dec7c87020..77057c1e20e 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -47,6 +47,8 @@ class TestAccountingDimensionFilter(unittest.TestCase): def tearDown(self): disable_dimension_filter() disable_dimension() + frappe.flags.accounting_dimensions_details = None + frappe.flags.dimension_filter_map = None for si in self.invoice_list: si.load_from_db() diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 11f78ae1763..2c4ed7781aa 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -142,6 +142,8 @@ class Budget(Document): def validate_expense_against_budget(args, expense_amount=0): args = frappe._dict(args) + if not frappe.get_all("Budget", limit=1): + return if args.get("company") and not args.fiscal_year: args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0] @@ -149,6 +151,11 @@ def validate_expense_against_budget(args, expense_amount=0): "Company", args.get("company"), "exception_budget_approver_role" ) + if not frappe.get_cached_value( + "Budget", {"fiscal_year": args.fiscal_year, "company": args.company} + ): # nosec + return + if not args.account: args.account = args.get("expense_account") @@ -175,14 +182,19 @@ def validate_expense_against_budget(args, expense_amount=0): if ( args.get(budget_against) and args.account - and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}) + and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense") ): doctype = dimension.get("document_type") if frappe.get_cached_value("DocType", doctype, "is_tree"): - lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"]) - condition = f"""and exists(select name from `tab{doctype}` - where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec + lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"]) + condition = """and exists(select name from `tab%s` + where lft<=%s and rgt>=%s and name=b.%s)""" % ( + doctype, + lft, + rgt, + budget_against, + ) # nosec args.is_tree = True else: condition = f"and b.{budget_against}={frappe.db.escape(args.get(budget_against))}" diff --git a/erpnext/accounts/doctype/budget_account/budget_account.json b/erpnext/accounts/doctype/budget_account/budget_account.json index ead07614a7f..c7d872647f1 100644 --- a/erpnext/accounts/doctype/budget_account/budget_account.json +++ b/erpnext/accounts/doctype/budget_account/budget_account.json @@ -1,94 +1,42 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-05-16 11:54:09.286135", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2016-05-16 11:54:09.286135", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account", + "budget_amount" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1, + "search_index": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "budget_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Budget Amount", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "budget_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Budget Amount", + "options": "Company:company:default_currency", + "reqd": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-01-02 17:02:53.339420", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Budget Account", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2024-03-04 15:43:27.016947", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Budget Account", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 594c3054cfd..facfbf51e00 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -328,8 +328,10 @@ def update_outstanding_amt( party_condition = "" if against_voucher_type == "Sales Invoice": - party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to") - account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})" + party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to") + account_condition = "and account in ({0}, {1})".format( + frappe.db.escape(account), frappe.db.escape(party_account) + ) else: account_condition = f" and account = {frappe.db.escape(account)}" @@ -392,7 +394,9 @@ def update_outstanding_amt( def validate_frozen_account(account, adv_adj=None): frozen_account = frappe.get_cached_value("Account", account, "freeze_account") if frozen_account == "Yes" and not adv_adj: - frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", None, "frozen_accounts_modifier") + frozen_accounts_modifier = frappe.get_cached_value( + "Accounts Settings", None, "frozen_accounts_modifier" + ) if not frozen_accounts_modifier: frappe.throw(_("Account {0} is frozen").format(account)) diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py index ca591d42073..2bc44893c20 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py @@ -161,11 +161,12 @@ class PaymentLedgerEntry(Document): def on_update(self): adv_adj = self.flags.adv_adj if not self.flags.from_repost: - self.validate_account_details() - self.validate_dimensions_for_pl_and_bs() - self.validate_allowed_dimensions() - validate_balance_type(self.account, adv_adj) validate_frozen_account(self.account, adv_adj) + if not self.delinked: + self.validate_account_details() + self.validate_dimensions_for_pl_and_bs() + self.validate_allowed_dimensions() + validate_balance_type(self.account, adv_adj) # update outstanding amount if ( diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dce742d1021..e519f069aa4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -795,13 +795,12 @@ class PurchaseInvoice(BuyingController): self.db_set("repost_required", self.needs_repost) def make_gl_entries(self, gl_entries=None, from_repost=False): - if not gl_entries: - gl_entries = self.get_gl_entries() + update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" + if self.docstatus == 1: + if not gl_entries: + gl_entries = self.get_gl_entries() - if gl_entries: - update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" - - if self.docstatus == 1: + if gl_entries: make_gl_entries( gl_entries, update_outstanding=update_outstanding, @@ -809,32 +808,43 @@ class PurchaseInvoice(BuyingController): from_repost=from_repost, ) self.make_exchange_gain_loss_journal() - elif self.docstatus == 2: - provisional_entries = [a for a in gl_entries if a.voucher_type == "Purchase Receipt"] - make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - if provisional_entries: - for entry in provisional_entries: - frappe.db.set_value( - "GL Entry", - { - "voucher_type": "Purchase Receipt", - "voucher_detail_no": entry.voucher_detail_no, - }, - "is_cancelled", - 1, - ) - - if update_outstanding == "No": - update_outstanding_amt( - self.credit_to, - "Supplier", - self.supplier, - self.doctype, - self.return_against if cint(self.is_return) and self.return_against else self.name, - ) - - elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock: + elif self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + self.cancel_provisional_entries() + + self.update_supplier_outstanding(update_outstanding) + + def cancel_provisional_entries(self): + rows = set() + purchase_receipts = set() + for d in self.items: + if d.purchase_receipt: + purchase_receipts.add(d.purchase_receipt) + rows.add(d.name) + + if rows: + # cancel gl entries + gle = qb.DocType("GL Entry") + gle_update_query = ( + qb.update(gle) + .set(gle.is_cancelled, 1) + .where( + (gle.voucher_type == "Purchase Receipt") + & (gle.voucher_no.isin(purchase_receipts)) + & (gle.voucher_detail_no.isin(rows)) + ) + ) + gle_update_query.run() + + def update_supplier_outstanding(self, update_outstanding): + if update_outstanding == "No": + update_outstanding_amt( + self.credit_to, + "Supplier", + self.supplier, + self.doctype, + self.return_against if cint(self.is_return) and self.return_against else self.name, + ) def get_gl_entries(self, warehouse_account=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) @@ -947,8 +957,9 @@ class PurchaseInvoice(BuyingController): "Company", self.company, "enable_provisional_accounting_for_non_stock_items" ) ) - - purchase_receipt_doc_map = {} + self.provisional_enpenses_booked_in_pr = False + if provisional_accounting_for_non_stock_items: + self.get_provisional_accounts() for item in self.get("items"): if flt(item.base_net_amount): @@ -1087,49 +1098,7 @@ class PurchaseInvoice(BuyingController): dummy, amount = self.get_amount_and_base_amount(item, None) if provisional_accounting_for_non_stock_items: - if item.purchase_receipt: - provisional_account, pr_qty, pr_base_rate, pr_rate = frappe.get_cached_value( - "Purchase Receipt Item", - item.pr_detail, - ["provisional_expense_account", "qty", "base_rate", "rate"], - ) - provisional_account = provisional_account or self.get_company_default( - "default_provisional_account" - ) - purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt) - - if not purchase_receipt_doc: - purchase_receipt_doc = frappe.get_doc( - "Purchase Receipt", item.purchase_receipt - ) - purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc - - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - expense_booked_in_pr = frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "voucher_type": "Purchase Receipt", - "voucher_no": item.purchase_receipt, - "voucher_detail_no": item.pr_detail, - "account": provisional_account, - }, - "name", - ) - - if expense_booked_in_pr: - # Intentionally passing purchase invoice item to handle partial billing - purchase_receipt_doc.add_provisional_gl_entry( - item, - gl_entries, - self.posting_date, - provisional_account, - reverse=1, - item_amount=( - (min(item.qty, pr_qty) * pr_rate) - * purchase_receipt_doc.get("conversion_rate") - ), - ) + self.make_provisional_gl_entry(gl_entries, item) if not self.is_internal_transfer(): gl_entries.append( @@ -1225,6 +1194,58 @@ class PurchaseInvoice(BuyingController): if item.is_fixed_asset and item.landed_cost_voucher_amount: self.update_gross_purchase_amount_for_linked_assets(item) + def get_provisional_accounts(self): + self.provisional_accounts = frappe._dict() + linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt]) + pr_items = frappe.get_all( + "Purchase Receipt Item", + filters={"parent": ("in", linked_purchase_receipts)}, + fields=["name", "provisional_expense_account", "qty", "base_rate"], + ) + default_provisional_account = self.get_company_default("default_provisional_account") + for item in pr_items: + self.provisional_accounts[item.name] = { + "provisional_account": item.provisional_expense_account or default_provisional_account, + "qty": item.qty, + "base_rate": item.base_rate, + } + + def make_provisional_gl_entry(self, gl_entries, item): + if item.purchase_receipt: + if not self.provisional_enpenses_booked_in_pr: + pr_item = self.provisional_accounts.get(item.pr_detail, {}) + provisional_account = pr_item.get("provisional_account") + pr_qty = pr_item.get("qty") + pr_base_rate = pr_item.get("base_rate") + + # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt + provision_gle_against_pr = frappe.db.get_value( + "GL Entry", + { + "is_cancelled": 0, + "voucher_type": "Purchase Receipt", + "voucher_no": item.purchase_receipt, + "voucher_detail_no": item.pr_detail, + "account": provisional_account, + }, + ["name"], + ) + if provision_gle_against_pr: + self.provisional_enpenses_booked_in_pr = True + + if self.provisional_enpenses_booked_in_pr: + purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt) + + # Intentionally passing purchase invoice item to handle partial billing + purchase_receipt_doc.add_provisional_gl_entry( + item, + gl_entries, + self.posting_date, + provisional_account, + reverse=1, + item_amount=(min(item.qty, pr_qty) * pr_base_rate), + ) + def update_gross_purchase_amount_for_linked_assets(self, item): assets = frappe.db.get_all( "Asset", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 3f01dee888d..2fd7b5d3c81 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -7,7 +7,7 @@ import copy import frappe from frappe import _ from frappe.model.meta import get_field_precision -from frappe.utils import cint, cstr, flt, formatdate, getdate, now +from frappe.utils import cint, flt, formatdate, getdate, now import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -228,11 +228,13 @@ def get_cost_center_allocation_data(company, posting_date): def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() + merge_properties = get_merge_properties(accounting_dimensions) for entry in gl_map: + entry.merge_key = get_merge_key(entry, merge_properties) # if there is already an entry in this account then just add it # to that entry - same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions) + same_head = check_if_in_list(entry, merged_gl_map) if same_head: same_head.debit = flt(same_head.debit) + flt(entry.debit) same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt( @@ -273,34 +275,35 @@ def merge_similar_entries(gl_map, precision=None): return merged_gl_map -def check_if_in_list(gle, gl_map, dimensions=None): - account_head_fieldnames = [ - "voucher_detail_no", - "party", - "against_voucher", +def get_merge_properties(dimensions=None): + merge_properties = [ + "account", "cost_center", - "against_voucher_type", + "party", "party_type", + "voucher_detail_no", + "against_voucher", + "against_voucher_type", "project", "finance_book", "voucher_no", ] - if dimensions: - account_head_fieldnames = account_head_fieldnames + dimensions + merge_properties.extend(dimensions) + return merge_properties + +def get_merge_key(entry, merge_properties): + merge_key = [] + for fieldname in merge_properties: + merge_key.append(entry.get(fieldname, "")) + + return tuple(merge_key) + + +def check_if_in_list(gle, gl_map): for e in gl_map: - same_head = True - if e.account != gle.account: - same_head = False - continue - - for fieldname in account_head_fieldnames: - if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)): - same_head = False - break - - if same_head: + if e.merge_key == gle.merge_key: return e diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f576ecc1b11..0cbf1b320ca 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1764,8 +1764,8 @@ class AccountsController(TransactionBase): item_allowance = {} global_qty_allowance, global_amount_allowance = None, None - role_allowed_to_over_bill = frappe.db.get_single_value( - "Accounts Settings", "role_allowed_to_over_bill" + role_allowed_to_over_bill = frappe.get_cached_value( + "Accounts Settings", None, "role_allowed_to_over_bill" ) user_roles = frappe.get_roles() diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 896413aaed8..492000d71d4 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -554,6 +554,7 @@ class StatusUpdater(Document): ref_doc.set_status(update=True) +@frappe.request_cache def get_allowance_for( item_code, item_allowance=None, @@ -583,20 +584,20 @@ def get_allowance_for( global_amount_allowance, ) - qty_allowance, over_billing_allowance = frappe.db.get_value( + qty_allowance, over_billing_allowance = frappe.get_cached_value( "Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"] ) if qty_or_amount == "qty" and not qty_allowance: if global_qty_allowance is None: global_qty_allowance = flt( - frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance") + frappe.get_cached_value("Stock Settings", None, "over_delivery_receipt_allowance") ) qty_allowance = global_qty_allowance elif qty_or_amount == "amount" and not over_billing_allowance: if global_amount_allowance is None: global_amount_allowance = flt( - frappe.db.get_single_value("Accounts Settings", "over_billing_allowance") + frappe.get_cached_value("Accounts Settings", None, "over_billing_allowance") ) over_billing_allowance = global_amount_allowance diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index 3daf4dc2bd8..c6950b9a10f 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -107,7 +107,8 @@ "in_standard_filter": 1, "label": "Price List", "options": "Price List", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "bold": 1, diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 5da3c066869..a7aa7dce452 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -103,19 +103,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) - if args.get("doctype") == "Material Request" and args.get("material_request_type") == "Material Transfer": - out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) - - 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, include_child_warehouses=True - ) - else: - bin_details = get_bin_details(args.item_code, out.warehouse, include_child_warehouses=True) - - out.update(bin_details) + if item.is_stock_item: + update_bin_details(args, out, doc) # update args with out, if key or value not exists for key, value in out.items(): @@ -166,6 +155,19 @@ def set_valuation_rate(out, args): out.update(get_valuation_rate(args.item_code, args.company, out.get("warehouse"))) +def update_bin_details(args, out, doc): + if args.get("doctype") == "Material Request" and args.get("material_request_type") == "Material Transfer": + out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) + + elif out.get("warehouse"): + company = args.company if (doc and doc.get("doctype") == "Purchase Order") else None + + # calculate company_total_stock only for po + bin_details = get_bin_details(args.item_code, out.warehouse, company, include_child_warehouses=True) + + out.update(bin_details) + + def process_args(args): if isinstance(args, str): args = json.loads(args) From c47dcc0eb7c3e0bbdc35873abed7ea46c6e22a4f Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Fri, 28 Jun 2024 09:19:54 +0000 Subject: [PATCH 169/203] chore(release): Bumped to Version 15.28.1 ## [15.28.1](https://github.com/frappe/erpnext/compare/v15.28.0...v15.28.1) (2024-06-28) ### Bug Fixes * not able to make purchase return (backport [#42053](https://github.com/frappe/erpnext/issues/42053)) ([#42055](https://github.com/frappe/erpnext/issues/42055)) ([460acc3](https://github.com/frappe/erpnext/commit/460acc3e2269966ab031f59381b72f5e0f34debf)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 3e0be774866..1ac1954d093 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.28.0" +__version__ = "15.28.1" def get_default_company(user=None): From 1f3374fcdf3e29a9fa6ad7d317db1b9c087bdbfb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 07:32:45 +0530 Subject: [PATCH 170/203] fix: reposting file attachment permission issue (backport #42068) (#42075) * fix: reposting file attachment permission issue (#42068) (cherry picked from commit 03e674e21d7ede46c43496d51db26ad523ae3fb1) # Conflicts: # erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- .../doctype/repost_item_valuation/repost_item_valuation.js | 4 +--- .../repost_item_valuation/repost_item_valuation.json | 5 +++-- .../doctype/repost_item_valuation/repost_item_valuation.py | 6 ++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index f0506ab6930..6f2548ac8ea 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -128,9 +128,7 @@ frappe.ui.form.on("Repost Item Valuation", { method: "restart_reposting", doc: frm.doc, callback: function (r) { - if (!r.exc) { - frm.refresh(); - } + frm.reload_doc(); }, }); }, diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 1c5b521c296..67e97964e18 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -218,13 +218,14 @@ "fieldname": "reposting_data_file", "fieldtype": "Attach", "label": "Reposting Data File", + "no_copy": 1, "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-05-31 12:48:57.138693", + "modified": "2024-06-27 16:55:23.150146", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", @@ -276,4 +277,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 40767704f4e..717b1c31026 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -179,7 +179,7 @@ class RepostItemValuation(Document): def clear_attachment(self): if attachments := get_attachments(self.doctype, self.name): attachment = attachments[0] - frappe.delete_doc("File", attachment.name) + frappe.delete_doc("File", attachment.name, ignore_permissions=True) if self.reposting_data_file: self.db_set("reposting_data_file", None) @@ -219,6 +219,7 @@ class RepostItemValuation(Document): self.distinct_item_and_warehouse = None self.items_to_be_repost = None self.gl_reposting_index = 0 + self.clear_attachment() self.db_update() def deduplicate_similar_repost(self): @@ -271,6 +272,7 @@ def repost(doc): repost_gl_entries(doc) doc.set_status("Completed") + doc.db_set("reposting_data_file", None) remove_attached_file(doc.name) except Exception as e: @@ -315,7 +317,7 @@ def remove_attached_file(docname): if file_name := frappe.db.get_value( "File", {"attached_to_name": docname, "attached_to_doctype": "Repost Item Valuation"}, "name" ): - frappe.delete_doc("File", file_name, delete_permanently=True) + frappe.delete_doc("File", file_name, ignore_permissions=True, delete_permanently=True) def repost_sl_entries(doc): From 760b2e24f27c1dfdc70eb685562e83afc9f35ae3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:59:57 +0530 Subject: [PATCH 171/203] fix: expense account from item group not fetched (backport #41957) (#41962) fix: expense account from item group not fetched (cherry picked from commit 86ebe58231bf1a0271c9274a2b312bc533e1e5f3) Co-authored-by: Rohit Waghchaure --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 266d74afbed..005ad287d78 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1627,7 +1627,7 @@ class StockEntry(StockController): "has_serial_no": item.has_serial_no, "has_batch_no": item.has_batch_no, "sample_quantity": item.sample_quantity, - "expense_account": item.expense_account, + "expense_account": item.expense_account or item_group_defaults.get("expense_account"), } ) From d310074222a5937b37bcd201b88eff233154b7ad Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:59:57 +0530 Subject: [PATCH 172/203] fix: expense account from item group not fetched (backport #41957) (#41962) fix: expense account from item group not fetched (cherry picked from commit 86ebe58231bf1a0271c9274a2b312bc533e1e5f3) Co-authored-by: Rohit Waghchaure (cherry picked from commit 760b2e24f27c1dfdc70eb685562e83afc9f35ae3) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 266d74afbed..005ad287d78 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1627,7 +1627,7 @@ class StockEntry(StockController): "has_serial_no": item.has_serial_no, "has_batch_no": item.has_batch_no, "sample_quantity": item.sample_quantity, - "expense_account": item.expense_account, + "expense_account": item.expense_account or item_group_defaults.get("expense_account"), } ) From fb76daaf9ee59a415af3017c5628659b04524549 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Sat, 29 Jun 2024 16:18:59 +0000 Subject: [PATCH 173/203] chore(release): Bumped to Version 15.28.2 ## [15.28.2](https://github.com/frappe/erpnext/compare/v15.28.1...v15.28.2) (2024-06-29) ### Bug Fixes * expense account from item group not fetched (backport [#41957](https://github.com/frappe/erpnext/issues/41957)) ([#41962](https://github.com/frappe/erpnext/issues/41962)) ([d310074](https://github.com/frappe/erpnext/commit/d310074222a5937b37bcd201b88eff233154b7ad)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 1ac1954d093..4a9236066cc 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "15.28.1" +__version__ = "15.28.2" def get_default_company(user=None): From 4e74257ba9bf520d0a92622adf979e77cd4d8222 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 25 Jun 2024 13:52:38 +0530 Subject: [PATCH 174/203] fix: incorrect dr/cr on Adv Payment against Journals (cherry picked from commit f6c1dffb3531c5ba9b9011b72ddb852b2403ac26) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a2882206b4a..f5fb49f0bba 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1218,12 +1218,17 @@ class PaymentEntry(AccountsController): return "credit", reference.account if reference.reference_doctype == "Payment Entry": + # reference.account_type and reference.payment_type is only available for Reverse payments if reference.account_type == "Receivable" and reference.payment_type == "Pay": return "credit", self.party_account else: return "debit", self.party_account - return "debit", reference.account + if reference.reference_doctype == "Journal Entry": + if self.party_type == "Customer" and self.payment_type == "Receive": + return "credit", reference.account + else: + return "debit", reference.account def add_advance_gl_for_reference(self, gl_entries, invoice): args_dict = { From e00348fd5271c5e375290e762a99b8c55d0de275 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 25 Jun 2024 16:14:05 +0530 Subject: [PATCH 175/203] test: advance payment against journal entry - customer type (cherry picked from commit 5e84272cf920492a2cdfb2163e8ee3e7f899e6e9) --- .../test_payment_reconciliation.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 53f69a47e75..f272fede589 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -109,6 +109,14 @@ class TestPaymentReconciliation(FrappeTestCase): "account_currency": "INR", "account_type": "Payable", }, + # 'Receivable' account for capturing advance received, under 'Liabilities' group + { + "attribute": "advance_receivable_account", + "account_name": "Advance Received", + "parent_account": "Current Liabilities - _PR", + "account_currency": "INR", + "account_type": "Receivable", + }, ] for x in accounts: @@ -1574,6 +1582,114 @@ class TestPaymentReconciliation(FrappeTestCase): ) self.assertEqual(len(pl_entries), 3) + def test_advance_payment_reconciliation_against_journal_for_customer(self): + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": self.advance_receivable_account, + "reconcile_on_advance_payment_date": 0, + }, + ) + amount = 200.0 + je = self.create_journal_entry(self.debit_to, self.bank, amount) + je.accounts[0].cost_center = self.main_cc.name + je.accounts[0].party_type = "Customer" + je.accounts[0].party = self.customer + je.accounts[1].cost_center = self.main_cc.name + je = je.save().submit() + + pe = self.create_payment_entry(amount=amount).save().submit() + + pr = self.create_payment_reconciliation() + pr.default_advance_account = self.advance_receivable_account + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # Assert Ledger Entries + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + ) + self.assertEqual(len(gl_entries), 4) + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name, "delinked": 0}, + ) + self.assertEqual(len(pl_entries), 3) + + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + fields=["account", "voucher_no", "against_voucher", "debit", "credit"], + order_by="account, against_voucher, debit", + ) + expected_gle = [ + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": 0.0, + "credit": amount, + }, + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": self.debit_to, + "voucher_no": pe.name, + "against_voucher": je.name, + "debit": 0.0, + "credit": amount, + }, + { + "account": self.bank, + "voucher_no": pe.name, + "against_voucher": None, + "debit": amount, + "credit": 0.0, + }, + ] + self.assertEqual(gl_entries, expected_gle) + + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name}, + fields=["account", "voucher_no", "against_voucher_no", "amount"], + order_by="account, against_voucher_no, amount", + ) + expected_ple = [ + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -amount, + }, + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": amount, + }, + { + "account": self.debit_to, + "voucher_no": pe.name, + "against_voucher_no": je.name, + "amount": -amount, + }, + ] + self.assertEqual(pl_entries, expected_ple) + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From 6a0111c7db2dbc4b778e3dd5cf425ccf9eb65374 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 25 Jun 2024 16:25:20 +0530 Subject: [PATCH 176/203] test: advance payment entry against journal - supplier type (cherry picked from commit 1b384b99429f8953b99a7d6a6c1206ba4a319638) --- .../test_payment_reconciliation.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index f272fede589..a5295585221 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1690,6 +1690,121 @@ class TestPaymentReconciliation(FrappeTestCase): ] self.assertEqual(pl_entries, expected_ple) + def test_advance_payment_reconciliation_against_journal_for_supplier(self): + self.supplier = make_supplier("_Test Supplier") + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": self.advance_payable_account, + "reconcile_on_advance_payment_date": 0, + }, + ) + amount = 200.0 + je = self.create_journal_entry(self.creditors, self.bank, -amount) + je.accounts[0].cost_center = self.main_cc.name + je.accounts[0].party_type = "Supplier" + je.accounts[0].party = self.supplier + je.accounts[1].cost_center = self.main_cc.name + je = je.save().submit() + + pe = self.create_payment_entry(amount=amount) + pe.payment_type = "Pay" + pe.party_type = "Supplier" + pe.paid_from = self.bank + pe.paid_to = self.creditors + pe.party = self.supplier + pe.save().submit() + + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.default_advance_account = self.advance_payable_account + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # Assert Ledger Entries + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + ) + self.assertEqual(len(gl_entries), 4) + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name, "delinked": 0}, + ) + self.assertEqual(len(pl_entries), 3) + + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + fields=["account", "voucher_no", "against_voucher", "debit", "credit"], + order_by="account, against_voucher, debit", + ) + expected_gle = [ + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": 0.0, + "credit": amount, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": self.creditors, + "voucher_no": pe.name, + "against_voucher": je.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": self.bank, + "voucher_no": pe.name, + "against_voucher": None, + "debit": 0.0, + "credit": amount, + }, + ] + self.assertEqual(gl_entries, expected_gle) + + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name}, + fields=["account", "voucher_no", "against_voucher_no", "amount"], + order_by="account, against_voucher_no, amount", + ) + expected_ple = [ + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -amount, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": amount, + }, + { + "account": self.creditors, + "voucher_no": pe.name, + "against_voucher_no": je.name, + "amount": -amount, + }, + ] + self.assertEqual(pl_entries, expected_ple) + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From cb703ff17c2add26412291b7aa4f84020eb2b92d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Jun 2024 20:38:32 +0530 Subject: [PATCH 177/203] chore: better test name (cherry picked from commit ad7efd59394aa645f72ff2aa5618a39c4b2277b2) --- .../doctype/unreconcile_payment/test_unreconcile_payment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 882dd1d6dab..43dfbfaef60 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -107,7 +107,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): self.assertEqual(len(pe.references), 1) self.assertEqual(pe.unallocated_amount, 100) - def test_02_unreconcile_one_payment_from_multi_payments(self): + def test_02_unreconcile_one_payment_among_multi_payments(self): """ Scenario: 2 payments, both split against 2 different invoices Unreconcile only one payment from one invoice From 3c58e0af50515ee78a2de10e56cdaba41ab47ebb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Jun 2024 20:48:22 +0530 Subject: [PATCH 178/203] refactor: handle purchase invoice as reference (cherry picked from commit 9ec6aef95d99c148731404f2f6b9e323fd2b8b16) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f5fb49f0bba..fb3500e2c58 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1217,6 +1217,9 @@ class PaymentEntry(AccountsController): if reference.reference_doctype == "Sales Invoice": return "credit", reference.account + if reference.reference_doctype == "Purchase Invoice": + return "debit", reference.account + if reference.reference_doctype == "Payment Entry": # reference.account_type and reference.payment_type is only available for Reverse payments if reference.account_type == "Receivable" and reference.payment_type == "Pay": From a0e06a4ba5c5efc2cd69a8b561c5832dcd210761 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:15:13 +0530 Subject: [PATCH 179/203] fix: timeout error while submitting JV (backport #42040) (#42099) * fix: timeout error while submitting JV (#42040) (cherry picked from commit 32bdcdb08f93e580abbacf57abea4242f456cbaa) # Conflicts: # erpnext/accounts/doctype/account/account.json # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py # erpnext/accounts/utils.py # erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/accounts/doctype/account/account.json | 7 ++-- .../payment_entry/test_payment_entry.py | 2 +- .../test_payment_ledger_entry.py | 6 +++- .../sales_invoice/test_sales_invoice.py | 31 +++++++++-------- erpnext/accounts/utils.py | 33 ++++++++++++++----- .../stock_ledger_entry.json | 5 +-- erpnext/stock/utils.py | 2 -- 7 files changed, 54 insertions(+), 32 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 0c9232015d9..e87b59ea9cb 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -121,7 +121,8 @@ "label": "Account Type", "oldfieldname": "account_type", "oldfieldtype": "Select", - "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary" + "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary", + "search_index": 1 }, { "description": "Rate at which this tax is applied", @@ -190,7 +191,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2023-07-20 18:18:44.405723", + "modified": "2024-06-27 16:23:04.444354", "modified_by": "Administrator", "module": "Accounts", "name": "Account", @@ -251,4 +252,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 4631f8905e6..cc03dc260bb 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -82,7 +82,7 @@ class TestPaymentEntry(FrappeTestCase): expected_gle = dict( (d[0], d) - for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], ["Cash - _TC", 5500.0, 0, None]] + for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) diff --git a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py index 3eac98d7910..9a33a7ccf6d 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py @@ -509,7 +509,11 @@ class TestPaymentLedgerEntry(FrappeTestCase): @change_settings( "Accounts Settings", - {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + { + "unlink_payment_on_cancellation_of_invoice": 1, + "delete_linked_ledger_entries": 1, + "unlink_advance_payment_on_cancelation_of_order": 1, + }, ) def test_advance_payment_unlink_on_order_cancellation(self): transaction_date = nowdate() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 5cffe11e995..b0d62339be0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2202,13 +2202,14 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(si.total_taxes_and_charges, 228.82) self.assertEqual(si.rounding_adjustment, -0.01) - expected_values = [ - ["_Test Account Service Tax - _TC", 0.0, 114.41], - ["_Test Account VAT - _TC", 0.0, 114.41], - [si.debit_to, 1500, 0.0], - ["Round Off - _TC", 0.01, 0.01], - ["Sales - _TC", 0.0, 1271.18], - ] + round_off_account = frappe.get_cached_value("Company", "_Test Company", "round_off_account") + expected_values = { + "_Test Account Service Tax - _TC": [0.0, 114.41], + "_Test Account VAT - _TC": [0.0, 114.41], + si.debit_to: [1500, 0.0], + round_off_account: [0.01, 0.01], + "Sales - _TC": [0.0, 1271.18], + } gl_entries = frappe.db.sql( """select account, sum(debit) as debit, sum(credit) as credit @@ -2219,10 +2220,10 @@ class TestSalesInvoice(FrappeTestCase): as_dict=1, ) - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[i][0], gle.account) - self.assertEqual(expected_values[i][1], gle.debit) - self.assertEqual(expected_values[i][2], gle.credit) + for gle in gl_entries: + expected_account_values = expected_values[gle.account] + self.assertEqual(expected_account_values[0], gle.debit) + self.assertEqual(expected_account_values[1], gle.credit) def test_rounding_adjustment_3(self): from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( @@ -2270,6 +2271,7 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(si.total_taxes_and_charges, 480.86) self.assertEqual(si.rounding_adjustment, -0.02) + round_off_account = frappe.get_cached_value("Company", "_Test Company", "round_off_account") expected_values = dict( (d[0], d) for d in [ @@ -2277,7 +2279,7 @@ class TestSalesInvoice(FrappeTestCase): ["_Test Account Service Tax - _TC", 0.0, 240.43], ["_Test Account VAT - _TC", 0.0, 240.43], ["Sales - _TC", 0.0, 4007.15], - ["Round Off - _TC", 0.02, 0.01], + [round_off_account, 0.02, 0.01], ] ) @@ -2306,8 +2308,9 @@ class TestSalesInvoice(FrappeTestCase): as_dict=1, ) - self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC") - self.assertEqual(round_off_gle.location, "Block 1") + if round_off_gle: + self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC") + self.assertEqual(round_off_gle.location, "Block 1") disable_dimension() diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1ad24c46603..2558e976bea 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -10,7 +10,7 @@ import frappe.defaults from frappe import _, qb, throw from frappe.model.meta import get_field_precision from frappe.query_builder import AliasedQuery, Criterion, Table -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Count, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( add_days, @@ -1492,24 +1492,39 @@ def get_stock_accounts(company, voucher_type=None, voucher_no=None): ) ] - return stock_accounts + return list(set(stock_accounts)) def get_stock_and_account_balance(account=None, posting_date=None, company=None): if not posting_date: posting_date = nowdate() - warehouse_account = get_warehouse_account_map(company) - account_balance = get_balance_on( account, posting_date, in_account_currency=False, ignore_account_permission=True ) - related_warehouses = [ - wh - for wh, wh_details in warehouse_account.items() - if wh_details.account == account and not wh_details.is_group - ] + account_table = frappe.qb.DocType("Account") + query = ( + frappe.qb.from_(account_table) + .select(Count(account_table.name)) + .where( + (account_table.account_type == "Stock") + & (account_table.company == company) + & (account_table.is_group == 0) + ) + ) + + no_of_stock_accounts = cint(query.run()[0][0]) + + related_warehouses = [] + if no_of_stock_accounts > 1: + warehouse_account = get_warehouse_account_map(company) + + related_warehouses = [ + wh + for wh, wh_details in warehouse_account.items() + if wh_details.account == account and not wh_details.is_group + ] total_stock_value = get_stock_value_on(related_warehouses, posting_date) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index e8e82af25ac..58b6e4a74b6 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -101,6 +101,7 @@ "oldfieldtype": "Date", "print_width": "100px", "read_only": 1, + "search_index": 1, "width": "100px" }, { @@ -360,7 +361,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-03-13 09:56:13.021696", + "modified": "2024-06-27 16:23:18.820049", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", @@ -384,4 +385,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index f2388e5737a..4c502e106ec 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -68,8 +68,6 @@ def get_stock_value_on( frappe.qb.from_(sle) .select(IfNull(Sum(sle.stock_value_difference), 0)) .where((sle.posting_date <= posting_date) & (sle.is_cancelled == 0)) - .orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=frappe.qb.desc) - .orderby(sle.creation, order=frappe.qb.desc) ) if warehouses: From c3f5a494f32a4fff787dbd04c5d36f006bac0865 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:12:36 +0530 Subject: [PATCH 180/203] fix: batch reset while making SABB (backport #42076) (#42123) fix: batch reset while making SABB (#42076) (cherry picked from commit 8f424528ddf4e8ab198a1fa6b909a825c4a233d7) Co-authored-by: rohitwaghchaure --- .../test_serial_and_batch_bundle.py | 55 +++++++++++++++++++ erpnext/stock/serial_batch_bundle.py | 12 +++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index c313917bd4c..2913af4a724 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -646,6 +646,61 @@ class TestSerialandBatchBundle(FrappeTestCase): self.assertEqual(flt(stock_value_difference, 2), 353.0 * -1) + def test_pick_serial_nos_for_batch_item(self): + item_code = make_item( + "Test Pick Serial Nos for Batch Item 1", + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_no_series": "PSNBI-TSNVL-.#####", + "has_serial_no": 1, + "serial_no_series": "SN-PSNBI-TSNVL-.#####", + }, + ).name + + se = make_stock_entry( + item_code=item_code, + qty=10, + target="_Test Warehouse - _TC", + rate=500, + ) + + batch1 = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + serial_nos1 = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + + se = make_stock_entry( + item_code=item_code, + qty=10, + target="_Test Warehouse - _TC", + rate=500, + ) + + batch2 = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + serial_nos2 = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + + se = make_stock_entry( + item_code=item_code, + qty=10, + source="_Test Warehouse - _TC", + use_serial_batch_fields=True, + batch_no=batch2, + ) + + serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + self.assertEqual(serial_nos, serial_nos2) + + se = make_stock_entry( + item_code=item_code, + qty=10, + source="_Test Warehouse - _TC", + use_serial_batch_fields=True, + batch_no=batch1, + ) + + serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + self.assertEqual(serial_nos, serial_nos1) + def get_batch_from_bundle(bundle): from erpnext.stock.serial_batch_bundle import get_batch_nos diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 1fa5665c141..c5346d2b0a3 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -950,7 +950,17 @@ class SerialBatchCreation: if self.get("ignore_serial_nos"): kwargs["ignore_serial_nos"] = self.ignore_serial_nos - if self.has_serial_no and not self.get("serial_nos"): + if ( + self.has_serial_no + and self.has_batch_no + and not self.get("serial_nos") + and self.get("batches") + and len(self.get("batches")) == 1 + ): + # If only one batch is available and no serial no is available + kwargs["batches"] = next(iter(self.get("batches").keys())) + self.serial_nos = get_serial_nos_for_outward(kwargs) + elif self.has_serial_no and not self.get("serial_nos"): self.serial_nos = get_serial_nos_for_outward(kwargs) elif not self.has_serial_no and self.has_batch_no and not self.get("batches"): self.batches = get_available_batches(kwargs) From 97c49b93b673f5a9472d505cc981a12e5e4aa00b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:53:29 +0530 Subject: [PATCH 181/203] =?UTF-8?q?fix:=20refactor=20Asset=20Repair=20and?= =?UTF-8?q?=20Stock=20Entry=20linkage=20to=20resolve=20amendme=E2=80=A6=20?= =?UTF-8?q?(backport=20#41919)=20(#42058)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: refactor Asset Repair and Stock Entry linkage to resolve amendme… (#41919) * fix: refactor Asset Repair and Stock Entry linkage to resolve amendment issues * chore: added missing patch to patches.txt * chore: fixing previous changes * chore: fixing minor issues * fix: code changes to enhance efficiency * chore: replaced frappe.qb with db.sql because of conflict * fix: minor changes (cherry picked from commit ba79e68190b1d2c5b5e618c1beeffeec4061fc8f) # Conflicts: # erpnext/assets/doctype/asset_repair/asset_repair.json # erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json # erpnext/patches.txt # erpnext/stock/doctype/stock_entry/stock_entry.json * chore: fixed conflicts * fix: removed unmerged patches * fix: use f-string instead of format call --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- erpnext/accounts/doctype/budget/budget.py | 13 +-- erpnext/accounts/doctype/gl_entry/gl_entry.py | 4 +- .../doctype/asset_repair/asset_repair.js | 45 ++++++++-- .../doctype/asset_repair/asset_repair.json | 26 ++---- .../doctype/asset_repair/asset_repair.py | 90 +++++++++---------- .../doctype/asset_repair/test_asset_repair.py | 29 +++--- .../asset_repair_consumed_item.json | 14 ++- .../asset_repair_consumed_item.py | 3 +- erpnext/patches.txt | 2 + ...pdate_asset_repair_field_in_stock_entry.py | 15 ++++ ...d_in_asset_repair_consumed_item_doctype.py | 14 +++ .../doctype/stock_entry/stock_entry.json | 11 ++- .../stock/doctype/stock_entry/stock_entry.py | 1 + 13 files changed, 163 insertions(+), 104 deletions(-) create mode 100644 erpnext/patches/v15_0/update_asset_repair_field_in_stock_entry.py create mode 100644 erpnext/patches/v15_0/update_warehouse_field_in_asset_repair_consumed_item_doctype.py diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 2c4ed7781aa..145480138d6 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -151,9 +151,7 @@ def validate_expense_against_budget(args, expense_amount=0): "Company", args.get("company"), "exception_budget_approver_role" ) - if not frappe.get_cached_value( - "Budget", {"fiscal_year": args.fiscal_year, "company": args.company} - ): # nosec + if not frappe.get_cached_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}): # nosec return if not args.account: @@ -188,13 +186,8 @@ def validate_expense_against_budget(args, expense_amount=0): if frappe.get_cached_value("DocType", doctype, "is_tree"): lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"]) - condition = """and exists(select name from `tab%s` - where lft<=%s and rgt>=%s and name=b.%s)""" % ( - doctype, - lft, - rgt, - budget_against, - ) # nosec + condition = f"""and exists(select name from `tab{doctype}` + where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec args.is_tree = True else: condition = f"and b.{budget_against}={frappe.db.escape(args.get(budget_against))}" diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index facfbf51e00..75e1b993141 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -329,9 +329,7 @@ def update_outstanding_amt( if against_voucher_type == "Sales Invoice": party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to") - account_condition = "and account in ({0}, {1})".format( - frappe.db.escape(account), frappe.db.escape(party_account) - ) + account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})" else: account_condition = f" and account = {frappe.db.escape(account)}" diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 27a4eb6e995..489fbaca6b2 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -20,14 +20,14 @@ frappe.ui.form.on("Asset Repair", { }; }; - frm.fields_dict.warehouse.get_query = function (doc) { + frm.set_query("warehouse", "stock_items", function () { return { filters: { is_group: 0, - company: doc.company, + company: frm.doc.company, }, }; - }; + }); frm.set_query("serial_and_batch_bundle", "stock_items", (doc, cdt, cdn) => { let row = locals[cdt][cdn]; @@ -79,7 +79,7 @@ frappe.ui.form.on("Asset Repair", { }); } - if (frm.doc.repair_status == "Completed") { + if (frm.doc.repair_status == "Completed" && !frm.doc.completion_date) { frm.set_value("completion_date", frappe.datetime.now_datetime()); } }, @@ -87,15 +87,48 @@ frappe.ui.form.on("Asset Repair", { stock_items_on_form_rendered() { erpnext.setup_serial_or_batch_no(); }, + + stock_consumption: function (frm) { + if (!frm.doc.stock_consumption) { + frm.clear_table("stock_items"); + frm.refresh_field("stock_items"); + } + }, + + purchase_invoice: function (frm) { + if (frm.doc.purchase_invoice) { + frappe.call({ + method: "frappe.client.get_value", + args: { + doctype: "Purchase Invoice", + fieldname: "base_net_total", + filters: { name: frm.doc.purchase_invoice }, + }, + callback: function (r) { + if (r.message) { + frm.set_value("repair_cost", r.message.base_net_total); + } + }, + }); + } else { + frm.set_value("repair_cost", 0); + } + }, }); frappe.ui.form.on("Asset Repair Consumed Item", { - item_code: function (frm, cdt, cdn) { + warehouse: function (frm, cdt, cdn) { var item = locals[cdt][cdn]; + if (!item.item_code) { + frappe.msgprint(__("Please select an item code before setting the warehouse.")); + frappe.model.set_value(cdt, cdn, "warehouse", ""); + return; + } + let item_args = { item_code: item.item_code, - warehouse: frm.doc.warehouse, + warehouse: item.warehouse, qty: item.consumed_quantity, serial_no: item.serial_no, company: frm.doc.company, diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index accb5bf54b5..c98f5a8d7f4 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -22,16 +22,14 @@ "column_break_14", "project", "accounting_details", - "repair_cost", + "purchase_invoice", "capitalize_repair_cost", "stock_consumption", "column_break_8", - "purchase_invoice", + "repair_cost", "stock_consumption_details_section", - "warehouse", "stock_items", "total_repair_cost", - "stock_entry", "asset_depreciation_details_section", "increase_in_asset_life", "section_break_9", @@ -122,7 +120,8 @@ "default": "0", "fieldname": "repair_cost", "fieldtype": "Currency", - "label": "Repair Cost" + "label": "Repair Cost", + "read_only": 1 }, { "fieldname": "amended_from", @@ -218,13 +217,6 @@ "label": "Total Repair Cost", "read_only": 1 }, - { - "depends_on": "stock_consumption", - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Warehouse", - "options": "Warehouse" - }, { "depends_on": "capitalize_repair_cost", "fieldname": "asset_depreciation_details_section", @@ -251,20 +243,12 @@ "fieldtype": "Link", "label": "Company", "options": "Company" - }, - { - "fieldname": "stock_entry", - "fieldtype": "Link", - "label": "Stock Entry", - "no_copy": 1, - "options": "Stock Entry", - "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-08-16 15:55:25.023471", + "modified": "2024-06-13 16:14:14.398356", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index ccde836fe0d..903e68e32e0 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -47,20 +47,25 @@ class AssetRepair(AccountsController): repair_cost: DF.Currency repair_status: DF.Literal["Pending", "Completed", "Cancelled"] stock_consumption: DF.Check - stock_entry: DF.Link | None stock_items: DF.Table[AssetRepairConsumedItem] total_repair_cost: DF.Currency - warehouse: DF.Link | None # end: auto-generated types def validate(self): self.asset_doc = frappe.get_doc("Asset", self.asset) + self.validate_dates() self.update_status() if self.get("stock_items"): self.set_stock_items_cost() self.calculate_total_repair_cost() + def validate_dates(self): + if self.completion_date and (self.failure_date > self.completion_date): + frappe.throw( + _("Completion Date can not be before Failure Date. Please adjust the dates accordingly.") + ) + def update_status(self): if self.repair_status == "Pending" and self.asset_doc.status != "Out of Order": frappe.db.set_value("Asset", self.asset, "status", "Out of Order") @@ -105,22 +110,22 @@ class AssetRepair(AccountsController): if self.asset_doc.calculate_depreciation and self.increase_in_asset_life: self.modify_depreciation_schedule() - notes = _( - "This schedule was created when Asset {0} was repaired through Asset Repair {1}." - ).format( - get_link_to_form(self.asset_doc.doctype, self.asset_doc.name), - get_link_to_form(self.doctype, self.name), - ) - self.asset_doc.flags.ignore_validate_update_after_submit = True - make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes) - self.asset_doc.save() + notes = _( + "This schedule was created when Asset {0} was repaired through Asset Repair {1}." + ).format( + get_link_to_form(self.asset_doc.doctype, self.asset_doc.name), + get_link_to_form(self.doctype, self.name), + ) + self.asset_doc.flags.ignore_validate_update_after_submit = True + make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes) + self.asset_doc.save() - add_asset_activity( - self.asset, - _("Asset updated after completion of Asset Repair {0}").format( - get_link_to_form("Asset Repair", self.name) - ), - ) + add_asset_activity( + self.asset, + _("Asset updated after completion of Asset Repair {0}").format( + get_link_to_form("Asset Repair", self.name) + ), + ) def before_cancel(self): self.asset_doc = frappe.get_doc("Asset", self.asset) @@ -136,29 +141,28 @@ class AssetRepair(AccountsController): self.asset_doc.total_asset_cost -= self.repair_cost self.asset_doc.additional_asset_cost -= self.repair_cost - if self.get("stock_consumption"): - self.increase_stock_quantity() if self.get("capitalize_repair_cost"): self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.make_gl_entries(cancel=True) - self.db_set("stock_entry", None) if self.asset_doc.calculate_depreciation and self.increase_in_asset_life: self.revert_depreciation_schedule_on_cancellation() - notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format( - get_link_to_form(self.asset_doc.doctype, self.asset_doc.name), - get_link_to_form(self.doctype, self.name), - ) - self.asset_doc.flags.ignore_validate_update_after_submit = True - make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes) - self.asset_doc.save() + notes = _( + "This schedule was created when Asset {0}'s Asset Repair {1} was cancelled." + ).format( + get_link_to_form(self.asset_doc.doctype, self.asset_doc.name), + get_link_to_form(self.doctype, self.name), + ) + self.asset_doc.flags.ignore_validate_update_after_submit = True + make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes) + self.asset_doc.save() - add_asset_activity( - self.asset, - _("Asset updated after cancellation of Asset Repair {0}").format( - get_link_to_form("Asset Repair", self.name) - ), - ) + add_asset_activity( + self.asset, + _("Asset updated after cancellation of Asset Repair {0}").format( + get_link_to_form("Asset Repair", self.name) + ), + ) def after_delete(self): frappe.get_doc("Asset", self.asset).set_status() @@ -170,11 +174,6 @@ class AssetRepair(AccountsController): def check_for_stock_items_and_warehouse(self): if not self.get("stock_items"): frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items")) - if not self.warehouse: - frappe.throw( - _("Please enter Warehouse from which Stock Items consumed during the Repair were taken."), - title=_("Missing Warehouse"), - ) def increase_asset_value(self): total_value_of_stock_consumed = self.get_total_value_of_stock_consumed() @@ -208,6 +207,7 @@ class AssetRepair(AccountsController): stock_entry = frappe.get_doc( {"doctype": "Stock Entry", "stock_entry_type": "Material Issue", "company": self.company} ) + stock_entry.asset_repair = self.name for stock_item in self.get("stock_items"): self.validate_serial_no(stock_item) @@ -215,7 +215,7 @@ class AssetRepair(AccountsController): stock_entry.append( "items", { - "s_warehouse": self.warehouse, + "s_warehouse": stock_item.warehouse, "item_code": stock_item.item_code, "qty": stock_item.consumed_quantity, "basic_rate": stock_item.valuation_rate, @@ -228,8 +228,6 @@ class AssetRepair(AccountsController): stock_entry.insert() stock_entry.submit() - self.db_set("stock_entry", stock_entry.name) - def validate_serial_no(self, stock_item): if not stock_item.serial_and_batch_bundle and frappe.get_cached_value( "Item", stock_item.item_code, "has_serial_no" @@ -247,12 +245,6 @@ class AssetRepair(AccountsController): "Serial and Batch Bundle", stock_item.serial_and_batch_bundle, values_to_update ) - def increase_stock_quantity(self): - if self.stock_entry: - stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) - stock_entry.flags.ignore_links = True - stock_entry.cancel() - def make_gl_entries(self, cancel=False): if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries() @@ -316,7 +308,7 @@ class AssetRepair(AccountsController): return # creating GL Entries for each row in Stock Items based on the Stock Entry created for it - stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) + stock_entry = frappe.get_doc("Stock Entry", {"asset_repair": self.name}) default_expense_account = None if not erpnext.is_perpetual_inventory_enabled(self.company): @@ -357,7 +349,7 @@ class AssetRepair(AccountsController): "cost_center": self.cost_center, "posting_date": getdate(), "against_voucher_type": "Stock Entry", - "against_voucher": self.stock_entry, + "against_voucher": stock_entry.name, "company": self.company, }, item=self, diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 278da1b08bf..44d08869a63 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -76,14 +76,14 @@ class TestAssetRepair(unittest.TestCase): def test_warehouse(self): asset_repair = create_asset_repair(stock_consumption=1) self.assertTrue(asset_repair.stock_consumption) - self.assertTrue(asset_repair.warehouse) + self.assertTrue(asset_repair.stock_items[0].warehouse) def test_decrease_stock_quantity(self): asset_repair = create_asset_repair(stock_consumption=1, submit=1) stock_entry = frappe.get_last_doc("Stock Entry") self.assertEqual(stock_entry.stock_entry_type, "Material Issue") - self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse) + self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.stock_items[0].warehouse) self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code) self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) @@ -114,14 +114,14 @@ class TestAssetRepair(unittest.TestCase): asset_repair.repair_status = "Completed" self.assertRaises(frappe.ValidationError, asset_repair.submit) - def test_increase_in_asset_value_due_to_stock_consumption(self): + def test_no_increase_in_asset_value_when_not_capitalized(self): asset = create_asset(calculate_depreciation=1, submit=1) initial_asset_value = get_asset_value_after_depreciation(asset.name) - asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1) + create_asset_repair(asset=asset, stock_consumption=1, submit=1) asset.reload() increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value - self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value) + self.assertEqual(increase_in_asset_value, 0) def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self): asset = create_asset(calculate_depreciation=1, submit=1) @@ -185,7 +185,7 @@ class TestAssetRepair(unittest.TestCase): frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account ) stock_entry_expense_account = ( - frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account + frappe.get_doc("Stock Entry", {"asset_repair": asset_repair.name}).get("items")[0].expense_account ) expected_values = { @@ -260,6 +260,12 @@ class TestAssetRepair(unittest.TestCase): asset.finance_books[0].value_after_depreciation, ) + def test_asset_repiar_link_in_stock_entry(self): + asset = create_asset(calculate_depreciation=1, submit=1) + asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1) + stock_entry = frappe.get_last_doc("Stock Entry") + self.assertEqual(stock_entry.asset_repair, asset_repair.name) + def num_of_depreciations(asset): return asset.finance_books[0].total_number_of_depreciations @@ -289,7 +295,7 @@ def create_asset_repair(**args): if args.stock_consumption: asset_repair.stock_consumption = 1 - asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company=asset.company) + warehouse = args.warehouse or create_warehouse("Test Warehouse", company=asset.company) bundle = None if args.serial_no: @@ -297,8 +303,8 @@ def create_asset_repair(**args): frappe._dict( { "item_code": args.item_code, - "warehouse": asset_repair.warehouse, - "company": frappe.get_cached_value("Warehouse", asset_repair.warehouse, "company"), + "warehouse": warehouse, + "company": frappe.get_cached_value("Warehouse", warehouse, "company"), "qty": (flt(args.stock_qty) or 1) * -1, "voucher_type": "Asset Repair", "type_of_transaction": "Asset Repair", @@ -314,6 +320,7 @@ def create_asset_repair(**args): "stock_items", { "item_code": args.item_code or "_Test Stock Item", + "warehouse": warehouse, "valuation_rate": args.rate if args.get("rate") is not None else 100, "consumed_quantity": args.qty or 1, "serial_and_batch_bundle": bundle, @@ -333,7 +340,7 @@ def create_asset_repair(**args): stock_entry.append( "items", { - "t_warehouse": asset_repair.warehouse, + "t_warehouse": asset_repair.stock_items[0].warehouse, "item_code": asset_repair.stock_items[0].item_code, "qty": asset_repair.stock_items[0].consumed_quantity, "basic_rate": args.rate if args.get("rate") is not None else 100, @@ -351,7 +358,7 @@ def create_asset_repair(**args): company=asset.company, expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"), cost_center=asset_repair.cost_center, - warehouse=asset_repair.warehouse, + warehouse=args.warehouse or create_warehouse("Test Warehouse", company=asset.company), ) asset_repair.purchase_invoice = pi.name diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json index 6910c2eebf6..c4c13ce413b 100644 --- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json +++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "item_code", + "warehouse", "valuation_rate", "consumed_quantity", "total_value", @@ -44,19 +45,28 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Item", - "options": "Item" + "options": "Item", + "reqd": 1 }, { "fieldname": "serial_and_batch_bundle", "fieldtype": "Link", "label": "Serial and Batch Bundle", "options": "Serial and Batch Bundle" + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Warehouse", + "options": "Warehouse", + "reqd": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-04-06 02:24:20.375870", + "modified": "2024-06-13 12:01:47.147333", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair Consumed Item", diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py index ab43cfe62aa..4d41397a0b1 100644 --- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py +++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py @@ -15,7 +15,7 @@ class AssetRepairConsumedItem(Document): from frappe.types import DF consumed_quantity: DF.Data | None - item_code: DF.Link | None + item_code: DF.Link parent: DF.Data parentfield: DF.Data parenttype: DF.Data @@ -23,6 +23,7 @@ class AssetRepairConsumedItem(Document): serial_no: DF.SmallText | None total_value: DF.Currency valuation_rate: DF.Currency + warehouse: DF.Link # end: auto-generated types pass diff --git a/erpnext/patches.txt b/erpnext/patches.txt index dba491a728d..66e98dde5b4 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -366,4 +366,6 @@ erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v15_0.rename_number_of_depreciations_booked_to_opening_booked_depreciations +erpnext.patches.v15_0.update_warehouse_field_in_asset_repair_consumed_item_doctype +erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry erpnext.patches.v15_0.update_total_number_of_booked_depreciations diff --git a/erpnext/patches/v15_0/update_asset_repair_field_in_stock_entry.py b/erpnext/patches/v15_0/update_asset_repair_field_in_stock_entry.py new file mode 100644 index 00000000000..cc0668d9caa --- /dev/null +++ b/erpnext/patches/v15_0/update_asset_repair_field_in_stock_entry.py @@ -0,0 +1,15 @@ +import frappe +from frappe.query_builder import DocType + + +def execute(): + if frappe.db.has_column("Asset Repair", "stock_entry"): + AssetRepair = DocType("Asset Repair") + StockEntry = DocType("Stock Entry") + + ( + frappe.qb.update(StockEntry) + .join(AssetRepair) + .on(StockEntry.name == AssetRepair.stock_entry) + .set(StockEntry.asset_repair, AssetRepair.name) + ).run() diff --git a/erpnext/patches/v15_0/update_warehouse_field_in_asset_repair_consumed_item_doctype.py b/erpnext/patches/v15_0/update_warehouse_field_in_asset_repair_consumed_item_doctype.py new file mode 100644 index 00000000000..e0291826930 --- /dev/null +++ b/erpnext/patches/v15_0/update_warehouse_field_in_asset_repair_consumed_item_doctype.py @@ -0,0 +1,14 @@ +import frappe + + +# not able to use frappe.qb because of this bug https://github.com/frappe/frappe/issues/20292 +def execute(): + if frappe.db.has_column("Asset Repair", "warehouse"): + # nosemgrep + frappe.db.sql( + """UPDATE `tabAsset Repair Consumed Item` ar_item + JOIN `tabAsset Repair` ar + ON ar.name = ar_item.parent + SET ar_item.warehouse = ar.warehouse + WHERE ifnull(ar.warehouse, '') != ''""" + ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index d45296f1310..a090b37033b 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -20,6 +20,7 @@ "sales_invoice_no", "pick_list", "purchase_receipt_no", + "asset_repair", "col2", "company", "posting_date", @@ -674,6 +675,14 @@ { "fieldname": "column_break_eaoa", "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.asset_repair", + "fieldname": "asset_repair", + "fieldtype": "Link", + "label": "Asset Repair", + "options": "Asset Repair", + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -681,7 +690,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-01-12 11:56:58.644882", + "modified": "2024-06-26 19:12:17.937088", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 005ad287d78..f08ae6c286c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -98,6 +98,7 @@ class StockEntry(StockController): address_display: DF.SmallText | None amended_from: DF.Link | None apply_putaway_rule: DF.Check + asset_repair: DF.Link | None bom_no: DF.Link | None company: DF.Link credit_note: DF.Link | None From 58e18e2b1fa7d8e9e6fb43282be777af711ac865 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:54:14 +0530 Subject: [PATCH 182/203] fix: resolve gl entries duplication in asset purchase workflow (backport #41845) (#42120) * fix: resolve gl entries duplication in asset purchase workflow (#41845) * fix: resolve gl entries duplication in asset purchase workflow * fix: prevent duplicate entry when creating purchase receipt from purchase invoice * chore: test case added * fix: fixed missing asset category issue (cherry picked from commit 55a4bd469b17f8c8abe5e77d7c7f5413cc09c5bc) * fix: use f-string instead of format call --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../purchase_invoice/purchase_invoice.py | 15 +++++ .../purchase_invoice/test_purchase_invoice.py | 56 ++++++++++++++++++- .../purchase_receipt/purchase_receipt.py | 2 +- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e519f069aa4..2938b2a5309 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -549,6 +549,21 @@ class PurchaseInvoice(BuyingController): item.expense_account = stock_not_billed_account elif item.is_fixed_asset: account = None + if not item.pr_detail and item.po_detail: + receipt_item = frappe.get_cached_value( + "Purchase Receipt Item", + { + "purchase_order": item.purchase_order, + "purchase_order_item": item.po_detail, + "docstatus": 1, + }, + ["name", "parent"], + as_dict=1, + ) + if receipt_item: + item.pr_detail = receipt_item.name + item.purchase_receipt = receipt_item.parent + if item.pr_detail: if not self.asset_received_but_not_billed: self.asset_received_but_not_billed = self.get_company_default( diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 3c3b081fa2f..4980c22fac1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -10,7 +10,11 @@ import erpnext from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice -from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice as make_pi_from_po +from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_pr_against_po, + create_purchase_order, +) from erpnext.buying.doctype.supplier.test_supplier import create_supplier from erpnext.controllers.accounts_controller import get_payment_terms from erpnext.controllers.buying_controller import QtyMismatchError @@ -2185,6 +2189,56 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self.assertEqual(row.serial_no, "\n".join(serial_nos[:2])) self.assertEqual(row.rejected_serial_no, serial_nos[2]) + def test_make_pr_and_pi_from_po(self): + from erpnext.assets.doctype.asset.test_asset import create_asset_category + + if not frappe.db.exists("Asset Category", "Computers"): + create_asset_category() + + item = create_item( + item_code="_Test_Item", is_stock_item=0, is_fixed_asset=1, asset_category="Computers" + ) + po = create_purchase_order(item_code=item.item_code) + pr = create_pr_against_po(po.name, 10) + pi = make_pi_from_po(po.name) + pi.insert() + pi.submit() + + pr_gl_entries = frappe.db.sql( + """select account, debit, credit + from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s + order by account asc""", + pr.name, + as_dict=1, + ) + + pr_expected_values = [ + ["Asset Received But Not Billed - _TC", 0, 5000], + ["CWIP Account - _TC", 5000, 0], + ] + + for i, gle in enumerate(pr_gl_entries): + self.assertEqual(pr_expected_values[i][0], gle.account) + self.assertEqual(pr_expected_values[i][1], gle.debit) + self.assertEqual(pr_expected_values[i][2], gle.credit) + + pi_gl_entries = frappe.db.sql( + """select account, debit, credit + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", + pi.name, + as_dict=1, + ) + pi_expected_values = [ + ["Asset Received But Not Billed - _TC", 5000, 0], + ["Creditors - _TC", 0, 5000], + ] + + for i, gle in enumerate(pi_gl_entries): + self.assertEqual(pi_expected_values[i][0], gle.account) + self.assertEqual(pi_expected_values[i][1], gle.debit) + self.assertEqual(pi_expected_values[i][2], gle.credit) + def set_advance_flag(company, flag, default_account): frappe.db.set_value( diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6a00fed5588..3136cdffeff 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -661,7 +661,7 @@ class PurchaseReceipt(BuyingController): if not ( (erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items) - or d.is_fixed_asset + or (d.is_fixed_asset and not d.purchase_invoice) ): continue From 317cc0358c0e3cb07da0d72657dddeb9ef7ee546 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:55:49 +0530 Subject: [PATCH 183/203] fix: add auto-update for overdue status (#42105) * fix: auto-update for overdue status * chore: use qb.update (cherry picked from commit c5e474f4f52019d431da6b3ed6b3ad4368a0f3f1) --- .../doctype/asset_maintenance/asset_maintenance.py | 9 ++++++--- .../asset_maintenance_log/asset_maintenance_log.py | 14 +++++++++++++- erpnext/hooks.py | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index 570c9751a57..b44164f2dae 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -18,9 +18,7 @@ class AssetMaintenance(Document): if TYPE_CHECKING: from frappe.types import DF - from erpnext.assets.doctype.asset_maintenance_task.asset_maintenance_task import ( - AssetMaintenanceTask, - ) + from erpnext.assets.doctype.asset_maintenance_task.asset_maintenance_task import AssetMaintenanceTask asset_category: DF.ReadOnly | None asset_maintenance_tasks: DF.Table[AssetMaintenanceTask] @@ -47,6 +45,11 @@ class AssetMaintenance(Document): assign_tasks(self.name, task.assign_to, task.maintenance_task, task.next_due_date) self.sync_maintenance_tasks() + def after_delete(self): + asset = frappe.get_doc("Asset", self.asset_name) + if asset.status == "In Maintenance": + asset.set_status() + def sync_maintenance_tasks(self): tasks_names = [] for task in self.get("asset_maintenance_tasks"): diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py index 009bcc3e69a..95d02714c5b 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py @@ -5,7 +5,8 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import getdate, nowdate +from frappe.query_builder import DocType +from frappe.utils import getdate, nowdate, today from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate_next_due_date @@ -75,6 +76,17 @@ class AssetMaintenanceLog(Document): asset_maintenance_doc.save() +def update_asset_maintenance_log_status(): + AssetMaintenanceLog = DocType("Asset Maintenance Log") + ( + frappe.qb.update(AssetMaintenanceLog) + .set(AssetMaintenanceLog.maintenance_status, "Overdue") + .where( + (AssetMaintenanceLog.maintenance_status == "Planned") & (AssetMaintenanceLog.due_date < today()) + ) + ).run() + + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 64cbc297d20..a080af9a827 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -442,6 +442,7 @@ scheduler_events = { "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily", "erpnext.accounts.utils.run_ledger_health_checks", + "erpnext.assets.doctype.asset.asset_maintenance_log.update_asset_maintenance_log_status", ], "weekly": [ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly", From 7fcb0f578ae7456c71313f1240d3a6e855d8f86e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 15:49:48 +0530 Subject: [PATCH 184/203] fix: Re-open allows SO's to be over credit limit (cherry picked from commit 5eed78126374843d90328198cf5919dad8358018) --- erpnext/selling/doctype/sales_order/sales_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index af67f07a360..a00b08ab097 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -496,6 +496,10 @@ class SalesOrder(SellingController): def update_status(self, status): self.check_modified_date() self.set_status(update=True, status=status) + # Upon Sales Order Re-open, check for credit limit. + # Limit should be checked after the 'Hold/Closed' status is reset. + if status == "Draft" and self.docstatus == 1: + self.check_credit_limit() self.update_reserved_qty() self.notify_update() clear_doctype_notifications(self) From b63eab8cbb41a3261080af844e9aa289fc067209 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 16:43:16 +0530 Subject: [PATCH 185/203] test: credit check on Sales Order re-open (cherry picked from commit 60694e09c4feefefb98f52e542a9193c9ec2f0d0) # Conflicts: # erpnext/selling/doctype/sales_order/test_sales_order.py --- .../doctype/sales_order/test_sales_order.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index bd51543b36e..c57af93b953 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -9,7 +9,12 @@ from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today +<<<<<<< HEAD from erpnext.controllers.accounts_controller import update_child_qty_rate +======= +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate +>>>>>>> 60694e09c4 (test: credit check on Sales Order re-open) from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( make_maintenance_schedule, ) @@ -31,7 +36,7 @@ from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -class TestSalesOrder(FrappeTestCase): +class TestSalesOrder(AccountsTestMixin, FrappeTestCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -49,6 +54,9 @@ class TestSalesOrder(FrappeTestCase): ) super().tearDownClass() + def setUp(self): + self.create_customer("_Test Customer Credit") + def tearDown(self): frappe.set_user("Administrator") @@ -2086,6 +2094,28 @@ class TestSalesOrder(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0) frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) + def test_credit_limit_on_so_reopning(self): + # set credit limit + company = "_Test Company" + customer = frappe.get_doc("Customer", self.customer) + customer.credit_limits = [] + customer.append( + "credit_limits", {"company": company, "credit_limit": 1000, "bypass_credit_limit_check": False} + ) + customer.save() + + so1 = make_sales_order(qty=9, rate=100, do_not_submit=True) + so1.customer = self.customer + so1.save().submit() + + so1.update_status("Closed") + + so2 = make_sales_order(qty=9, rate=100, do_not_submit=True) + so2.customer = self.customer + so2.save().submit() + + self.assertRaises(frappe.ValidationError, so1.update_status, "Draft") + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") From bff99d89b9922ce2676bb369cb00db286c6897ce Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 17:05:22 +0530 Subject: [PATCH 186/203] chore: resolve conflict --- erpnext/selling/doctype/sales_order/test_sales_order.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index c57af93b953..53c629a90b4 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -9,12 +9,8 @@ from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today -<<<<<<< HEAD -from erpnext.controllers.accounts_controller import update_child_qty_rate -======= from erpnext.accounts.test.accounts_mixin import AccountsTestMixin -from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate ->>>>>>> 60694e09c4 (test: credit check on Sales Order re-open) +from erpnext.controllers.accounts_controller import update_child_qty_rate from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( make_maintenance_schedule, ) From 3b4d39766f78492bd2ba92dc6c6c5b91263d3e6d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:18:58 +0530 Subject: [PATCH 187/203] fix: this.frm.events.update_cost is not a function (backport #41960) (#41965) * fix: this.frm.events.update_cost is not a function (#41960) (cherry picked from commit d5ed4582c360784863caa223ac74b1bed769032a) # Conflicts: # erpnext/manufacturing/doctype/workstation/workstation.json # erpnext/manufacturing/doctype/workstation/workstation.py * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- .../doctype/bom_creator/bom_creator.js | 1 - .../doctype/workstation/workstation.json | 16 ++-------------- .../doctype/workstation/workstation.py | 3 +++ 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js index 32231aa4949..34d0fc7131b 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js @@ -212,7 +212,6 @@ erpnext.bom.BomConfigurator = class BomConfigurator extends erpnext.TransactionC item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item)); refresh_field("stock_qty", item.name, item.parentfield); this.toggle_conversion_factor(item); - this.frm.events.update_cost(this.frm); } } }; diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index 5912714052b..4758e5d3588 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -17,8 +17,6 @@ "column_break_3", "production_capacity", "warehouse", - "production_capacity_section", - "parts_per_hour", "workstation_status_tab", "status", "column_break_glcv", @@ -210,16 +208,6 @@ "label": "Warehouse", "options": "Warehouse" }, - { - "fieldname": "production_capacity_section", - "fieldtype": "Section Break", - "label": "Production Capacity" - }, - { - "fieldname": "parts_per_hour", - "fieldtype": "Float", - "label": "Parts Per Hour" - }, { "fieldname": "total_working_hours", "fieldtype": "Float", @@ -252,7 +240,7 @@ "idx": 1, "image_field": "on_status_image", "links": [], - "modified": "2023-11-30 12:43:35.808845", + "modified": "2024-06-20 14:17:13.806609", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation", @@ -277,4 +265,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index 0966d26b781..326a8b37efc 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -55,6 +55,9 @@ class Workstation(Document): hour_rate_electricity: DF.Currency hour_rate_labour: DF.Currency hour_rate_rent: DF.Currency + off_status_image: DF.AttachImage | None + on_status_image: DF.AttachImage | None + plant_floor: DF.Link | None production_capacity: DF.Int working_hours: DF.Table[WorkstationWorkingHour] workstation_name: DF.Data From d9e62fef2121efb39f9a2ef220e92bae98bf848d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:12:58 +0530 Subject: [PATCH 188/203] fix: stock qty validation in SCR (backport #42124) (#42133) fix: stock qty validation in SCR (#42124) (cherry picked from commit 99f2735ad3440aa3690bb514b32d7d9610753041) Co-authored-by: rohitwaghchaure --- .../subcontracting_receipt.py | 6 ++++++ .../test_subcontracting_receipt.py | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 5e717e1f22a..48203167187 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -426,6 +426,12 @@ class SubcontractingReceipt(SubcontractingController): ) def validate_available_qty_for_consumption(self): + if ( + frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") + == "BOM" + ): + return + for item in self.get("supplied_items"): precision = item.precision("consumed_qty") if ( diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 0f5fe7ab958..8ff5c8f27b0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -81,6 +81,7 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost)) def test_available_qty_for_consumption(self): + set_backflush_based_on("BOM") make_stock_entry(item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100) make_stock_entry( item_code="_Test Item Home Desktop 100", @@ -125,7 +126,7 @@ class TestSubcontractingReceipt(FrappeTestCase): ) scr = make_subcontracting_receipt(sco.name) scr.save() - self.assertRaises(frappe.ValidationError, scr.submit) + scr.submit() def test_subcontracting_gle_fg_item_rate_zero(self): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries @@ -476,6 +477,21 @@ class TestSubcontractingReceipt(FrappeTestCase): # consumed_qty should be (accepted_qty * qty_consumed_per_unit) = (6 * 1) = 6 self.assertEqual(scr.supplied_items[0].consumed_qty, 6) + # Do not transfer materials to the supplier warehouse and check whether system allows to consumed directly from the supplier's warehouse + sco = get_subcontracting_order(service_items=service_items) + + # Transfer RM's + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items, warehouse="_Test Warehouse 1 - _TC") + + # Create Subcontracting Receipt + scr = make_subcontracting_receipt(sco.name) + scr.submit() + self.assertEqual(scr.docstatus, 1) + + for item in scr.supplied_items: + self.assertFalse(item.available_qty_for_consumption) + def test_supplied_items_cost_after_reposting(self): # Set Backflush Based On as "BOM" set_backflush_based_on("BOM") From a45f8ca5fd7403e61f4c836dc28ea7742c24441e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:51:06 +0530 Subject: [PATCH 189/203] fix: batch picking in pick list based on Stock Settings (backport #42021) (#42134) fix: batch picking in pick list based on Stock Settings (#42021) (cherry picked from commit 97c9941143ebf29a06240516b103b5b171d642ab) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list/pick_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 69a1bdf17d8..6dd53f290c9 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -963,6 +963,7 @@ def get_available_item_locations_for_batched_item( { "item_code": item_code, "warehouse": from_warehouses, + "based_on": frappe.db.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"), } ) ) From d61dab856942ff94092c90963dedef71fdf6dfed Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Jul 2024 14:37:44 +0530 Subject: [PATCH 190/203] fix: provisional entry for non stock items --- .../accounts/doctype/gl_entry/gl_entry.json | 9 ++-- erpnext/accounts/doctype/gl_entry/gl_entry.py | 2 - .../purchase_invoice/purchase_invoice.py | 50 +++++++++---------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 0800971269b..2d106ad8cee 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -179,7 +179,8 @@ "fieldname": "voucher_detail_no", "fieldtype": "Data", "label": "Voucher Detail No", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "project", @@ -290,7 +291,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-12-18 15:38:14.006208", + "modified": "2024-07-02 14:31:51.496466", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", @@ -322,7 +323,7 @@ ], "quick_entry": 1, "search_fields": "voucher_no,account,posting_date,against_voucher", - "sort_field": "modified", + "sort_field": "creation", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 75e1b993141..d74224c4aa2 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -32,8 +32,6 @@ class GLEntry(Document): account: DF.Link | None account_currency: DF.Link | None against: DF.Text | None - against_link: DF.DynamicLink | None - against_type: DF.Link | None against_voucher: DF.DynamicLink | None against_voucher_type: DF.Link | None company: DF.Link | None diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2938b2a5309..0f64c9d697f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -972,7 +972,6 @@ class PurchaseInvoice(BuyingController): "Company", self.company, "enable_provisional_accounting_for_non_stock_items" ) ) - self.provisional_enpenses_booked_in_pr = False if provisional_accounting_for_non_stock_items: self.get_provisional_accounts() @@ -1218,37 +1217,38 @@ class PurchaseInvoice(BuyingController): fields=["name", "provisional_expense_account", "qty", "base_rate"], ) default_provisional_account = self.get_company_default("default_provisional_account") + provisional_accounts = set( + [ + d.provisional_expense_account + if d.provisional_expense_account + else default_provisional_account + for d in pr_items + ] + ) + + provisional_gl_entries = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": "Purchase Receipt", + "voucher_no": ("in", linked_purchase_receipts), + "account": ("in", provisional_accounts), + "is_cancelled": 0, + }, + fields=["voucher_detail_no"], + ) + rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries] for item in pr_items: self.provisional_accounts[item.name] = { "provisional_account": item.provisional_expense_account or default_provisional_account, "qty": item.qty, "base_rate": item.base_rate, + "has_provisional_entry": item.name in rows_with_provisional_entries, } def make_provisional_gl_entry(self, gl_entries, item): if item.purchase_receipt: - if not self.provisional_enpenses_booked_in_pr: - pr_item = self.provisional_accounts.get(item.pr_detail, {}) - provisional_account = pr_item.get("provisional_account") - pr_qty = pr_item.get("qty") - pr_base_rate = pr_item.get("base_rate") - - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - provision_gle_against_pr = frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "voucher_type": "Purchase Receipt", - "voucher_no": item.purchase_receipt, - "voucher_detail_no": item.pr_detail, - "account": provisional_account, - }, - ["name"], - ) - if provision_gle_against_pr: - self.provisional_enpenses_booked_in_pr = True - - if self.provisional_enpenses_booked_in_pr: + pr_item = self.provisional_accounts.get(item.pr_detail, {}) + if pr_item.get("has_provisional_entry"): purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt) # Intentionally passing purchase invoice item to handle partial billing @@ -1256,9 +1256,9 @@ class PurchaseInvoice(BuyingController): item, gl_entries, self.posting_date, - provisional_account, + pr_item.get("provisional_account"), reverse=1, - item_amount=(min(item.qty, pr_qty) * pr_base_rate), + item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")), ) def update_gross_purchase_amount_for_linked_assets(self, item): From b0aef9e42b34dfe6b8b0002647b8ba966a735a9a Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:10:47 +0530 Subject: [PATCH 191/203] fix: handle none type object error (cherry picked from commit 6760c9c4e2aff70f3494aeb9e5b52d66128ab04e) --- .../v15_0/update_total_number_of_booked_depreciations.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py index 4b556c2b512..82f1c88903d 100644 --- a/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py +++ b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py @@ -18,9 +18,10 @@ def execute(): depr_schedule = get_depr_schedule(asset.name, "Active", fb_row.finance_book) total_number_of_booked_depreciations = asset.opening_number_of_booked_depreciations or 0 - for je in depr_schedule: - if je.journal_entry: - total_number_of_booked_depreciations += 1 + if depr_schedule: + for je in depr_schedule: + if je.journal_entry: + total_number_of_booked_depreciations += 1 frappe.db.set_value( "Asset Finance Book", fb_row.name, From 706a6c1ad7c7850974d7dc7b6a0ff162ef4c1a28 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Jun 2024 20:28:01 +0530 Subject: [PATCH 192/203] fix: always post to tax account heads if LCV is booked (cherry picked from commit 0fcd5d51309ed3fd2dfcc16d44d31489990848d1) --- .../stock/doctype/purchase_receipt/purchase_receipt.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 3136cdffeff..a8cb7fd83b4 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -776,6 +776,13 @@ class PurchaseReceipt(BuyingController): posting_date=posting_date, ) + def is_landed_cost_booked_for_any_item(self) -> bool: + for x in self.items: + if x.landed_cost_voucher_amount != 0: + return True + + return False + def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) @@ -811,7 +818,7 @@ class PurchaseReceipt(BuyingController): i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): - if via_landed_cost_voucher: + if via_landed_cost_voucher or self.is_landed_cost_booked_for_any_item(): account = tax.account_head else: negative_expense_booked_in_pi = frappe.db.sql( From b7cbafae140918087ca43ac9d9427669fb52a849 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 17:50:36 +0530 Subject: [PATCH 193/203] test: Repost should not merge expense accounts from LCV (cherry picked from commit fa56555150a36d2880f0d409c4924ca7bbab121d) --- .../purchase_receipt/test_purchase_receipt.py | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 16553749d0a..6ddf756a7a2 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3020,6 +3020,142 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(pr_return.items[0].rejected_qty, 0.0) self.assertEqual(pr_return.items[0].rejected_warehouse, "") + def test_tax_account_heads_on_lcv_and_item_repost(self): + """ + PO -> PR -> PI + PR -> LCV + Backdated `Repost Item valuation` should not merge tax account heads into stock_rbnb + """ + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_purchase_order, + make_pr_against_po, + ) + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice + + stock_rbnb = "Stock Received But Not Billed - _TC" + stock_in_hand = "Stock In Hand - _TC" + test_cc = "_Test Cost Center - _TC" + test_company = "_Test Company" + creditors = "Creditors - _TC" + lcv_expense_account = "Expenses Included In Valuation - _TC" + + company_doc = frappe.get_doc("Company", test_company) + company_doc.enable_perpetual_inventory = True + company_doc.stock_received_but_not_billed = stock_rbnb + company_doc.save() + + packaging_charges_account = create_account( + account_name="Packaging Charges", + parent_account="Indirect Expenses - _TC", + company=test_company, + account_type="Tax", + ) + + po = create_purchase_order(qty=10, rate=100, do_not_save=1) + po.taxes = [] + po.append( + "taxes", + { + "category": "Valuation and Total", + "account_head": packaging_charges_account, + "cost_center": test_cc, + "description": "Test", + "add_deduct_tax": "Add", + "charge_type": "Actual", + "tax_amount": 250, + }, + ) + po.save().submit() + + pr = make_pr_against_po(po.name, received_qty=10) + pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1250.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + ] + self.assertEqual(expected_pr_gles, pr_gl_entries) + + # Make PI against Purchase Receipt + pi = make_purchase_invoice(pr.name).save().submit() + pi_gl_entries = get_gl_entries(pi.doctype, pi.name, skip_cancelled=True) + expected_pi_gles = [ + {"account": stock_rbnb, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 250.0, "credit": 0.0, "cost_center": test_cc}, + {"account": creditors, "debit": 0.0, "credit": 1250.0, "cost_center": None}, + ] + self.assertEqual(expected_pi_gles, pi_gl_entries) + + self.create_lcv(pr.doctype, pr.name, test_company, lcv_expense_account) + pr_gles_after_lcv = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles_after_lcv = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1300.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + {"account": lcv_expense_account, "debit": 0.0, "credit": 50.0, "cost_center": test_cc}, + ] + self.assertEqual(expected_pr_gles_after_lcv, pr_gles_after_lcv) + + # Trigger Repost Item Valudation on a older date + repost_doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "based_on": "Item and Warehouse", + "item_code": pr.items[0].item_code, + "warehouse": pr.items[0].warehouse, + "posting_date": add_days(pr.posting_date, -1), + "posting_time": "00:00:00", + "company": pr.company, + "allow_negative_stock": 1, + "via_landed_cost_voucher": 0, + "allow_zero_rate": 0, + } + ) + repost_doc.save().submit() + + pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles_after_repost = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1300.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + {"account": lcv_expense_account, "debit": 0.0, "credit": 50.0, "cost_center": test_cc}, + ] + self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost)) + self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost) + + def create_lcv(self, receipt_document_type, receipt_document, company, expense_account, charges=50): + ref_doc = frappe.get_doc(receipt_document_type, receipt_document) + + lcv = frappe.new_doc("Landed Cost Voucher") + lcv.company = company + lcv.distribute_charges_based_on = "Qty" + lcv.set( + "purchase_receipts", + [ + { + "receipt_document_type": receipt_document_type, + "receipt_document": receipt_document, + "supplier": ref_doc.supplier, + "posting_date": ref_doc.posting_date, + "grand_total": ref_doc.base_grand_total, + } + ], + ) + + lcv.set( + "taxes", + [ + { + "description": "Testing", + "expense_account": expense_account, + "amount": charges, + } + ], + ) + lcv.save().submit() + return lcv + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From e55fd7204a3ead3793c18099f9ad8c33fc632cf4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 2 Jul 2024 12:45:04 +0530 Subject: [PATCH 194/203] refactor(test): cleanup test data (cherry picked from commit 6ba6b5aa33a6a5d9f3ad042dd9cf2c423960dd55) --- .../purchase_receipt/test_purchase_receipt.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 6ddf756a7a2..d92de03b341 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3087,7 +3087,7 @@ class TestPurchaseReceipt(FrappeTestCase): ] self.assertEqual(expected_pi_gles, pi_gl_entries) - self.create_lcv(pr.doctype, pr.name, test_company, lcv_expense_account) + lcv = self.create_lcv(pr.doctype, pr.name, test_company, lcv_expense_account) pr_gles_after_lcv = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) expected_pr_gles_after_lcv = [ {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, @@ -3124,6 +3124,18 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost)) self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost) + # teardown + lcv.reload() + lcv.cancel() + pi.reload() + pi.cancel() + pr.reload() + pr.cancel() + + company_doc.enable_perpetual_inventory = False + company_doc.stock_received_but_not_billed = None + company_doc.save() + def create_lcv(self, receipt_document_type, receipt_document, company, expense_account, charges=50): ref_doc = frappe.get_doc(receipt_document_type, receipt_document) From c3cc36364896bcc759a05ad535017bad76d987bf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 2 Jul 2024 17:00:16 +0530 Subject: [PATCH 195/203] refactor(test): fix flaky test (cherry picked from commit 0e256b8b29d2779bf84c3adb311cc3b122dd4b42) --- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d92de03b341..968cb68dac0 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3043,6 +3043,7 @@ class TestPurchaseReceipt(FrappeTestCase): company_doc = frappe.get_doc("Company", test_company) company_doc.enable_perpetual_inventory = True company_doc.stock_received_but_not_billed = stock_rbnb + company_doc.default_inventory_account = stock_in_hand company_doc.save() packaging_charges_account = create_account( @@ -3134,6 +3135,7 @@ class TestPurchaseReceipt(FrappeTestCase): company_doc.enable_perpetual_inventory = False company_doc.stock_received_but_not_billed = None + company_doc.default_inventory_account = None company_doc.save() def create_lcv(self, receipt_document_type, receipt_document, company, expense_account, charges=50): From 11ebbf2a9cfad99c1e2a6406f537f75a4526185b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:09:09 +0530 Subject: [PATCH 196/203] fix: show zero stock items filter in the stock balance report (backport #42147) (#42152) fix: show zero stock items filter in the stock balance report (#42147) (cherry picked from commit 1dae2156e3d2de35c0406253ecd48c07be0db483) Co-authored-by: rohitwaghchaure --- erpnext/stock/report/stock_balance/stock_balance.js | 6 ++++++ erpnext/stock/report/stock_balance/stock_balance.py | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index ca2c053fdb1..d80261895aa 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -101,6 +101,12 @@ frappe.query_reports["Stock Balance"] = { fieldtype: "Check", default: 0, }, + { + fieldname: "include_zero_stock_items", + label: __("Include Zero Stock Items"), + fieldtype: "Check", + default: 0, + }, ], formatter: function (value, row, column, data, default_formatter) { diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 27d9f1164bc..2694ba03c8b 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -138,7 +138,12 @@ class StockBalanceReport: {"reserved_stock": sre_details.get((report_data.item_code, report_data.warehouse), 0.0)} ) - if report_data and report_data.bal_qty == 0 and report_data.bal_val == 0: + if ( + not self.filters.get("include_zero_stock_items") + and report_data + and report_data.bal_qty == 0 + and report_data.bal_val == 0 + ): continue self.data.append(report_data) From a7b6530fde4ad7ae298f685d9917d9a701fcfd72 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:42:37 +0530 Subject: [PATCH 197/203] fix: manual pick allow to pick more than available stock (backport #42155) (#42159) fix: manual pick allow to pick more than available stock (#42155) (cherry picked from commit 938dd4b2aa4c3d85c9c29692331a9df54fed0cbc) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list/pick_list.py | 59 ++++++++++++++++++- .../stock/doctype/pick_list/test_pick_list.py | 42 +++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 6dd53f290c9..066dba95b1e 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -6,7 +6,7 @@ from collections import OrderedDict, defaultdict from itertools import groupby import frappe -from frappe import _ +from frappe import _, bold from frappe.model.document import Document from frappe.model.mapper import map_child_doc from frappe.query_builder import Case @@ -73,6 +73,9 @@ class PickList(Document): def validate(self): self.validate_for_qty() + if self.pick_manually and self.get("locations"): + self.validate_stock_qty() + self.check_serial_no_status() def before_save(self): self.update_status() @@ -82,6 +85,60 @@ class PickList(Document): if self.get("locations"): self.validate_sales_order_percentage() + def validate_stock_qty(self): + from erpnext.stock.doctype.batch.batch import get_batch_qty + + for row in self.get("locations"): + if row.batch_no and not row.qty: + batch_qty = get_batch_qty(row.batch_no, row.warehouse, row.item_code) + + if row.qty > batch_qty: + frappe.throw( + _( + "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}." + ).format(row.idx, row.item_code, batch_qty, row.batch_no, bold(row.warehouse)), + title=_("Insufficient Stock"), + ) + + continue + + bin_qty = frappe.db.get_value( + "Bin", + {"item_code": row.item_code, "warehouse": row.warehouse}, + "actual_qty", + ) + + if row.qty > bin_qty: + frappe.throw( + _( + "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}." + ).format(row.idx, row.qty, bold(row.item_code), bin_qty, bold(row.warehouse)), + title=_("Insufficient Stock"), + ) + + def check_serial_no_status(self): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + for row in self.get("locations"): + if not row.serial_no: + continue + + picked_serial_nos = get_serial_nos(row.serial_no) + validated_serial_nos = frappe.get_all( + "Serial No", + filters={"name": ("in", picked_serial_nos), "warehouse": row.warehouse}, + pluck="name", + ) + + incorrect_serial_nos = set(picked_serial_nos) - set(validated_serial_nos) + if incorrect_serial_nos: + frappe.throw( + _("The Serial No at Row #{0}: {1} is not available in warehouse {2}.").format( + row.idx, ", ".join(incorrect_serial_nos), row.warehouse + ), + title=_("Incorrect Warehouse"), + ) + def validate_sales_order_percentage(self): # set percentage picked in SO for location in self.get("locations"): diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 116a0bd833d..3341b1fc6de 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -1132,3 +1132,45 @@ class TestPickList(FrappeTestCase): pl.save() self.assertEqual(pl.locations[0].qty, 80.0) + + def test_validate_picked_qty_with_manual_option(self): + warehouse = "_Test Warehouse - _TC" + non_serialized_item = make_item( + "Test Non Serialized Pick List Item For Manual Option", properties={"is_stock_item": 1} + ).name + + serialized_item = make_item( + "Test Serialized Pick List Item For Manual Option", + properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-HSNMSPLI-.####"}, + ).name + + batched_item = make_item( + "Test Batched Pick List Item For Manual Option", + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "SN-HBNMSPLI-.####", + "create_new_batch": 1, + }, + ).name + + make_stock_entry(item=non_serialized_item, to_warehouse=warehouse, qty=10, basic_rate=100) + make_stock_entry(item=serialized_item, to_warehouse=warehouse, qty=10, basic_rate=100) + make_stock_entry(item=batched_item, to_warehouse=warehouse, qty=10, basic_rate=100) + + so = make_sales_order( + item_code=non_serialized_item, qty=10, rate=100, do_not_save=True, warehouse=warehouse + ) + so.append("items", {"item_code": serialized_item, "qty": 10, "rate": 100, "warehouse": warehouse}) + so.append("items", {"item_code": batched_item, "qty": 10, "rate": 100, "warehouse": warehouse}) + so.set_missing_values() + so.save() + so.submit() + + pl = create_pick_list(so.name) + pl.pick_manually = 1 + + for row in pl.locations: + row.qty = row.qty + 10 + + self.assertRaises(frappe.ValidationError, pl.save) From 5317418a53815f421296d3861a7d10aa25d31136 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:12:00 +0530 Subject: [PATCH 198/203] fix: path of automatically updates the status of asset maintenance log (cherry picked from commit 909aa8f359c77cb92c3607cb46fea6a5292730dc) --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a080af9a827..028acc9cb11 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -442,7 +442,7 @@ scheduler_events = { "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily", "erpnext.accounts.utils.run_ledger_health_checks", - "erpnext.assets.doctype.asset.asset_maintenance_log.update_asset_maintenance_log_status", + "erpnext.assets.doctype.asset_maintenance_log.asset_maintenance_log.update_asset_maintenance_log_status", ], "weekly": [ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly", From 93528631c3652edecfee3e712d94c4ace9d4b26a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 15:58:19 +0530 Subject: [PATCH 199/203] fix: multiple free items on same Item Group (cherry picked from commit c4ae0d283fe24ef0bba45266df8c2f4d1cdf22ba) --- erpnext/public/js/controllers/transaction.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 950914953aa..dfb0ded8139 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1750,12 +1750,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe apply_product_discount(args) { const items = this.frm.doc.items.filter(d => (d.is_free_item)) || []; - const exist_items = items.map(row => (row.item_code, row.pricing_rules)); + const exist_items = items.map(row => { return {item_code: row.item_code, pricing_rules: row.pricing_rules};}); args.free_item_data.forEach(pr_row => { let row_to_modify = {}; - if (!items || !in_list(exist_items, (pr_row.item_code, pr_row.pricing_rules))) { + // If there are no free items, or if the current free item doesn't exist in the table, add it + if (!items || !exist_items.filter(e_row => { + return e_row.item_code == pr_row.item_code && e_row.pricing_rules == pr_row.pricing_rules; + }).length) { row_to_modify = frappe.model.add_child(this.frm.doc, this.frm.doc.doctype + ' Item', 'items'); From 4ecb02cb41783056c6365735180bd9edca367a32 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 2 Jul 2024 15:44:54 +0530 Subject: [PATCH 200/203] refactor: validation to prevent recursion with mixed conditions (cherry picked from commit 406dfd528f2cabc60ece0c917fb02092abed50cc) --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 5 +++++ .../doctype/promotional_scheme/promotional_scheme.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 420fd3bee9d..de29ec0f004 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -139,6 +139,7 @@ class PricingRule(Document): self.validate_price_list_with_currency() self.validate_dates() self.validate_condition() + self.validate_mixed_with_recursion() if not self.margin_type: self.margin_rate_or_amount = 0.0 @@ -308,6 +309,10 @@ class PricingRule(Document): ): frappe.throw(_("Invalid condition expression")) + def validate_mixed_with_recursion(self): + if self.mixed_conditions and self.is_recursive: + frappe.throw(_("Recursive Discounts with Mixed condition is not supported by the system")) + # -------------------------------------------------------------------------------- diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index ed4e3d2a8f3..6767594b10f 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -146,6 +146,7 @@ class PromotionalScheme(Document): self.validate_applicable_for() self.validate_pricing_rules() + self.validate_mixed_with_recursion() def validate_applicable_for(self): if self.applicable_for: @@ -177,6 +178,7 @@ class PromotionalScheme(Document): frappe.delete_doc("Pricing Rule", docname.name) def on_update(self): + self.validate() pricing_rules = ( frappe.get_all( "Pricing Rule", @@ -188,6 +190,15 @@ class PromotionalScheme(Document): ) self.update_pricing_rules(pricing_rules) + def validate_mixed_with_recursion(self): + if self.mixed_conditions: + if self.product_discount_slabs: + for slab in self.product_discount_slabs: + if slab.is_recursive: + frappe.throw( + _("Recursive Discounts with Mixed condition is not supported by the system") + ) + def update_pricing_rules(self, pricing_rules): rules = {} count = 0 From cfda5f6d0b1b6f9757b6010c98c675c768e0cbb5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 17:05:13 +0530 Subject: [PATCH 201/203] fix: use standard method to get `_doc_before_save` (cherry picked from commit 9d7be293ae9d7d2a7cf9f81fa1aadcc071bc29e6) --- .../accounts/doctype/promotional_scheme/promotional_scheme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 6767594b10f..71fe156dd7a 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -164,7 +164,7 @@ class PromotionalScheme(Document): docnames = [] # If user has changed applicable for - if self._doc_before_save.applicable_for == self.applicable_for: + if self.get_doc_before_save() and self.get_doc_before_save().applicable_for == self.applicable_for: return docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name}) From 0774607f522b769637aaaefedcc612016f02096a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 17:11:32 +0530 Subject: [PATCH 202/203] test: validation on mixed condition with recursion (cherry picked from commit 9bd4e7b7094830f56fdb6fd492d1a0873bf5b977) --- .../test_promotional_scheme.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index 9b40c98829c..74ba6cf923c 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -129,6 +129,25 @@ class TestPromotionalScheme(unittest.TestCase): [pr.min_qty, pr.free_item, pr.free_qty, pr.recurse_for], [12, "_Test Item 2", 1, 12] ) + def test_validation_on_recurse_with_mixed_condition(self): + ps = make_promotional_scheme() + ps.set("price_discount_slabs", []) + ps.set( + "product_discount_slabs", + [ + { + "rule_description": "12+1", + "min_qty": 12, + "free_item": "_Test Item 2", + "free_qty": 1, + "is_recursive": 1, + "recurse_for": 12, + } + ], + ) + ps.mixed_conditions = True + self.assertRaises(frappe.ValidationError, ps.save) + def make_promotional_scheme(**args): args = frappe._dict(args) From 12bec3be9de575693b068f78661613dc137fe923 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 17:54:41 +0530 Subject: [PATCH 203/203] test: validation on mixed condition and recursion on pricing rule (cherry picked from commit eb4af58bf0371437ba7bca36f345488c6c03331b) --- .../doctype/pricing_rule/test_pricing_rule.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 72961a6b6ec..87f6fa76bc8 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1299,6 +1299,18 @@ class TestPricingRule(unittest.TestCase): item_group_rule.delete() item_code_rule.delete() + def test_validation_on_mixed_condition_with_recursion(self): + pricing_rule = make_pricing_rule( + discount_percentage=10, + selling=1, + priority=2, + min_qty=4, + title="_Test Pricing Rule with Min Qty - 2", + ) + pricing_rule.mixed_conditions = True + pricing_rule.is_recursive = True + self.assertRaises(frappe.ValidationError, pricing_rule.save) + test_dependencies = ["Campaign"]